From 55dd8cdb4c5c6af8dc598602d3e9f9baaeada818 Mon Sep 17 00:00:00 2001 From: Vanilagy <1696106+Vanilagy@users.noreply.github.com> Date: Tue, 2 Sep 2025 17:20:09 +0200 Subject: [PATCH] Add NullTarget --- dev/convert.html | 6 +++-- docs/guide/writing-media-files.md | 39 +++++++++++++++++++++++++++++++ src/index.ts | 2 +- src/target.ts | 14 ++++++++++- src/writer.ts | 21 +++++++++++++++++ 5 files changed, 78 insertions(+), 4 deletions(-) diff --git a/dev/convert.html b/dev/convert.html index bdd82c1..fd662fd 100644 --- a/dev/convert.html +++ b/dev/convert.html @@ -18,13 +18,15 @@ const file = fileInput.files[0]; const source = new Mediabunny.BlobSource(file); - const target = new Mediabunny.BufferTarget() ?? new Mediabunny.StreamTarget(new WritableStream({ + const target = new Mediabunny.NullTarget() ?? new Mediabunny.BufferTarget() ?? new Mediabunny.StreamTarget(new WritableStream({ write: console.log }), { chunked: true, chunkSize: 2**20 }); - const outputFormat = new Mediabunny.WavOutputFormat({}); + const outputFormat = new Mediabunny.Mp4OutputFormat({ + onMoov: console.log + }); const button = document.createElement('button'); button.textContent = 'Cancel'; diff --git a/docs/guide/writing-media-files.md b/docs/guide/writing-media-files.md index 5a20e6a..384ce9a 100644 --- a/docs/guide/writing-media-files.md +++ b/docs/guide/writing-media-files.md @@ -297,6 +297,45 @@ const output = new Output({ await output.finalize(); // Will automatically close the writable stream ``` +### `NullTarget` + +This target simply discards all data that is passed into it. It is useful for when you need an `Output` but extract data from it differently, for example through output format-specific callbacks or encoder events. + +As an example, here we create a fragmented MP4 file and directly handle the individual fragments: +```ts +import { Output, NullTarget, Mp4OutputFormat } from 'mediabunny'; + +let ftyp: Uint8Array; +let lastMoof: Uint8Array; + +const output = new Output({ + target: new NullTarget(), + format: new Mp4OutputFormat({ + fastStart: 'fragmented', + onFtyp: (data) => { + ftyp = data; + }, + onMoov: (data) => { + const header = new Uint8Array(ftyp.length + data.length); + header.set(ftyp, 0); + header.set(data, ftyp.length); + + // Do something with the header... + }, + onMoof: (data) => { + lastMoof = data; + }, + onMdat: (data) => { + const segment = new Uint8Array(lastMoof.length + data.length); + segment.set(lastMoof, 0); + segment.set(data, lastMoof.length); + + // Do something with the segment... + }, + }), +}); +``` + ## Packet buffering Some [output formats](./output-formats) require *packet buffering* for multi-track outputs. Packet buffering occurs because the `Output` must wait for data from all tracks for a given timestamp to continue writing data. For example, should you first encode all your video frames and then encode the audio afterward, the `Output` will have to hold all of the video frames in memory until the audio packets start coming in. This might lead to memory exhaustion should your video be very long. When there is only one media track, this issue does not arise. diff --git a/src/index.ts b/src/index.ts index e03149e..da1f4ce 100644 --- a/src/index.ts +++ b/src/index.ts @@ -89,7 +89,7 @@ export { getFirstEncodableAudioCodec, getFirstEncodableSubtitleCodec, } from './encode'; -export { Target, BufferTarget, StreamTarget, StreamTargetChunk, StreamTargetOptions } from './target'; +export { Target, BufferTarget, StreamTarget, StreamTargetChunk, StreamTargetOptions, NullTarget } from './target'; export { Rotation, AnyIterable, SetRequired, MaybePromise } from './misc'; export { Source, diff --git a/src/target.ts b/src/target.ts index 78fc777..91d9960 100644 --- a/src/target.ts +++ b/src/target.ts @@ -6,7 +6,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -import { BufferTargetWriter, StreamTargetWriter, Writer } from './writer'; +import { BufferTargetWriter, NullTargetWriter, StreamTargetWriter, Writer } from './writer'; import { Output } from './output'; /** @@ -104,3 +104,15 @@ export class StreamTarget extends Target { return new StreamTargetWriter(this); } } + +/** + * This target just discards all incoming data. It is useful for when you need an `Output` but extract data from it + * differently, for example through format-specific callbacks (`onMoof`, `onMdat`, ...) or encoder events. + * @public + */ +export class NullTarget extends Target { + /** @internal */ + _createWriter() { + return new NullTargetWriter(); + } +} diff --git a/src/writer.ts b/src/writer.ts index d9853c4..5f2b8f7 100644 --- a/src/writer.ts +++ b/src/writer.ts @@ -459,3 +459,24 @@ export class StreamTargetWriter extends Writer { return this.writer?.close(); } } + +export class NullTargetWriter extends Writer { + private pos = 0; + + write(data: Uint8Array) { + this.maybeTrackWrites(data); + this.pos += data.byteLength; + } + + getPos() { + return this.pos; + } + + seek(newPos: number) { + this.pos = newPos; + } + + async flush() {} + async finalize() {} + async close() {} +}