From 266fcd473e419ac5ecd12a2e6bc648f25242556c Mon Sep 17 00:00:00 2001 From: Vanilagy <1696106+Vanilagy@users.noreply.github.com> Date: Thu, 28 Nov 2024 22:06:49 +0100 Subject: [PATCH] Use WritableStream for StreamTarget & integrate backpressure concepts --- dev/index.html | 15 +- dist/metamuxer.d.ts | 80 +++-- dist/metamuxer.js | 521 +++++++++++++++++++-------------- dist/metamuxer.min.js | 10 +- dist/metamuxer.min.mjs | 12 +- dist/metamuxer.mjs | 521 +++++++++++++++++++-------------- package-lock.json | 8 +- package.json | 2 +- src/index.ts | 4 +- src/isobmff/isobmff-muxer.ts | 152 ++++++---- src/matroska/matroska-muxer.ts | 150 ++++++---- src/misc.ts | 20 +- src/muxer.ts | 12 +- src/output-format.ts | 31 +- src/output.ts | 31 +- src/source.ts | 59 +++- src/target.ts | 86 +++--- src/writer.ts | 102 ++++--- tsconfig.json | 3 +- 19 files changed, 1078 insertions(+), 741 deletions(-) diff --git a/dev/index.html b/dev/index.html index 858e1c8..653c075 100644 --- a/dev/index.html +++ b/dev/index.html @@ -39,7 +39,14 @@ let format = new Metamuxer.WebMOutputFormat({ streamable: true }); format = new Metamuxer.Mp4OutputFormat({ fastStart: false }); // new Metamuxer.MkvOutputFormat();// new Metamuxer.Mp4OutputFormat({ fastStart: false }); let target = new Metamuxer.ArrayBufferTarget(); - //target = new Metamuxer.StreamTarget({ onData: (data, pos) => console.log(data, pos), chunked: true }) ?? + + target = new Metamuxer.StreamTarget(new WritableStream({ + async write(chunk) { + console.log(chunk) + + await new Promise(resolve => setTimeout(resolve, 250)); + } + }), { chunked: true }); let output = new Metamuxer.Output({ format, target }); @@ -150,7 +157,7 @@ Testing... <00:17.350>One... <00:18.125>Two... context.fillStyle = ['red', 'green', 'blue', 'yellow'][i % 4]; context.fillRect(canvas.width * Math.random(), canvas.height * Math.random(), canvas.width * Math.random(), canvas.height * Math.random()); - videoSource.digest(i / 10, 1 / 10); + await videoSource.digest(i / 10, 1 / 10); } let audioContext = new AudioContext(); @@ -158,10 +165,10 @@ Testing... <00:17.350>One... <00:18.125>Two... let length = 10; let slicedAudioBuffer = sliceAudioBuffer(audioBuffer, length, audioContext); - audioSource.digest(slicedAudioBuffer); + await audioSource.digest(slicedAudioBuffer); await output.finalize(); console.log(target); - download(new Blob([target.buffer]), 'test.mp4'); + //download(new Blob([target.buffer]), 'test.mp4'); \ No newline at end of file diff --git a/dist/metamuxer.d.ts b/dist/metamuxer.d.ts index 925f470..58f7f94 100644 --- a/dist/metamuxer.d.ts +++ b/dist/metamuxer.d.ts @@ -9,7 +9,7 @@ export declare const AUDIO_CODECS: readonly ["aac", "opus"]; /** @public */ export declare class AudioBufferSource extends AudioSource { constructor(codecConfig: AudioCodecConfig); - digest(audioBuffer: AudioBuffer): void; + digest(audioBuffer: AudioBuffer): Promise; } /** @public */ @@ -24,7 +24,7 @@ export declare type AudioCodecConfig = { /** @public */ export declare class AudioDataSource extends AudioSource { constructor(codecConfig: AudioCodecConfig); - digest(audioData: AudioData): void; + digest(audioData: AudioData): Promise; } /** @public */ @@ -38,30 +38,19 @@ export declare type AudioTrackMetadata = {}; /** @public */ export declare class CanvasSource extends VideoSource { constructor(canvas: HTMLCanvasElement, codecConfig: VideoCodecConfig); - digest(timestamp: number, duration?: number): void; + digest(timestamp: number, duration?: number): Promise; } /** @public */ export declare class EncodedAudioChunkSource extends AudioSource { constructor(codec: AudioCodec); - digest(chunk: EncodedAudioChunk, meta?: EncodedAudioChunkMetadata): void; + digest(chunk: EncodedAudioChunk, meta?: EncodedAudioChunkMetadata): Promise; } /** @public */ export declare class EncodedVideoChunkSource extends VideoSource { constructor(codec: VideoCodec); - digest(chunk: EncodedVideoChunk, meta?: EncodedVideoChunkMetadata): void; -} - -/** @public */ -export declare class FileSystemWritableFileStreamTarget extends Target { - stream: FileSystemWritableFileStream; - options?: { - chunkSize?: number; - } | undefined; - constructor(stream: FileSystemWritableFileStream, options?: { - chunkSize?: number; - } | undefined); + digest(chunk: EncodedVideoChunk, meta?: EncodedVideoChunkMetadata): Promise; } /** @public */ @@ -72,7 +61,6 @@ export { MediaSource_2 as MediaSource } /** @public */ export declare class MediaStreamAudioTrackSource extends AudioSource { - _offsetTimestamps: boolean; constructor(track: MediaStreamAudioTrack, codecConfig: AudioCodecConfig); } @@ -83,31 +71,31 @@ export declare class MediaStreamVideoTrackSource extends VideoSource { /** @public */ export declare class MkvOutputFormat extends OutputFormat { - options: { - streamable?: boolean; - }; - constructor(options?: { - streamable?: boolean; - }); + constructor(options?: MkvOutputFormatOptions); } +/** @public */ +export declare type MkvOutputFormatOptions = { + streamable?: boolean; +}; + /** @public */ export declare class Mp4OutputFormat extends OutputFormat { - options: { - fastStart?: false | 'in-memory' | 'fragmented'; - }; - constructor(options?: { - fastStart?: false | 'in-memory' | 'fragmented'; - }); + constructor(options?: Mp4OutputFormatOptions); } +/** @public */ +export declare type Mp4OutputFormatOptions = { + fastStart?: false | 'in-memory' | 'fragmented'; +}; + /** @public */ export declare class Output { constructor(options: OutputOptions); addVideoTrack(source: VideoSource, metadata?: VideoTrackMetadata): void; addAudioTrack(source: AudioSource, metadata?: AudioTrackMetadata): void; addSubtitleTrack(source: SubtitleSource, metadata?: SubtitleTrackMetadata): void; - start(): void; + start(): Promise; finalize(): Promise; } @@ -123,18 +111,22 @@ export declare type OutputOptions = { /** @public */ export declare class StreamTarget extends Target { - options: { - onData?: (data: Uint8Array, position: number) => void; - chunked?: boolean; - chunkSize?: number; - }; - constructor(options: { - onData?: (data: Uint8Array, position: number) => void; - chunked?: boolean; - chunkSize?: number; - }); + constructor(writable: WritableStream, options?: StreamTargetOptions); } +/** @public */ +export declare type StreamTargetChunk = { + type: 'write'; + data: Uint8Array; + position: number; +}; + +/** @public */ +export declare type StreamTargetOptions = { + chunked?: boolean; + chunkSize?: number; +}; + /** @public */ export declare const SUBTITLE_CODECS: readonly ["webvtt"]; @@ -151,13 +143,12 @@ export declare type SubtitleTrackMetadata = {}; /** @public */ export declare abstract class Target { - output: Output | null; } /** @public */ export declare class TextSubtitleSource extends SubtitleSource { constructor(codec: SubtitleCodec); - digest(text: string): void; + digest(text: string): Promise; } /** @public */ @@ -179,7 +170,7 @@ export declare type VideoCodecConfig = { /** @public */ export declare class VideoFrameSource extends VideoSource { constructor(codecConfig: VideoCodecConfig); - digest(videoFrame: VideoFrame): void; + digest(videoFrame: VideoFrame): Promise; } /** @public */ @@ -197,6 +188,9 @@ export declare type VideoTrackMetadata = { export declare class WebMOutputFormat extends MkvOutputFormat { } +/** @public */ +export declare type WebmOutputFormatOptions = MkvOutputFormatOptions; + export { } export as namespace Metamuxer; \ No newline at end of file diff --git a/dist/metamuxer.js b/dist/metamuxer.js index c1def09..9bab4dc 100644 --- a/dist/metamuxer.js +++ b/dist/metamuxer.js @@ -29,7 +29,6 @@ var Metamuxer = (() => { CanvasSource: () => CanvasSource, EncodedAudioChunkSource: () => EncodedAudioChunkSource, EncodedVideoChunkSource: () => EncodedVideoChunkSource, - FileSystemWritableFileStreamTarget: () => FileSystemWritableFileStreamTarget, MediaSource: () => MediaSource, MediaStreamAudioTrackSource: () => MediaStreamAudioTrackSource, MediaStreamVideoTrackSource: () => MediaStreamVideoTrackSource, @@ -122,6 +121,21 @@ var Metamuxer = (() => { var isAllowSharedBufferSource = (x) => { return x instanceof ArrayBuffer || typeof SharedArrayBuffer !== "undefined" && x instanceof SharedArrayBuffer || ArrayBuffer.isView(x) && !(x instanceof DataView); }; + var AsyncMutex = class { + constructor() { + this.currentPromise = Promise.resolve(); + } + async acquire() { + let resolver; + let nextPromise = new Promise((resolve) => { + resolver = resolve; + }); + let currentPromiseAlias = this.currentPromise; + this.currentPromise = nextPromise; + await currentPromiseAlias; + return resolver; + } + }; // src/subtitles.ts var cueBlockHeaderRegex = /(?:(.+?)\n)?((?:\d{2}:)?\d{2}:\d{2}.\d{3})\s+-->\s+((?:\d{2}:)?\d{2}:\d{2}.\d{3})/g; @@ -1107,6 +1121,7 @@ var Metamuxer = (() => { // src/muxer.ts var Muxer = class { constructor(output) { + this.mutex = new AsyncMutex(); this.trackTimestampInfo = /* @__PURE__ */ new WeakMap(); this.output = output; } @@ -1151,82 +1166,16 @@ var Metamuxer = (() => { } }; - // src/target.ts - var Target = class { - constructor() { - this.output = null; - } - }; - var ArrayBufferTarget = class extends Target { - constructor() { - super(...arguments); - this.buffer = null; - } - /** @internal */ - _createWriter() { - return new ArrayBufferTargetWriter(this); - } - }; - var StreamTarget = class extends Target { - constructor(options) { - super(); - this.options = options; - if (typeof options !== "object") { - throw new TypeError("StreamTarget requires an options object to be passed to its constructor."); - } - if (options.onData) { - if (typeof options.onData !== "function") { - throw new TypeError("options.onData, when provided, must be a function."); - } - if (options.onData.length < 2) { - throw new TypeError( - "options.onData, when provided, must be a function that takes in at least two arguments (data and position). Ignoring the position argument, which specifies the byte offset at which the data is to be written, can lead to broken outputs." - ); - } - } - if (options.chunked !== void 0 && typeof options.chunked !== "boolean") { - throw new TypeError("options.chunked, when provided, must be a boolean."); - } - if (options.chunkSize !== void 0 && (!Number.isInteger(options.chunkSize) || options.chunkSize <= 0)) { - throw new TypeError("options.chunkSize, when provided, must be a positive integer."); - } - } - /** @internal */ - _createWriter() { - return this.options.chunked ? new ChunkedStreamTargetWriter(this) : new StreamTargetWriter(this); - } - }; - var FileSystemWritableFileStreamTarget = class extends Target { - constructor(stream, options) { - super(); - this.stream = stream; - this.options = options; - if (!(stream instanceof FileSystemWritableFileStream)) { - throw new TypeError("FileSystemWritableFileStreamTarget requires a FileSystemWritableFileStream instance."); - } - if (options !== void 0 && typeof options !== "object") { - throw new TypeError("FileSystemWritableFileStreamTarget's options, when provided, must be an object."); - } - if (options) { - if (options.chunkSize !== void 0 && (!Number.isInteger(options.chunkSize) || options.chunkSize <= 0)) { - throw new TypeError("options.chunkSize, when provided, must be a positive integer"); - } - } - } - /** @internal */ - _createWriter() { - return new FileSystemWritableFileStreamTargetWriter(this); - } - }; - // src/writer.ts - var Writer2 = class { + var Writer = class { constructor() { /** Setting this to true will cause the writer to ensure data is written in a strictly monotonic, streamable way. */ this.ensureMonotonicity = false; } + start() { + } }; - var ArrayBufferTargetWriter = class extends Writer2 { + var ArrayBufferTargetWriter = class extends Writer { constructor(target) { super(); this.pos = 0; @@ -1257,9 +1206,9 @@ var Metamuxer = (() => { getPos() { return this.pos; } - flush() { + async flush() { } - finalize() { + async finalize() { this.ensureSize(this.pos); this.target.buffer = this.buffer.slice(0, Math.max(this.maxPos, this.pos)); } @@ -1267,14 +1216,18 @@ var Metamuxer = (() => { return this.bytes.slice(start, end); } }; - var StreamTargetWriter = class extends Writer2 { + var StreamTargetWriter = class extends Writer { constructor(target) { super(); this.pos = 0; this.sections = []; this.lastFlushEnd = 0; + this.writer = null; this.target = target; } + start() { + this.writer = this.target._writable.getWriter(); + } write(data) { this.sections.push({ data: data.slice(), @@ -1288,7 +1241,8 @@ var Metamuxer = (() => { getPos() { return this.pos; } - flush() { + async flush() { + assert(this.writer); if (this.sections.length === 0) return; let chunks = []; let sorted = [...this.sections].sort((a, b) => a.start - b.start); @@ -1318,17 +1272,26 @@ var Metamuxer = (() => { if (this.ensureMonotonicity && chunk.start !== this.lastFlushEnd) { throw new Error("Internal error: Monotonicity violation."); } - this.target.options.onData?.(chunk.data, chunk.start); + if (this.writer.desiredSize !== null && this.writer.desiredSize <= 0) { + await this.writer.ready; + } + this.writer.write({ + type: "write", + data: chunk.data, + position: chunk.start + }); this.lastFlushEnd = chunk.start + chunk.data.byteLength; } this.sections.length = 0; } finalize() { + assert(this.writer); + return this.writer.close(); } }; var DEFAULT_CHUNK_SIZE = 2 ** 24; var MAX_CHUNKS_AT_ONCE = 2; - var ChunkedStreamTargetWriter = class extends Writer2 { + var ChunkedStreamTargetWriter = class extends Writer { constructor(target) { super(); this.pos = 0; @@ -1338,15 +1301,20 @@ var Metamuxer = (() => { */ this.chunks = []; this.lastFlushEnd = 0; + this.writer = null; + this.flushedChunkQueue = []; this.target = target; - this.chunkSize = target.options?.chunkSize ?? DEFAULT_CHUNK_SIZE; + this.chunkSize = target._options?.chunkSize ?? DEFAULT_CHUNK_SIZE; if (!Number.isInteger(this.chunkSize) || this.chunkSize < 2 ** 10) { throw new Error("Invalid StreamTarget options: chunkSize must be an integer not smaller than 1024."); } } + start() { + this.writer = this.target._writable.getWriter(); + } write(data) { this.writeDataIntoChunks(data, this.pos); - this.flushChunks(); + this.queueChunksForFlush(); this.pos += data.byteLength; } seek(newPos) { @@ -1374,7 +1342,7 @@ var Metamuxer = (() => { for (let i = 0; i < this.chunks.length - 1; i++) { this.chunks[i].shouldFlush = true; } - this.flushChunks(); + this.queueChunksForFlush(); } if (toWrite.byteLength < data.byteLength) { this.writeDataIntoChunks(data.subarray(toWrite.byteLength), position + toWrite.byteLength); @@ -1412,7 +1380,8 @@ var Metamuxer = (() => { this.chunks.sort((a, b) => a.start - b.start); return this.chunks.indexOf(chunk); } - flushChunks(force = false) { + queueChunksForFlush(force = false) { + assert(this.writer); for (let i = 0; i < this.chunks.length; i++) { let chunk = this.chunks[i]; if (!chunk.shouldFlush && !force) continue; @@ -1420,31 +1389,73 @@ var Metamuxer = (() => { if (this.ensureMonotonicity && chunk.start + section.start !== this.lastFlushEnd) { throw new Error("Internal error: Monotonicity violation."); } - this.target.options.onData?.( - chunk.data.subarray(section.start, section.end), - chunk.start + section.start - ); + this.flushedChunkQueue.push({ + type: "write", + data: chunk.data.subarray(section.start, section.end), + position: chunk.start + section.start + }); this.lastFlushEnd = chunk.start + section.end; } this.chunks.splice(i--, 1); } } - flush() { + async flush() { + assert(this.writer); + if (this.flushedChunkQueue.length === 0) return; + for (let chunk of this.flushedChunkQueue) { + if (this.writer.desiredSize !== null && this.writer.desiredSize <= 0) { + await this.writer.ready; + } + this.writer.write(chunk); + } + this.flushedChunkQueue.length = 0; } - finalize() { - this.flushChunks(true); + async finalize() { + assert(this.writer); + this.queueChunksForFlush(true); + await this.flush(); + return this.writer.close(); } }; - var FileSystemWritableFileStreamTargetWriter = class extends ChunkedStreamTargetWriter { - constructor(target) { - super(new StreamTarget({ - onData: (data, position) => target.stream.write({ - type: "write", - data, - position - }), - chunkSize: target.options?.chunkSize - })); + + // src/target.ts + var Target = class { + constructor() { + /** @internal */ + this._output = null; + } + }; + var ArrayBufferTarget = class extends Target { + constructor() { + super(...arguments); + this.buffer = null; + } + /** @internal */ + _createWriter() { + return new ArrayBufferTargetWriter(this); + } + }; + var StreamTarget = class extends Target { + constructor(writable, options = {}) { + super(); + if (!(writable instanceof WritableStream)) { + throw new TypeError("StreamTarget requires a WritableStream instance."); + } + if (options != null && typeof options !== "object") { + throw new TypeError("StreamTarget options, when provided, must be an object."); + } + if (options.chunked !== void 0 && typeof options.chunked !== "boolean") { + throw new TypeError("options.chunked, when provided, must be a boolean."); + } + if (options.chunkSize !== void 0 && (!Number.isInteger(options.chunkSize) || options.chunkSize <= 0)) { + throw new TypeError("options.chunkSize, when provided, must be a positive integer."); + } + this._writable = writable; + this._options = options; + } + /** @internal */ + _createWriter() { + return this._options.chunked ? new ChunkedStreamTargetWriter(this) : new StreamTargetWriter(this); } }; @@ -1667,12 +1678,13 @@ var Metamuxer = (() => { this.nextFragmentNumber = 1; this.writer = output._writer; this.boxWriter = new IsobmffBoxWriter(this.writer); - this.fastStart = format.options.fastStart ?? (this.writer instanceof ArrayBufferTargetWriter ? "in-memory" : false); + this.fastStart = format._options.fastStart ?? (this.writer instanceof ArrayBufferTargetWriter ? "in-memory" : false); if (this.fastStart === "in-memory" || this.fastStart === "fragmented") { this.writer.ensureMonotonicity = true; } } - start() { + async start() { + const release = await this.mutex.acquire(); const holdsAvc = this.output._tracks.some((x) => x.type === "video" && x.source._codec === "avc"); this.boxWriter.writeBox(ftyp({ holdsAvc, @@ -1686,7 +1698,8 @@ var Metamuxer = (() => { this.mdat = mdat(true); this.boxWriter.writeBox(this.mdat); } - this.writer.flush(); + await this.writer.flush(); + release(); } getVideoTrackData(track, meta) { const existingTrackData = this.trackDatas.find((x) => x.track === track); @@ -1790,32 +1803,47 @@ var Metamuxer = (() => { this.validateAndNormalizeTimestamp(track, 0, true); return newTrackData; } - addEncodedVideoChunk(track, chunk, meta) { - const trackData = this.getVideoTrackData(track, meta); - let data = new Uint8Array(chunk.byteLength); - chunk.copyTo(data); - let timestamp = this.validateAndNormalizeTimestamp(trackData.track, chunk.timestamp, chunk.type === "key"); - let sample = this.createSampleForTrack(trackData, data, timestamp, (chunk.duration ?? 0) / 1e6, chunk.type); - this.registerSample(trackData, sample); - } - addEncodedAudioChunk(track, chunk, meta) { - const trackData = this.getAudioTrackData(track, meta); - let data = new Uint8Array(chunk.byteLength); - chunk.copyTo(data); - let timestamp = this.validateAndNormalizeTimestamp(trackData.track, chunk.timestamp, chunk.type === "key"); - let sample = this.createSampleForTrack(trackData, data, timestamp, (chunk.duration ?? 0) / 1e6, chunk.type); - this.registerSample(trackData, sample); - } - addSubtitleCue(track, cue, meta) { - const trackData = this.getSubtitleTrackData(track, meta); - this.validateAndNormalizeTimestamp(trackData.track, 1e6 * cue.timestamp, true); - if (track.source._codec === "webvtt") { - trackData.cueQueue.push(cue); - this.processWebVTTCues(trackData, cue.timestamp); - } else { + async addEncodedVideoChunk(track, chunk, meta) { + const release = await this.mutex.acquire(); + try { + const trackData = this.getVideoTrackData(track, meta); + let data = new Uint8Array(chunk.byteLength); + chunk.copyTo(data); + let timestamp = this.validateAndNormalizeTimestamp(trackData.track, chunk.timestamp, chunk.type === "key"); + let sample = this.createSampleForTrack(trackData, data, timestamp, (chunk.duration ?? 0) / 1e6, chunk.type); + await this.registerSample(trackData, sample); + } finally { + release(); } } - processWebVTTCues(trackData, until) { + async addEncodedAudioChunk(track, chunk, meta) { + const release = await this.mutex.acquire(); + try { + const trackData = this.getAudioTrackData(track, meta); + let data = new Uint8Array(chunk.byteLength); + chunk.copyTo(data); + let timestamp = this.validateAndNormalizeTimestamp(trackData.track, chunk.timestamp, chunk.type === "key"); + let sample = this.createSampleForTrack(trackData, data, timestamp, (chunk.duration ?? 0) / 1e6, chunk.type); + await this.registerSample(trackData, sample); + } finally { + release(); + } + } + async addSubtitleCue(track, cue, meta) { + const release = await this.mutex.acquire(); + try { + const trackData = this.getSubtitleTrackData(track, meta); + this.validateAndNormalizeTimestamp(trackData.track, 1e6 * cue.timestamp, true); + if (track.source._codec === "webvtt") { + trackData.cueQueue.push(cue); + await this.processWebVTTCues(trackData, cue.timestamp); + } else { + } + } finally { + release(); + } + } + async processWebVTTCues(trackData, until) { while (trackData.cueQueue.length > 0) { let timestamps = /* @__PURE__ */ new Set([]); for (let cue of trackData.cueQueue) { @@ -1836,7 +1864,7 @@ var Metamuxer = (() => { this.auxBoxWriter.writeBox(box2); let body2 = this.auxWriter.getSlice(0, this.auxWriter.getPos()); let sample2 = this.createSampleForTrack(trackData, body2, trackData.lastCueEndTimestamp, sampleStart - trackData.lastCueEndTimestamp, "key"); - this.registerSample(trackData, sample2); + await this.registerSample(trackData, sample2); trackData.lastCueEndTimestamp = sampleStart; } this.auxWriter.seek(0); @@ -1865,7 +1893,7 @@ var Metamuxer = (() => { } let body = this.auxWriter.getSlice(0, this.auxWriter.getPos()); let sample = this.createSampleForTrack(trackData, body, sampleStart, sampleEnd - sampleStart, "key"); - this.registerSample(trackData, sample); + await this.registerSample(trackData, sample); trackData.lastCueEndTimestamp = sampleEnd; } } @@ -1953,15 +1981,15 @@ var Metamuxer = (() => { } trackData.timestampProcessingQueue.length = 0; } - registerSample(trackData, sample) { + async registerSample(trackData, sample) { if (this.fastStart === "fragmented") { trackData.sampleQueue.push(sample); - this.interleaveSamples(); + await this.interleaveSamples(); } else { - this.addSampleToTrack(trackData, sample); + await this.addSampleToTrack(trackData, sample); } } - addSampleToTrack(trackData, sample) { + async addSampleToTrack(trackData, sample) { if (sample.type === "key") { this.processTimestamps(trackData); } @@ -1983,7 +2011,7 @@ var Metamuxer = (() => { }); if (currentChunkDuration >= 1 && keyFrameQueuedEverywhere) { beginNewChunk = true; - this.finalizeFragment(); + await this.finalizeFragment(); } } else { beginNewChunk = currentChunkDuration >= 0.5; @@ -1991,7 +2019,7 @@ var Metamuxer = (() => { } if (beginNewChunk) { if (trackData.currentChunk) { - this.finalizeCurrentChunk(trackData); + await this.finalizeCurrentChunk(trackData); } trackData.currentChunk = { startTimestamp: sample.timestamp, @@ -2004,7 +2032,7 @@ var Metamuxer = (() => { trackData.currentChunk.samples.push(sample); trackData.timestampProcessingQueue.push(sample); } - finalizeCurrentChunk(trackData) { + async finalizeCurrentChunk(trackData) { assert(this.fastStart !== "fragmented"); if (!trackData.currentChunk) return; trackData.finalizedChunks.push(trackData.currentChunk); @@ -2026,9 +2054,9 @@ var Metamuxer = (() => { this.writer.write(sample.data); sample.data = null; } - this.writer.flush(); + await this.writer.flush(); } - interleaveSamples() { + async interleaveSamples() { assert(this.fastStart === "fragmented"); for (const track of this.output._tracks) { if (!track.source._closed && !this.trackDatas.some((x) => x.track === track)) { @@ -2052,10 +2080,10 @@ var Metamuxer = (() => { break; } let sample = trackWithMinTimestamp.sampleQueue.shift(); - this.addSampleToTrack(trackWithMinTimestamp, sample); + await this.addSampleToTrack(trackWithMinTimestamp, sample); } } - finalizeFragment(flushWriter = true) { + async finalizeFragment(flushWriter = true) { assert(this.fastStart === "fragmented"); let fragmentNumber = this.nextFragmentNumber++; if (fragmentNumber === 1) { @@ -2101,39 +2129,42 @@ var Metamuxer = (() => { trackData.currentChunk = null; } if (flushWriter) { - this.writer.flush(); + await this.writer.flush(); } } - onTrackClose(track) { + async onTrackClose(track) { + const release = await this.mutex.acquire(); if (track.type === "subtitle" && track.source._codec === "webvtt") { let trackData = this.trackDatas.find((x) => x.track === track); if (trackData) { - this.processWebVTTCues(trackData, Infinity); + await this.processWebVTTCues(trackData, Infinity); } } if (this.fastStart === "fragmented") { - this.interleaveSamples(); + await this.interleaveSamples(); } + release(); } /** Finalizes the file, making it ready for use. Must be called after all video and audio chunks have been added. */ - finalize() { + async finalize() { + const release = await this.mutex.acquire(); for (let trackData of this.trackDatas) { if (trackData.type === "subtitle" && trackData.track.source._codec === "webvtt") { - this.processWebVTTCues(trackData, Infinity); + await this.processWebVTTCues(trackData, Infinity); } } if (this.fastStart === "fragmented") { for (let trackData of this.trackDatas) { for (let sample of trackData.sampleQueue) { - this.addSampleToTrack(trackData, sample); + await this.addSampleToTrack(trackData, sample); } this.processTimestamps(trackData); } - this.finalizeFragment(false); + await this.finalizeFragment(false); } else { for (let trackData of this.trackDatas) { this.processTimestamps(trackData); - this.finalizeCurrentChunk(trackData); + await this.finalizeCurrentChunk(trackData); } } if (this.fastStart === "in-memory") { @@ -2192,6 +2223,7 @@ var Metamuxer = (() => { this.boxWriter.writeBox(movieBox); } } + release(); } }; @@ -2305,7 +2337,7 @@ var Metamuxer = (() => { this.duration = 0; this.writer = output._writer; this.format = format; - if (this.format.options.streamable) { + if (this.format._options.streamable) { this.writer.ensureMonotonicity = true; } } @@ -2465,14 +2497,16 @@ var Metamuxer = (() => { throw new Error("WebM only supports video, audio and subtitle tracks. Switching to MKV removes this restriction."); } } - start() { + async start() { + const release = await this.mutex.acquire(); this.writeEBMLHeader(); - if (!this.format.options.streamable) { + if (!this.format._options.streamable) { this.createSeekHead(); } this.createSegmentInfo(); this.createCues(); - this.writer.flush(); + await this.writer.flush(); + release(); } writeEBMLHeader() { let ebmlHeader = { id: 440786851 /* EBML */, data: [ @@ -2517,7 +2551,7 @@ var Metamuxer = (() => { { id: 2807729 /* TimestampScale */, data: 1e6 }, { id: 19840 /* MuxingApp */, data: APP_NAME }, { id: 22337 /* WritingApp */, data: APP_NAME }, - !this.format.options.streamable ? segmentDuration : null + !this.format._options.streamable ? segmentDuration : null ] }; this.segmentInfo = segmentInfo; } @@ -2570,9 +2604,9 @@ var Metamuxer = (() => { createSegment() { let segment = { id: 408125543 /* Segment */, - size: this.format.options.streamable ? -1 : SEGMENT_SIZE_BYTES, + size: this.format._options.streamable ? -1 : SEGMENT_SIZE_BYTES, data: [ - !this.format.options.streamable ? this.seekHead : null, + !this.format._options.streamable ? this.seekHead : null, this.segmentInfo, this.tracksElement ] @@ -2656,45 +2690,60 @@ var Metamuxer = (() => { this.trackDatas.sort((a, b) => a.track.id - b.track.id); return newTrackData; } - addEncodedVideoChunk(track, chunk, meta) { - const trackData = this.getVideoTrackData(track, meta); - let data = new Uint8Array(chunk.byteLength); - chunk.copyTo(data); - let timestamp = this.validateAndNormalizeTimestamp(trackData.track, chunk.timestamp, chunk.type === "key"); - let videoChunk = this.createInternalChunk(data, timestamp, (chunk.duration ?? 0) / 1e6, chunk.type); - if (track.source._codec === "vp9") this.fixVP9ColorSpace(trackData, videoChunk); - trackData.chunkQueue.push(videoChunk); - this.interleaveChunks(); + async addEncodedVideoChunk(track, chunk, meta) { + const release = await this.mutex.acquire(); + try { + const trackData = this.getVideoTrackData(track, meta); + let data = new Uint8Array(chunk.byteLength); + chunk.copyTo(data); + let timestamp = this.validateAndNormalizeTimestamp(trackData.track, chunk.timestamp, chunk.type === "key"); + let videoChunk = this.createInternalChunk(data, timestamp, (chunk.duration ?? 0) / 1e6, chunk.type); + if (track.source._codec === "vp9") this.fixVP9ColorSpace(trackData, videoChunk); + trackData.chunkQueue.push(videoChunk); + await this.interleaveChunks(); + } finally { + release(); + } } - addEncodedAudioChunk(track, chunk, meta) { - const trackData = this.getAudioTrackData(track, meta); - let data = new Uint8Array(chunk.byteLength); - chunk.copyTo(data); - let timestamp = this.validateAndNormalizeTimestamp(trackData.track, chunk.timestamp, chunk.type === "key"); - let audioChunk = this.createInternalChunk(data, timestamp, (chunk.duration ?? 0) / 1e6, chunk.type); - trackData.chunkQueue.push(audioChunk); - this.interleaveChunks(); + async addEncodedAudioChunk(track, chunk, meta) { + const release = await this.mutex.acquire(); + try { + const trackData = this.getAudioTrackData(track, meta); + let data = new Uint8Array(chunk.byteLength); + chunk.copyTo(data); + let timestamp = this.validateAndNormalizeTimestamp(trackData.track, chunk.timestamp, chunk.type === "key"); + let audioChunk = this.createInternalChunk(data, timestamp, (chunk.duration ?? 0) / 1e6, chunk.type); + trackData.chunkQueue.push(audioChunk); + await this.interleaveChunks(); + } finally { + release(); + } } - addSubtitleCue(track, cue, meta) { - const trackData = this.getSubtitleTrackData(track, meta); - const timestamp = this.validateAndNormalizeTimestamp(trackData.track, 1e6 * cue.timestamp, true); - let bodyText = cue.text; - const timestampMs = Math.floor(timestamp * 1e3); - inlineTimestampRegex.lastIndex = 0; - bodyText = bodyText.replace(inlineTimestampRegex, (match) => { - let time = parseSubtitleTimestamp(match.slice(1, -1)); - let offsetTime = time - timestampMs; - return `<${formatSubtitleTimestamp(offsetTime)}>`; - }); - const body = textEncoder.encode(bodyText); - const additions = `${cue.settings ?? ""} + async addSubtitleCue(track, cue, meta) { + const release = await this.mutex.acquire(); + try { + const trackData = this.getSubtitleTrackData(track, meta); + const timestamp = this.validateAndNormalizeTimestamp(trackData.track, 1e6 * cue.timestamp, true); + let bodyText = cue.text; + const timestampMs = Math.floor(timestamp * 1e3); + inlineTimestampRegex.lastIndex = 0; + bodyText = bodyText.replace(inlineTimestampRegex, (match) => { + let time = parseSubtitleTimestamp(match.slice(1, -1)); + let offsetTime = time - timestampMs; + return `<${formatSubtitleTimestamp(offsetTime)}>`; + }); + const body = textEncoder.encode(bodyText); + const additions = `${cue.settings ?? ""} ${cue.identifier ?? ""} ${cue.notes ?? ""}`; - let subtitleChunk = this.createInternalChunk(body, timestamp, cue.duration, "key", additions.trim() ? textEncoder.encode(additions) : null); - trackData.chunkQueue.push(subtitleChunk); - this.interleaveChunks(); + let subtitleChunk = this.createInternalChunk(body, timestamp, cue.duration, "key", additions.trim() ? textEncoder.encode(additions) : null); + trackData.chunkQueue.push(subtitleChunk); + await this.interleaveChunks(); + } finally { + release(); + } } - interleaveChunks() { + async interleaveChunks() { for (const track of this.output._tracks) { if (!track.source._closed && !this.trackDatas.some((x) => x.track === track)) { return; @@ -2719,7 +2768,7 @@ ${cue.notes ?? ""}`; let chunk = trackWithMinTimestamp.chunkQueue.shift(); this.writeBlock(trackWithMinTimestamp, chunk); } - this.writer.flush(); + await this.writer.flush(); } /** Due to [a bug in Chromium](https://bugs.chromium.org/p/chromium/issues/detail?id=1377842), VP9 streams often * lack color space information. This method patches in that information. */ @@ -2828,12 +2877,12 @@ ${cue.notes ?? ""}`; } /** Creates a new Cluster element to contain media chunks. */ createNewCluster(msTimestamp) { - if (this.currentCluster && !this.format.options.streamable) { + if (this.currentCluster && !this.format._options.streamable) { this.finalizeCurrentCluster(); } this.currentCluster = { id: 524531317 /* Cluster */, - size: this.format.options.streamable ? -1 : CLUSTER_SIZE_BYTES, + size: this.format._options.streamable ? -1 : CLUSTER_SIZE_BYTES, data: [ { id: 231 /* Timestamp */, data: msTimestamp } ] @@ -2862,11 +2911,14 @@ ${cue.notes ?? ""}`; }) ] }); } - onTrackClose() { - this.interleaveChunks(); + async onTrackClose() { + const release = await this.mutex.acquire(); + await this.interleaveChunks(); + release(); } /** Finalizes the file, making it ready for use. Must be called after all media chunks have been added. */ - finalize() { + async finalize() { + const release = await this.mutex.acquire(); if (!this.segment) { this.createTracks(); this.createSegment(); @@ -2876,12 +2928,12 @@ ${cue.notes ?? ""}`; this.writeBlock(trackData, trackData.chunkQueue.shift()); } } - if (!this.format.options.streamable && this.currentCluster) { + if (!this.format._options.streamable && this.currentCluster) { this.finalizeCurrentCluster(); } assert(this.cues); this.writeEBML(this.cues); - if (!this.format.options.streamable) { + if (!this.format._options.streamable) { let endPos = this.writer.getPos(); let segmentSize = this.writer.getPos() - this.segmentDataOffset; this.writer.seek(this.offsets.get(this.segment) + 4); @@ -2896,6 +2948,7 @@ ${cue.notes ?? ""}`; this.writeEBML(this.seekHead); this.writer.seek(endPos); } + release(); } }; @@ -2911,7 +2964,7 @@ ${cue.notes ?? ""}`; throw new TypeError('options.fastStart, when provided, must be false, "in-memory", or "fragmented".'); } super(); - this.options = options; + this._options = options; } /** @internal */ _createMuxer(output) { @@ -2927,7 +2980,7 @@ ${cue.notes ?? ""}`; throw new TypeError("options.streamable, when provided, must be a boolean."); } super(); - this.options = options; + this._options = options; } /** @internal */ _createMuxer(output) { @@ -3008,7 +3061,7 @@ ${cue.notes ?? ""}`; throw new TypeError("chunk must be an EncodedVideoChunk."); } this._ensureValidDigest(); - this._connectedTrack?.output._muxer.addEncodedVideoChunk(this._connectedTrack, chunk, meta); + return this._connectedTrack.output._muxer.addEncodedVideoChunk(this._connectedTrack, chunk, meta); } }; var KEY_FRAME_INTERVAL = 5; @@ -3031,12 +3084,13 @@ ${cue.notes ?? ""}`; this.source = source; this.codecConfig = codecConfig; this.encoder = null; + this.muxer = null; this.lastMultipleOfKeyFrameInterval = -1; this.lastWidth = null; this.lastHeight = null; validateVideoCodecConfig(codecConfig); } - digest(videoFrame) { + async digest(videoFrame) { this.source._ensureValidDigest(); if (this.lastWidth !== null && this.lastHeight !== null) { if (videoFrame.codedWidth !== this.lastWidth || videoFrame.codedHeight !== this.lastHeight) { @@ -3051,13 +3105,17 @@ ${cue.notes ?? ""}`; const multipleOfKeyFrameInterval = Math.floor(videoFrame.timestamp / 1e6 / KEY_FRAME_INTERVAL); this.encoder.encode(videoFrame, { keyFrame: multipleOfKeyFrameInterval !== this.lastMultipleOfKeyFrameInterval }); this.lastMultipleOfKeyFrameInterval = multipleOfKeyFrameInterval; + if (this.encoder.encodeQueueSize >= 4) { + await new Promise((resolve) => this.encoder.addEventListener("dequeue", resolve, { once: true })); + } + await this.muxer.mutex.currentPromise; } ensureEncoder(videoFrame) { if (this.encoder) { return; } this.encoder = new VideoEncoder({ - output: (chunk, meta) => this.source._connectedTrack?.output._muxer.addEncodedVideoChunk(this.source._connectedTrack, chunk, meta), + output: (chunk, meta) => this.muxer.addEncodedVideoChunk(this.source._connectedTrack, chunk, meta), error: (error) => console.error("Video encode error:", error) }); this.encoder.configure({ @@ -3068,9 +3126,14 @@ ${cue.notes ?? ""}`; framerate: this.source._connectedTrack?.metadata.frameRate, latencyMode: this.codecConfig.latencyMode }); + assert(this.source._connectedTrack); + this.muxer = this.source._connectedTrack.output._muxer; } async flush() { - return this.encoder?.flush(); + if (this.encoder) { + await this.encoder.flush(); + this.encoder.close(); + } } }; var VideoFrameSource = class extends VideoSource { @@ -3082,7 +3145,7 @@ ${cue.notes ?? ""}`; if (!(videoFrame instanceof VideoFrame)) { throw new TypeError("videoFrame must be a VideoFrame."); } - this._encoder.digest(videoFrame); + return this._encoder.digest(videoFrame); } /** @internal */ _flush() { @@ -3110,8 +3173,9 @@ ${cue.notes ?? ""}`; duration: Math.round(1e6 * duration), alpha: "discard" }); - this._encoder.digest(frame); + const promise = this._encoder.digest(frame); frame.close(); + return promise; } /** @internal */ _flush() { @@ -3181,7 +3245,7 @@ ${cue.notes ?? ""}`; throw new TypeError("chunk must be an EncodedAudioChunk."); } this._ensureValidDigest(); - this._connectedTrack?.output._muxer.addEncodedAudioChunk(this._connectedTrack, chunk, meta); + return this._connectedTrack.output._muxer.addEncodedAudioChunk(this._connectedTrack, chunk, meta); } }; var validateAudioCodecConfig = (config) => { @@ -3200,11 +3264,12 @@ ${cue.notes ?? ""}`; this.source = source; this.codecConfig = codecConfig; this.encoder = null; + this.muxer = null; this.lastNumberOfChannels = null; this.lastSampleRate = null; validateAudioCodecConfig(codecConfig); } - digest(audioData) { + async digest(audioData) { this.source._ensureValidDigest(); if (this.lastNumberOfChannels !== null && this.lastSampleRate !== null) { if (audioData.numberOfChannels !== this.lastNumberOfChannels || audioData.sampleRate !== this.lastSampleRate) { @@ -3217,13 +3282,17 @@ ${cue.notes ?? ""}`; this.ensureEncoder(audioData); assert(this.encoder); this.encoder.encode(audioData); + if (this.encoder.encodeQueueSize >= 4) { + await new Promise((resolve) => this.encoder.addEventListener("dequeue", resolve, { once: true })); + } + await this.muxer.mutex.currentPromise; } ensureEncoder(audioData) { if (this.encoder) { return; } this.encoder = new AudioEncoder({ - output: (chunk, meta) => this.source._connectedTrack?.output._muxer.addEncodedAudioChunk(this.source._connectedTrack, chunk, meta), + output: (chunk, meta) => this.muxer.addEncodedAudioChunk(this.source._connectedTrack, chunk, meta), error: (error) => console.error("Audio encode error:", error) }); this.encoder.configure({ @@ -3232,9 +3301,14 @@ ${cue.notes ?? ""}`; sampleRate: audioData.sampleRate, bitrate: this.codecConfig.bitrate }); + assert(this.source._connectedTrack); + this.muxer = this.source._connectedTrack.output._muxer; } async flush() { - return this.encoder?.flush(); + if (this.encoder) { + await this.encoder.flush(); + this.encoder.close(); + } } }; var AudioDataSource = class extends AudioSource { @@ -3246,7 +3320,7 @@ ${cue.notes ?? ""}`; if (!(audioData instanceof AudioData)) { throw new TypeError("audioData must be an AudioData."); } - this._encoder.digest(audioData); + return this._encoder.digest(audioData); } /** @internal */ _flush() { @@ -3280,9 +3354,10 @@ ${cue.notes ?? ""}`; timestamp: Math.round(1e6 * this._accumulatedFrameCount / sampleRate), data }); - this._encoder.digest(audioData); + const promise = this._encoder.digest(audioData); audioData.close(); this._accumulatedFrameCount += numberOfFrames; + return promise; } /** @internal */ _flush() { @@ -3297,6 +3372,7 @@ ${cue.notes ?? ""}`; super(codecConfig.codec); /** @internal */ this._abortController = null; + /** @internal */ this._offsetTimestamps = true; this._encoder = new AudioEncoderWrapper(this, codecConfig); this._track = track; @@ -3353,6 +3429,7 @@ ${cue.notes ?? ""}`; } this._ensureValidDigest(); this._parser.parse(text); + return this._connectedTrack.output._muxer.mutex.currentPromise; } }; @@ -3365,6 +3442,8 @@ ${cue.notes ?? ""}`; this._started = false; /** @internal */ this._finalizing = false; + /** @internal */ + this._mutex = new AsyncMutex(); if (!options || typeof options !== "object") { throw new TypeError("options must be an object."); } @@ -3374,10 +3453,10 @@ ${cue.notes ?? ""}`; if (!(options.target instanceof Target)) { throw new TypeError("options.target must be a Target."); } - if (options.target.output) { + if (options.target._output) { throw new Error("Target is already used for another output."); } - options.target.output = this; + options.target._output = this; this._writer = options.target._createWriter(); this._muxer = options.format._createMuxer(this); } @@ -3437,26 +3516,34 @@ ${cue.notes ?? ""}`; this._tracks.push(track); source._connectedTrack = track; } - start() { + async start() { if (this._started) { throw new Error("Output already started."); } this._started = true; - this._muxer.start(); + this._writer.start(); + const release = await this._mutex.acquire(); + await this._muxer.start(); for (const track of this._tracks) { track.source._start(); } + release(); } async finalize() { + if (!this._started) { + throw new Error("Cannot finalize before starting."); + } if (this._finalizing) { throw new Error("Cannot call finalize twice."); } this._finalizing = true; + const release = await this._mutex.acquire(); const promises = this._tracks.map((x) => x.source._flush()); await Promise.all(promises); - this._muxer.finalize(); - this._writer.flush(); - this._writer.finalize(); + await this._muxer.finalize(); + await this._writer.flush(); + await this._writer.finalize(); + release(); } }; return __toCommonJS(src_exports); diff --git a/dist/metamuxer.min.js b/dist/metamuxer.min.js index 4dd1785..fd098e5 100644 --- a/dist/metamuxer.min.js +++ b/dist/metamuxer.min.js @@ -1,10 +1,10 @@ -"use strict";var Metamuxer=(()=>{var De=Object.defineProperty;var ct=Object.getOwnPropertyDescriptor;var dt=Object.getOwnPropertyNames;var ft=Object.prototype.hasOwnProperty;var mt=(t,i)=>{for(var e in i)De(t,e,{get:i[e],enumerable:!0})},pt=(t,i,e,r)=>{if(i&&typeof i=="object"||typeof i=="function")for(let s of dt(i))!ft.call(t,s)&&s!==e&&De(t,s,{get:()=>i[s],enumerable:!(r=ct(i,s))||r.enumerable});return t};var ht=t=>pt(De({},"__esModule",{value:!0}),t);var yr={};mt(yr,{AUDIO_CODECS:()=>Y,ArrayBufferTarget:()=>K,AudioBufferSource:()=>Ve,AudioDataSource:()=>Oe,AudioSource:()=>E,CanvasSource:()=>Ae,EncodedAudioChunkSource:()=>Ee,EncodedVideoChunkSource:()=>xe,FileSystemWritableFileStreamTarget:()=>pe,MediaSource:()=>W,MediaStreamAudioTrackSource:()=>Me,MediaStreamVideoTrackSource:()=>_e,MkvOutputFormat:()=>oe,Mp4OutputFormat:()=>ye,Output:()=>ze,OutputFormat:()=>M,SUBTITLE_CODECS:()=>Se,StreamTarget:()=>L,SubtitleSource:()=>R,Target:()=>O,TextSubtitleSource:()=>Ie,VIDEO_CODECS:()=>q,VideoFrameSource:()=>ve,VideoSource:()=>_,WebMOutputFormat:()=>P});function c(t){if(!t)throw new Error("Assertion failed.")}var F=t=>t&&t[t.length-1],I=t=>t>=0&&t<2**32,z=(t,i,e)=>{let r=0;for(let s=i;s>a;r<<=1,r|=u}return r},je=(t,i,e,r)=>{for(let s=i;s>e-s-1<t instanceof ArrayBuffer?new Uint8Array(t):new Uint8Array(t.buffer,t.byteOffset,t.byteLength),S=new TextEncoder,N={bt709:1,bt470bg:5,smpte170m:6},H={bt709:1,smpte170m:6,"iec61966-2-1":13},$={rgb:0,bt709:1,bt470bg:5,smpte170m:6},ue=t=>!!t&&!!t.primaries&&!!t.transfer&&!!t.matrix&&t.fullRange!==void 0,Ue=t=>t instanceof ArrayBuffer||typeof SharedArrayBuffer<"u"&&t instanceof SharedArrayBuffer||ArrayBuffer.isView(t)&&!(t instanceof DataView);var Z=/(?:(.+?)\n)?((?:\d{2}:)?\d{2}:\d{2}.\d{3})\s+-->\s+((?:\d{2}:)?\d{2}:\d{2}.\d{3})/g,bt=/^WEBVTT(.|\n)*?\n{2}/,j=/<(?:(\d{2}):)?(\d{2}):(\d{2}).(\d{3})>/g,le=class{constructor(i){this.preambleText=null;this.preambleEmitted=!1;this.options=i}parse(i){i=i.replaceAll(`\r +"use strict";var Metamuxer=(()=>{var ze=Object.defineProperty;var lt=Object.getOwnPropertyDescriptor;var ct=Object.getOwnPropertyNames;var dt=Object.prototype.hasOwnProperty;var mt=(t,i)=>{for(var e in i)ze(t,e,{get:i[e],enumerable:!0})},ft=(t,i,e,r)=>{if(i&&typeof i=="object"||typeof i=="function")for(let s of ct(i))!dt.call(t,s)&&s!==e&&ze(t,s,{get:()=>i[s],enumerable:!(r=lt(i,s))||r.enumerable});return t};var pt=t=>ft(ze({},"__esModule",{value:!0}),t);var kr={};mt(kr,{AUDIO_CODECS:()=>Y,ArrayBufferTarget:()=>L,AudioBufferSource:()=>Ee,AudioDataSource:()=>Oe,AudioSource:()=>O,CanvasSource:()=>ve,EncodedAudioChunkSource:()=>_e,EncodedVideoChunkSource:()=>xe,MediaSource:()=>W,MediaStreamAudioTrackSource:()=>Ve,MediaStreamVideoTrackSource:()=>Ae,MkvOutputFormat:()=>se,Mp4OutputFormat:()=>ke,Output:()=>Ie,OutputFormat:()=>M,SUBTITLE_CODECS:()=>ye,StreamTarget:()=>he,SubtitleSource:()=>R,Target:()=>V,TextSubtitleSource:()=>Me,VIDEO_CODECS:()=>G,VideoFrameSource:()=>Se,VideoSource:()=>_,WebMOutputFormat:()=>D});function c(t){if(!t)throw new Error("Assertion failed.")}var N=t=>t&&t[t.length-1],I=t=>t>=0&&t<2**32,z=(t,i,e)=>{let r=0;for(let s=i;s>n;r<<=1,r|=u}return r},Qe=(t,i,e,r)=>{for(let s=i;s>e-s-1<t instanceof ArrayBuffer?new Uint8Array(t):new Uint8Array(t.buffer,t.byteOffset,t.byteLength),x=new TextEncoder,H={bt709:1,bt470bg:5,smpte170m:6},Q={bt709:1,smpte170m:6,"iec61966-2-1":13},$={rgb:0,bt709:1,bt470bg:5,smpte170m:6},ne=t=>!!t&&!!t.primaries&&!!t.transfer&&!!t.matrix&&t.fullRange!==void 0,Pe=t=>t instanceof ArrayBuffer||typeof SharedArrayBuffer<"u"&&t instanceof SharedArrayBuffer||ArrayBuffer.isView(t)&&!(t instanceof DataView),B=class{constructor(){this.currentPromise=Promise.resolve()}async acquire(){let i,e=new Promise(s=>{i=s}),r=this.currentPromise;return this.currentPromise=e,await r,i}};var Z=/(?:(.+?)\n)?((?:\d{2}:)?\d{2}:\d{2}.\d{3})\s+-->\s+((?:\d{2}:)?\d{2}:\d{2}.\d{3})/g,ht=/^WEBVTT(.|\n)*?\n{2}/,j=/<(?:(\d{2}):)?(\d{2}):(\d{2}).(\d{3})>/g,ue=class{constructor(i){this.preambleText=null;this.preambleEmitted=!1;this.options=i}parse(i){i=i.replaceAll(`\r `,` `).replaceAll("\r",` -`),Z.lastIndex=0;let e;if(!this.preambleText){if(!bt.test(i)){let s=new Error("WebVTT preamble incorrect.");throw this.options.error(s),s}e=Z.exec(i);let r=i.slice(0,e?.index??i.length).trimEnd();if(!r){let s=new Error("No WebVTT preamble provided.");throw this.options.error(s),s}this.preambleText=r,e&&(i=i.slice(e.index),Z.lastIndex=0)}for(;e=Z.exec(i);){let r=i.slice(0,e.index),s=e[1],o=e.index+e[0].length,n=i.indexOf(` -`,o)+1,a=i.slice(o,n).trim(),u=i.indexOf(` +`),Z.lastIndex=0;let e;if(!this.preambleText){if(!ht.test(i)){let s=new Error("WebVTT preamble incorrect.");throw this.options.error(s),s}e=Z.exec(i);let r=i.slice(0,e?.index??i.length).trimEnd();if(!r){let s=new Error("No WebVTT preamble provided.");throw this.options.error(s),s}this.preambleText=r,e&&(i=i.slice(e.index),Z.lastIndex=0)}for(;e=Z.exec(i);){let r=i.slice(0,e.index),s=e[1],o=e.index+e[0].length,a=i.indexOf(` +`,o)+1,n=i.slice(o,a).trim(),u=i.indexOf(` -`,o);u===-1&&(u=i.length);let d=ce(e[2]),m=ce(e[3])-d,C=i.slice(n,u).trim();i=i.slice(u).trimStart(),Z.lastIndex=0;let V={timestamp:d/1e3,duration:m/1e3,text:C,identifier:s,settings:a,notes:r},k={};this.preambleEmitted||(k.config={description:this.preambleText},this.preambleEmitted=!0),this.options.output(V,k)}}},Tt=/(?:(\d{2}):)?(\d{2}):(\d{2}).(\d{3})/,ce=t=>{let i=Tt.exec(t);if(!i)throw new Error("Expected match.");return 60*60*1e3*Number(i[1]||"0")+60*1e3*Number(i[2])+1e3*Number(i[3])+Number(i[4])},de=t=>{let i=Math.floor(t/36e5),e=Math.floor(t%(60*60*1e3)/(60*1e3)),r=Math.floor(t%(60*1e3)/1e3),s=t%1e3;return i.toString().padStart(2,"0")+":"+e.toString().padStart(2,"0")+":"+r.toString().padStart(2,"0")+"."+s.toString().padStart(3,"0")};var J=class{constructor(i){this.writer=i;this.helper=new Uint8Array(8);this.helperView=new DataView(this.helper.buffer);this.offsets=new WeakMap}writeU32(i){this.helperView.setUint32(0,i,!1),this.writer.write(this.helper.subarray(0,4))}writeU64(i){this.helperView.setUint32(0,Math.floor(i/2**32),!1),this.helperView.setUint32(4,i,!1),this.writer.write(this.helper.subarray(0,8))}writeAscii(i){for(let e=0;e[(t%256+256)%256],h=t=>(A.setUint16(0,t,!1),[p[0],p[1]]),Ct=t=>(A.setInt16(0,t,!1),[p[0],p[1]]),Ke=t=>(A.setUint32(0,t,!1),[p[1],p[2],p[3]]),l=t=>(A.setUint32(0,t,!1),[p[0],p[1],p[2],p[3]]),Le=t=>(A.setInt32(0,t,!1),[p[0],p[1],p[2],p[3]]),U=t=>(A.setUint32(0,Math.floor(t/2**32),!1),A.setUint32(4,t,!1),[p[0],p[1],p[2],p[3],p[4],p[5],p[6],p[7]]),Re=t=>(A.setInt16(0,2**8*t,!1),[p[0],p[1]]),v=t=>(A.setInt32(0,2**16*t,!1),[p[0],p[1],p[2],p[3]]),Pe=t=>(A.setInt32(0,2**30*t,!1),[p[0],p[1],p[2],p[3]]),We=(t,i)=>{let e=[],r=t;do{let s=r&127;r>>=7,e.length>0&&(s|=128),e.push(s),i!==void 0&&i--}while(r>0||i);return e.reverse()},w=(t,i=!1)=>{let e=Array(t.length).fill(null).map((r,s)=>t.charCodeAt(s));return i&&e.push(0),e},Be=t=>{let i=null;for(let e of t)(!i||e.timestamp>i.timestamp)&&(i=e);return i},Xe=t=>{let i=t*(Math.PI/180),e=Math.cos(i),r=Math.sin(i);return[e,r,0,-r,e,0,0,0,1]},Ge=Xe(0),qe=t=>[v(t[0]),v(t[1]),Pe(t[2]),v(t[3]),v(t[4]),Pe(t[5]),v(t[6]),v(t[7]),Pe(t[8])],b=(t,i,e)=>({type:t,contents:i&&new Uint8Array(i.flat(10)),children:e}),T=(t,i,e,r,s)=>b(t,[g(i),Ke(e),r??[]],s),Ye=t=>{let i=512;return t.fragmented?b("ftyp",[w("iso5"),l(i),w("iso5"),w("iso6"),w("mp41")]):b("ftyp",[w("isom"),l(i),w("isom"),t.holdsAvc?w("avc1"):[],w("mp41")])},me=t=>({type:"mdat",largeSize:t}),Ze=t=>({type:"free",size:t}),ee=(t,i,e=!1)=>b("moov",void 0,[gt(i,t),...t.map(r=>kt(r,i)),e?Jt(t):null]),gt=(t,i)=>{let e=x(Math.max(0,...i.filter(n=>n.samples.length>0).map(n=>{let a=Be(n.samples);return a.timestamp+a.duration})),fe),r=Math.max(0,...i.map(n=>n.track.id))+1,s=!I(t)||!I(e),o=s?U:l;return T("mvhd",+s,0,[o(t),o(t),l(fe),o(e),v(1),Re(1),Array(10).fill(0),qe(Ge),Array(24).fill(0),l(r)])},kt=(t,i)=>b("trak",void 0,[wt(t,i),yt(t,i)]),wt=(t,i)=>{let e=Be(t.samples),r=x(e?e.timestamp+e.duration:0,fe),s=!I(i)||!I(r),o=s?U:l,n;if(t.type==="video"){let a=t.track.metadata.rotation;n=a===void 0||typeof a=="number"?Xe(a??0):a}else n=Ge;return T("tkhd",+s,3,[o(i),o(i),l(t.track.id),l(0),o(r),Array(8).fill(0),h(0),h(t.track.id),Re(t.type==="audio"?1:0),h(0),qe(n),v(t.type==="video"?t.info.width:0),v(t.type==="video"?t.info.height:0)])},yt=(t,i)=>b("mdia",void 0,[St(t,i),At(t),_t(t)]),St=(t,i)=>{let e=Be(t.samples),r=x(e?e.timestamp+e.duration:0,t.timescale),s=!I(i)||!I(r),o=s?U:l;return T("mdhd",+s,0,[o(i),o(i),l(t.timescale),o(r),h(21956),h(0)])},xt={video:"vide",audio:"soun",subtitle:"text"},vt={video:"VideoHandler",audio:"SoundHandler",subtitle:"TextHandler"},At=t=>T("hdlr",0,0,[w("mhlr"),w(xt[t.type]),l(0),l(0),l(0),w(vt[t.type],!0)]),_t=t=>b("minf",void 0,[Mt[t.type](),It(),Ut(t)]),Et=()=>T("vmhd",0,1,[h(0),h(0),h(0),h(0)]),Ot=()=>T("smhd",0,0,[h(0),h(0)]),Vt=()=>T("nmhd",0,0),Mt={video:Et,audio:Ot,subtitle:Vt},It=()=>b("dinf",void 0,[zt()]),zt=()=>T("dref",0,0,[l(1)],[Dt()]),Dt=()=>T("url ",0,1),Ut=t=>{let i=t.compositionTimeOffsetTable.length>1||t.compositionTimeOffsetTable.some(e=>e.sampleCompositionTimeOffset!==0);return b("stbl",void 0,[Pt(t),Lt(t),Xt(t),Gt(t),qt(t),Yt(t),i?Zt(t):null])},Pt=t=>{let i;return t.type==="video"?i=Wt(ur[t.track.source._codec],t):t.type==="audio"?i=Ht(cr[t.track.source._codec],t):t.type==="subtitle"&&(i=Qt(fr[t.track.source._codec],t)),c(i),T("stsd",0,0,[l(1)],[i])},Wt=(t,i)=>b(t,[Array(6).fill(0),h(1),h(0),h(0),Array(12).fill(0),h(i.info.width),h(i.info.height),l(4718592),l(4718592),l(0),h(1),Array(32).fill(0),h(24),Ct(65535)],[lr[i.track.source._codec](i),ue(i.info.decoderConfig.colorSpace)?Rt(i):null]),Rt=t=>b("colr",[w("nclx"),h(N[t.info.decoderConfig.colorSpace.primaries]),h(H[t.info.decoderConfig.colorSpace.transfer]),h($[t.info.decoderConfig.colorSpace.matrix]),g((t.info.decoderConfig.colorSpace.fullRange?1:0)<<7)]),Bt=t=>t.info.decoderConfig&&b("avcC",[...D(t.info.decoderConfig.description)]),Ft=t=>t.info.decoderConfig&&b("hvcC",[...D(t.info.decoderConfig.description)]),Qe=t=>{if(!t.info.decoderConfig)return null;let i=t.info.decoderConfig;c(i.colorSpace);let e=i.codec.split("."),r=Number(e[1]),s=Number(e[2]),a=(Number(e[3])<<4)+(0<<1)+Number(i.colorSpace.fullRange);return T("vpcC",1,0,[g(r),g(s),g(a),g(2),g(2),g(2),h(0)])},Nt=()=>{let e=(1<<7)+1;return b("av1C",[e,0,0,0])},Ht=(t,i)=>b(t,[Array(6).fill(0),h(1),h(0),h(0),l(0),h(i.info.numberOfChannels),h(16),h(0),h(0),v(i.info.sampleRate)],[dr[i.track.source._codec](i)]),$t=t=>{let e=[...D(t.info.decoderConfig.description??new ArrayBuffer(0))];return e=[...g(64),...g(21),...Ke(0),...l(0),...l(0),...g(5),...We(e.length),...e],e=[...h(1),...g(0),...g(4),...We(e.length),...e,...g(6),...g(1),...g(2)],e=[...g(3),...We(e.length),...e],T("esds",0,0,e)},jt=t=>{let i=3840,e=0,r=t.info.decoderConfig?.description;if(r){c(r.byteLength<18);let s=ArrayBuffer.isView(r)?new DataView(r.buffer,r.byteOffset,r.byteLength):new DataView(r);i=s.getUint16(10,!0),e=s.getInt16(14,!0)}return b("dOps",[g(0),g(t.info.numberOfChannels),h(i),l(t.info.sampleRate),Re(e),g(0)])},Qt=(t,i)=>b(t,[Array(6).fill(0),h(1)],[mr[i.track.source._codec](i)]),Kt=t=>b("vttC",[...S.encode(t.info.config.description)]);var Lt=t=>T("stts",0,0,[l(t.timeToSampleTable.length),t.timeToSampleTable.map(i=>[l(i.sampleCount),l(i.sampleDelta)])]),Xt=t=>{if(t.samples.every(e=>e.type==="key"))return null;let i=[...t.samples.entries()].filter(([,e])=>e.type==="key");return T("stss",0,0,[l(i.length),i.map(([e])=>l(e+1))])},Gt=t=>T("stsc",0,0,[l(t.compactlyCodedChunkTable.length),t.compactlyCodedChunkTable.map(i=>[l(i.firstChunk),l(i.samplesPerChunk),l(1)])]),qt=t=>T("stsz",0,0,[l(0),l(t.samples.length),t.samples.map(i=>l(i.size))]),Yt=t=>t.finalizedChunks.length>0&&F(t.finalizedChunks).offset>=2**32?T("co64",0,0,[l(t.finalizedChunks.length),t.finalizedChunks.map(i=>U(i.offset))]):T("stco",0,0,[l(t.finalizedChunks.length),t.finalizedChunks.map(i=>l(i.offset))]),Zt=t=>T("ctts",0,0,[l(t.compositionTimeOffsetTable.length),t.compositionTimeOffsetTable.map(i=>[l(i.sampleCount),l(i.sampleCompositionTimeOffset)])]),Jt=t=>b("mvex",void 0,t.map(er)),er=t=>T("trex",0,0,[l(t.track.id),l(1),l(0),l(0),l(0)]),Fe=(t,i)=>b("moof",void 0,[tr(t),...i.map(rr)]),tr=t=>T("mfhd",0,0,[l(t)]),Je=t=>{let i=0,e=0,r=0,s=0,o=t.type==="delta";return e|=+o,o?i|=1:i|=2,i<<24|e<<16|r<<8|s},rr=t=>b("traf",void 0,[ir(t),sr(t),or(t)]),ir=t=>{c(t.currentChunk);let i=0;i|=8,i|=16,i|=32,i|=131072;let e=t.currentChunk.samples[1]??t.currentChunk.samples[0],r={duration:e.timescaleUnitsToNextSample,size:e.size,flags:Je(e)};return T("tfhd",0,i,[l(t.track.id),l(r.duration),l(r.size),l(r.flags)])},sr=t=>(c(t.currentChunk),T("tfdt",1,0,[U(x(t.currentChunk.startTimestamp,t.timescale))])),or=t=>{c(t.currentChunk);let i=t.currentChunk.samples.map(y=>y.timescaleUnitsToNextSample),e=t.currentChunk.samples.map(y=>y.size),r=t.currentChunk.samples.map(Je),s=t.currentChunk.samples.map(y=>x(y.timestamp-y.decodeTimestamp,t.timescale)),o=new Set(i),n=new Set(e),a=new Set(r),u=new Set(s),d=a.size===2&&r[0]!==r[1],f=o.size>1,m=n.size>1,C=!d&&a.size>1,V=u.size>1||[...u].some(y=>y!==0),k=0;return k|=1,k|=4*+d,k|=256*+f,k|=512*+m,k|=1024*+C,k|=2048*+V,T("trun",1,k,[l(t.currentChunk.samples.length),l(t.currentChunk.offset-t.currentChunk.moofOffset||0),d?l(r[0]):[],t.currentChunk.samples.map((y,B)=>[f?l(i[B]):[],m?l(e[B]):[],C?l(r[B]):[],V?Le(s[B]):[]])])},et=t=>b("mfra",void 0,[...t.map(nr),ar()]),nr=(t,i)=>T("tfra",1,0,[l(t.track.id),l(63),l(t.finalizedChunks.length),t.finalizedChunks.map(r=>[U(x(r.startTimestamp,t.timescale)),U(r.moofOffset),l(i+1),l(1),l(1)])]),ar=()=>T("mfro",0,0,[l(0)]),tt=()=>b("vtte"),rt=(t,i,e,r,s)=>b("vttc",void 0,[s!==null?b("vsid",[Le(s)]):null,e!==null?b("iden",[...S.encode(e)]):null,i!==null?b("ctim",[...S.encode(de(i))]):null,r!==null?b("sttg",[...S.encode(r)]):null,b("payl",[...S.encode(t)])]),it=t=>b("vtta",[...S.encode(t)]),ur={avc:"avc1",hevc:"hvc1",vp8:"vp08",vp9:"vp09",av1:"av01"},lr={avc:Bt,hevc:Ft,vp8:Qe,vp9:Qe,av1:Nt},cr={aac:"mp4a",opus:"Opus"},dr={aac:$t,opus:jt},fr={webvtt:"wvtt"},mr={webvtt:Kt};var Q=class{constructor(i){this.trackTimestampInfo=new WeakMap;this.output=i}beforeTrackAdd(i){}onTrackClose(i){}validateAndNormalizeTimestamp(i,e,r){let s=e/1e6,o=this.trackTimestampInfo.get(i);if(!o){if(!r)throw new Error("First frame must be a key frame.");if(this.timestampsMustStartAtZero&&s>0)throw new Error(`Timestamps must start at zero (got ${s}s).`);o={timestampOffset:s,maxTimestamp:i.source._offsetTimestamps?0:s,lastKeyFrameTimestamp:i.source._offsetTimestamps?0:s},this.trackTimestampInfo.set(i,o)}if(i.source._offsetTimestamps&&(s-=o.timestampOffset),s<0)throw new Error(`Timestamps must be non-negative (got ${s}s).`);if(ss.start-o.start);e.push({start:r[0].start,size:r[0].data.byteLength});for(let s=1;sd.start<=r&&rhr){for(let d=0;d=e.written[n+1].start;)e.written[n].end=Math.max(e.written[n].end,e.written[n+1].end),e.written.splice(n+1,1)}createChunk(e){let s={start:Math.floor(e/this.chunkSize)*this.chunkSize,data:new Uint8Array(this.chunkSize),written:[],shouldFlush:!1};return this.chunks.push(s),this.chunks.sort((o,n)=>o.start-n.start),this.chunks.indexOf(s)}flushChunks(e=!1){for(let r=0;ri.stream.write({type:"write",data:e,position:r}),chunkSize:i.options?.chunkSize}))}};var st=(t,i,e)=>{if(t==="avc"){let r=100;i<=768&&e<=432?r=66:i<=1920&&e<=1080&&(r=77);let s=0,o=i>1920||e>1080?50:41,n=r.toString(16).padStart(2,"0"),a=s.toString(16).padStart(2,"0"),u=o.toString(16).padStart(2,"0");return`avc1.${n}${a}${u}`}else if(t==="hevc"){let r=0,s=1,o=Array(32).fill(0);o[s]=1;let n=parseInt(o.reverse().join(""),2).toString(16).replace(/^0+/,""),a="L",u=120;return i<=1280&&e<=720?u=93:i<=1920&&e<=1080?u=120:i<=3840&&e<=2160?u=150:(a="H",u=180),`hev1.${r===0?"":String.fromCharCode(65+r-1)}${s}.${n}.${a}${u}.B0`}else{if(t==="vp8")return"vp8";if(t==="vp9"){let r="00",s;return i<=854&&e<=480?s="21":i<=1280&&e<=720?s="31":i<=1920&&e<=1080?s="41":i<=3840&&e<=2160?s="51":s="61",`vp09.${r}.${s}.08`}else if(t==="av1"){let s;return i<=854&&e<=480?s="01":i<=1280&&e<=720?s="03":i<=1920&&e<=1080?s="04":i<=3840&&e<=2160?s="07":s="09",`av01.0.${s}M.08`}}throw new TypeError(`Unhandled codec '${t}'.`)},ot=(t,i,e)=>{if(t==="aac")return i>=2&&e<=24e3?"mp4a.40.29":e<=24e3?"mp4a.40.5":"mp4a.40.2";if(t==="opus")return"opus";if(t==="vorbis")return"vorbis";throw new TypeError(`Unhandled codec '${t}'.`)},Te=t=>{if(!t)throw new TypeError("Video chunk metadata must be provided.");if(typeof t!="object")throw new TypeError("Video chunk metadata must be an object.");if(!t.decoderConfig)throw new TypeError("Video chunk metadata must include a decoder configuration.");if(typeof t.decoderConfig!="object")throw new TypeError("Video chunk metadata decoder configuration must be an object.");if(typeof t.decoderConfig.codec!="string")throw new TypeError("Video chunk metadata decoder configuration must specify a codec string.");if(!Number.isInteger(t.decoderConfig.codedWidth)||t.decoderConfig.codedWidth<=0)throw new TypeError("Video chunk metadata decoder configuration must specify a valid codedWidth (positive integer).");if(!Number.isInteger(t.decoderConfig.codedHeight)||t.decoderConfig.codedHeight<=0)throw new TypeError("Video chunk metadata decoder configuration must specify a valid codedHeight (positive integer).");if(t.decoderConfig.description!==void 0&&!Ue(t.decoderConfig.description))throw new TypeError("Video chunk metadata decoder configuration description, when defined, must be an ArrayBuffer or an ArrayBuffer view.");if(t.decoderConfig.colorSpace!==void 0){let{colorSpace:i}=t.decoderConfig;if(typeof i!="object")throw new TypeError("Video chunk metadata decoder configuration colorSpace, when provided, must be an object.");let e=Object.keys(N);if(i.primaries!=null&&!e.includes(i.primaries))throw new TypeError(`Video chunk metadata decoder configuration colorSpace primaries, when defined, must be one of ${e.join(", ")}.`);let r=Object.keys(H);if(i.transfer!=null&&!r.includes(i.transfer))throw new TypeError(`Video chunk metadata decoder configuration colorSpace transfer, when defined, must be one of ${r.join(", ")}.`);let s=Object.keys($);if(i.matrix!=null&&!s.includes(i.matrix))throw new TypeError(`Video chunk metadata decoder configuration colorSpace matrix, when defined, must be one of ${s.join(", ")}.`);if(i.fullRange!=null&&typeof i.fullRange!="boolean")throw new TypeError("Video chunk metadata decoder configuration colorSpace fullRange, when defined, must be a boolean.")}if((t.decoderConfig.codec.startsWith("avc1")||t.decoderConfig.codec.startsWith("avc3"))&&!t.decoderConfig.description)throw new TypeError("Video chunk metadata decoder configuration for AVC must include a description, which is expected to be an AVCDecoderConfigurationRecord as specified in ISO 14496-15.");if((t.decoderConfig.codec.startsWith("hev1")||t.decoderConfig.codec.startsWith("hvc1"))&&!t.decoderConfig.description)throw new TypeError("Video chunk metadata decoder configuration for HEVC must include a description, which is expected to be an HEVCDecoderConfigurationRecord as specified in ISO 14496-15.");if((t.decoderConfig.codec==="vp8"||t.decoderConfig.codec.startsWith("vp09"))&&t.decoderConfig.colorSpace===void 0)throw new TypeError("Video chunk metadata decoder configuration for VP8/VP9 must include a colorSpace.")},Ce=t=>{if(!t)throw new TypeError("Audio chunk metadata must be provided.");if(typeof t!="object")throw new TypeError("Audio chunk metadata must be an object.");if(!t.decoderConfig)throw new TypeError("Audio chunk metadata must include a decoder configuration.");if(typeof t.decoderConfig!="object")throw new TypeError("Audio chunk metadata decoder configuration must be an object.");if(typeof t.decoderConfig.codec!="string")throw new TypeError("Audio chunk metadata decoder configuration must specify a codec string.");if(!Number.isInteger(t.decoderConfig.sampleRate)||t.decoderConfig.sampleRate<=0)throw new TypeError("Audio chunk metadata decoder configuration must specify a valid sampleRate (positive integer).");if(!Number.isInteger(t.decoderConfig.numberOfChannels)||t.decoderConfig.numberOfChannels<=0)throw new TypeError("Audio chunk metadata decoder configuration must specify a valid numberOfChannels (positive integer).");if(t.decoderConfig.description!==void 0&&!Ue(t.decoderConfig.description))throw new TypeError("Audio chunk metadata decoder configuration description, when defined, must be an ArrayBuffer or an ArrayBuffer view.");if(t.decoderConfig.codec==="opus"&&t.decoderConfig.description&&t.decoderConfig.description.byteLength<18)throw new TypeError("Invalid decoder description provided for Opus; must be at least 18 bytes long.")},ge=t=>{if(!t)throw new TypeError("Subtitle metadata must be provided.");if(typeof t!="object")throw new TypeError("Subtitle metadata must be an object.");if(!t.config)throw new TypeError("Subtitle metadata must include a config object.");if(typeof t.config!="object")throw new TypeError("Subtitle metadata config must be an object.");if(typeof t.config.description!="string")throw new TypeError("Subtitle metadata config description must be a string.")};var fe=1e3,br=2082844800,x=(t,i,e=!0)=>{let r=t*i;return e?Math.round(r):r},ke=class extends Q{constructor(e,r){super(e);this.timestampsMustStartAtZero=!0;this.auxTarget=new K;this.auxWriter=this.auxTarget._createWriter();this.auxBoxWriter=new J(this.auxWriter);this.ftypSize=null;this.mdat=null;this.trackDatas=[];this.creationTime=Math.floor(Date.now()/1e3)+br;this.finalizedChunks=[];this.nextFragmentNumber=1;this.writer=e._writer,this.boxWriter=new J(this.writer),this.fastStart=r.options.fastStart??(this.writer instanceof X?"in-memory":!1),(this.fastStart==="in-memory"||this.fastStart==="fragmented")&&(this.writer.ensureMonotonicity=!0)}start(){let e=this.output._tracks.some(r=>r.type==="video"&&r.source._codec==="avc");this.boxWriter.writeBox(Ye({holdsAvc:e,fragmented:this.fastStart==="fragmented"})),this.ftypSize=this.writer.getPos(),this.fastStart==="in-memory"?this.mdat=me(!1):this.fastStart==="fragmented"||(this.mdat=me(!0),this.boxWriter.writeBox(this.mdat)),this.writer.flush()}getVideoTrackData(e,r){let s=this.trackDatas.find(n=>n.track===e);if(s)return s;Te(r),c(r),c(r.decoderConfig),c(r.decoderConfig.codedWidth!==void 0),c(r.decoderConfig.codedHeight!==void 0);let o={track:e,type:"video",info:{width:r.decoderConfig.codedWidth,height:r.decoderConfig.codedHeight,decoderConfig:r.decoderConfig},timescale:e.metadata.frameRate??57600,samples:[],sampleQueue:[],timestampProcessingQueue:[],timeToSampleTable:[],compositionTimeOffsetTable:[],lastTimescaleUnits:null,lastSample:null,finalizedChunks:[],currentChunk:null,compactlyCodedChunkTable:[]};return this.trackDatas.push(o),this.trackDatas.sort((n,a)=>n.track.id-a.track.id),o}getAudioTrackData(e,r){let s=this.trackDatas.find(n=>n.track===e);if(s)return s;Ce(r),c(r),c(r.decoderConfig);let o={track:e,type:"audio",info:{numberOfChannels:r.decoderConfig.numberOfChannels,sampleRate:r.decoderConfig.sampleRate,decoderConfig:r.decoderConfig},timescale:r.decoderConfig.sampleRate,samples:[],sampleQueue:[],timestampProcessingQueue:[],timeToSampleTable:[],compositionTimeOffsetTable:[],lastTimescaleUnits:null,lastSample:null,finalizedChunks:[],currentChunk:null,compactlyCodedChunkTable:[]};return this.trackDatas.push(o),this.trackDatas.sort((n,a)=>n.track.id-a.track.id),o}getSubtitleTrackData(e,r){let s=this.trackDatas.find(n=>n.track===e);if(s)return s;ge(r),c(r),c(r.config);let o={track:e,type:"subtitle",info:{config:r.config},timescale:1e3,samples:[],sampleQueue:[],timestampProcessingQueue:[],timeToSampleTable:[],compositionTimeOffsetTable:[],lastTimescaleUnits:null,lastSample:null,finalizedChunks:[],currentChunk:null,compactlyCodedChunkTable:[],lastCueEndTimestamp:0,cueQueue:[],nextSourceId:0,cueToSourceId:new WeakMap};return this.trackDatas.push(o),this.trackDatas.sort((n,a)=>n.track.id-a.track.id),this.validateAndNormalizeTimestamp(e,0,!0),o}addEncodedVideoChunk(e,r,s){let o=this.getVideoTrackData(e,s),n=new Uint8Array(r.byteLength);r.copyTo(n);let a=this.validateAndNormalizeTimestamp(o.track,r.timestamp,r.type==="key"),u=this.createSampleForTrack(o,n,a,(r.duration??0)/1e6,r.type);this.registerSample(o,u)}addEncodedAudioChunk(e,r,s){let o=this.getAudioTrackData(e,s),n=new Uint8Array(r.byteLength);r.copyTo(n);let a=this.validateAndNormalizeTimestamp(o.track,r.timestamp,r.type==="key"),u=this.createSampleForTrack(o,n,a,(r.duration??0)/1e6,r.type);this.registerSample(o,u)}addSubtitleCue(e,r,s){let o=this.getSubtitleTrackData(e,s);this.validateAndNormalizeTimestamp(o.track,1e6*r.timestamp,!0),e.source._codec==="webvtt"&&(o.cueQueue.push(r),this.processWebVTTCues(o,r.timestamp))}processWebVTTCues(e,r){for(;e.cueQueue.length>0;){let s=new Set([]);for(let f of e.cueQueue)c(f.timestamp<=r),c(e.lastCueEndTimestamp<=f.timestamp+f.duration),s.add(Math.max(f.timestamp,e.lastCueEndTimestamp)),s.add(f.timestamp+f.duration);let o=[...s].sort((f,m)=>f-m),n=o[0],a=o[1]??n;if(r=a)break;j.lastIndex=0;let C=j.test(m.text),V=m.timestamp+m.duration,k=e.cueToSourceId.get(m);if(k===void 0&&as.timestamp).sort((s,o)=>s-o);for(let s=0;s{if(e===a)return r.type==="key";let u=a.sampleQueue[0];return u&&u.type==="key"});o>=1&&n&&(s=!0,this.finalizeFragment())}else s=o>=.5}s&&(e.currentChunk&&this.finalizeCurrentChunk(e),e.currentChunk={startTimestamp:r.timestamp,samples:[],offset:null,moofOffset:null}),c(e.currentChunk),e.currentChunk.samples.push(r),e.timestampProcessingQueue.push(r)}finalizeCurrentChunk(e){if(c(this.fastStart!=="fragmented"),!!e.currentChunk){if(e.finalizedChunks.push(e.currentChunk),this.finalizedChunks.push(e.currentChunk),(e.compactlyCodedChunkTable.length===0||F(e.compactlyCodedChunkTable).samplesPerChunk!==e.currentChunk.samples.length)&&e.compactlyCodedChunkTable.push({firstChunk:e.finalizedChunks.length,samplesPerChunk:e.currentChunk.samples.length}),this.fastStart==="in-memory"){e.currentChunk.offset=0;return}e.currentChunk.offset=this.writer.getPos();for(let r of e.currentChunk.samples)c(r.data),this.writer.write(r.data),r.data=null;this.writer.flush()}}interleaveSamples(){c(this.fastStart==="fragmented");for(let e of this.output._tracks)if(!e.source._closed&&!this.trackDatas.some(r=>r.track===e))return;e:for(;;){let e=null,r=1/0;for(let o of this.trackDatas){if(o.sampleQueue.length===0&&!o.track.source._closed)break e;o.sampleQueue.length>0&&o.sampleQueue[0].timestamp=2**32&&(u.largeSize=!0,f=this.boxWriter.measureBox(u)+d),u.size=f,this.boxWriter.writeBox(u)}for(let u of this.trackDatas){u.currentChunk.offset=this.writer.getPos(),u.currentChunk.moofOffset=s;for(let d of u.currentChunk.samples)this.writer.write(d.data),d.data=null}let n=this.writer.getPos();this.writer.seek(this.boxWriter.offsets.get(o));let a=Fe(r,this.trackDatas);this.boxWriter.writeBox(a),this.writer.seek(n);for(let u of this.trackDatas)u.finalizedChunks.push(u.currentChunk),this.finalizedChunks.push(u.currentChunk),u.currentChunk=null;e&&this.writer.flush()}onTrackClose(e){if(e.type==="subtitle"&&e.source._codec==="webvtt"){let r=this.trackDatas.find(s=>s.track===e);r&&this.processWebVTTCues(r,1/0)}this.fastStart==="fragmented"&&this.interleaveSamples()}finalize(){for(let e of this.trackDatas)e.type==="subtitle"&&e.track.source._codec==="webvtt"&&this.processWebVTTCues(e,1/0);if(this.fastStart==="fragmented"){for(let e of this.trackDatas){for(let r of e.sampleQueue)this.addSampleToTrack(e,r);this.processTimestamps(e)}this.finalizeFragment(!1)}else for(let e of this.trackDatas)this.processTimestamps(e),this.finalizeCurrentChunk(e);if(this.fastStart==="in-memory"){c(this.mdat);let e;for(let s=0;s<2;s++){let o=ee(this.trackDatas,this.creationTime),n=this.boxWriter.measureBox(o);e=this.boxWriter.measureBox(this.mdat);let a=this.writer.getPos()+n+e;for(let u of this.finalizedChunks){u.offset=a;for(let{data:d}of u.samples)c(d),a+=d.byteLength,e+=d.byteLength}if(a<2**32)break;e>=2**32&&(this.mdat.largeSize=!0)}let r=ee(this.trackDatas,this.creationTime);this.boxWriter.writeBox(r),this.mdat.size=e,this.boxWriter.writeBox(this.mdat);for(let s of this.finalizedChunks)for(let o of s.samples)c(o.data),this.writer.write(o.data),o.data=null}else if(this.fastStart==="fragmented"){let e=this.writer.getPos(),r=et(this.trackDatas);this.boxWriter.writeBox(r);let s=this.writer.getPos()-e;this.writer.seek(this.writer.getPos()-4),this.boxWriter.writeU32(s)}else{c(this.mdat),c(this.ftypSize!==null);let e=this.boxWriter.offsets.get(this.mdat);c(e!==void 0);let r=this.writer.getPos()-e;this.mdat.size=r,this.mdat.largeSize=r>=2**32,this.boxWriter.patchBox(this.mdat);let s=ee(this.trackDatas,this.creationTime);if(typeof this.fastStart=="object"){this.writer.seek(this.ftypSize),this.boxWriter.writeBox(s);let o=e-this.writer.getPos();this.boxWriter.writeBox(Ze(o))}else this.boxWriter.writeBox(s)}}};var ie=class{constructor(i){this.value=i}},G=class{constructor(i){this.value=i}},se=class{constructor(i){this.value=i}};var Ne=t=>t<256?1:t<65536?2:t<1<<24?3:t<2**32?4:t<2**40?5:6,He=t=>t>=-64&&t<64?1:t>=-8192&&t<8192?2:t>=-(1<<20)&&t<1<<20?3:t>=-(1<<27)&&t<1<<27?4:t>=-(2**34)&&t<2**34?5:6,nt=t=>{if(t<127)return 1;if(t<16383)return 2;if(t<(1<<21)-1)return 3;if(t<(1<<28)-1)return 4;if(t<2**35-1)return 5;if(t<2**42-1)return 6;throw new Error("EBML VINT size not supported "+t)};var $e=2**15,at="https://github.com/Vanilagy/webm-muxer",ut=6,lt=5,Tr={avc:"V_MPEG4/ISO/AVC",hevc:"V_MPEGH/ISO/HEVC",vp8:"V_VP8",vp9:"V_VP9",av1:"V_AV1",aac:"A_AAC",opus:"A_OPUS",webvtt:"S_TEXT/WEBVTT"},Cr={video:1,audio:2,subtitle:17},we=class extends Q{constructor(e,r){super(e);this.timestampsMustStartAtZero=!1;this.helper=new Uint8Array(8);this.helperView=new DataView(this.helper.buffer);this.offsets=new WeakMap;this.dataOffsets=new WeakMap;this.trackDatas=[];this.segment=null;this.segmentInfo=null;this.seekHead=null;this.tracksElement=null;this.segmentDuration=null;this.cues=null;this.currentCluster=null;this.currentClusterMsTimestamp=null;this.trackDatasInCurrentCluster=new Set;this.duration=0;this.writer=e._writer,this.format=r,this.format.options.streamable&&(this.writer.ensureMonotonicity=!0)}writeByte(e){this.helperView.setUint8(0,e),this.writer.write(this.helper.subarray(0,1))}writeFloat32(e){this.helperView.setFloat32(0,e,!1),this.writer.write(this.helper.subarray(0,4))}writeFloat64(e){this.helperView.setFloat64(0,e,!1),this.writer.write(this.helper)}writeUnsignedInt(e,r=Ne(e)){let s=0;switch(r){case 6:this.helperView.setUint8(s++,e/2**40|0);case 5:this.helperView.setUint8(s++,e/2**32|0);case 4:this.helperView.setUint8(s++,e>>24);case 3:this.helperView.setUint8(s++,e>>16);case 2:this.helperView.setUint8(s++,e>>8);case 1:this.helperView.setUint8(s++,e);break;default:throw new Error("Bad UINT size "+r)}this.writer.write(this.helper.subarray(0,s))}writeSignedInt(e,r=He(e)){e<0&&(e+=2**(r*8)),this.writeUnsignedInt(e,r)}writeEBMLVarInt(e,r=nt(e)){let s=0;switch(r){case 1:this.helperView.setUint8(s++,128|e);break;case 2:this.helperView.setUint8(s++,64|e>>8),this.helperView.setUint8(s++,e);break;case 3:this.helperView.setUint8(s++,32|e>>16),this.helperView.setUint8(s++,e>>8),this.helperView.setUint8(s++,e);break;case 4:this.helperView.setUint8(s++,16|e>>24),this.helperView.setUint8(s++,e>>16),this.helperView.setUint8(s++,e>>8),this.helperView.setUint8(s++,e);break;case 5:this.helperView.setUint8(s++,8|e/2**32&7),this.helperView.setUint8(s++,e>>24),this.helperView.setUint8(s++,e>>16),this.helperView.setUint8(s++,e>>8),this.helperView.setUint8(s++,e);break;case 6:this.helperView.setUint8(s++,4|e/2**40&3),this.helperView.setUint8(s++,e/2**32|0),this.helperView.setUint8(s++,e>>24),this.helperView.setUint8(s++,e>>16),this.helperView.setUint8(s++,e>>8),this.helperView.setUint8(s++,e);break;default:throw new Error("Bad EBML VINT size "+r)}this.writer.write(this.helper.subarray(0,s))}writeString(e){this.writer.write(new Uint8Array(e.split("").map(r=>r.charCodeAt(0))))}writeEBML(e){if(e!==null){if(e instanceof Uint8Array)this.writer.write(e);else if(Array.isArray(e))for(let r of e)this.writeEBML(r);else if(this.offsets.set(e,this.writer.getPos()),this.writeUnsignedInt(e.id),Array.isArray(e.data)){let r=this.writer.getPos(),s=e.size===-1?1:e.size??4;e.size===-1?this.writeByte(255):this.writer.seek(this.writer.getPos()+s);let o=this.writer.getPos();if(this.dataOffsets.set(e,o),this.writeEBML(e.data),e.size!==-1){let n=this.writer.getPos()-o,a=this.writer.getPos();this.writer.seek(r),this.writeEBMLVarInt(n,s),this.writer.seek(a)}}else if(typeof e.data=="number"){let r=e.size??Ne(e.data);this.writeEBMLVarInt(r),this.writeUnsignedInt(e.data,r)}else if(typeof e.data=="string")this.writeEBMLVarInt(e.data.length),this.writeString(e.data);else if(e.data instanceof Uint8Array)this.writeEBMLVarInt(e.data.byteLength,e.size),this.writer.write(e.data);else if(e.data instanceof ie)this.writeEBMLVarInt(4),this.writeFloat32(e.data.value);else if(e.data instanceof G)this.writeEBMLVarInt(8),this.writeFloat64(e.data.value);else if(e.data instanceof se){let r=e.size??He(e.data.value);this.writeEBMLVarInt(r),this.writeSignedInt(e.data.value,r)}}}beforeTrackAdd(e){if(this.format instanceof P)if(e.type==="video"){if(!["vp8","vp9","av1"].includes(e.source._codec))throw new Error("WebM only supports VP8, VP9 and AV1 as video codecs. Switching to MKV removes this restriction.")}else if(e.type==="audio"){if(!["opus","vorbis"].includes(e.source._codec))throw new Error("WebM only supports Opus and Vorbis as audio codecs. Switching to MKV removes this restriction.")}else if(e.type==="subtitle"){if(e.source._codec!=="webvtt")throw new Error("WebM only supports WebVTT as subtitle codec. Switching to MKV removes this restriction.")}else throw new Error("WebM only supports video, audio and subtitle tracks. Switching to MKV removes this restriction.")}start(){this.writeEBMLHeader(),this.format.options.streamable||this.createSeekHead(),this.createSegmentInfo(),this.createCues(),this.writer.flush()}writeEBMLHeader(){let e={id:440786851,data:[{id:17030,data:1},{id:17143,data:1},{id:17138,data:4},{id:17139,data:8},{id:17026,data:this.format instanceof P?"webm":"matroska"},{id:17031,data:2},{id:17029,data:2}]};this.writeEBML(e)}createSeekHead(){let e=new Uint8Array([28,83,187,107]),r=new Uint8Array([21,73,169,102]),s=new Uint8Array([22,84,174,107]),o={id:290298740,data:[{id:19899,data:[{id:21419,data:e},{id:21420,size:5,data:0}]},{id:19899,data:[{id:21419,data:r},{id:21420,size:5,data:0}]},{id:19899,data:[{id:21419,data:s},{id:21420,size:5,data:0}]}]};this.seekHead=o}createSegmentInfo(){let e={id:17545,data:new G(0)};this.segmentDuration=e;let r={id:357149030,data:[{id:2807729,data:1e6},{id:19840,data:at},{id:22337,data:at},this.format.options.streamable?null:e]};this.segmentInfo=r}createTracks(){let e={id:374648427,data:[]};this.tracksElement=e;for(let r of this.trackDatas)e.data.push({id:174,data:[{id:215,data:r.track.id},{id:29637,data:r.track.id},{id:131,data:Cr[r.type]},{id:134,data:Tr[r.track.source._codec]},...r.type==="video"?[r.info.decoderConfig.description?{id:25506,data:D(r.info.decoderConfig.description)}:null,r.track.metadata.frameRate?{id:2352003,data:1e9/r.track.metadata.frameRate}:null,{id:224,data:[{id:176,data:r.info.width},{id:186,data:r.info.height},(()=>{if(r.info.decoderConfig.colorSpace){let s=r.info.decoderConfig.colorSpace;return ue(s)?{id:21936,data:[{id:21937,data:$[s.matrix]},{id:21946,data:H[s.transfer]},{id:21947,data:N[s.primaries]},{id:21945,data:[1,2][Number(s.fullRange)]}]}:null}return null})()]}]:[],...r.type==="audio"?[r.info.decoderConfig.description?{id:25506,data:D(r.info.decoderConfig.description)}:null,{id:225,data:[{id:181,data:new ie(r.info.sampleRate)},{id:159,data:r.info.numberOfChannels}]}]:[],...r.type==="subtitle"?[{id:25506,data:S.encode(r.info.config.description)}]:[]]})}createSegment(){let e={id:408125543,size:this.format.options.streamable?-1:ut,data:[this.format.options.streamable?null:this.seekHead,this.segmentInfo,this.tracksElement]};this.segment=e,this.writeEBML(e)}createCues(){this.cues={id:475249515,data:[]}}get segmentDataOffset(){return c(this.segment),this.dataOffsets.get(this.segment)}getVideoTrackData(e,r){let s=this.trackDatas.find(n=>n.track===e);if(s)return s;Te(r),c(r),c(r.decoderConfig),c(r.decoderConfig.codedWidth!==void 0),c(r.decoderConfig.codedHeight!==void 0);let o={track:e,type:"video",info:{width:r.decoderConfig.codedWidth,height:r.decoderConfig.codedHeight,decoderConfig:r.decoderConfig},chunkQueue:[],lastWrittenMsTimestamp:null};return this.trackDatas.push(o),this.trackDatas.sort((n,a)=>n.track.id-a.track.id),o}getAudioTrackData(e,r){let s=this.trackDatas.find(n=>n.track===e);if(s)return s;Ce(r),c(r),c(r.decoderConfig);let o={track:e,type:"audio",info:{numberOfChannels:r.decoderConfig.numberOfChannels,sampleRate:r.decoderConfig.sampleRate,decoderConfig:r.decoderConfig},chunkQueue:[],lastWrittenMsTimestamp:null};return this.trackDatas.push(o),this.trackDatas.sort((n,a)=>n.track.id-a.track.id),o}getSubtitleTrackData(e,r){let s=this.trackDatas.find(n=>n.track===e);if(s)return s;ge(r),c(r),c(r.config);let o={track:e,type:"subtitle",info:{config:r.config},chunkQueue:[],lastWrittenMsTimestamp:null};return this.trackDatas.push(o),this.trackDatas.sort((n,a)=>n.track.id-a.track.id),o}addEncodedVideoChunk(e,r,s){let o=this.getVideoTrackData(e,s),n=new Uint8Array(r.byteLength);r.copyTo(n);let a=this.validateAndNormalizeTimestamp(o.track,r.timestamp,r.type==="key"),u=this.createInternalChunk(n,a,(r.duration??0)/1e6,r.type);e.source._codec==="vp9"&&this.fixVP9ColorSpace(o,u),o.chunkQueue.push(u),this.interleaveChunks()}addEncodedAudioChunk(e,r,s){let o=this.getAudioTrackData(e,s),n=new Uint8Array(r.byteLength);r.copyTo(n);let a=this.validateAndNormalizeTimestamp(o.track,r.timestamp,r.type==="key"),u=this.createInternalChunk(n,a,(r.duration??0)/1e6,r.type);o.chunkQueue.push(u),this.interleaveChunks()}addSubtitleCue(e,r,s){let o=this.getSubtitleTrackData(e,s),n=this.validateAndNormalizeTimestamp(o.track,1e6*r.timestamp,!0),a=r.text,u=Math.floor(n*1e3);j.lastIndex=0,a=a.replace(j,C=>{let k=ce(C.slice(1,-1))-u;return`<${de(k)}>`});let d=S.encode(a),f=`${r.settings??""} +`,o);u===-1&&(u=i.length);let d=le(e[2]),f=le(e[3])-d,w=i.slice(a,u).trim();i=i.slice(u).trimStart(),Z.lastIndex=0;let E={timestamp:d/1e3,duration:f/1e3,text:w,identifier:s,settings:n,notes:r},g={};this.preambleEmitted||(g.config={description:this.preambleText},this.preambleEmitted=!0),this.options.output(E,g)}}},bt=/(?:(\d{2}):)?(\d{2}):(\d{2}).(\d{3})/,le=t=>{let i=bt.exec(t);if(!i)throw new Error("Expected match.");return 60*60*1e3*Number(i[1]||"0")+60*1e3*Number(i[2])+1e3*Number(i[3])+Number(i[4])},ce=t=>{let i=Math.floor(t/36e5),e=Math.floor(t%(60*60*1e3)/(60*1e3)),r=Math.floor(t%(60*1e3)/1e3),s=t%1e3;return i.toString().padStart(2,"0")+":"+e.toString().padStart(2,"0")+":"+r.toString().padStart(2,"0")+"."+s.toString().padStart(3,"0")};var J=class{constructor(i){this.writer=i;this.helper=new Uint8Array(8);this.helperView=new DataView(this.helper.buffer);this.offsets=new WeakMap}writeU32(i){this.helperView.setUint32(0,i,!1),this.writer.write(this.helper.subarray(0,4))}writeU64(i){this.helperView.setUint32(0,Math.floor(i/2**32),!1),this.helperView.setUint32(4,i,!1),this.writer.write(this.helper.subarray(0,8))}writeAscii(i){for(let e=0;e[(t%256+256)%256],h=t=>(A.setUint16(0,t,!1),[p[0],p[1]]),Tt=t=>(A.setInt16(0,t,!1),[p[0],p[1]]),je=t=>(A.setUint32(0,t,!1),[p[1],p[2],p[3]]),l=t=>(A.setUint32(0,t,!1),[p[0],p[1],p[2],p[3]]),qe=t=>(A.setInt32(0,t,!1),[p[0],p[1],p[2],p[3]]),U=t=>(A.setUint32(0,Math.floor(t/2**32),!1),A.setUint32(4,t,!1),[p[0],p[1],p[2],p[3],p[4],p[5],p[6],p[7]]),We=t=>(A.setInt16(0,2**8*t,!1),[p[0],p[1]]),v=t=>(A.setInt32(0,2**16*t,!1),[p[0],p[1],p[2],p[3]]),Ue=t=>(A.setInt32(0,2**30*t,!1),[p[0],p[1],p[2],p[3]]),De=(t,i)=>{let e=[],r=t;do{let s=r&127;r>>=7,e.length>0&&(s|=128),e.push(s),i!==void 0&&i--}while(r>0||i);return e.reverse()},y=(t,i=!1)=>{let e=Array(t.length).fill(null).map((r,s)=>t.charCodeAt(s));return i&&e.push(0),e},Re=t=>{let i=null;for(let e of t)(!i||e.timestamp>i.timestamp)&&(i=e);return i},Ke=t=>{let i=t*(Math.PI/180),e=Math.cos(i),r=Math.sin(i);return[e,r,0,-r,e,0,0,0,1]},Le=Ke(0),Xe=t=>[v(t[0]),v(t[1]),Ue(t[2]),v(t[3]),v(t[4]),Ue(t[5]),v(t[6]),v(t[7]),Ue(t[8])],b=(t,i,e)=>({type:t,contents:i&&new Uint8Array(i.flat(10)),children:e}),T=(t,i,e,r,s)=>b(t,[C(i),je(e),r??[]],s),Ge=t=>{let i=512;return t.fragmented?b("ftyp",[y("iso5"),l(i),y("iso5"),y("iso6"),y("mp41")]):b("ftyp",[y("isom"),l(i),y("isom"),t.holdsAvc?y("avc1"):[],y("mp41")])},me=t=>({type:"mdat",largeSize:t}),Ye=t=>({type:"free",size:t}),ee=(t,i,e=!1)=>b("moov",void 0,[wt(i,t),...t.map(r=>Ct(r,i)),e?Zt(t):null]),wt=(t,i)=>{let e=S(Math.max(0,...i.filter(a=>a.samples.length>0).map(a=>{let n=Re(a.samples);return n.timestamp+n.duration})),de),r=Math.max(0,...i.map(a=>a.track.id))+1,s=!I(t)||!I(e),o=s?U:l;return T("mvhd",+s,0,[o(t),o(t),l(de),o(e),v(1),We(1),Array(10).fill(0),Xe(Le),Array(24).fill(0),l(r)])},Ct=(t,i)=>b("trak",void 0,[gt(t,i),kt(t,i)]),gt=(t,i)=>{let e=Re(t.samples),r=S(e?e.timestamp+e.duration:0,de),s=!I(i)||!I(r),o=s?U:l,a;if(t.type==="video"){let n=t.track.metadata.rotation;a=n===void 0||typeof n=="number"?Ke(n??0):n}else a=Le;return T("tkhd",+s,3,[o(i),o(i),l(t.track.id),l(0),o(r),Array(8).fill(0),h(0),h(t.track.id),We(t.type==="audio"?1:0),h(0),Xe(a),v(t.type==="video"?t.info.width:0),v(t.type==="video"?t.info.height:0)])},kt=(t,i)=>b("mdia",void 0,[yt(t,i),vt(t),At(t)]),yt=(t,i)=>{let e=Re(t.samples),r=S(e?e.timestamp+e.duration:0,t.timescale),s=!I(i)||!I(r),o=s?U:l;return T("mdhd",+s,0,[o(i),o(i),l(t.timescale),o(r),h(21956),h(0)])},xt={video:"vide",audio:"soun",subtitle:"text"},St={video:"VideoHandler",audio:"SoundHandler",subtitle:"TextHandler"},vt=t=>T("hdlr",0,0,[y("mhlr"),y(xt[t.type]),l(0),l(0),l(0),y(St[t.type],!0)]),At=t=>b("minf",void 0,[Vt[t.type](),Mt(),Pt(t)]),_t=()=>T("vmhd",0,1,[h(0),h(0),h(0),h(0)]),Ot=()=>T("smhd",0,0,[h(0),h(0)]),Et=()=>T("nmhd",0,0),Vt={video:_t,audio:Ot,subtitle:Et},Mt=()=>b("dinf",void 0,[It()]),It=()=>T("dref",0,0,[l(1)],[zt()]),zt=()=>T("url ",0,1),Pt=t=>{let i=t.compositionTimeOffsetTable.length>1||t.compositionTimeOffsetTable.some(e=>e.sampleCompositionTimeOffset!==0);return b("stbl",void 0,[Ut(t),qt(t),Kt(t),Lt(t),Xt(t),Gt(t),i?Yt(t):null])},Ut=t=>{let i;return t.type==="video"?i=Dt(nr[t.track.source._codec],t):t.type==="audio"?i=Nt(lr[t.track.source._codec],t):t.type==="subtitle"&&(i=$t(dr[t.track.source._codec],t)),c(i),T("stsd",0,0,[l(1)],[i])},Dt=(t,i)=>b(t,[Array(6).fill(0),h(1),h(0),h(0),Array(12).fill(0),h(i.info.width),h(i.info.height),l(4718592),l(4718592),l(0),h(1),Array(32).fill(0),h(24),Tt(65535)],[ur[i.track.source._codec](i),ne(i.info.decoderConfig.colorSpace)?Wt(i):null]),Wt=t=>b("colr",[y("nclx"),h(H[t.info.decoderConfig.colorSpace.primaries]),h(Q[t.info.decoderConfig.colorSpace.transfer]),h($[t.info.decoderConfig.colorSpace.matrix]),C((t.info.decoderConfig.colorSpace.fullRange?1:0)<<7)]),Rt=t=>t.info.decoderConfig&&b("avcC",[...P(t.info.decoderConfig.description)]),Ft=t=>t.info.decoderConfig&&b("hvcC",[...P(t.info.decoderConfig.description)]),$e=t=>{if(!t.info.decoderConfig)return null;let i=t.info.decoderConfig;c(i.colorSpace);let e=i.codec.split("."),r=Number(e[1]),s=Number(e[2]),n=(Number(e[3])<<4)+(0<<1)+Number(i.colorSpace.fullRange);return T("vpcC",1,0,[C(r),C(s),C(n),C(2),C(2),C(2),h(0)])},Bt=()=>{let e=(1<<7)+1;return b("av1C",[e,0,0,0])},Nt=(t,i)=>b(t,[Array(6).fill(0),h(1),h(0),h(0),l(0),h(i.info.numberOfChannels),h(16),h(0),h(0),v(i.info.sampleRate)],[cr[i.track.source._codec](i)]),Ht=t=>{let e=[...P(t.info.decoderConfig.description??new ArrayBuffer(0))];return e=[...C(64),...C(21),...je(0),...l(0),...l(0),...C(5),...De(e.length),...e],e=[...h(1),...C(0),...C(4),...De(e.length),...e,...C(6),...C(1),...C(2)],e=[...C(3),...De(e.length),...e],T("esds",0,0,e)},Qt=t=>{let i=3840,e=0,r=t.info.decoderConfig?.description;if(r){c(r.byteLength<18);let s=ArrayBuffer.isView(r)?new DataView(r.buffer,r.byteOffset,r.byteLength):new DataView(r);i=s.getUint16(10,!0),e=s.getInt16(14,!0)}return b("dOps",[C(0),C(t.info.numberOfChannels),h(i),l(t.info.sampleRate),We(e),C(0)])},$t=(t,i)=>b(t,[Array(6).fill(0),h(1)],[mr[i.track.source._codec](i)]),jt=t=>b("vttC",[...x.encode(t.info.config.description)]);var qt=t=>T("stts",0,0,[l(t.timeToSampleTable.length),t.timeToSampleTable.map(i=>[l(i.sampleCount),l(i.sampleDelta)])]),Kt=t=>{if(t.samples.every(e=>e.type==="key"))return null;let i=[...t.samples.entries()].filter(([,e])=>e.type==="key");return T("stss",0,0,[l(i.length),i.map(([e])=>l(e+1))])},Lt=t=>T("stsc",0,0,[l(t.compactlyCodedChunkTable.length),t.compactlyCodedChunkTable.map(i=>[l(i.firstChunk),l(i.samplesPerChunk),l(1)])]),Xt=t=>T("stsz",0,0,[l(0),l(t.samples.length),t.samples.map(i=>l(i.size))]),Gt=t=>t.finalizedChunks.length>0&&N(t.finalizedChunks).offset>=2**32?T("co64",0,0,[l(t.finalizedChunks.length),t.finalizedChunks.map(i=>U(i.offset))]):T("stco",0,0,[l(t.finalizedChunks.length),t.finalizedChunks.map(i=>l(i.offset))]),Yt=t=>T("ctts",0,0,[l(t.compositionTimeOffsetTable.length),t.compositionTimeOffsetTable.map(i=>[l(i.sampleCount),l(i.sampleCompositionTimeOffset)])]),Zt=t=>b("mvex",void 0,t.map(Jt)),Jt=t=>T("trex",0,0,[l(t.track.id),l(1),l(0),l(0),l(0)]),Fe=(t,i)=>b("moof",void 0,[er(t),...i.map(tr)]),er=t=>T("mfhd",0,0,[l(t)]),Ze=t=>{let i=0,e=0,r=0,s=0,o=t.type==="delta";return e|=+o,o?i|=1:i|=2,i<<24|e<<16|r<<8|s},tr=t=>b("traf",void 0,[rr(t),ir(t),sr(t)]),rr=t=>{c(t.currentChunk);let i=0;i|=8,i|=16,i|=32,i|=131072;let e=t.currentChunk.samples[1]??t.currentChunk.samples[0],r={duration:e.timescaleUnitsToNextSample,size:e.size,flags:Ze(e)};return T("tfhd",0,i,[l(t.track.id),l(r.duration),l(r.size),l(r.flags)])},ir=t=>(c(t.currentChunk),T("tfdt",1,0,[U(S(t.currentChunk.startTimestamp,t.timescale))])),sr=t=>{c(t.currentChunk);let i=t.currentChunk.samples.map(k=>k.timescaleUnitsToNextSample),e=t.currentChunk.samples.map(k=>k.size),r=t.currentChunk.samples.map(Ze),s=t.currentChunk.samples.map(k=>S(k.timestamp-k.decodeTimestamp,t.timescale)),o=new Set(i),a=new Set(e),n=new Set(r),u=new Set(s),d=n.size===2&&r[0]!==r[1],m=o.size>1,f=a.size>1,w=!d&&n.size>1,E=u.size>1||[...u].some(k=>k!==0),g=0;return g|=1,g|=4*+d,g|=256*+m,g|=512*+f,g|=1024*+w,g|=2048*+E,T("trun",1,g,[l(t.currentChunk.samples.length),l(t.currentChunk.offset-t.currentChunk.moofOffset||0),d?l(r[0]):[],t.currentChunk.samples.map((k,F)=>[m?l(i[F]):[],f?l(e[F]):[],w?l(r[F]):[],E?qe(s[F]):[]])])},Je=t=>b("mfra",void 0,[...t.map(or),ar()]),or=(t,i)=>T("tfra",1,0,[l(t.track.id),l(63),l(t.finalizedChunks.length),t.finalizedChunks.map(r=>[U(S(r.startTimestamp,t.timescale)),U(r.moofOffset),l(i+1),l(1),l(1)])]),ar=()=>T("mfro",0,0,[l(0)]),et=()=>b("vtte"),tt=(t,i,e,r,s)=>b("vttc",void 0,[s!==null?b("vsid",[qe(s)]):null,e!==null?b("iden",[...x.encode(e)]):null,i!==null?b("ctim",[...x.encode(ce(i))]):null,r!==null?b("sttg",[...x.encode(r)]):null,b("payl",[...x.encode(t)])]),rt=t=>b("vtta",[...x.encode(t)]),nr={avc:"avc1",hevc:"hvc1",vp8:"vp08",vp9:"vp09",av1:"av01"},ur={avc:Rt,hevc:Ft,vp8:$e,vp9:$e,av1:Bt},lr={aac:"mp4a",opus:"Opus"},cr={aac:Ht,opus:Qt},dr={webvtt:"wvtt"},mr={webvtt:jt};var q=class{constructor(i){this.mutex=new B;this.trackTimestampInfo=new WeakMap;this.output=i}beforeTrackAdd(i){}onTrackClose(i){}validateAndNormalizeTimestamp(i,e,r){let s=e/1e6,o=this.trackTimestampInfo.get(i);if(!o){if(!r)throw new Error("First frame must be a key frame.");if(this.timestampsMustStartAtZero&&s>0)throw new Error(`Timestamps must start at zero (got ${s}s).`);o={timestampOffset:s,maxTimestamp:i.source._offsetTimestamps?0:s,lastKeyFrameTimestamp:i.source._offsetTimestamps?0:s},this.trackTimestampInfo.set(i,o)}if(i.source._offsetTimestamps&&(s-=o.timestampOffset),s<0)throw new Error(`Timestamps must be non-negative (got ${s}s).`);if(ss.start-o.start);e.push({start:r[0].start,size:r[0].data.byteLength});for(let s=1;sd.start<=r&&rpr){for(let d=0;d=e.written[a+1].start;)e.written[a].end=Math.max(e.written[a].end,e.written[a+1].end),e.written.splice(a+1,1)}createChunk(e){let s={start:Math.floor(e/this.chunkSize)*this.chunkSize,data:new Uint8Array(this.chunkSize),written:[],shouldFlush:!1};return this.chunks.push(s),this.chunks.sort((o,a)=>o.start-a.start),this.chunks.indexOf(s)}queueChunksForFlush(e=!1){c(this.writer);for(let r=0;r{if(t==="avc"){let r=100;i<=768&&e<=432?r=66:i<=1920&&e<=1080&&(r=77);let s=0,o=i>1920||e>1080?50:41,a=r.toString(16).padStart(2,"0"),n=s.toString(16).padStart(2,"0"),u=o.toString(16).padStart(2,"0");return`avc1.${a}${n}${u}`}else if(t==="hevc"){let r=0,s=1,o=Array(32).fill(0);o[s]=1;let a=parseInt(o.reverse().join(""),2).toString(16).replace(/^0+/,""),n="L",u=120;return i<=1280&&e<=720?u=93:i<=1920&&e<=1080?u=120:i<=3840&&e<=2160?u=150:(n="H",u=180),`hev1.${r===0?"":String.fromCharCode(65+r-1)}${s}.${a}.${n}${u}.B0`}else{if(t==="vp8")return"vp8";if(t==="vp9"){let r="00",s;return i<=854&&e<=480?s="21":i<=1280&&e<=720?s="31":i<=1920&&e<=1080?s="41":i<=3840&&e<=2160?s="51":s="61",`vp09.${r}.${s}.08`}else if(t==="av1"){let s;return i<=854&&e<=480?s="01":i<=1280&&e<=720?s="03":i<=1920&&e<=1080?s="04":i<=3840&&e<=2160?s="07":s="09",`av01.0.${s}M.08`}}throw new TypeError(`Unhandled codec '${t}'.`)},st=(t,i,e)=>{if(t==="aac")return i>=2&&e<=24e3?"mp4a.40.29":e<=24e3?"mp4a.40.5":"mp4a.40.2";if(t==="opus")return"opus";if(t==="vorbis")return"vorbis";throw new TypeError(`Unhandled codec '${t}'.`)},be=t=>{if(!t)throw new TypeError("Video chunk metadata must be provided.");if(typeof t!="object")throw new TypeError("Video chunk metadata must be an object.");if(!t.decoderConfig)throw new TypeError("Video chunk metadata must include a decoder configuration.");if(typeof t.decoderConfig!="object")throw new TypeError("Video chunk metadata decoder configuration must be an object.");if(typeof t.decoderConfig.codec!="string")throw new TypeError("Video chunk metadata decoder configuration must specify a codec string.");if(!Number.isInteger(t.decoderConfig.codedWidth)||t.decoderConfig.codedWidth<=0)throw new TypeError("Video chunk metadata decoder configuration must specify a valid codedWidth (positive integer).");if(!Number.isInteger(t.decoderConfig.codedHeight)||t.decoderConfig.codedHeight<=0)throw new TypeError("Video chunk metadata decoder configuration must specify a valid codedHeight (positive integer).");if(t.decoderConfig.description!==void 0&&!Pe(t.decoderConfig.description))throw new TypeError("Video chunk metadata decoder configuration description, when defined, must be an ArrayBuffer or an ArrayBuffer view.");if(t.decoderConfig.colorSpace!==void 0){let{colorSpace:i}=t.decoderConfig;if(typeof i!="object")throw new TypeError("Video chunk metadata decoder configuration colorSpace, when provided, must be an object.");let e=Object.keys(H);if(i.primaries!=null&&!e.includes(i.primaries))throw new TypeError(`Video chunk metadata decoder configuration colorSpace primaries, when defined, must be one of ${e.join(", ")}.`);let r=Object.keys(Q);if(i.transfer!=null&&!r.includes(i.transfer))throw new TypeError(`Video chunk metadata decoder configuration colorSpace transfer, when defined, must be one of ${r.join(", ")}.`);let s=Object.keys($);if(i.matrix!=null&&!s.includes(i.matrix))throw new TypeError(`Video chunk metadata decoder configuration colorSpace matrix, when defined, must be one of ${s.join(", ")}.`);if(i.fullRange!=null&&typeof i.fullRange!="boolean")throw new TypeError("Video chunk metadata decoder configuration colorSpace fullRange, when defined, must be a boolean.")}if((t.decoderConfig.codec.startsWith("avc1")||t.decoderConfig.codec.startsWith("avc3"))&&!t.decoderConfig.description)throw new TypeError("Video chunk metadata decoder configuration for AVC must include a description, which is expected to be an AVCDecoderConfigurationRecord as specified in ISO 14496-15.");if((t.decoderConfig.codec.startsWith("hev1")||t.decoderConfig.codec.startsWith("hvc1"))&&!t.decoderConfig.description)throw new TypeError("Video chunk metadata decoder configuration for HEVC must include a description, which is expected to be an HEVCDecoderConfigurationRecord as specified in ISO 14496-15.");if((t.decoderConfig.codec==="vp8"||t.decoderConfig.codec.startsWith("vp09"))&&t.decoderConfig.colorSpace===void 0)throw new TypeError("Video chunk metadata decoder configuration for VP8/VP9 must include a colorSpace.")},Te=t=>{if(!t)throw new TypeError("Audio chunk metadata must be provided.");if(typeof t!="object")throw new TypeError("Audio chunk metadata must be an object.");if(!t.decoderConfig)throw new TypeError("Audio chunk metadata must include a decoder configuration.");if(typeof t.decoderConfig!="object")throw new TypeError("Audio chunk metadata decoder configuration must be an object.");if(typeof t.decoderConfig.codec!="string")throw new TypeError("Audio chunk metadata decoder configuration must specify a codec string.");if(!Number.isInteger(t.decoderConfig.sampleRate)||t.decoderConfig.sampleRate<=0)throw new TypeError("Audio chunk metadata decoder configuration must specify a valid sampleRate (positive integer).");if(!Number.isInteger(t.decoderConfig.numberOfChannels)||t.decoderConfig.numberOfChannels<=0)throw new TypeError("Audio chunk metadata decoder configuration must specify a valid numberOfChannels (positive integer).");if(t.decoderConfig.description!==void 0&&!Pe(t.decoderConfig.description))throw new TypeError("Audio chunk metadata decoder configuration description, when defined, must be an ArrayBuffer or an ArrayBuffer view.");if(t.decoderConfig.codec==="opus"&&t.decoderConfig.description&&t.decoderConfig.description.byteLength<18)throw new TypeError("Invalid decoder description provided for Opus; must be at least 18 bytes long.")},we=t=>{if(!t)throw new TypeError("Subtitle metadata must be provided.");if(typeof t!="object")throw new TypeError("Subtitle metadata must be an object.");if(!t.config)throw new TypeError("Subtitle metadata must include a config object.");if(typeof t.config!="object")throw new TypeError("Subtitle metadata config must be an object.");if(typeof t.config.description!="string")throw new TypeError("Subtitle metadata config description must be a string.")};var de=1e3,hr=2082844800,S=(t,i,e=!0)=>{let r=t*i;return e?Math.round(r):r},Ce=class extends q{constructor(e,r){super(e);this.timestampsMustStartAtZero=!0;this.auxTarget=new L;this.auxWriter=this.auxTarget._createWriter();this.auxBoxWriter=new J(this.auxWriter);this.ftypSize=null;this.mdat=null;this.trackDatas=[];this.creationTime=Math.floor(Date.now()/1e3)+hr;this.finalizedChunks=[];this.nextFragmentNumber=1;this.writer=e._writer,this.boxWriter=new J(this.writer),this.fastStart=r._options.fastStart??(this.writer instanceof K?"in-memory":!1),(this.fastStart==="in-memory"||this.fastStart==="fragmented")&&(this.writer.ensureMonotonicity=!0)}async start(){let e=await this.mutex.acquire(),r=this.output._tracks.some(s=>s.type==="video"&&s.source._codec==="avc");this.boxWriter.writeBox(Ge({holdsAvc:r,fragmented:this.fastStart==="fragmented"})),this.ftypSize=this.writer.getPos(),this.fastStart==="in-memory"?this.mdat=me(!1):this.fastStart==="fragmented"||(this.mdat=me(!0),this.boxWriter.writeBox(this.mdat)),await this.writer.flush(),e()}getVideoTrackData(e,r){let s=this.trackDatas.find(a=>a.track===e);if(s)return s;be(r),c(r),c(r.decoderConfig),c(r.decoderConfig.codedWidth!==void 0),c(r.decoderConfig.codedHeight!==void 0);let o={track:e,type:"video",info:{width:r.decoderConfig.codedWidth,height:r.decoderConfig.codedHeight,decoderConfig:r.decoderConfig},timescale:e.metadata.frameRate??57600,samples:[],sampleQueue:[],timestampProcessingQueue:[],timeToSampleTable:[],compositionTimeOffsetTable:[],lastTimescaleUnits:null,lastSample:null,finalizedChunks:[],currentChunk:null,compactlyCodedChunkTable:[]};return this.trackDatas.push(o),this.trackDatas.sort((a,n)=>a.track.id-n.track.id),o}getAudioTrackData(e,r){let s=this.trackDatas.find(a=>a.track===e);if(s)return s;Te(r),c(r),c(r.decoderConfig);let o={track:e,type:"audio",info:{numberOfChannels:r.decoderConfig.numberOfChannels,sampleRate:r.decoderConfig.sampleRate,decoderConfig:r.decoderConfig},timescale:r.decoderConfig.sampleRate,samples:[],sampleQueue:[],timestampProcessingQueue:[],timeToSampleTable:[],compositionTimeOffsetTable:[],lastTimescaleUnits:null,lastSample:null,finalizedChunks:[],currentChunk:null,compactlyCodedChunkTable:[]};return this.trackDatas.push(o),this.trackDatas.sort((a,n)=>a.track.id-n.track.id),o}getSubtitleTrackData(e,r){let s=this.trackDatas.find(a=>a.track===e);if(s)return s;we(r),c(r),c(r.config);let o={track:e,type:"subtitle",info:{config:r.config},timescale:1e3,samples:[],sampleQueue:[],timestampProcessingQueue:[],timeToSampleTable:[],compositionTimeOffsetTable:[],lastTimescaleUnits:null,lastSample:null,finalizedChunks:[],currentChunk:null,compactlyCodedChunkTable:[],lastCueEndTimestamp:0,cueQueue:[],nextSourceId:0,cueToSourceId:new WeakMap};return this.trackDatas.push(o),this.trackDatas.sort((a,n)=>a.track.id-n.track.id),this.validateAndNormalizeTimestamp(e,0,!0),o}async addEncodedVideoChunk(e,r,s){let o=await this.mutex.acquire();try{let a=this.getVideoTrackData(e,s),n=new Uint8Array(r.byteLength);r.copyTo(n);let u=this.validateAndNormalizeTimestamp(a.track,r.timestamp,r.type==="key"),d=this.createSampleForTrack(a,n,u,(r.duration??0)/1e6,r.type);await this.registerSample(a,d)}finally{o()}}async addEncodedAudioChunk(e,r,s){let o=await this.mutex.acquire();try{let a=this.getAudioTrackData(e,s),n=new Uint8Array(r.byteLength);r.copyTo(n);let u=this.validateAndNormalizeTimestamp(a.track,r.timestamp,r.type==="key"),d=this.createSampleForTrack(a,n,u,(r.duration??0)/1e6,r.type);await this.registerSample(a,d)}finally{o()}}async addSubtitleCue(e,r,s){let o=await this.mutex.acquire();try{let a=this.getSubtitleTrackData(e,s);this.validateAndNormalizeTimestamp(a.track,1e6*r.timestamp,!0),e.source._codec==="webvtt"&&(a.cueQueue.push(r),await this.processWebVTTCues(a,r.timestamp))}finally{o()}}async processWebVTTCues(e,r){for(;e.cueQueue.length>0;){let s=new Set([]);for(let m of e.cueQueue)c(m.timestamp<=r),c(e.lastCueEndTimestamp<=m.timestamp+m.duration),s.add(Math.max(m.timestamp,e.lastCueEndTimestamp)),s.add(m.timestamp+m.duration);let o=[...s].sort((m,f)=>m-f),a=o[0],n=o[1]??a;if(r=n)break;j.lastIndex=0;let w=j.test(f.text),E=f.timestamp+f.duration,g=e.cueToSourceId.get(f);if(g===void 0&&ns.timestamp).sort((s,o)=>s-o);for(let s=0;s{if(e===n)return r.type==="key";let u=n.sampleQueue[0];return u&&u.type==="key"});o>=1&&a&&(s=!0,await this.finalizeFragment())}else s=o>=.5}s&&(e.currentChunk&&await this.finalizeCurrentChunk(e),e.currentChunk={startTimestamp:r.timestamp,samples:[],offset:null,moofOffset:null}),c(e.currentChunk),e.currentChunk.samples.push(r),e.timestampProcessingQueue.push(r)}async finalizeCurrentChunk(e){if(c(this.fastStart!=="fragmented"),!!e.currentChunk){if(e.finalizedChunks.push(e.currentChunk),this.finalizedChunks.push(e.currentChunk),(e.compactlyCodedChunkTable.length===0||N(e.compactlyCodedChunkTable).samplesPerChunk!==e.currentChunk.samples.length)&&e.compactlyCodedChunkTable.push({firstChunk:e.finalizedChunks.length,samplesPerChunk:e.currentChunk.samples.length}),this.fastStart==="in-memory"){e.currentChunk.offset=0;return}e.currentChunk.offset=this.writer.getPos();for(let r of e.currentChunk.samples)c(r.data),this.writer.write(r.data),r.data=null;await this.writer.flush()}}async interleaveSamples(){c(this.fastStart==="fragmented");for(let e of this.output._tracks)if(!e.source._closed&&!this.trackDatas.some(r=>r.track===e))return;e:for(;;){let e=null,r=1/0;for(let o of this.trackDatas){if(o.sampleQueue.length===0&&!o.track.source._closed)break e;o.sampleQueue.length>0&&o.sampleQueue[0].timestamp=2**32&&(u.largeSize=!0,m=this.boxWriter.measureBox(u)+d),u.size=m,this.boxWriter.writeBox(u)}for(let u of this.trackDatas){u.currentChunk.offset=this.writer.getPos(),u.currentChunk.moofOffset=s;for(let d of u.currentChunk.samples)this.writer.write(d.data),d.data=null}let a=this.writer.getPos();this.writer.seek(this.boxWriter.offsets.get(o));let n=Fe(r,this.trackDatas);this.boxWriter.writeBox(n),this.writer.seek(a);for(let u of this.trackDatas)u.finalizedChunks.push(u.currentChunk),this.finalizedChunks.push(u.currentChunk),u.currentChunk=null;e&&await this.writer.flush()}async onTrackClose(e){let r=await this.mutex.acquire();if(e.type==="subtitle"&&e.source._codec==="webvtt"){let s=this.trackDatas.find(o=>o.track===e);s&&await this.processWebVTTCues(s,1/0)}this.fastStart==="fragmented"&&await this.interleaveSamples(),r()}async finalize(){let e=await this.mutex.acquire();for(let r of this.trackDatas)r.type==="subtitle"&&r.track.source._codec==="webvtt"&&await this.processWebVTTCues(r,1/0);if(this.fastStart==="fragmented"){for(let r of this.trackDatas){for(let s of r.sampleQueue)await this.addSampleToTrack(r,s);this.processTimestamps(r)}await this.finalizeFragment(!1)}else for(let r of this.trackDatas)this.processTimestamps(r),await this.finalizeCurrentChunk(r);if(this.fastStart==="in-memory"){c(this.mdat);let r;for(let o=0;o<2;o++){let a=ee(this.trackDatas,this.creationTime),n=this.boxWriter.measureBox(a);r=this.boxWriter.measureBox(this.mdat);let u=this.writer.getPos()+n+r;for(let d of this.finalizedChunks){d.offset=u;for(let{data:m}of d.samples)c(m),u+=m.byteLength,r+=m.byteLength}if(u<2**32)break;r>=2**32&&(this.mdat.largeSize=!0)}let s=ee(this.trackDatas,this.creationTime);this.boxWriter.writeBox(s),this.mdat.size=r,this.boxWriter.writeBox(this.mdat);for(let o of this.finalizedChunks)for(let a of o.samples)c(a.data),this.writer.write(a.data),a.data=null}else if(this.fastStart==="fragmented"){let r=this.writer.getPos(),s=Je(this.trackDatas);this.boxWriter.writeBox(s);let o=this.writer.getPos()-r;this.writer.seek(this.writer.getPos()-4),this.boxWriter.writeU32(o)}else{c(this.mdat),c(this.ftypSize!==null);let r=this.boxWriter.offsets.get(this.mdat);c(r!==void 0);let s=this.writer.getPos()-r;this.mdat.size=s,this.mdat.largeSize=s>=2**32,this.boxWriter.patchBox(this.mdat);let o=ee(this.trackDatas,this.creationTime);if(typeof this.fastStart=="object"){this.writer.seek(this.ftypSize),this.boxWriter.writeBox(o);let a=r-this.writer.getPos();this.boxWriter.writeBox(Ye(a))}else this.boxWriter.writeBox(o)}e()}};var re=class{constructor(i){this.value=i}},X=class{constructor(i){this.value=i}},ie=class{constructor(i){this.value=i}};var Be=t=>t<256?1:t<65536?2:t<1<<24?3:t<2**32?4:t<2**40?5:6,Ne=t=>t>=-64&&t<64?1:t>=-8192&&t<8192?2:t>=-(1<<20)&&t<1<<20?3:t>=-(1<<27)&&t<1<<27?4:t>=-(2**34)&&t<2**34?5:6,ot=t=>{if(t<127)return 1;if(t<16383)return 2;if(t<(1<<21)-1)return 3;if(t<(1<<28)-1)return 4;if(t<2**35-1)return 5;if(t<2**42-1)return 6;throw new Error("EBML VINT size not supported "+t)};var He=2**15,at="https://github.com/Vanilagy/webm-muxer",nt=6,ut=5,br={avc:"V_MPEG4/ISO/AVC",hevc:"V_MPEGH/ISO/HEVC",vp8:"V_VP8",vp9:"V_VP9",av1:"V_AV1",aac:"A_AAC",opus:"A_OPUS",webvtt:"S_TEXT/WEBVTT"},Tr={video:1,audio:2,subtitle:17},ge=class extends q{constructor(e,r){super(e);this.timestampsMustStartAtZero=!1;this.helper=new Uint8Array(8);this.helperView=new DataView(this.helper.buffer);this.offsets=new WeakMap;this.dataOffsets=new WeakMap;this.trackDatas=[];this.segment=null;this.segmentInfo=null;this.seekHead=null;this.tracksElement=null;this.segmentDuration=null;this.cues=null;this.currentCluster=null;this.currentClusterMsTimestamp=null;this.trackDatasInCurrentCluster=new Set;this.duration=0;this.writer=e._writer,this.format=r,this.format._options.streamable&&(this.writer.ensureMonotonicity=!0)}writeByte(e){this.helperView.setUint8(0,e),this.writer.write(this.helper.subarray(0,1))}writeFloat32(e){this.helperView.setFloat32(0,e,!1),this.writer.write(this.helper.subarray(0,4))}writeFloat64(e){this.helperView.setFloat64(0,e,!1),this.writer.write(this.helper)}writeUnsignedInt(e,r=Be(e)){let s=0;switch(r){case 6:this.helperView.setUint8(s++,e/2**40|0);case 5:this.helperView.setUint8(s++,e/2**32|0);case 4:this.helperView.setUint8(s++,e>>24);case 3:this.helperView.setUint8(s++,e>>16);case 2:this.helperView.setUint8(s++,e>>8);case 1:this.helperView.setUint8(s++,e);break;default:throw new Error("Bad UINT size "+r)}this.writer.write(this.helper.subarray(0,s))}writeSignedInt(e,r=Ne(e)){e<0&&(e+=2**(r*8)),this.writeUnsignedInt(e,r)}writeEBMLVarInt(e,r=ot(e)){let s=0;switch(r){case 1:this.helperView.setUint8(s++,128|e);break;case 2:this.helperView.setUint8(s++,64|e>>8),this.helperView.setUint8(s++,e);break;case 3:this.helperView.setUint8(s++,32|e>>16),this.helperView.setUint8(s++,e>>8),this.helperView.setUint8(s++,e);break;case 4:this.helperView.setUint8(s++,16|e>>24),this.helperView.setUint8(s++,e>>16),this.helperView.setUint8(s++,e>>8),this.helperView.setUint8(s++,e);break;case 5:this.helperView.setUint8(s++,8|e/2**32&7),this.helperView.setUint8(s++,e>>24),this.helperView.setUint8(s++,e>>16),this.helperView.setUint8(s++,e>>8),this.helperView.setUint8(s++,e);break;case 6:this.helperView.setUint8(s++,4|e/2**40&3),this.helperView.setUint8(s++,e/2**32|0),this.helperView.setUint8(s++,e>>24),this.helperView.setUint8(s++,e>>16),this.helperView.setUint8(s++,e>>8),this.helperView.setUint8(s++,e);break;default:throw new Error("Bad EBML VINT size "+r)}this.writer.write(this.helper.subarray(0,s))}writeString(e){this.writer.write(new Uint8Array(e.split("").map(r=>r.charCodeAt(0))))}writeEBML(e){if(e!==null){if(e instanceof Uint8Array)this.writer.write(e);else if(Array.isArray(e))for(let r of e)this.writeEBML(r);else if(this.offsets.set(e,this.writer.getPos()),this.writeUnsignedInt(e.id),Array.isArray(e.data)){let r=this.writer.getPos(),s=e.size===-1?1:e.size??4;e.size===-1?this.writeByte(255):this.writer.seek(this.writer.getPos()+s);let o=this.writer.getPos();if(this.dataOffsets.set(e,o),this.writeEBML(e.data),e.size!==-1){let a=this.writer.getPos()-o,n=this.writer.getPos();this.writer.seek(r),this.writeEBMLVarInt(a,s),this.writer.seek(n)}}else if(typeof e.data=="number"){let r=e.size??Be(e.data);this.writeEBMLVarInt(r),this.writeUnsignedInt(e.data,r)}else if(typeof e.data=="string")this.writeEBMLVarInt(e.data.length),this.writeString(e.data);else if(e.data instanceof Uint8Array)this.writeEBMLVarInt(e.data.byteLength,e.size),this.writer.write(e.data);else if(e.data instanceof re)this.writeEBMLVarInt(4),this.writeFloat32(e.data.value);else if(e.data instanceof X)this.writeEBMLVarInt(8),this.writeFloat64(e.data.value);else if(e.data instanceof ie){let r=e.size??Ne(e.data.value);this.writeEBMLVarInt(r),this.writeSignedInt(e.data.value,r)}}}beforeTrackAdd(e){if(this.format instanceof D)if(e.type==="video"){if(!["vp8","vp9","av1"].includes(e.source._codec))throw new Error("WebM only supports VP8, VP9 and AV1 as video codecs. Switching to MKV removes this restriction.")}else if(e.type==="audio"){if(!["opus","vorbis"].includes(e.source._codec))throw new Error("WebM only supports Opus and Vorbis as audio codecs. Switching to MKV removes this restriction.")}else if(e.type==="subtitle"){if(e.source._codec!=="webvtt")throw new Error("WebM only supports WebVTT as subtitle codec. Switching to MKV removes this restriction.")}else throw new Error("WebM only supports video, audio and subtitle tracks. Switching to MKV removes this restriction.")}async start(){let e=await this.mutex.acquire();this.writeEBMLHeader(),this.format._options.streamable||this.createSeekHead(),this.createSegmentInfo(),this.createCues(),await this.writer.flush(),e()}writeEBMLHeader(){let e={id:440786851,data:[{id:17030,data:1},{id:17143,data:1},{id:17138,data:4},{id:17139,data:8},{id:17026,data:this.format instanceof D?"webm":"matroska"},{id:17031,data:2},{id:17029,data:2}]};this.writeEBML(e)}createSeekHead(){let e=new Uint8Array([28,83,187,107]),r=new Uint8Array([21,73,169,102]),s=new Uint8Array([22,84,174,107]),o={id:290298740,data:[{id:19899,data:[{id:21419,data:e},{id:21420,size:5,data:0}]},{id:19899,data:[{id:21419,data:r},{id:21420,size:5,data:0}]},{id:19899,data:[{id:21419,data:s},{id:21420,size:5,data:0}]}]};this.seekHead=o}createSegmentInfo(){let e={id:17545,data:new X(0)};this.segmentDuration=e;let r={id:357149030,data:[{id:2807729,data:1e6},{id:19840,data:at},{id:22337,data:at},this.format._options.streamable?null:e]};this.segmentInfo=r}createTracks(){let e={id:374648427,data:[]};this.tracksElement=e;for(let r of this.trackDatas)e.data.push({id:174,data:[{id:215,data:r.track.id},{id:29637,data:r.track.id},{id:131,data:Tr[r.type]},{id:134,data:br[r.track.source._codec]},...r.type==="video"?[r.info.decoderConfig.description?{id:25506,data:P(r.info.decoderConfig.description)}:null,r.track.metadata.frameRate?{id:2352003,data:1e9/r.track.metadata.frameRate}:null,{id:224,data:[{id:176,data:r.info.width},{id:186,data:r.info.height},(()=>{if(r.info.decoderConfig.colorSpace){let s=r.info.decoderConfig.colorSpace;return ne(s)?{id:21936,data:[{id:21937,data:$[s.matrix]},{id:21946,data:Q[s.transfer]},{id:21947,data:H[s.primaries]},{id:21945,data:[1,2][Number(s.fullRange)]}]}:null}return null})()]}]:[],...r.type==="audio"?[r.info.decoderConfig.description?{id:25506,data:P(r.info.decoderConfig.description)}:null,{id:225,data:[{id:181,data:new re(r.info.sampleRate)},{id:159,data:r.info.numberOfChannels}]}]:[],...r.type==="subtitle"?[{id:25506,data:x.encode(r.info.config.description)}]:[]]})}createSegment(){let e={id:408125543,size:this.format._options.streamable?-1:nt,data:[this.format._options.streamable?null:this.seekHead,this.segmentInfo,this.tracksElement]};this.segment=e,this.writeEBML(e)}createCues(){this.cues={id:475249515,data:[]}}get segmentDataOffset(){return c(this.segment),this.dataOffsets.get(this.segment)}getVideoTrackData(e,r){let s=this.trackDatas.find(a=>a.track===e);if(s)return s;be(r),c(r),c(r.decoderConfig),c(r.decoderConfig.codedWidth!==void 0),c(r.decoderConfig.codedHeight!==void 0);let o={track:e,type:"video",info:{width:r.decoderConfig.codedWidth,height:r.decoderConfig.codedHeight,decoderConfig:r.decoderConfig},chunkQueue:[],lastWrittenMsTimestamp:null};return this.trackDatas.push(o),this.trackDatas.sort((a,n)=>a.track.id-n.track.id),o}getAudioTrackData(e,r){let s=this.trackDatas.find(a=>a.track===e);if(s)return s;Te(r),c(r),c(r.decoderConfig);let o={track:e,type:"audio",info:{numberOfChannels:r.decoderConfig.numberOfChannels,sampleRate:r.decoderConfig.sampleRate,decoderConfig:r.decoderConfig},chunkQueue:[],lastWrittenMsTimestamp:null};return this.trackDatas.push(o),this.trackDatas.sort((a,n)=>a.track.id-n.track.id),o}getSubtitleTrackData(e,r){let s=this.trackDatas.find(a=>a.track===e);if(s)return s;we(r),c(r),c(r.config);let o={track:e,type:"subtitle",info:{config:r.config},chunkQueue:[],lastWrittenMsTimestamp:null};return this.trackDatas.push(o),this.trackDatas.sort((a,n)=>a.track.id-n.track.id),o}async addEncodedVideoChunk(e,r,s){let o=await this.mutex.acquire();try{let a=this.getVideoTrackData(e,s),n=new Uint8Array(r.byteLength);r.copyTo(n);let u=this.validateAndNormalizeTimestamp(a.track,r.timestamp,r.type==="key"),d=this.createInternalChunk(n,u,(r.duration??0)/1e6,r.type);e.source._codec==="vp9"&&this.fixVP9ColorSpace(a,d),a.chunkQueue.push(d),await this.interleaveChunks()}finally{o()}}async addEncodedAudioChunk(e,r,s){let o=await this.mutex.acquire();try{let a=this.getAudioTrackData(e,s),n=new Uint8Array(r.byteLength);r.copyTo(n);let u=this.validateAndNormalizeTimestamp(a.track,r.timestamp,r.type==="key"),d=this.createInternalChunk(n,u,(r.duration??0)/1e6,r.type);a.chunkQueue.push(d),await this.interleaveChunks()}finally{o()}}async addSubtitleCue(e,r,s){let o=await this.mutex.acquire();try{let a=this.getSubtitleTrackData(e,s),n=this.validateAndNormalizeTimestamp(a.track,1e6*r.timestamp,!0),u=r.text,d=Math.floor(n*1e3);j.lastIndex=0,u=u.replace(j,E=>{let k=le(E.slice(1,-1))-d;return`<${ce(k)}>`});let m=x.encode(u),f=`${r.settings??""} ${r.identifier??""} -${r.notes??""}`,m=this.createInternalChunk(d,n,r.duration,"key",f.trim()?S.encode(f):null);o.chunkQueue.push(m),this.interleaveChunks()}interleaveChunks(){for(let e of this.output._tracks)if(!e.source._closed&&!this.trackDatas.some(r=>r.track===e))return;e:for(;;){let e=null,r=1/0;for(let o of this.trackDatas){if(o.chunkQueue.length===0&&!o.track.source._closed)break e;o.chunkQueue.length>0&&o.chunkQueue[0].timestamp=2&&s++;let d={rgb:7,bt709:2,bt470bg:1,smpte170m:3}[e.info.decoderConfig.colorSpace.matrix];je(r.data,s+0,s+3,d)}createInternalChunk(e,r,s,o,n=null){return{data:e,type:o,timestamp:r,duration:s,additions:n}}writeBlock(e,r){this.segment||(this.createTracks(),this.createSegment());let s=Math.floor(1e3*r.timestamp),o=this.trackDatas.every(m=>{if(m.track.source._closed)return!0;if(e===m)return r.type==="key";let C=m.chunkQueue[0];return C&&C.type==="key"});(!this.currentCluster||o&&s-this.currentClusterMsTimestamp>=1e3)&&this.createNewCluster(s);let n=s-this.currentClusterMsTimestamp;if(n<0)return;if(n>=$e)throw new Error(`Current Matroska cluster exceeded its maximum allowed length of ${$e} milliseconds. In order to produce a correct WebM file, you must pass in a key frame at least every ${$e} milliseconds.`);let u=new Uint8Array(4),d=new DataView(u.buffer);d.setUint8(0,128|e.track.id),d.setInt16(1,n,!1);let f=Math.floor(1e3*r.duration);if(f===0&&!r.additions){d.setUint8(3,+(r.type==="key")<<7);let m={id:163,data:[u,r.data]};this.writeEBML(m)}else{let m={id:160,data:[{id:161,data:[u,r.data]},r.type==="delta"?{id:251,data:new se(e.lastWrittenMsTimestamp-s)}:null,r.additions?{id:30113,data:[{id:166,data:[{id:165,data:r.additions},{id:238,data:1}]}]}:null,f>0?{id:155,data:f}:null]};this.writeEBML(m)}this.duration=Math.max(this.duration,s+f),e.lastWrittenMsTimestamp=s,this.trackDatasInCurrentCluster.add(e)}createNewCluster(e){this.currentCluster&&!this.format.options.streamable&&this.finalizeCurrentCluster(),this.currentCluster={id:524531317,size:this.format.options.streamable?-1:lt,data:[{id:231,data:e}]},this.writeEBML(this.currentCluster),this.currentClusterMsTimestamp=e,this.trackDatasInCurrentCluster.clear()}finalizeCurrentCluster(){c(this.currentCluster);let e=this.writer.getPos()-this.dataOffsets.get(this.currentCluster),r=this.writer.getPos();this.writer.seek(this.offsets.get(this.currentCluster)+4),this.writeEBMLVarInt(e,lt),this.writer.seek(r);let s=this.offsets.get(this.currentCluster)-this.segmentDataOffset;c(this.cues),this.cues.data.push({id:187,data:[{id:179,data:this.currentClusterMsTimestamp},...[...this.trackDatasInCurrentCluster].map(o=>({id:183,data:[{id:247,data:o.track.id},{id:241,data:s}]}))]})}onTrackClose(){this.interleaveChunks()}finalize(){this.segment||(this.createTracks(),this.createSegment());for(let e of this.trackDatas)for(;e.chunkQueue.length>0;)this.writeBlock(e,e.chunkQueue.shift());if(!this.format.options.streamable&&this.currentCluster&&this.finalizeCurrentCluster(),c(this.cues),this.writeEBML(this.cues),!this.format.options.streamable){let e=this.writer.getPos(),r=this.writer.getPos()-this.segmentDataOffset;this.writer.seek(this.offsets.get(this.segment)+4),this.writeEBMLVarInt(r,ut),this.segmentDuration.data=new G(this.duration),this.writer.seek(this.offsets.get(this.segmentDuration)),this.writeEBML(this.segmentDuration),this.seekHead.data[0].data[1].data=this.offsets.get(this.cues)-this.segmentDataOffset,this.seekHead.data[1].data[1].data=this.offsets.get(this.segmentInfo)-this.segmentDataOffset,this.seekHead.data[2].data[1].data=this.offsets.get(this.tracksElement)-this.segmentDataOffset,this.writer.seek(this.offsets.get(this.seekHead)),this.writeEBML(this.seekHead),this.writer.seek(e)}}};var M=class{},ye=class extends M{constructor(e={}){if(!e||typeof e!="object")throw new TypeError("options must be an object.");if(e.fastStart!==void 0&&![!1,"in-memory","fragmented"].includes(e.fastStart))throw new TypeError('options.fastStart, when provided, must be false, "in-memory", or "fragmented".');super();this.options=e}_createMuxer(e){return new ke(e,this)}},oe=class extends M{constructor(e={}){if(!e||typeof e!="object")throw new TypeError("options must be an object.");if(e.streamable!==void 0&&typeof e.streamable!="boolean")throw new TypeError("options.streamable, when provided, must be a boolean.");super();this.options=e}_createMuxer(e){return new we(e,this)}},P=class extends oe{};var q=["avc","hevc","vp8","vp9","av1"],Y=["aac","opus"],Se=["webvtt"],W=class{constructor(){this._connectedTrack=null;this._closed=!1;this._offsetTimestamps=!1}_ensureValidDigest(){if(!this._connectedTrack)throw new Error("Cannot call digest without connecting the source to an output track.");if(!this._connectedTrack.output._started)throw new Error("Cannot call digest before output has been started.");if(this._connectedTrack.output._finalizing)throw new Error("Cannot call digest after output has started finalizing.");if(this._closed)throw new Error("Cannot call digest after source has been closed.")}_start(){}async _flush(){}close(){if(this._closed)throw new Error("Source already closed.");if(!this._connectedTrack)throw new Error("Cannot call close without connecting the source to an output track.");if(!this._connectedTrack.output._started)throw new Error("Cannot call close before output has been started.");this._closed=!0,!this._connectedTrack.output._finalizing&&this._connectedTrack.output._muxer.onTrackClose(this._connectedTrack)}},_=class extends W{constructor(e){super();this._connectedTrack=null;if(!q.includes(e))throw new TypeError(`Invalid video codec '${e}'. Must be one of: ${q.join(", ")}.`);this._codec=e}},xe=class extends _{constructor(i){super(i)}digest(i,e){if(!(i instanceof EncodedVideoChunk))throw new TypeError("chunk must be an EncodedVideoChunk.");this._ensureValidDigest(),this._connectedTrack?.output._muxer.addEncodedVideoChunk(this._connectedTrack,i,e)}},gr=5,kr=t=>{if(!t||typeof t!="object")throw new TypeError("Codec config must be an object.");if(!q.includes(t.codec))throw new TypeError(`Invalid video codec '${t.codec}'. Must be one of: ${q.join(", ")}.`);if(!Number.isInteger(t.bitrate)||t.bitrate<=0)throw new TypeError("config.bitrate must be a positive integer.");if(t.latencyMode!==void 0&&["quality","realtime"].includes(t.latencyMode))throw new TypeError("config.latencyMode, when provided, must be 'quality' or 'realtime'.")},ne=class{constructor(i,e){this.source=i;this.codecConfig=e;this.encoder=null;this.lastMultipleOfKeyFrameInterval=-1;this.lastWidth=null;this.lastHeight=null;kr(e)}digest(i){if(this.source._ensureValidDigest(),this.lastWidth!==null&&this.lastHeight!==null){if(i.codedWidth!==this.lastWidth||i.codedHeight!==this.lastHeight)throw new Error(`Video frame size must remain constant. Expected ${this.lastWidth}x${this.lastHeight}, got ${i.codedWidth}x${i.codedHeight}.`)}else this.lastWidth=i.codedWidth,this.lastHeight=i.codedHeight;this.ensureEncoder(i),c(this.encoder);let e=Math.floor(i.timestamp/1e6/gr);this.encoder.encode(i,{keyFrame:e!==this.lastMultipleOfKeyFrameInterval}),this.lastMultipleOfKeyFrameInterval=e}ensureEncoder(i){this.encoder||(this.encoder=new VideoEncoder({output:(e,r)=>this.source._connectedTrack?.output._muxer.addEncodedVideoChunk(this.source._connectedTrack,e,r),error:e=>console.error("Video encode error:",e)}),this.encoder.configure({codec:st(this.codecConfig.codec,i.codedWidth,i.codedHeight),width:i.codedWidth,height:i.codedHeight,bitrate:this.codecConfig.bitrate,framerate:this.source._connectedTrack?.metadata.frameRate,latencyMode:this.codecConfig.latencyMode}))}async flush(){return this.encoder?.flush()}},ve=class extends _{constructor(i){super(i.codec),this._encoder=new ne(this,i)}digest(i){if(!(i instanceof VideoFrame))throw new TypeError("videoFrame must be a VideoFrame.");this._encoder.digest(i)}_flush(){return this._encoder.flush()}},Ae=class extends _{constructor(i,e){if(!(i instanceof HTMLCanvasElement))throw new TypeError("canvas must be an HTMLCanvasElement.");super(e.codec),this._encoder=new ne(this,e),this._canvas=i}digest(i,e=0){if(!Number.isFinite(i)||i<0)throw new TypeError("timestamp must be a non-negative number.");if(!Number.isFinite(e)||e<0)throw new TypeError("duration must be a non-negative number.");let r=new VideoFrame(this._canvas,{timestamp:Math.round(1e6*i),duration:Math.round(1e6*e),alpha:"discard"});this._encoder.digest(r),r.close()}_flush(){return this._encoder.flush()}},_e=class extends _{constructor(e,r){if(!(e instanceof MediaStreamTrack)||e.kind!=="video")throw new TypeError("track must be a video MediaStreamTrack.");r={...r,latencyMode:"realtime"};super(r.codec);this._abortController=null;this._offsetTimestamps=!0;this._encoder=new ne(this,r),this._track=e}_start(){this._abortController=new AbortController;let e=new MediaStreamTrackProcessor({track:this._track}),r=new WritableStream({write:s=>{this._encoder.digest(s),s.close()}});e.readable.pipeTo(r,{signal:this._abortController.signal}).catch(s=>{s instanceof DOMException&&s.name==="AbortError"||console.error("Pipe error:",s)})}async _flush(){this._abortController&&(this._abortController.abort(),this._abortController=null),await this._encoder.flush()}},E=class extends W{constructor(e){super();this._connectedTrack=null;if(!Y.includes(e))throw new TypeError(`Invalid audio codec '${e}'. Must be one of: ${Y.join(", ")}.`);this._codec=e}},Ee=class extends E{constructor(i){super(i)}digest(i,e){if(!(i instanceof EncodedAudioChunk))throw new TypeError("chunk must be an EncodedAudioChunk.");this._ensureValidDigest(),this._connectedTrack?.output._muxer.addEncodedAudioChunk(this._connectedTrack,i,e)}},wr=t=>{if(!t||typeof t!="object")throw new TypeError("Codec config must be an object.");if(!Y.includes(t.codec))throw new TypeError(`Invalid audio codec '${t.codec}'. Must be one of: ${Y.join(", ")}.`);if(!Number.isInteger(t.bitrate)||t.bitrate<=0)throw new TypeError("config.bitrate must be a positive integer.")},ae=class{constructor(i,e){this.source=i;this.codecConfig=e;this.encoder=null;this.lastNumberOfChannels=null;this.lastSampleRate=null;wr(e)}digest(i){if(this.source._ensureValidDigest(),this.lastNumberOfChannels!==null&&this.lastSampleRate!==null){if(i.numberOfChannels!==this.lastNumberOfChannels||i.sampleRate!==this.lastSampleRate)throw new Error(`Audio parameters must remain constant. Expected ${this.lastNumberOfChannels} channels at ${this.lastSampleRate} Hz, got ${i.numberOfChannels} channels at ${i.sampleRate} Hz.`)}else this.lastNumberOfChannels=i.numberOfChannels,this.lastSampleRate=i.sampleRate;this.ensureEncoder(i),c(this.encoder),this.encoder.encode(i)}ensureEncoder(i){this.encoder||(this.encoder=new AudioEncoder({output:(e,r)=>this.source._connectedTrack?.output._muxer.addEncodedAudioChunk(this.source._connectedTrack,e,r),error:e=>console.error("Audio encode error:",e)}),this.encoder.configure({codec:ot(this.codecConfig.codec,i.numberOfChannels,i.sampleRate),numberOfChannels:i.numberOfChannels,sampleRate:i.sampleRate,bitrate:this.codecConfig.bitrate}))}async flush(){return this.encoder?.flush()}},Oe=class extends E{constructor(i){super(i.codec),this._encoder=new ae(this,i)}digest(i){if(!(i instanceof AudioData))throw new TypeError("audioData must be an AudioData.");this._encoder.digest(i)}_flush(){return this._encoder.flush()}},Ve=class extends E{constructor(e){super(e.codec);this._accumulatedFrameCount=0;this._encoder=new ae(this,e)}digest(e){if(!(e instanceof AudioBuffer))throw new TypeError("audioBuffer must be an AudioBuffer.");let r=e.numberOfChannels,s=e.sampleRate,o=e.length,n=new Float32Array(r*o);for(let u=0;u{this._encoder.digest(s),s.close()}});e.readable.pipeTo(r,{signal:this._abortController.signal}).catch(s=>{s instanceof DOMException&&s.name==="AbortError"||console.error("Pipe error:",s)})}async _flush(){this._abortController&&(this._abortController.abort(),this._abortController=null),await this._encoder.flush()}},R=class extends W{constructor(e){super();this._connectedTrack=null;if(!Se.includes(e))throw new TypeError(`Invalid subtitle codec '${e}'. Must be one of: ${Se.join(", ")}.`);this._codec=e}},Ie=class extends R{constructor(i){super(i),this._parser=new le({codec:i,output:(e,r)=>this._connectedTrack?.output._muxer.addSubtitleCue(this._connectedTrack,e,r),error:e=>console.error("Subtitle parse error:",e)})}digest(i){if(typeof i!="string")throw new TypeError("text must be a string.");this._ensureValidDigest(),this._parser.parse(i)}};var ze=class{constructor(i){this._tracks=[];this._started=!1;this._finalizing=!1;if(!i||typeof i!="object")throw new TypeError("options must be an object.");if(!(i.format instanceof M))throw new TypeError("options.format must be an OutputFormat.");if(!(i.target instanceof O))throw new TypeError("options.target must be a Target.");if(i.target.output)throw new Error("Target is already used for another output.");i.target.output=this,this._writer=i.target._createWriter(),this._muxer=i.format._createMuxer(this)}addVideoTrack(i,e={}){if(!(i instanceof _))throw new TypeError("source must be a VideoSource.");if(!e||typeof e!="object")throw new TypeError("metadata must be an object.");if(typeof e.rotation=="number"&&![0,90,180,270].includes(e.rotation))throw new TypeError(`Invalid video rotation: ${e.rotation}. Has to be 0, 90, 180 or 270.`);if(Array.isArray(e.rotation)&&(e.rotation.length!==9||e.rotation.some(r=>!Number.isFinite(r))))throw new TypeError(`Invalid video transformation matrix: ${e.rotation.join()}`);if(e.frameRate!==void 0&&(!Number.isInteger(e.frameRate)||e.frameRate<=0))throw new TypeError(`Invalid video frame rate: ${e.frameRate}. Must be a positive integer.`);this._addTrack("video",i,e)}addAudioTrack(i,e={}){if(!(i instanceof E))throw new TypeError("source must be an AudioSource.");if(!e||typeof e!="object")throw new TypeError("metadata must be an object.");this._addTrack("audio",i,e)}addSubtitleTrack(i,e={}){if(!(i instanceof R))throw new TypeError("source must be a SubtitleSource.");if(!e||typeof e!="object")throw new TypeError("metadata must be an object.");this._addTrack("subtitle",i,e)}_addTrack(i,e,r){if(this._started)throw new Error("Cannot add track after output has started.");if(e._connectedTrack)throw new Error("Source is already used for a track.");let s={id:this._tracks.length+1,output:this,type:i,source:e,metadata:r};this._muxer.beforeTrackAdd(s),this._tracks.push(s),e._connectedTrack=s}start(){if(this._started)throw new Error("Output already started.");this._started=!0,this._muxer.start();for(let i of this._tracks)i.source._start()}async finalize(){if(this._finalizing)throw new Error("Cannot call finalize twice.");this._finalizing=!0;let i=this._tracks.map(e=>e.source._flush());await Promise.all(i),this._muxer.finalize(),this._writer.flush(),this._writer.finalize()}};return ht(yr);})(); +${r.notes??""}`,w=this.createInternalChunk(m,n,r.duration,"key",f.trim()?x.encode(f):null);a.chunkQueue.push(w),await this.interleaveChunks()}finally{o()}}async interleaveChunks(){for(let e of this.output._tracks)if(!e.source._closed&&!this.trackDatas.some(r=>r.track===e))return;e:for(;;){let e=null,r=1/0;for(let o of this.trackDatas){if(o.chunkQueue.length===0&&!o.track.source._closed)break e;o.chunkQueue.length>0&&o.chunkQueue[0].timestamp=2&&s++;let d={rgb:7,bt709:2,bt470bg:1,smpte170m:3}[e.info.decoderConfig.colorSpace.matrix];Qe(r.data,s+0,s+3,d)}createInternalChunk(e,r,s,o,a=null){return{data:e,type:o,timestamp:r,duration:s,additions:a}}writeBlock(e,r){this.segment||(this.createTracks(),this.createSegment());let s=Math.floor(1e3*r.timestamp),o=this.trackDatas.every(f=>{if(f.track.source._closed)return!0;if(e===f)return r.type==="key";let w=f.chunkQueue[0];return w&&w.type==="key"});(!this.currentCluster||o&&s-this.currentClusterMsTimestamp>=1e3)&&this.createNewCluster(s);let a=s-this.currentClusterMsTimestamp;if(a<0)return;if(a>=He)throw new Error(`Current Matroska cluster exceeded its maximum allowed length of ${He} milliseconds. In order to produce a correct WebM file, you must pass in a key frame at least every ${He} milliseconds.`);let u=new Uint8Array(4),d=new DataView(u.buffer);d.setUint8(0,128|e.track.id),d.setInt16(1,a,!1);let m=Math.floor(1e3*r.duration);if(m===0&&!r.additions){d.setUint8(3,+(r.type==="key")<<7);let f={id:163,data:[u,r.data]};this.writeEBML(f)}else{let f={id:160,data:[{id:161,data:[u,r.data]},r.type==="delta"?{id:251,data:new ie(e.lastWrittenMsTimestamp-s)}:null,r.additions?{id:30113,data:[{id:166,data:[{id:165,data:r.additions},{id:238,data:1}]}]}:null,m>0?{id:155,data:m}:null]};this.writeEBML(f)}this.duration=Math.max(this.duration,s+m),e.lastWrittenMsTimestamp=s,this.trackDatasInCurrentCluster.add(e)}createNewCluster(e){this.currentCluster&&!this.format._options.streamable&&this.finalizeCurrentCluster(),this.currentCluster={id:524531317,size:this.format._options.streamable?-1:ut,data:[{id:231,data:e}]},this.writeEBML(this.currentCluster),this.currentClusterMsTimestamp=e,this.trackDatasInCurrentCluster.clear()}finalizeCurrentCluster(){c(this.currentCluster);let e=this.writer.getPos()-this.dataOffsets.get(this.currentCluster),r=this.writer.getPos();this.writer.seek(this.offsets.get(this.currentCluster)+4),this.writeEBMLVarInt(e,ut),this.writer.seek(r);let s=this.offsets.get(this.currentCluster)-this.segmentDataOffset;c(this.cues),this.cues.data.push({id:187,data:[{id:179,data:this.currentClusterMsTimestamp},...[...this.trackDatasInCurrentCluster].map(o=>({id:183,data:[{id:247,data:o.track.id},{id:241,data:s}]}))]})}async onTrackClose(){let e=await this.mutex.acquire();await this.interleaveChunks(),e()}async finalize(){let e=await this.mutex.acquire();this.segment||(this.createTracks(),this.createSegment());for(let r of this.trackDatas)for(;r.chunkQueue.length>0;)this.writeBlock(r,r.chunkQueue.shift());if(!this.format._options.streamable&&this.currentCluster&&this.finalizeCurrentCluster(),c(this.cues),this.writeEBML(this.cues),!this.format._options.streamable){let r=this.writer.getPos(),s=this.writer.getPos()-this.segmentDataOffset;this.writer.seek(this.offsets.get(this.segment)+4),this.writeEBMLVarInt(s,nt),this.segmentDuration.data=new X(this.duration),this.writer.seek(this.offsets.get(this.segmentDuration)),this.writeEBML(this.segmentDuration),this.seekHead.data[0].data[1].data=this.offsets.get(this.cues)-this.segmentDataOffset,this.seekHead.data[1].data[1].data=this.offsets.get(this.segmentInfo)-this.segmentDataOffset,this.seekHead.data[2].data[1].data=this.offsets.get(this.tracksElement)-this.segmentDataOffset,this.writer.seek(this.offsets.get(this.seekHead)),this.writeEBML(this.seekHead),this.writer.seek(r)}e()}};var M=class{},ke=class extends M{constructor(i={}){if(!i||typeof i!="object")throw new TypeError("options must be an object.");if(i.fastStart!==void 0&&![!1,"in-memory","fragmented"].includes(i.fastStart))throw new TypeError('options.fastStart, when provided, must be false, "in-memory", or "fragmented".');super(),this._options=i}_createMuxer(i){return new Ce(i,this)}},se=class extends M{constructor(i={}){if(!i||typeof i!="object")throw new TypeError("options must be an object.");if(i.streamable!==void 0&&typeof i.streamable!="boolean")throw new TypeError("options.streamable, when provided, must be a boolean.");super(),this._options=i}_createMuxer(i){return new ge(i,this)}},D=class extends se{};var G=["avc","hevc","vp8","vp9","av1"],Y=["aac","opus"],ye=["webvtt"],W=class{constructor(){this._connectedTrack=null;this._closed=!1;this._offsetTimestamps=!1}_ensureValidDigest(){if(!this._connectedTrack)throw new Error("Cannot call digest without connecting the source to an output track.");if(!this._connectedTrack.output._started)throw new Error("Cannot call digest before output has been started.");if(this._connectedTrack.output._finalizing)throw new Error("Cannot call digest after output has started finalizing.");if(this._closed)throw new Error("Cannot call digest after source has been closed.")}_start(){}async _flush(){}close(){if(this._closed)throw new Error("Source already closed.");if(!this._connectedTrack)throw new Error("Cannot call close without connecting the source to an output track.");if(!this._connectedTrack.output._started)throw new Error("Cannot call close before output has been started.");this._closed=!0,!this._connectedTrack.output._finalizing&&this._connectedTrack.output._muxer.onTrackClose(this._connectedTrack)}},_=class extends W{constructor(e){super();this._connectedTrack=null;if(!G.includes(e))throw new TypeError(`Invalid video codec '${e}'. Must be one of: ${G.join(", ")}.`);this._codec=e}},xe=class extends _{constructor(i){super(i)}digest(i,e){if(!(i instanceof EncodedVideoChunk))throw new TypeError("chunk must be an EncodedVideoChunk.");return this._ensureValidDigest(),this._connectedTrack.output._muxer.addEncodedVideoChunk(this._connectedTrack,i,e)}},wr=5,Cr=t=>{if(!t||typeof t!="object")throw new TypeError("Codec config must be an object.");if(!G.includes(t.codec))throw new TypeError(`Invalid video codec '${t.codec}'. Must be one of: ${G.join(", ")}.`);if(!Number.isInteger(t.bitrate)||t.bitrate<=0)throw new TypeError("config.bitrate must be a positive integer.");if(t.latencyMode!==void 0&&["quality","realtime"].includes(t.latencyMode))throw new TypeError("config.latencyMode, when provided, must be 'quality' or 'realtime'.")},oe=class{constructor(i,e){this.source=i;this.codecConfig=e;this.encoder=null;this.muxer=null;this.lastMultipleOfKeyFrameInterval=-1;this.lastWidth=null;this.lastHeight=null;Cr(e)}async digest(i){if(this.source._ensureValidDigest(),this.lastWidth!==null&&this.lastHeight!==null){if(i.codedWidth!==this.lastWidth||i.codedHeight!==this.lastHeight)throw new Error(`Video frame size must remain constant. Expected ${this.lastWidth}x${this.lastHeight}, got ${i.codedWidth}x${i.codedHeight}.`)}else this.lastWidth=i.codedWidth,this.lastHeight=i.codedHeight;this.ensureEncoder(i),c(this.encoder);let e=Math.floor(i.timestamp/1e6/wr);this.encoder.encode(i,{keyFrame:e!==this.lastMultipleOfKeyFrameInterval}),this.lastMultipleOfKeyFrameInterval=e,this.encoder.encodeQueueSize>=4&&await new Promise(r=>this.encoder.addEventListener("dequeue",r,{once:!0})),await this.muxer.mutex.currentPromise}ensureEncoder(i){this.encoder||(this.encoder=new VideoEncoder({output:(e,r)=>this.muxer.addEncodedVideoChunk(this.source._connectedTrack,e,r),error:e=>console.error("Video encode error:",e)}),this.encoder.configure({codec:it(this.codecConfig.codec,i.codedWidth,i.codedHeight),width:i.codedWidth,height:i.codedHeight,bitrate:this.codecConfig.bitrate,framerate:this.source._connectedTrack?.metadata.frameRate,latencyMode:this.codecConfig.latencyMode}),c(this.source._connectedTrack),this.muxer=this.source._connectedTrack.output._muxer)}async flush(){this.encoder&&(await this.encoder.flush(),this.encoder.close())}},Se=class extends _{constructor(i){super(i.codec),this._encoder=new oe(this,i)}digest(i){if(!(i instanceof VideoFrame))throw new TypeError("videoFrame must be a VideoFrame.");return this._encoder.digest(i)}_flush(){return this._encoder.flush()}},ve=class extends _{constructor(i,e){if(!(i instanceof HTMLCanvasElement))throw new TypeError("canvas must be an HTMLCanvasElement.");super(e.codec),this._encoder=new oe(this,e),this._canvas=i}digest(i,e=0){if(!Number.isFinite(i)||i<0)throw new TypeError("timestamp must be a non-negative number.");if(!Number.isFinite(e)||e<0)throw new TypeError("duration must be a non-negative number.");let r=new VideoFrame(this._canvas,{timestamp:Math.round(1e6*i),duration:Math.round(1e6*e),alpha:"discard"}),s=this._encoder.digest(r);return r.close(),s}_flush(){return this._encoder.flush()}},Ae=class extends _{constructor(e,r){if(!(e instanceof MediaStreamTrack)||e.kind!=="video")throw new TypeError("track must be a video MediaStreamTrack.");r={...r,latencyMode:"realtime"};super(r.codec);this._abortController=null;this._offsetTimestamps=!0;this._encoder=new oe(this,r),this._track=e}_start(){this._abortController=new AbortController;let e=new MediaStreamTrackProcessor({track:this._track}),r=new WritableStream({write:s=>{this._encoder.digest(s),s.close()}});e.readable.pipeTo(r,{signal:this._abortController.signal}).catch(s=>{s instanceof DOMException&&s.name==="AbortError"||console.error("Pipe error:",s)})}async _flush(){this._abortController&&(this._abortController.abort(),this._abortController=null),await this._encoder.flush()}},O=class extends W{constructor(e){super();this._connectedTrack=null;if(!Y.includes(e))throw new TypeError(`Invalid audio codec '${e}'. Must be one of: ${Y.join(", ")}.`);this._codec=e}},_e=class extends O{constructor(i){super(i)}digest(i,e){if(!(i instanceof EncodedAudioChunk))throw new TypeError("chunk must be an EncodedAudioChunk.");return this._ensureValidDigest(),this._connectedTrack.output._muxer.addEncodedAudioChunk(this._connectedTrack,i,e)}},gr=t=>{if(!t||typeof t!="object")throw new TypeError("Codec config must be an object.");if(!Y.includes(t.codec))throw new TypeError(`Invalid audio codec '${t.codec}'. Must be one of: ${Y.join(", ")}.`);if(!Number.isInteger(t.bitrate)||t.bitrate<=0)throw new TypeError("config.bitrate must be a positive integer.")},ae=class{constructor(i,e){this.source=i;this.codecConfig=e;this.encoder=null;this.muxer=null;this.lastNumberOfChannels=null;this.lastSampleRate=null;gr(e)}async digest(i){if(this.source._ensureValidDigest(),this.lastNumberOfChannels!==null&&this.lastSampleRate!==null){if(i.numberOfChannels!==this.lastNumberOfChannels||i.sampleRate!==this.lastSampleRate)throw new Error(`Audio parameters must remain constant. Expected ${this.lastNumberOfChannels} channels at ${this.lastSampleRate} Hz, got ${i.numberOfChannels} channels at ${i.sampleRate} Hz.`)}else this.lastNumberOfChannels=i.numberOfChannels,this.lastSampleRate=i.sampleRate;this.ensureEncoder(i),c(this.encoder),this.encoder.encode(i),this.encoder.encodeQueueSize>=4&&await new Promise(e=>this.encoder.addEventListener("dequeue",e,{once:!0})),await this.muxer.mutex.currentPromise}ensureEncoder(i){this.encoder||(this.encoder=new AudioEncoder({output:(e,r)=>this.muxer.addEncodedAudioChunk(this.source._connectedTrack,e,r),error:e=>console.error("Audio encode error:",e)}),this.encoder.configure({codec:st(this.codecConfig.codec,i.numberOfChannels,i.sampleRate),numberOfChannels:i.numberOfChannels,sampleRate:i.sampleRate,bitrate:this.codecConfig.bitrate}),c(this.source._connectedTrack),this.muxer=this.source._connectedTrack.output._muxer)}async flush(){this.encoder&&(await this.encoder.flush(),this.encoder.close())}},Oe=class extends O{constructor(i){super(i.codec),this._encoder=new ae(this,i)}digest(i){if(!(i instanceof AudioData))throw new TypeError("audioData must be an AudioData.");return this._encoder.digest(i)}_flush(){return this._encoder.flush()}},Ee=class extends O{constructor(e){super(e.codec);this._accumulatedFrameCount=0;this._encoder=new ae(this,e)}digest(e){if(!(e instanceof AudioBuffer))throw new TypeError("audioBuffer must be an AudioBuffer.");let r=e.numberOfChannels,s=e.sampleRate,o=e.length,a=new Float32Array(r*o);for(let d=0;d{this._encoder.digest(s),s.close()}});e.readable.pipeTo(r,{signal:this._abortController.signal}).catch(s=>{s instanceof DOMException&&s.name==="AbortError"||console.error("Pipe error:",s)})}async _flush(){this._abortController&&(this._abortController.abort(),this._abortController=null),await this._encoder.flush()}},R=class extends W{constructor(e){super();this._connectedTrack=null;if(!ye.includes(e))throw new TypeError(`Invalid subtitle codec '${e}'. Must be one of: ${ye.join(", ")}.`);this._codec=e}},Me=class extends R{constructor(i){super(i),this._parser=new ue({codec:i,output:(e,r)=>this._connectedTrack?.output._muxer.addSubtitleCue(this._connectedTrack,e,r),error:e=>console.error("Subtitle parse error:",e)})}digest(i){if(typeof i!="string")throw new TypeError("text must be a string.");return this._ensureValidDigest(),this._parser.parse(i),this._connectedTrack.output._muxer.mutex.currentPromise}};var Ie=class{constructor(i){this._tracks=[];this._started=!1;this._finalizing=!1;this._mutex=new B;if(!i||typeof i!="object")throw new TypeError("options must be an object.");if(!(i.format instanceof M))throw new TypeError("options.format must be an OutputFormat.");if(!(i.target instanceof V))throw new TypeError("options.target must be a Target.");if(i.target._output)throw new Error("Target is already used for another output.");i.target._output=this,this._writer=i.target._createWriter(),this._muxer=i.format._createMuxer(this)}addVideoTrack(i,e={}){if(!(i instanceof _))throw new TypeError("source must be a VideoSource.");if(!e||typeof e!="object")throw new TypeError("metadata must be an object.");if(typeof e.rotation=="number"&&![0,90,180,270].includes(e.rotation))throw new TypeError(`Invalid video rotation: ${e.rotation}. Has to be 0, 90, 180 or 270.`);if(Array.isArray(e.rotation)&&(e.rotation.length!==9||e.rotation.some(r=>!Number.isFinite(r))))throw new TypeError(`Invalid video transformation matrix: ${e.rotation.join()}`);if(e.frameRate!==void 0&&(!Number.isInteger(e.frameRate)||e.frameRate<=0))throw new TypeError(`Invalid video frame rate: ${e.frameRate}. Must be a positive integer.`);this._addTrack("video",i,e)}addAudioTrack(i,e={}){if(!(i instanceof O))throw new TypeError("source must be an AudioSource.");if(!e||typeof e!="object")throw new TypeError("metadata must be an object.");this._addTrack("audio",i,e)}addSubtitleTrack(i,e={}){if(!(i instanceof R))throw new TypeError("source must be a SubtitleSource.");if(!e||typeof e!="object")throw new TypeError("metadata must be an object.");this._addTrack("subtitle",i,e)}_addTrack(i,e,r){if(this._started)throw new Error("Cannot add track after output has started.");if(e._connectedTrack)throw new Error("Source is already used for a track.");let s={id:this._tracks.length+1,output:this,type:i,source:e,metadata:r};this._muxer.beforeTrackAdd(s),this._tracks.push(s),e._connectedTrack=s}async start(){if(this._started)throw new Error("Output already started.");this._started=!0,this._writer.start();let i=await this._mutex.acquire();await this._muxer.start();for(let e of this._tracks)e.source._start();i()}async finalize(){if(!this._started)throw new Error("Cannot finalize before starting.");if(this._finalizing)throw new Error("Cannot call finalize twice.");this._finalizing=!0;let i=await this._mutex.acquire(),e=this._tracks.map(r=>r.source._flush());await Promise.all(e),await this._muxer.finalize(),await this._writer.flush(),await this._writer.finalize(),i()}};return pt(kr);})(); if (typeof module === "object" && typeof module.exports === "object") Object.assign(module.exports, Metamuxer) diff --git a/dist/metamuxer.min.mjs b/dist/metamuxer.min.mjs index 2e6c9d9..3f07b92 100644 --- a/dist/metamuxer.min.mjs +++ b/dist/metamuxer.min.mjs @@ -1,9 +1,9 @@ -function c(t){if(!t)throw new Error("Assertion failed.")}var W=t=>t&&t[t.length-1],M=t=>t>=0&&t<2**32,I=(t,i,e)=>{let r=0;for(let s=i;s>a;r<<=1,r|=u}return r},$e=(t,i,e,r)=>{for(let s=i;s>e-s-1<t instanceof ArrayBuffer?new Uint8Array(t):new Uint8Array(t.buffer,t.byteOffset,t.byteLength),S=new TextEncoder,R={bt709:1,bt470bg:5,smpte170m:6},B={bt709:1,smpte170m:6,"iec61966-2-1":13},F={rgb:0,bt709:1,bt470bg:5,smpte170m:6},ae=t=>!!t&&!!t.primaries&&!!t.transfer&&!!t.matrix&&t.fullRange!==void 0,we=t=>t instanceof ArrayBuffer||typeof SharedArrayBuffer<"u"&&t instanceof SharedArrayBuffer||ArrayBuffer.isView(t)&&!(t instanceof DataView);var X=/(?:(.+?)\n)?((?:\d{2}:)?\d{2}:\d{2}.\d{3})\s+-->\s+((?:\d{2}:)?\d{2}:\d{2}.\d{3})/g,lt=/^WEBVTT(.|\n)*?\n{2}/,N=/<(?:(\d{2}):)?(\d{2}):(\d{2}).(\d{3})>/g,ue=class{constructor(i){this.preambleText=null;this.preambleEmitted=!1;this.options=i}parse(i){i=i.replaceAll(`\r +function c(r){if(!r)throw new Error("Assertion failed.")}var R=r=>r&&r[r.length-1],V=r=>r>=0&&r<2**32,M=(r,i,e)=>{let t=0;for(let s=i;s>n;t<<=1,t|=u}return t},He=(r,i,e,t)=>{for(let s=i;s>e-s-1<r instanceof ArrayBuffer?new Uint8Array(r):new Uint8Array(r.buffer,r.byteOffset,r.byteLength),x=new TextEncoder,F={bt709:1,bt470bg:5,smpte170m:6},B={bt709:1,smpte170m:6,"iec61966-2-1":13},N={rgb:0,bt709:1,bt470bg:5,smpte170m:6},ae=r=>!!r&&!!r.primaries&&!!r.transfer&&!!r.matrix&&r.fullRange!==void 0,ge=r=>r instanceof ArrayBuffer||typeof SharedArrayBuffer<"u"&&r instanceof SharedArrayBuffer||ArrayBuffer.isView(r)&&!(r instanceof DataView),W=class{constructor(){this.currentPromise=Promise.resolve()}async acquire(){let i,e=new Promise(s=>{i=s}),t=this.currentPromise;return this.currentPromise=e,await t,i}};var X=/(?:(.+?)\n)?((?:\d{2}:)?\d{2}:\d{2}.\d{3})\s+-->\s+((?:\d{2}:)?\d{2}:\d{2}.\d{3})/g,ut=/^WEBVTT(.|\n)*?\n{2}/,H=/<(?:(\d{2}):)?(\d{2}):(\d{2}).(\d{3})>/g,ne=class{constructor(i){this.preambleText=null;this.preambleEmitted=!1;this.options=i}parse(i){i=i.replaceAll(`\r `,` `).replaceAll("\r",` -`),X.lastIndex=0;let e;if(!this.preambleText){if(!lt.test(i)){let s=new Error("WebVTT preamble incorrect.");throw this.options.error(s),s}e=X.exec(i);let r=i.slice(0,e?.index??i.length).trimEnd();if(!r){let s=new Error("No WebVTT preamble provided.");throw this.options.error(s),s}this.preambleText=r,e&&(i=i.slice(e.index),X.lastIndex=0)}for(;e=X.exec(i);){let r=i.slice(0,e.index),s=e[1],o=e.index+e[0].length,n=i.indexOf(` -`,o)+1,a=i.slice(o,n).trim(),u=i.indexOf(` +`),X.lastIndex=0;let e;if(!this.preambleText){if(!ut.test(i)){let s=new Error("WebVTT preamble incorrect.");throw this.options.error(s),s}e=X.exec(i);let t=i.slice(0,e?.index??i.length).trimEnd();if(!t){let s=new Error("No WebVTT preamble provided.");throw this.options.error(s),s}this.preambleText=t,e&&(i=i.slice(e.index),X.lastIndex=0)}for(;e=X.exec(i);){let t=i.slice(0,e.index),s=e[1],o=e.index+e[0].length,a=i.indexOf(` +`,o)+1,n=i.slice(o,a).trim(),u=i.indexOf(` -`,o);u===-1&&(u=i.length);let d=le(e[2]),m=le(e[3])-d,C=i.slice(n,u).trim();i=i.slice(u).trimStart(),X.lastIndex=0;let O={timestamp:d/1e3,duration:m/1e3,text:C,identifier:s,settings:a,notes:r},k={};this.preambleEmitted||(k.config={description:this.preambleText},this.preambleEmitted=!0),this.options.output(O,k)}}},ct=/(?:(\d{2}):)?(\d{2}):(\d{2}).(\d{3})/,le=t=>{let i=ct.exec(t);if(!i)throw new Error("Expected match.");return 60*60*1e3*Number(i[1]||"0")+60*1e3*Number(i[2])+1e3*Number(i[3])+Number(i[4])},ce=t=>{let i=Math.floor(t/36e5),e=Math.floor(t%(60*60*1e3)/(60*1e3)),r=Math.floor(t%(60*1e3)/1e3),s=t%1e3;return i.toString().padStart(2,"0")+":"+e.toString().padStart(2,"0")+":"+r.toString().padStart(2,"0")+"."+s.toString().padStart(3,"0")};var G=class{constructor(i){this.writer=i;this.helper=new Uint8Array(8);this.helperView=new DataView(this.helper.buffer);this.offsets=new WeakMap}writeU32(i){this.helperView.setUint32(0,i,!1),this.writer.write(this.helper.subarray(0,4))}writeU64(i){this.helperView.setUint32(0,Math.floor(i/2**32),!1),this.helperView.setUint32(4,i,!1),this.writer.write(this.helper.subarray(0,8))}writeAscii(i){for(let e=0;e[(t%256+256)%256],h=t=>(A.setUint16(0,t,!1),[p[0],p[1]]),dt=t=>(A.setInt16(0,t,!1),[p[0],p[1]]),Qe=t=>(A.setUint32(0,t,!1),[p[1],p[2],p[3]]),l=t=>(A.setUint32(0,t,!1),[p[0],p[1],p[2],p[3]]),Ke=t=>(A.setInt32(0,t,!1),[p[0],p[1],p[2],p[3]]),D=t=>(A.setUint32(0,Math.floor(t/2**32),!1),A.setUint32(4,t,!1),[p[0],p[1],p[2],p[3],p[4],p[5],p[6],p[7]]),xe=t=>(A.setInt16(0,2**8*t,!1),[p[0],p[1]]),v=t=>(A.setInt32(0,2**16*t,!1),[p[0],p[1],p[2],p[3]]),ye=t=>(A.setInt32(0,2**30*t,!1),[p[0],p[1],p[2],p[3]]),Se=(t,i)=>{let e=[],r=t;do{let s=r&127;r>>=7,e.length>0&&(s|=128),e.push(s),i!==void 0&&i--}while(r>0||i);return e.reverse()},w=(t,i=!1)=>{let e=Array(t.length).fill(null).map((r,s)=>t.charCodeAt(s));return i&&e.push(0),e},ve=t=>{let i=null;for(let e of t)(!i||e.timestamp>i.timestamp)&&(i=e);return i},Le=t=>{let i=t*(Math.PI/180),e=Math.cos(i),r=Math.sin(i);return[e,r,0,-r,e,0,0,0,1]},Xe=Le(0),Ge=t=>[v(t[0]),v(t[1]),ye(t[2]),v(t[3]),v(t[4]),ye(t[5]),v(t[6]),v(t[7]),ye(t[8])],b=(t,i,e)=>({type:t,contents:i&&new Uint8Array(i.flat(10)),children:e}),T=(t,i,e,r,s)=>b(t,[g(i),Qe(e),r??[]],s),qe=t=>{let i=512;return t.fragmented?b("ftyp",[w("iso5"),l(i),w("iso5"),w("iso6"),w("mp41")]):b("ftyp",[w("isom"),l(i),w("isom"),t.holdsAvc?w("avc1"):[],w("mp41")])},fe=t=>({type:"mdat",largeSize:t}),Ye=t=>({type:"free",size:t}),q=(t,i,e=!1)=>b("moov",void 0,[ft(i,t),...t.map(r=>mt(r,i)),e?Kt(t):null]),ft=(t,i)=>{let e=x(Math.max(0,...i.filter(n=>n.samples.length>0).map(n=>{let a=ve(n.samples);return a.timestamp+a.duration})),de),r=Math.max(0,...i.map(n=>n.track.id))+1,s=!M(t)||!M(e),o=s?D:l;return T("mvhd",+s,0,[o(t),o(t),l(de),o(e),v(1),xe(1),Array(10).fill(0),Ge(Xe),Array(24).fill(0),l(r)])},mt=(t,i)=>b("trak",void 0,[pt(t,i),ht(t,i)]),pt=(t,i)=>{let e=ve(t.samples),r=x(e?e.timestamp+e.duration:0,de),s=!M(i)||!M(r),o=s?D:l,n;if(t.type==="video"){let a=t.track.metadata.rotation;n=a===void 0||typeof a=="number"?Le(a??0):a}else n=Xe;return T("tkhd",+s,3,[o(i),o(i),l(t.track.id),l(0),o(r),Array(8).fill(0),h(0),h(t.track.id),xe(t.type==="audio"?1:0),h(0),Ge(n),v(t.type==="video"?t.info.width:0),v(t.type==="video"?t.info.height:0)])},ht=(t,i)=>b("mdia",void 0,[bt(t,i),gt(t),kt(t)]),bt=(t,i)=>{let e=ve(t.samples),r=x(e?e.timestamp+e.duration:0,t.timescale),s=!M(i)||!M(r),o=s?D:l;return T("mdhd",+s,0,[o(i),o(i),l(t.timescale),o(r),h(21956),h(0)])},Tt={video:"vide",audio:"soun",subtitle:"text"},Ct={video:"VideoHandler",audio:"SoundHandler",subtitle:"TextHandler"},gt=t=>T("hdlr",0,0,[w("mhlr"),w(Tt[t.type]),l(0),l(0),l(0),w(Ct[t.type],!0)]),kt=t=>b("minf",void 0,[xt[t.type](),vt(),Et(t)]),wt=()=>T("vmhd",0,1,[h(0),h(0),h(0),h(0)]),yt=()=>T("smhd",0,0,[h(0),h(0)]),St=()=>T("nmhd",0,0),xt={video:wt,audio:yt,subtitle:St},vt=()=>b("dinf",void 0,[At()]),At=()=>T("dref",0,0,[l(1)],[_t()]),_t=()=>T("url ",0,1),Et=t=>{let i=t.compositionTimeOffsetTable.length>1||t.compositionTimeOffsetTable.some(e=>e.sampleCompositionTimeOffset!==0);return b("stbl",void 0,[Ot(t),Ft(t),Nt(t),Ht(t),$t(t),jt(t),i?Qt(t):null])},Ot=t=>{let i;return t.type==="video"?i=Vt(tr[t.track.source._codec],t):t.type==="audio"?i=Ut(ir[t.track.source._codec],t):t.type==="subtitle"&&(i=Rt(or[t.track.source._codec],t)),c(i),T("stsd",0,0,[l(1)],[i])},Vt=(t,i)=>b(t,[Array(6).fill(0),h(1),h(0),h(0),Array(12).fill(0),h(i.info.width),h(i.info.height),l(4718592),l(4718592),l(0),h(1),Array(32).fill(0),h(24),dt(65535)],[rr[i.track.source._codec](i),ae(i.info.decoderConfig.colorSpace)?Mt(i):null]),Mt=t=>b("colr",[w("nclx"),h(R[t.info.decoderConfig.colorSpace.primaries]),h(B[t.info.decoderConfig.colorSpace.transfer]),h(F[t.info.decoderConfig.colorSpace.matrix]),g((t.info.decoderConfig.colorSpace.fullRange?1:0)<<7)]),It=t=>t.info.decoderConfig&&b("avcC",[...z(t.info.decoderConfig.description)]),zt=t=>t.info.decoderConfig&&b("hvcC",[...z(t.info.decoderConfig.description)]),je=t=>{if(!t.info.decoderConfig)return null;let i=t.info.decoderConfig;c(i.colorSpace);let e=i.codec.split("."),r=Number(e[1]),s=Number(e[2]),a=(Number(e[3])<<4)+(0<<1)+Number(i.colorSpace.fullRange);return T("vpcC",1,0,[g(r),g(s),g(a),g(2),g(2),g(2),h(0)])},Dt=()=>{let e=(1<<7)+1;return b("av1C",[e,0,0,0])},Ut=(t,i)=>b(t,[Array(6).fill(0),h(1),h(0),h(0),l(0),h(i.info.numberOfChannels),h(16),h(0),h(0),v(i.info.sampleRate)],[sr[i.track.source._codec](i)]),Pt=t=>{let e=[...z(t.info.decoderConfig.description??new ArrayBuffer(0))];return e=[...g(64),...g(21),...Qe(0),...l(0),...l(0),...g(5),...Se(e.length),...e],e=[...h(1),...g(0),...g(4),...Se(e.length),...e,...g(6),...g(1),...g(2)],e=[...g(3),...Se(e.length),...e],T("esds",0,0,e)},Wt=t=>{let i=3840,e=0,r=t.info.decoderConfig?.description;if(r){c(r.byteLength<18);let s=ArrayBuffer.isView(r)?new DataView(r.buffer,r.byteOffset,r.byteLength):new DataView(r);i=s.getUint16(10,!0),e=s.getInt16(14,!0)}return b("dOps",[g(0),g(t.info.numberOfChannels),h(i),l(t.info.sampleRate),xe(e),g(0)])},Rt=(t,i)=>b(t,[Array(6).fill(0),h(1)],[nr[i.track.source._codec](i)]),Bt=t=>b("vttC",[...S.encode(t.info.config.description)]);var Ft=t=>T("stts",0,0,[l(t.timeToSampleTable.length),t.timeToSampleTable.map(i=>[l(i.sampleCount),l(i.sampleDelta)])]),Nt=t=>{if(t.samples.every(e=>e.type==="key"))return null;let i=[...t.samples.entries()].filter(([,e])=>e.type==="key");return T("stss",0,0,[l(i.length),i.map(([e])=>l(e+1))])},Ht=t=>T("stsc",0,0,[l(t.compactlyCodedChunkTable.length),t.compactlyCodedChunkTable.map(i=>[l(i.firstChunk),l(i.samplesPerChunk),l(1)])]),$t=t=>T("stsz",0,0,[l(0),l(t.samples.length),t.samples.map(i=>l(i.size))]),jt=t=>t.finalizedChunks.length>0&&W(t.finalizedChunks).offset>=2**32?T("co64",0,0,[l(t.finalizedChunks.length),t.finalizedChunks.map(i=>D(i.offset))]):T("stco",0,0,[l(t.finalizedChunks.length),t.finalizedChunks.map(i=>l(i.offset))]),Qt=t=>T("ctts",0,0,[l(t.compositionTimeOffsetTable.length),t.compositionTimeOffsetTable.map(i=>[l(i.sampleCount),l(i.sampleCompositionTimeOffset)])]),Kt=t=>b("mvex",void 0,t.map(Lt)),Lt=t=>T("trex",0,0,[l(t.track.id),l(1),l(0),l(0),l(0)]),Ae=(t,i)=>b("moof",void 0,[Xt(t),...i.map(Gt)]),Xt=t=>T("mfhd",0,0,[l(t)]),Ze=t=>{let i=0,e=0,r=0,s=0,o=t.type==="delta";return e|=+o,o?i|=1:i|=2,i<<24|e<<16|r<<8|s},Gt=t=>b("traf",void 0,[qt(t),Yt(t),Zt(t)]),qt=t=>{c(t.currentChunk);let i=0;i|=8,i|=16,i|=32,i|=131072;let e=t.currentChunk.samples[1]??t.currentChunk.samples[0],r={duration:e.timescaleUnitsToNextSample,size:e.size,flags:Ze(e)};return T("tfhd",0,i,[l(t.track.id),l(r.duration),l(r.size),l(r.flags)])},Yt=t=>(c(t.currentChunk),T("tfdt",1,0,[D(x(t.currentChunk.startTimestamp,t.timescale))])),Zt=t=>{c(t.currentChunk);let i=t.currentChunk.samples.map(y=>y.timescaleUnitsToNextSample),e=t.currentChunk.samples.map(y=>y.size),r=t.currentChunk.samples.map(Ze),s=t.currentChunk.samples.map(y=>x(y.timestamp-y.decodeTimestamp,t.timescale)),o=new Set(i),n=new Set(e),a=new Set(r),u=new Set(s),d=a.size===2&&r[0]!==r[1],f=o.size>1,m=n.size>1,C=!d&&a.size>1,O=u.size>1||[...u].some(y=>y!==0),k=0;return k|=1,k|=4*+d,k|=256*+f,k|=512*+m,k|=1024*+C,k|=2048*+O,T("trun",1,k,[l(t.currentChunk.samples.length),l(t.currentChunk.offset-t.currentChunk.moofOffset||0),d?l(r[0]):[],t.currentChunk.samples.map((y,P)=>[f?l(i[P]):[],m?l(e[P]):[],C?l(r[P]):[],O?Ke(s[P]):[]])])},Je=t=>b("mfra",void 0,[...t.map(Jt),er()]),Jt=(t,i)=>T("tfra",1,0,[l(t.track.id),l(63),l(t.finalizedChunks.length),t.finalizedChunks.map(r=>[D(x(r.startTimestamp,t.timescale)),D(r.moofOffset),l(i+1),l(1),l(1)])]),er=()=>T("mfro",0,0,[l(0)]),et=()=>b("vtte"),tt=(t,i,e,r,s)=>b("vttc",void 0,[s!==null?b("vsid",[Ke(s)]):null,e!==null?b("iden",[...S.encode(e)]):null,i!==null?b("ctim",[...S.encode(ce(i))]):null,r!==null?b("sttg",[...S.encode(r)]):null,b("payl",[...S.encode(t)])]),rt=t=>b("vtta",[...S.encode(t)]),tr={avc:"avc1",hevc:"hvc1",vp8:"vp08",vp9:"vp09",av1:"av01"},rr={avc:It,hevc:zt,vp8:je,vp9:je,av1:Dt},ir={aac:"mp4a",opus:"Opus"},sr={aac:Pt,opus:Wt},or={webvtt:"wvtt"},nr={webvtt:Bt};var H=class{constructor(i){this.trackTimestampInfo=new WeakMap;this.output=i}beforeTrackAdd(i){}onTrackClose(i){}validateAndNormalizeTimestamp(i,e,r){let s=e/1e6,o=this.trackTimestampInfo.get(i);if(!o){if(!r)throw new Error("First frame must be a key frame.");if(this.timestampsMustStartAtZero&&s>0)throw new Error(`Timestamps must start at zero (got ${s}s).`);o={timestampOffset:s,maxTimestamp:i.source._offsetTimestamps?0:s,lastKeyFrameTimestamp:i.source._offsetTimestamps?0:s},this.trackTimestampInfo.set(i,o)}if(i.source._offsetTimestamps&&(s-=o.timestampOffset),s<0)throw new Error(`Timestamps must be non-negative (got ${s}s).`);if(ss.start-o.start);e.push({start:r[0].start,size:r[0].data.byteLength});for(let s=1;sd.start<=r&&rur){for(let d=0;d=e.written[n+1].start;)e.written[n].end=Math.max(e.written[n].end,e.written[n+1].end),e.written.splice(n+1,1)}createChunk(e){let s={start:Math.floor(e/this.chunkSize)*this.chunkSize,data:new Uint8Array(this.chunkSize),written:[],shouldFlush:!1};return this.chunks.push(s),this.chunks.sort((o,n)=>o.start-n.start),this.chunks.indexOf(s)}flushChunks(e=!1){for(let r=0;ri.stream.write({type:"write",data:e,position:r}),chunkSize:i.options?.chunkSize}))}};var it=(t,i,e)=>{if(t==="avc"){let r=100;i<=768&&e<=432?r=66:i<=1920&&e<=1080&&(r=77);let s=0,o=i>1920||e>1080?50:41,n=r.toString(16).padStart(2,"0"),a=s.toString(16).padStart(2,"0"),u=o.toString(16).padStart(2,"0");return`avc1.${n}${a}${u}`}else if(t==="hevc"){let r=0,s=1,o=Array(32).fill(0);o[s]=1;let n=parseInt(o.reverse().join(""),2).toString(16).replace(/^0+/,""),a="L",u=120;return i<=1280&&e<=720?u=93:i<=1920&&e<=1080?u=120:i<=3840&&e<=2160?u=150:(a="H",u=180),`hev1.${r===0?"":String.fromCharCode(65+r-1)}${s}.${n}.${a}${u}.B0`}else{if(t==="vp8")return"vp8";if(t==="vp9"){let r="00",s;return i<=854&&e<=480?s="21":i<=1280&&e<=720?s="31":i<=1920&&e<=1080?s="41":i<=3840&&e<=2160?s="51":s="61",`vp09.${r}.${s}.08`}else if(t==="av1"){let s;return i<=854&&e<=480?s="01":i<=1280&&e<=720?s="03":i<=1920&&e<=1080?s="04":i<=3840&&e<=2160?s="07":s="09",`av01.0.${s}M.08`}}throw new TypeError(`Unhandled codec '${t}'.`)},st=(t,i,e)=>{if(t==="aac")return i>=2&&e<=24e3?"mp4a.40.29":e<=24e3?"mp4a.40.5":"mp4a.40.2";if(t==="opus")return"opus";if(t==="vorbis")return"vorbis";throw new TypeError(`Unhandled codec '${t}'.`)},he=t=>{if(!t)throw new TypeError("Video chunk metadata must be provided.");if(typeof t!="object")throw new TypeError("Video chunk metadata must be an object.");if(!t.decoderConfig)throw new TypeError("Video chunk metadata must include a decoder configuration.");if(typeof t.decoderConfig!="object")throw new TypeError("Video chunk metadata decoder configuration must be an object.");if(typeof t.decoderConfig.codec!="string")throw new TypeError("Video chunk metadata decoder configuration must specify a codec string.");if(!Number.isInteger(t.decoderConfig.codedWidth)||t.decoderConfig.codedWidth<=0)throw new TypeError("Video chunk metadata decoder configuration must specify a valid codedWidth (positive integer).");if(!Number.isInteger(t.decoderConfig.codedHeight)||t.decoderConfig.codedHeight<=0)throw new TypeError("Video chunk metadata decoder configuration must specify a valid codedHeight (positive integer).");if(t.decoderConfig.description!==void 0&&!we(t.decoderConfig.description))throw new TypeError("Video chunk metadata decoder configuration description, when defined, must be an ArrayBuffer or an ArrayBuffer view.");if(t.decoderConfig.colorSpace!==void 0){let{colorSpace:i}=t.decoderConfig;if(typeof i!="object")throw new TypeError("Video chunk metadata decoder configuration colorSpace, when provided, must be an object.");let e=Object.keys(R);if(i.primaries!=null&&!e.includes(i.primaries))throw new TypeError(`Video chunk metadata decoder configuration colorSpace primaries, when defined, must be one of ${e.join(", ")}.`);let r=Object.keys(B);if(i.transfer!=null&&!r.includes(i.transfer))throw new TypeError(`Video chunk metadata decoder configuration colorSpace transfer, when defined, must be one of ${r.join(", ")}.`);let s=Object.keys(F);if(i.matrix!=null&&!s.includes(i.matrix))throw new TypeError(`Video chunk metadata decoder configuration colorSpace matrix, when defined, must be one of ${s.join(", ")}.`);if(i.fullRange!=null&&typeof i.fullRange!="boolean")throw new TypeError("Video chunk metadata decoder configuration colorSpace fullRange, when defined, must be a boolean.")}if((t.decoderConfig.codec.startsWith("avc1")||t.decoderConfig.codec.startsWith("avc3"))&&!t.decoderConfig.description)throw new TypeError("Video chunk metadata decoder configuration for AVC must include a description, which is expected to be an AVCDecoderConfigurationRecord as specified in ISO 14496-15.");if((t.decoderConfig.codec.startsWith("hev1")||t.decoderConfig.codec.startsWith("hvc1"))&&!t.decoderConfig.description)throw new TypeError("Video chunk metadata decoder configuration for HEVC must include a description, which is expected to be an HEVCDecoderConfigurationRecord as specified in ISO 14496-15.");if((t.decoderConfig.codec==="vp8"||t.decoderConfig.codec.startsWith("vp09"))&&t.decoderConfig.colorSpace===void 0)throw new TypeError("Video chunk metadata decoder configuration for VP8/VP9 must include a colorSpace.")},be=t=>{if(!t)throw new TypeError("Audio chunk metadata must be provided.");if(typeof t!="object")throw new TypeError("Audio chunk metadata must be an object.");if(!t.decoderConfig)throw new TypeError("Audio chunk metadata must include a decoder configuration.");if(typeof t.decoderConfig!="object")throw new TypeError("Audio chunk metadata decoder configuration must be an object.");if(typeof t.decoderConfig.codec!="string")throw new TypeError("Audio chunk metadata decoder configuration must specify a codec string.");if(!Number.isInteger(t.decoderConfig.sampleRate)||t.decoderConfig.sampleRate<=0)throw new TypeError("Audio chunk metadata decoder configuration must specify a valid sampleRate (positive integer).");if(!Number.isInteger(t.decoderConfig.numberOfChannels)||t.decoderConfig.numberOfChannels<=0)throw new TypeError("Audio chunk metadata decoder configuration must specify a valid numberOfChannels (positive integer).");if(t.decoderConfig.description!==void 0&&!we(t.decoderConfig.description))throw new TypeError("Audio chunk metadata decoder configuration description, when defined, must be an ArrayBuffer or an ArrayBuffer view.");if(t.decoderConfig.codec==="opus"&&t.decoderConfig.description&&t.decoderConfig.description.byteLength<18)throw new TypeError("Invalid decoder description provided for Opus; must be at least 18 bytes long.")},Te=t=>{if(!t)throw new TypeError("Subtitle metadata must be provided.");if(typeof t!="object")throw new TypeError("Subtitle metadata must be an object.");if(!t.config)throw new TypeError("Subtitle metadata must include a config object.");if(typeof t.config!="object")throw new TypeError("Subtitle metadata config must be an object.");if(typeof t.config.description!="string")throw new TypeError("Subtitle metadata config description must be a string.")};var de=1e3,lr=2082844800,x=(t,i,e=!0)=>{let r=t*i;return e?Math.round(r):r},Ce=class extends H{constructor(e,r){super(e);this.timestampsMustStartAtZero=!0;this.auxTarget=new Y;this.auxWriter=this.auxTarget._createWriter();this.auxBoxWriter=new G(this.auxWriter);this.ftypSize=null;this.mdat=null;this.trackDatas=[];this.creationTime=Math.floor(Date.now()/1e3)+lr;this.finalizedChunks=[];this.nextFragmentNumber=1;this.writer=e._writer,this.boxWriter=new G(this.writer),this.fastStart=r.options.fastStart??(this.writer instanceof $?"in-memory":!1),(this.fastStart==="in-memory"||this.fastStart==="fragmented")&&(this.writer.ensureMonotonicity=!0)}start(){let e=this.output._tracks.some(r=>r.type==="video"&&r.source._codec==="avc");this.boxWriter.writeBox(qe({holdsAvc:e,fragmented:this.fastStart==="fragmented"})),this.ftypSize=this.writer.getPos(),this.fastStart==="in-memory"?this.mdat=fe(!1):this.fastStart==="fragmented"||(this.mdat=fe(!0),this.boxWriter.writeBox(this.mdat)),this.writer.flush()}getVideoTrackData(e,r){let s=this.trackDatas.find(n=>n.track===e);if(s)return s;he(r),c(r),c(r.decoderConfig),c(r.decoderConfig.codedWidth!==void 0),c(r.decoderConfig.codedHeight!==void 0);let o={track:e,type:"video",info:{width:r.decoderConfig.codedWidth,height:r.decoderConfig.codedHeight,decoderConfig:r.decoderConfig},timescale:e.metadata.frameRate??57600,samples:[],sampleQueue:[],timestampProcessingQueue:[],timeToSampleTable:[],compositionTimeOffsetTable:[],lastTimescaleUnits:null,lastSample:null,finalizedChunks:[],currentChunk:null,compactlyCodedChunkTable:[]};return this.trackDatas.push(o),this.trackDatas.sort((n,a)=>n.track.id-a.track.id),o}getAudioTrackData(e,r){let s=this.trackDatas.find(n=>n.track===e);if(s)return s;be(r),c(r),c(r.decoderConfig);let o={track:e,type:"audio",info:{numberOfChannels:r.decoderConfig.numberOfChannels,sampleRate:r.decoderConfig.sampleRate,decoderConfig:r.decoderConfig},timescale:r.decoderConfig.sampleRate,samples:[],sampleQueue:[],timestampProcessingQueue:[],timeToSampleTable:[],compositionTimeOffsetTable:[],lastTimescaleUnits:null,lastSample:null,finalizedChunks:[],currentChunk:null,compactlyCodedChunkTable:[]};return this.trackDatas.push(o),this.trackDatas.sort((n,a)=>n.track.id-a.track.id),o}getSubtitleTrackData(e,r){let s=this.trackDatas.find(n=>n.track===e);if(s)return s;Te(r),c(r),c(r.config);let o={track:e,type:"subtitle",info:{config:r.config},timescale:1e3,samples:[],sampleQueue:[],timestampProcessingQueue:[],timeToSampleTable:[],compositionTimeOffsetTable:[],lastTimescaleUnits:null,lastSample:null,finalizedChunks:[],currentChunk:null,compactlyCodedChunkTable:[],lastCueEndTimestamp:0,cueQueue:[],nextSourceId:0,cueToSourceId:new WeakMap};return this.trackDatas.push(o),this.trackDatas.sort((n,a)=>n.track.id-a.track.id),this.validateAndNormalizeTimestamp(e,0,!0),o}addEncodedVideoChunk(e,r,s){let o=this.getVideoTrackData(e,s),n=new Uint8Array(r.byteLength);r.copyTo(n);let a=this.validateAndNormalizeTimestamp(o.track,r.timestamp,r.type==="key"),u=this.createSampleForTrack(o,n,a,(r.duration??0)/1e6,r.type);this.registerSample(o,u)}addEncodedAudioChunk(e,r,s){let o=this.getAudioTrackData(e,s),n=new Uint8Array(r.byteLength);r.copyTo(n);let a=this.validateAndNormalizeTimestamp(o.track,r.timestamp,r.type==="key"),u=this.createSampleForTrack(o,n,a,(r.duration??0)/1e6,r.type);this.registerSample(o,u)}addSubtitleCue(e,r,s){let o=this.getSubtitleTrackData(e,s);this.validateAndNormalizeTimestamp(o.track,1e6*r.timestamp,!0),e.source._codec==="webvtt"&&(o.cueQueue.push(r),this.processWebVTTCues(o,r.timestamp))}processWebVTTCues(e,r){for(;e.cueQueue.length>0;){let s=new Set([]);for(let f of e.cueQueue)c(f.timestamp<=r),c(e.lastCueEndTimestamp<=f.timestamp+f.duration),s.add(Math.max(f.timestamp,e.lastCueEndTimestamp)),s.add(f.timestamp+f.duration);let o=[...s].sort((f,m)=>f-m),n=o[0],a=o[1]??n;if(r=a)break;N.lastIndex=0;let C=N.test(m.text),O=m.timestamp+m.duration,k=e.cueToSourceId.get(m);if(k===void 0&&as.timestamp).sort((s,o)=>s-o);for(let s=0;s{if(e===a)return r.type==="key";let u=a.sampleQueue[0];return u&&u.type==="key"});o>=1&&n&&(s=!0,this.finalizeFragment())}else s=o>=.5}s&&(e.currentChunk&&this.finalizeCurrentChunk(e),e.currentChunk={startTimestamp:r.timestamp,samples:[],offset:null,moofOffset:null}),c(e.currentChunk),e.currentChunk.samples.push(r),e.timestampProcessingQueue.push(r)}finalizeCurrentChunk(e){if(c(this.fastStart!=="fragmented"),!!e.currentChunk){if(e.finalizedChunks.push(e.currentChunk),this.finalizedChunks.push(e.currentChunk),(e.compactlyCodedChunkTable.length===0||W(e.compactlyCodedChunkTable).samplesPerChunk!==e.currentChunk.samples.length)&&e.compactlyCodedChunkTable.push({firstChunk:e.finalizedChunks.length,samplesPerChunk:e.currentChunk.samples.length}),this.fastStart==="in-memory"){e.currentChunk.offset=0;return}e.currentChunk.offset=this.writer.getPos();for(let r of e.currentChunk.samples)c(r.data),this.writer.write(r.data),r.data=null;this.writer.flush()}}interleaveSamples(){c(this.fastStart==="fragmented");for(let e of this.output._tracks)if(!e.source._closed&&!this.trackDatas.some(r=>r.track===e))return;e:for(;;){let e=null,r=1/0;for(let o of this.trackDatas){if(o.sampleQueue.length===0&&!o.track.source._closed)break e;o.sampleQueue.length>0&&o.sampleQueue[0].timestamp=2**32&&(u.largeSize=!0,f=this.boxWriter.measureBox(u)+d),u.size=f,this.boxWriter.writeBox(u)}for(let u of this.trackDatas){u.currentChunk.offset=this.writer.getPos(),u.currentChunk.moofOffset=s;for(let d of u.currentChunk.samples)this.writer.write(d.data),d.data=null}let n=this.writer.getPos();this.writer.seek(this.boxWriter.offsets.get(o));let a=Ae(r,this.trackDatas);this.boxWriter.writeBox(a),this.writer.seek(n);for(let u of this.trackDatas)u.finalizedChunks.push(u.currentChunk),this.finalizedChunks.push(u.currentChunk),u.currentChunk=null;e&&this.writer.flush()}onTrackClose(e){if(e.type==="subtitle"&&e.source._codec==="webvtt"){let r=this.trackDatas.find(s=>s.track===e);r&&this.processWebVTTCues(r,1/0)}this.fastStart==="fragmented"&&this.interleaveSamples()}finalize(){for(let e of this.trackDatas)e.type==="subtitle"&&e.track.source._codec==="webvtt"&&this.processWebVTTCues(e,1/0);if(this.fastStart==="fragmented"){for(let e of this.trackDatas){for(let r of e.sampleQueue)this.addSampleToTrack(e,r);this.processTimestamps(e)}this.finalizeFragment(!1)}else for(let e of this.trackDatas)this.processTimestamps(e),this.finalizeCurrentChunk(e);if(this.fastStart==="in-memory"){c(this.mdat);let e;for(let s=0;s<2;s++){let o=q(this.trackDatas,this.creationTime),n=this.boxWriter.measureBox(o);e=this.boxWriter.measureBox(this.mdat);let a=this.writer.getPos()+n+e;for(let u of this.finalizedChunks){u.offset=a;for(let{data:d}of u.samples)c(d),a+=d.byteLength,e+=d.byteLength}if(a<2**32)break;e>=2**32&&(this.mdat.largeSize=!0)}let r=q(this.trackDatas,this.creationTime);this.boxWriter.writeBox(r),this.mdat.size=e,this.boxWriter.writeBox(this.mdat);for(let s of this.finalizedChunks)for(let o of s.samples)c(o.data),this.writer.write(o.data),o.data=null}else if(this.fastStart==="fragmented"){let e=this.writer.getPos(),r=Je(this.trackDatas);this.boxWriter.writeBox(r);let s=this.writer.getPos()-e;this.writer.seek(this.writer.getPos()-4),this.boxWriter.writeU32(s)}else{c(this.mdat),c(this.ftypSize!==null);let e=this.boxWriter.offsets.get(this.mdat);c(e!==void 0);let r=this.writer.getPos()-e;this.mdat.size=r,this.mdat.largeSize=r>=2**32,this.boxWriter.patchBox(this.mdat);let s=q(this.trackDatas,this.creationTime);if(typeof this.fastStart=="object"){this.writer.seek(this.ftypSize),this.boxWriter.writeBox(s);let o=e-this.writer.getPos();this.boxWriter.writeBox(Ye(o))}else this.boxWriter.writeBox(s)}}};var te=class{constructor(i){this.value=i}},j=class{constructor(i){this.value=i}},re=class{constructor(i){this.value=i}};var Ee=t=>t<256?1:t<65536?2:t<1<<24?3:t<2**32?4:t<2**40?5:6,Oe=t=>t>=-64&&t<64?1:t>=-8192&&t<8192?2:t>=-(1<<20)&&t<1<<20?3:t>=-(1<<27)&&t<1<<27?4:t>=-(2**34)&&t<2**34?5:6,ot=t=>{if(t<127)return 1;if(t<16383)return 2;if(t<(1<<21)-1)return 3;if(t<(1<<28)-1)return 4;if(t<2**35-1)return 5;if(t<2**42-1)return 6;throw new Error("EBML VINT size not supported "+t)};var Ve=2**15,nt="https://github.com/Vanilagy/webm-muxer",at=6,ut=5,cr={avc:"V_MPEG4/ISO/AVC",hevc:"V_MPEGH/ISO/HEVC",vp8:"V_VP8",vp9:"V_VP9",av1:"V_AV1",aac:"A_AAC",opus:"A_OPUS",webvtt:"S_TEXT/WEBVTT"},dr={video:1,audio:2,subtitle:17},ge=class extends H{constructor(e,r){super(e);this.timestampsMustStartAtZero=!1;this.helper=new Uint8Array(8);this.helperView=new DataView(this.helper.buffer);this.offsets=new WeakMap;this.dataOffsets=new WeakMap;this.trackDatas=[];this.segment=null;this.segmentInfo=null;this.seekHead=null;this.tracksElement=null;this.segmentDuration=null;this.cues=null;this.currentCluster=null;this.currentClusterMsTimestamp=null;this.trackDatasInCurrentCluster=new Set;this.duration=0;this.writer=e._writer,this.format=r,this.format.options.streamable&&(this.writer.ensureMonotonicity=!0)}writeByte(e){this.helperView.setUint8(0,e),this.writer.write(this.helper.subarray(0,1))}writeFloat32(e){this.helperView.setFloat32(0,e,!1),this.writer.write(this.helper.subarray(0,4))}writeFloat64(e){this.helperView.setFloat64(0,e,!1),this.writer.write(this.helper)}writeUnsignedInt(e,r=Ee(e)){let s=0;switch(r){case 6:this.helperView.setUint8(s++,e/2**40|0);case 5:this.helperView.setUint8(s++,e/2**32|0);case 4:this.helperView.setUint8(s++,e>>24);case 3:this.helperView.setUint8(s++,e>>16);case 2:this.helperView.setUint8(s++,e>>8);case 1:this.helperView.setUint8(s++,e);break;default:throw new Error("Bad UINT size "+r)}this.writer.write(this.helper.subarray(0,s))}writeSignedInt(e,r=Oe(e)){e<0&&(e+=2**(r*8)),this.writeUnsignedInt(e,r)}writeEBMLVarInt(e,r=ot(e)){let s=0;switch(r){case 1:this.helperView.setUint8(s++,128|e);break;case 2:this.helperView.setUint8(s++,64|e>>8),this.helperView.setUint8(s++,e);break;case 3:this.helperView.setUint8(s++,32|e>>16),this.helperView.setUint8(s++,e>>8),this.helperView.setUint8(s++,e);break;case 4:this.helperView.setUint8(s++,16|e>>24),this.helperView.setUint8(s++,e>>16),this.helperView.setUint8(s++,e>>8),this.helperView.setUint8(s++,e);break;case 5:this.helperView.setUint8(s++,8|e/2**32&7),this.helperView.setUint8(s++,e>>24),this.helperView.setUint8(s++,e>>16),this.helperView.setUint8(s++,e>>8),this.helperView.setUint8(s++,e);break;case 6:this.helperView.setUint8(s++,4|e/2**40&3),this.helperView.setUint8(s++,e/2**32|0),this.helperView.setUint8(s++,e>>24),this.helperView.setUint8(s++,e>>16),this.helperView.setUint8(s++,e>>8),this.helperView.setUint8(s++,e);break;default:throw new Error("Bad EBML VINT size "+r)}this.writer.write(this.helper.subarray(0,s))}writeString(e){this.writer.write(new Uint8Array(e.split("").map(r=>r.charCodeAt(0))))}writeEBML(e){if(e!==null){if(e instanceof Uint8Array)this.writer.write(e);else if(Array.isArray(e))for(let r of e)this.writeEBML(r);else if(this.offsets.set(e,this.writer.getPos()),this.writeUnsignedInt(e.id),Array.isArray(e.data)){let r=this.writer.getPos(),s=e.size===-1?1:e.size??4;e.size===-1?this.writeByte(255):this.writer.seek(this.writer.getPos()+s);let o=this.writer.getPos();if(this.dataOffsets.set(e,o),this.writeEBML(e.data),e.size!==-1){let n=this.writer.getPos()-o,a=this.writer.getPos();this.writer.seek(r),this.writeEBMLVarInt(n,s),this.writer.seek(a)}}else if(typeof e.data=="number"){let r=e.size??Ee(e.data);this.writeEBMLVarInt(r),this.writeUnsignedInt(e.data,r)}else if(typeof e.data=="string")this.writeEBMLVarInt(e.data.length),this.writeString(e.data);else if(e.data instanceof Uint8Array)this.writeEBMLVarInt(e.data.byteLength,e.size),this.writer.write(e.data);else if(e.data instanceof te)this.writeEBMLVarInt(4),this.writeFloat32(e.data.value);else if(e.data instanceof j)this.writeEBMLVarInt(8),this.writeFloat64(e.data.value);else if(e.data instanceof re){let r=e.size??Oe(e.data.value);this.writeEBMLVarInt(r),this.writeSignedInt(e.data.value,r)}}}beforeTrackAdd(e){if(this.format instanceof Q)if(e.type==="video"){if(!["vp8","vp9","av1"].includes(e.source._codec))throw new Error("WebM only supports VP8, VP9 and AV1 as video codecs. Switching to MKV removes this restriction.")}else if(e.type==="audio"){if(!["opus","vorbis"].includes(e.source._codec))throw new Error("WebM only supports Opus and Vorbis as audio codecs. Switching to MKV removes this restriction.")}else if(e.type==="subtitle"){if(e.source._codec!=="webvtt")throw new Error("WebM only supports WebVTT as subtitle codec. Switching to MKV removes this restriction.")}else throw new Error("WebM only supports video, audio and subtitle tracks. Switching to MKV removes this restriction.")}start(){this.writeEBMLHeader(),this.format.options.streamable||this.createSeekHead(),this.createSegmentInfo(),this.createCues(),this.writer.flush()}writeEBMLHeader(){let e={id:440786851,data:[{id:17030,data:1},{id:17143,data:1},{id:17138,data:4},{id:17139,data:8},{id:17026,data:this.format instanceof Q?"webm":"matroska"},{id:17031,data:2},{id:17029,data:2}]};this.writeEBML(e)}createSeekHead(){let e=new Uint8Array([28,83,187,107]),r=new Uint8Array([21,73,169,102]),s=new Uint8Array([22,84,174,107]),o={id:290298740,data:[{id:19899,data:[{id:21419,data:e},{id:21420,size:5,data:0}]},{id:19899,data:[{id:21419,data:r},{id:21420,size:5,data:0}]},{id:19899,data:[{id:21419,data:s},{id:21420,size:5,data:0}]}]};this.seekHead=o}createSegmentInfo(){let e={id:17545,data:new j(0)};this.segmentDuration=e;let r={id:357149030,data:[{id:2807729,data:1e6},{id:19840,data:nt},{id:22337,data:nt},this.format.options.streamable?null:e]};this.segmentInfo=r}createTracks(){let e={id:374648427,data:[]};this.tracksElement=e;for(let r of this.trackDatas)e.data.push({id:174,data:[{id:215,data:r.track.id},{id:29637,data:r.track.id},{id:131,data:dr[r.type]},{id:134,data:cr[r.track.source._codec]},...r.type==="video"?[r.info.decoderConfig.description?{id:25506,data:z(r.info.decoderConfig.description)}:null,r.track.metadata.frameRate?{id:2352003,data:1e9/r.track.metadata.frameRate}:null,{id:224,data:[{id:176,data:r.info.width},{id:186,data:r.info.height},(()=>{if(r.info.decoderConfig.colorSpace){let s=r.info.decoderConfig.colorSpace;return ae(s)?{id:21936,data:[{id:21937,data:F[s.matrix]},{id:21946,data:B[s.transfer]},{id:21947,data:R[s.primaries]},{id:21945,data:[1,2][Number(s.fullRange)]}]}:null}return null})()]}]:[],...r.type==="audio"?[r.info.decoderConfig.description?{id:25506,data:z(r.info.decoderConfig.description)}:null,{id:225,data:[{id:181,data:new te(r.info.sampleRate)},{id:159,data:r.info.numberOfChannels}]}]:[],...r.type==="subtitle"?[{id:25506,data:S.encode(r.info.config.description)}]:[]]})}createSegment(){let e={id:408125543,size:this.format.options.streamable?-1:at,data:[this.format.options.streamable?null:this.seekHead,this.segmentInfo,this.tracksElement]};this.segment=e,this.writeEBML(e)}createCues(){this.cues={id:475249515,data:[]}}get segmentDataOffset(){return c(this.segment),this.dataOffsets.get(this.segment)}getVideoTrackData(e,r){let s=this.trackDatas.find(n=>n.track===e);if(s)return s;he(r),c(r),c(r.decoderConfig),c(r.decoderConfig.codedWidth!==void 0),c(r.decoderConfig.codedHeight!==void 0);let o={track:e,type:"video",info:{width:r.decoderConfig.codedWidth,height:r.decoderConfig.codedHeight,decoderConfig:r.decoderConfig},chunkQueue:[],lastWrittenMsTimestamp:null};return this.trackDatas.push(o),this.trackDatas.sort((n,a)=>n.track.id-a.track.id),o}getAudioTrackData(e,r){let s=this.trackDatas.find(n=>n.track===e);if(s)return s;be(r),c(r),c(r.decoderConfig);let o={track:e,type:"audio",info:{numberOfChannels:r.decoderConfig.numberOfChannels,sampleRate:r.decoderConfig.sampleRate,decoderConfig:r.decoderConfig},chunkQueue:[],lastWrittenMsTimestamp:null};return this.trackDatas.push(o),this.trackDatas.sort((n,a)=>n.track.id-a.track.id),o}getSubtitleTrackData(e,r){let s=this.trackDatas.find(n=>n.track===e);if(s)return s;Te(r),c(r),c(r.config);let o={track:e,type:"subtitle",info:{config:r.config},chunkQueue:[],lastWrittenMsTimestamp:null};return this.trackDatas.push(o),this.trackDatas.sort((n,a)=>n.track.id-a.track.id),o}addEncodedVideoChunk(e,r,s){let o=this.getVideoTrackData(e,s),n=new Uint8Array(r.byteLength);r.copyTo(n);let a=this.validateAndNormalizeTimestamp(o.track,r.timestamp,r.type==="key"),u=this.createInternalChunk(n,a,(r.duration??0)/1e6,r.type);e.source._codec==="vp9"&&this.fixVP9ColorSpace(o,u),o.chunkQueue.push(u),this.interleaveChunks()}addEncodedAudioChunk(e,r,s){let o=this.getAudioTrackData(e,s),n=new Uint8Array(r.byteLength);r.copyTo(n);let a=this.validateAndNormalizeTimestamp(o.track,r.timestamp,r.type==="key"),u=this.createInternalChunk(n,a,(r.duration??0)/1e6,r.type);o.chunkQueue.push(u),this.interleaveChunks()}addSubtitleCue(e,r,s){let o=this.getSubtitleTrackData(e,s),n=this.validateAndNormalizeTimestamp(o.track,1e6*r.timestamp,!0),a=r.text,u=Math.floor(n*1e3);N.lastIndex=0,a=a.replace(N,C=>{let k=le(C.slice(1,-1))-u;return`<${ce(k)}>`});let d=S.encode(a),f=`${r.settings??""} -${r.identifier??""} -${r.notes??""}`,m=this.createInternalChunk(d,n,r.duration,"key",f.trim()?S.encode(f):null);o.chunkQueue.push(m),this.interleaveChunks()}interleaveChunks(){for(let e of this.output._tracks)if(!e.source._closed&&!this.trackDatas.some(r=>r.track===e))return;e:for(;;){let e=null,r=1/0;for(let o of this.trackDatas){if(o.chunkQueue.length===0&&!o.track.source._closed)break e;o.chunkQueue.length>0&&o.chunkQueue[0].timestamp=2&&s++;let d={rgb:7,bt709:2,bt470bg:1,smpte170m:3}[e.info.decoderConfig.colorSpace.matrix];$e(r.data,s+0,s+3,d)}createInternalChunk(e,r,s,o,n=null){return{data:e,type:o,timestamp:r,duration:s,additions:n}}writeBlock(e,r){this.segment||(this.createTracks(),this.createSegment());let s=Math.floor(1e3*r.timestamp),o=this.trackDatas.every(m=>{if(m.track.source._closed)return!0;if(e===m)return r.type==="key";let C=m.chunkQueue[0];return C&&C.type==="key"});(!this.currentCluster||o&&s-this.currentClusterMsTimestamp>=1e3)&&this.createNewCluster(s);let n=s-this.currentClusterMsTimestamp;if(n<0)return;if(n>=Ve)throw new Error(`Current Matroska cluster exceeded its maximum allowed length of ${Ve} milliseconds. In order to produce a correct WebM file, you must pass in a key frame at least every ${Ve} milliseconds.`);let u=new Uint8Array(4),d=new DataView(u.buffer);d.setUint8(0,128|e.track.id),d.setInt16(1,n,!1);let f=Math.floor(1e3*r.duration);if(f===0&&!r.additions){d.setUint8(3,+(r.type==="key")<<7);let m={id:163,data:[u,r.data]};this.writeEBML(m)}else{let m={id:160,data:[{id:161,data:[u,r.data]},r.type==="delta"?{id:251,data:new re(e.lastWrittenMsTimestamp-s)}:null,r.additions?{id:30113,data:[{id:166,data:[{id:165,data:r.additions},{id:238,data:1}]}]}:null,f>0?{id:155,data:f}:null]};this.writeEBML(m)}this.duration=Math.max(this.duration,s+f),e.lastWrittenMsTimestamp=s,this.trackDatasInCurrentCluster.add(e)}createNewCluster(e){this.currentCluster&&!this.format.options.streamable&&this.finalizeCurrentCluster(),this.currentCluster={id:524531317,size:this.format.options.streamable?-1:ut,data:[{id:231,data:e}]},this.writeEBML(this.currentCluster),this.currentClusterMsTimestamp=e,this.trackDatasInCurrentCluster.clear()}finalizeCurrentCluster(){c(this.currentCluster);let e=this.writer.getPos()-this.dataOffsets.get(this.currentCluster),r=this.writer.getPos();this.writer.seek(this.offsets.get(this.currentCluster)+4),this.writeEBMLVarInt(e,ut),this.writer.seek(r);let s=this.offsets.get(this.currentCluster)-this.segmentDataOffset;c(this.cues),this.cues.data.push({id:187,data:[{id:179,data:this.currentClusterMsTimestamp},...[...this.trackDatasInCurrentCluster].map(o=>({id:183,data:[{id:247,data:o.track.id},{id:241,data:s}]}))]})}onTrackClose(){this.interleaveChunks()}finalize(){this.segment||(this.createTracks(),this.createSegment());for(let e of this.trackDatas)for(;e.chunkQueue.length>0;)this.writeBlock(e,e.chunkQueue.shift());if(!this.format.options.streamable&&this.currentCluster&&this.finalizeCurrentCluster(),c(this.cues),this.writeEBML(this.cues),!this.format.options.streamable){let e=this.writer.getPos(),r=this.writer.getPos()-this.segmentDataOffset;this.writer.seek(this.offsets.get(this.segment)+4),this.writeEBMLVarInt(r,at),this.segmentDuration.data=new j(this.duration),this.writer.seek(this.offsets.get(this.segmentDuration)),this.writeEBML(this.segmentDuration),this.seekHead.data[0].data[1].data=this.offsets.get(this.cues)-this.segmentDataOffset,this.seekHead.data[1].data[1].data=this.offsets.get(this.segmentInfo)-this.segmentDataOffset,this.seekHead.data[2].data[1].data=this.offsets.get(this.tracksElement)-this.segmentDataOffset,this.writer.seek(this.offsets.get(this.seekHead)),this.writeEBML(this.seekHead),this.writer.seek(e)}}};var U=class{},Me=class extends U{constructor(e={}){if(!e||typeof e!="object")throw new TypeError("options must be an object.");if(e.fastStart!==void 0&&![!1,"in-memory","fragmented"].includes(e.fastStart))throw new TypeError('options.fastStart, when provided, must be false, "in-memory", or "fragmented".');super();this.options=e}_createMuxer(e){return new Ce(e,this)}},ke=class extends U{constructor(e={}){if(!e||typeof e!="object")throw new TypeError("options must be an object.");if(e.streamable!==void 0&&typeof e.streamable!="boolean")throw new TypeError("options.streamable, when provided, must be a boolean.");super();this.options=e}_createMuxer(e){return new ge(e,this)}},Q=class extends ke{};var ie=["avc","hevc","vp8","vp9","av1"],se=["aac","opus"],Ie=["webvtt"],K=class{constructor(){this._connectedTrack=null;this._closed=!1;this._offsetTimestamps=!1}_ensureValidDigest(){if(!this._connectedTrack)throw new Error("Cannot call digest without connecting the source to an output track.");if(!this._connectedTrack.output._started)throw new Error("Cannot call digest before output has been started.");if(this._connectedTrack.output._finalizing)throw new Error("Cannot call digest after output has started finalizing.");if(this._closed)throw new Error("Cannot call digest after source has been closed.")}_start(){}async _flush(){}close(){if(this._closed)throw new Error("Source already closed.");if(!this._connectedTrack)throw new Error("Cannot call close without connecting the source to an output track.");if(!this._connectedTrack.output._started)throw new Error("Cannot call close before output has been started.");this._closed=!0,!this._connectedTrack.output._finalizing&&this._connectedTrack.output._muxer.onTrackClose(this._connectedTrack)}},_=class extends K{constructor(e){super();this._connectedTrack=null;if(!ie.includes(e))throw new TypeError(`Invalid video codec '${e}'. Must be one of: ${ie.join(", ")}.`);this._codec=e}},ze=class extends _{constructor(i){super(i)}digest(i,e){if(!(i instanceof EncodedVideoChunk))throw new TypeError("chunk must be an EncodedVideoChunk.");this._ensureValidDigest(),this._connectedTrack?.output._muxer.addEncodedVideoChunk(this._connectedTrack,i,e)}},fr=5,mr=t=>{if(!t||typeof t!="object")throw new TypeError("Codec config must be an object.");if(!ie.includes(t.codec))throw new TypeError(`Invalid video codec '${t.codec}'. Must be one of: ${ie.join(", ")}.`);if(!Number.isInteger(t.bitrate)||t.bitrate<=0)throw new TypeError("config.bitrate must be a positive integer.");if(t.latencyMode!==void 0&&["quality","realtime"].includes(t.latencyMode))throw new TypeError("config.latencyMode, when provided, must be 'quality' or 'realtime'.")},oe=class{constructor(i,e){this.source=i;this.codecConfig=e;this.encoder=null;this.lastMultipleOfKeyFrameInterval=-1;this.lastWidth=null;this.lastHeight=null;mr(e)}digest(i){if(this.source._ensureValidDigest(),this.lastWidth!==null&&this.lastHeight!==null){if(i.codedWidth!==this.lastWidth||i.codedHeight!==this.lastHeight)throw new Error(`Video frame size must remain constant. Expected ${this.lastWidth}x${this.lastHeight}, got ${i.codedWidth}x${i.codedHeight}.`)}else this.lastWidth=i.codedWidth,this.lastHeight=i.codedHeight;this.ensureEncoder(i),c(this.encoder);let e=Math.floor(i.timestamp/1e6/fr);this.encoder.encode(i,{keyFrame:e!==this.lastMultipleOfKeyFrameInterval}),this.lastMultipleOfKeyFrameInterval=e}ensureEncoder(i){this.encoder||(this.encoder=new VideoEncoder({output:(e,r)=>this.source._connectedTrack?.output._muxer.addEncodedVideoChunk(this.source._connectedTrack,e,r),error:e=>console.error("Video encode error:",e)}),this.encoder.configure({codec:it(this.codecConfig.codec,i.codedWidth,i.codedHeight),width:i.codedWidth,height:i.codedHeight,bitrate:this.codecConfig.bitrate,framerate:this.source._connectedTrack?.metadata.frameRate,latencyMode:this.codecConfig.latencyMode}))}async flush(){return this.encoder?.flush()}},De=class extends _{constructor(i){super(i.codec),this._encoder=new oe(this,i)}digest(i){if(!(i instanceof VideoFrame))throw new TypeError("videoFrame must be a VideoFrame.");this._encoder.digest(i)}_flush(){return this._encoder.flush()}},Ue=class extends _{constructor(i,e){if(!(i instanceof HTMLCanvasElement))throw new TypeError("canvas must be an HTMLCanvasElement.");super(e.codec),this._encoder=new oe(this,e),this._canvas=i}digest(i,e=0){if(!Number.isFinite(i)||i<0)throw new TypeError("timestamp must be a non-negative number.");if(!Number.isFinite(e)||e<0)throw new TypeError("duration must be a non-negative number.");let r=new VideoFrame(this._canvas,{timestamp:Math.round(1e6*i),duration:Math.round(1e6*e),alpha:"discard"});this._encoder.digest(r),r.close()}_flush(){return this._encoder.flush()}},Pe=class extends _{constructor(e,r){if(!(e instanceof MediaStreamTrack)||e.kind!=="video")throw new TypeError("track must be a video MediaStreamTrack.");r={...r,latencyMode:"realtime"};super(r.codec);this._abortController=null;this._offsetTimestamps=!0;this._encoder=new oe(this,r),this._track=e}_start(){this._abortController=new AbortController;let e=new MediaStreamTrackProcessor({track:this._track}),r=new WritableStream({write:s=>{this._encoder.digest(s),s.close()}});e.readable.pipeTo(r,{signal:this._abortController.signal}).catch(s=>{s instanceof DOMException&&s.name==="AbortError"||console.error("Pipe error:",s)})}async _flush(){this._abortController&&(this._abortController.abort(),this._abortController=null),await this._encoder.flush()}},E=class extends K{constructor(e){super();this._connectedTrack=null;if(!se.includes(e))throw new TypeError(`Invalid audio codec '${e}'. Must be one of: ${se.join(", ")}.`);this._codec=e}},We=class extends E{constructor(i){super(i)}digest(i,e){if(!(i instanceof EncodedAudioChunk))throw new TypeError("chunk must be an EncodedAudioChunk.");this._ensureValidDigest(),this._connectedTrack?.output._muxer.addEncodedAudioChunk(this._connectedTrack,i,e)}},pr=t=>{if(!t||typeof t!="object")throw new TypeError("Codec config must be an object.");if(!se.includes(t.codec))throw new TypeError(`Invalid audio codec '${t.codec}'. Must be one of: ${se.join(", ")}.`);if(!Number.isInteger(t.bitrate)||t.bitrate<=0)throw new TypeError("config.bitrate must be a positive integer.")},ne=class{constructor(i,e){this.source=i;this.codecConfig=e;this.encoder=null;this.lastNumberOfChannels=null;this.lastSampleRate=null;pr(e)}digest(i){if(this.source._ensureValidDigest(),this.lastNumberOfChannels!==null&&this.lastSampleRate!==null){if(i.numberOfChannels!==this.lastNumberOfChannels||i.sampleRate!==this.lastSampleRate)throw new Error(`Audio parameters must remain constant. Expected ${this.lastNumberOfChannels} channels at ${this.lastSampleRate} Hz, got ${i.numberOfChannels} channels at ${i.sampleRate} Hz.`)}else this.lastNumberOfChannels=i.numberOfChannels,this.lastSampleRate=i.sampleRate;this.ensureEncoder(i),c(this.encoder),this.encoder.encode(i)}ensureEncoder(i){this.encoder||(this.encoder=new AudioEncoder({output:(e,r)=>this.source._connectedTrack?.output._muxer.addEncodedAudioChunk(this.source._connectedTrack,e,r),error:e=>console.error("Audio encode error:",e)}),this.encoder.configure({codec:st(this.codecConfig.codec,i.numberOfChannels,i.sampleRate),numberOfChannels:i.numberOfChannels,sampleRate:i.sampleRate,bitrate:this.codecConfig.bitrate}))}async flush(){return this.encoder?.flush()}},Re=class extends E{constructor(i){super(i.codec),this._encoder=new ne(this,i)}digest(i){if(!(i instanceof AudioData))throw new TypeError("audioData must be an AudioData.");this._encoder.digest(i)}_flush(){return this._encoder.flush()}},Be=class extends E{constructor(e){super(e.codec);this._accumulatedFrameCount=0;this._encoder=new ne(this,e)}digest(e){if(!(e instanceof AudioBuffer))throw new TypeError("audioBuffer must be an AudioBuffer.");let r=e.numberOfChannels,s=e.sampleRate,o=e.length,n=new Float32Array(r*o);for(let u=0;u{this._encoder.digest(s),s.close()}});e.readable.pipeTo(r,{signal:this._abortController.signal}).catch(s=>{s instanceof DOMException&&s.name==="AbortError"||console.error("Pipe error:",s)})}async _flush(){this._abortController&&(this._abortController.abort(),this._abortController=null),await this._encoder.flush()}},L=class extends K{constructor(e){super();this._connectedTrack=null;if(!Ie.includes(e))throw new TypeError(`Invalid subtitle codec '${e}'. Must be one of: ${Ie.join(", ")}.`);this._codec=e}},Ne=class extends L{constructor(i){super(i),this._parser=new ue({codec:i,output:(e,r)=>this._connectedTrack?.output._muxer.addSubtitleCue(this._connectedTrack,e,r),error:e=>console.error("Subtitle parse error:",e)})}digest(i){if(typeof i!="string")throw new TypeError("text must be a string.");this._ensureValidDigest(),this._parser.parse(i)}};var He=class{constructor(i){this._tracks=[];this._started=!1;this._finalizing=!1;if(!i||typeof i!="object")throw new TypeError("options must be an object.");if(!(i.format instanceof U))throw new TypeError("options.format must be an OutputFormat.");if(!(i.target instanceof V))throw new TypeError("options.target must be a Target.");if(i.target.output)throw new Error("Target is already used for another output.");i.target.output=this,this._writer=i.target._createWriter(),this._muxer=i.format._createMuxer(this)}addVideoTrack(i,e={}){if(!(i instanceof _))throw new TypeError("source must be a VideoSource.");if(!e||typeof e!="object")throw new TypeError("metadata must be an object.");if(typeof e.rotation=="number"&&![0,90,180,270].includes(e.rotation))throw new TypeError(`Invalid video rotation: ${e.rotation}. Has to be 0, 90, 180 or 270.`);if(Array.isArray(e.rotation)&&(e.rotation.length!==9||e.rotation.some(r=>!Number.isFinite(r))))throw new TypeError(`Invalid video transformation matrix: ${e.rotation.join()}`);if(e.frameRate!==void 0&&(!Number.isInteger(e.frameRate)||e.frameRate<=0))throw new TypeError(`Invalid video frame rate: ${e.frameRate}. Must be a positive integer.`);this._addTrack("video",i,e)}addAudioTrack(i,e={}){if(!(i instanceof E))throw new TypeError("source must be an AudioSource.");if(!e||typeof e!="object")throw new TypeError("metadata must be an object.");this._addTrack("audio",i,e)}addSubtitleTrack(i,e={}){if(!(i instanceof L))throw new TypeError("source must be a SubtitleSource.");if(!e||typeof e!="object")throw new TypeError("metadata must be an object.");this._addTrack("subtitle",i,e)}_addTrack(i,e,r){if(this._started)throw new Error("Cannot add track after output has started.");if(e._connectedTrack)throw new Error("Source is already used for a track.");let s={id:this._tracks.length+1,output:this,type:i,source:e,metadata:r};this._muxer.beforeTrackAdd(s),this._tracks.push(s),e._connectedTrack=s}start(){if(this._started)throw new Error("Output already started.");this._started=!0,this._muxer.start();for(let i of this._tracks)i.source._start()}async finalize(){if(this._finalizing)throw new Error("Cannot call finalize twice.");this._finalizing=!0;let i=this._tracks.map(e=>e.source._flush());await Promise.all(i),this._muxer.finalize(),this._writer.flush(),this._writer.finalize()}};export{se as AUDIO_CODECS,Y as ArrayBufferTarget,Be as AudioBufferSource,Re as AudioDataSource,E as AudioSource,Ue as CanvasSource,We as EncodedAudioChunkSource,ze as EncodedVideoChunkSource,_e as FileSystemWritableFileStreamTarget,K as MediaSource,Fe as MediaStreamAudioTrackSource,Pe as MediaStreamVideoTrackSource,ke as MkvOutputFormat,Me as Mp4OutputFormat,He as Output,U as OutputFormat,Ie as SUBTITLE_CODECS,Z as StreamTarget,L as SubtitleSource,V as Target,Ne as TextSubtitleSource,ie as VIDEO_CODECS,De as VideoFrameSource,_ as VideoSource,Q as WebMOutputFormat}; +`,o);u===-1&&(u=i.length);let d=ue(e[2]),f=ue(e[3])-d,w=i.slice(a,u).trim();i=i.slice(u).trimStart(),X.lastIndex=0;let _={timestamp:d/1e3,duration:f/1e3,text:w,identifier:s,settings:n,notes:t},g={};this.preambleEmitted||(g.config={description:this.preambleText},this.preambleEmitted=!0),this.options.output(_,g)}}},lt=/(?:(\d{2}):)?(\d{2}):(\d{2}).(\d{3})/,ue=r=>{let i=lt.exec(r);if(!i)throw new Error("Expected match.");return 60*60*1e3*Number(i[1]||"0")+60*1e3*Number(i[2])+1e3*Number(i[3])+Number(i[4])},le=r=>{let i=Math.floor(r/36e5),e=Math.floor(r%(60*60*1e3)/(60*1e3)),t=Math.floor(r%(60*1e3)/1e3),s=r%1e3;return i.toString().padStart(2,"0")+":"+e.toString().padStart(2,"0")+":"+t.toString().padStart(2,"0")+"."+s.toString().padStart(3,"0")};var G=class{constructor(i){this.writer=i;this.helper=new Uint8Array(8);this.helperView=new DataView(this.helper.buffer);this.offsets=new WeakMap}writeU32(i){this.helperView.setUint32(0,i,!1),this.writer.write(this.helper.subarray(0,4))}writeU64(i){this.helperView.setUint32(0,Math.floor(i/2**32),!1),this.helperView.setUint32(4,i,!1),this.writer.write(this.helper.subarray(0,8))}writeAscii(i){for(let e=0;e[(r%256+256)%256],h=r=>(A.setUint16(0,r,!1),[p[0],p[1]]),ct=r=>(A.setInt16(0,r,!1),[p[0],p[1]]),$e=r=>(A.setUint32(0,r,!1),[p[1],p[2],p[3]]),l=r=>(A.setUint32(0,r,!1),[p[0],p[1],p[2],p[3]]),je=r=>(A.setInt32(0,r,!1),[p[0],p[1],p[2],p[3]]),z=r=>(A.setUint32(0,Math.floor(r/2**32),!1),A.setUint32(4,r,!1),[p[0],p[1],p[2],p[3],p[4],p[5],p[6],p[7]]),xe=r=>(A.setInt16(0,2**8*r,!1),[p[0],p[1]]),v=r=>(A.setInt32(0,2**16*r,!1),[p[0],p[1],p[2],p[3]]),ke=r=>(A.setInt32(0,2**30*r,!1),[p[0],p[1],p[2],p[3]]),ye=(r,i)=>{let e=[],t=r;do{let s=t&127;t>>=7,e.length>0&&(s|=128),e.push(s),i!==void 0&&i--}while(t>0||i);return e.reverse()},y=(r,i=!1)=>{let e=Array(r.length).fill(null).map((t,s)=>r.charCodeAt(s));return i&&e.push(0),e},Se=r=>{let i=null;for(let e of r)(!i||e.timestamp>i.timestamp)&&(i=e);return i},qe=r=>{let i=r*(Math.PI/180),e=Math.cos(i),t=Math.sin(i);return[e,t,0,-t,e,0,0,0,1]},Ke=qe(0),Le=r=>[v(r[0]),v(r[1]),ke(r[2]),v(r[3]),v(r[4]),ke(r[5]),v(r[6]),v(r[7]),ke(r[8])],b=(r,i,e)=>({type:r,contents:i&&new Uint8Array(i.flat(10)),children:e}),T=(r,i,e,t,s)=>b(r,[C(i),$e(e),t??[]],s),Xe=r=>{let i=512;return r.fragmented?b("ftyp",[y("iso5"),l(i),y("iso5"),y("iso6"),y("mp41")]):b("ftyp",[y("isom"),l(i),y("isom"),r.holdsAvc?y("avc1"):[],y("mp41")])},de=r=>({type:"mdat",largeSize:r}),Ge=r=>({type:"free",size:r}),Y=(r,i,e=!1)=>b("moov",void 0,[dt(i,r),...r.map(t=>mt(t,i)),e?jt(r):null]),dt=(r,i)=>{let e=S(Math.max(0,...i.filter(a=>a.samples.length>0).map(a=>{let n=Se(a.samples);return n.timestamp+n.duration})),ce),t=Math.max(0,...i.map(a=>a.track.id))+1,s=!V(r)||!V(e),o=s?z:l;return T("mvhd",+s,0,[o(r),o(r),l(ce),o(e),v(1),xe(1),Array(10).fill(0),Le(Ke),Array(24).fill(0),l(t)])},mt=(r,i)=>b("trak",void 0,[ft(r,i),pt(r,i)]),ft=(r,i)=>{let e=Se(r.samples),t=S(e?e.timestamp+e.duration:0,ce),s=!V(i)||!V(t),o=s?z:l,a;if(r.type==="video"){let n=r.track.metadata.rotation;a=n===void 0||typeof n=="number"?qe(n??0):n}else a=Ke;return T("tkhd",+s,3,[o(i),o(i),l(r.track.id),l(0),o(t),Array(8).fill(0),h(0),h(r.track.id),xe(r.type==="audio"?1:0),h(0),Le(a),v(r.type==="video"?r.info.width:0),v(r.type==="video"?r.info.height:0)])},pt=(r,i)=>b("mdia",void 0,[ht(r,i),wt(r),Ct(r)]),ht=(r,i)=>{let e=Se(r.samples),t=S(e?e.timestamp+e.duration:0,r.timescale),s=!V(i)||!V(t),o=s?z:l;return T("mdhd",+s,0,[o(i),o(i),l(r.timescale),o(t),h(21956),h(0)])},bt={video:"vide",audio:"soun",subtitle:"text"},Tt={video:"VideoHandler",audio:"SoundHandler",subtitle:"TextHandler"},wt=r=>T("hdlr",0,0,[y("mhlr"),y(bt[r.type]),l(0),l(0),l(0),y(Tt[r.type],!0)]),Ct=r=>b("minf",void 0,[xt[r.type](),St(),_t(r)]),gt=()=>T("vmhd",0,1,[h(0),h(0),h(0),h(0)]),kt=()=>T("smhd",0,0,[h(0),h(0)]),yt=()=>T("nmhd",0,0),xt={video:gt,audio:kt,subtitle:yt},St=()=>b("dinf",void 0,[vt()]),vt=()=>T("dref",0,0,[l(1)],[At()]),At=()=>T("url ",0,1),_t=r=>{let i=r.compositionTimeOffsetTable.length>1||r.compositionTimeOffsetTable.some(e=>e.sampleCompositionTimeOffset!==0);return b("stbl",void 0,[Ot(r),Ft(r),Bt(r),Nt(r),Ht(r),Qt(r),i?$t(r):null])},Ot=r=>{let i;return r.type==="video"?i=Et(er[r.track.source._codec],r):r.type==="audio"?i=Pt(rr[r.track.source._codec],r):r.type==="subtitle"&&(i=Wt(sr[r.track.source._codec],r)),c(i),T("stsd",0,0,[l(1)],[i])},Et=(r,i)=>b(r,[Array(6).fill(0),h(1),h(0),h(0),Array(12).fill(0),h(i.info.width),h(i.info.height),l(4718592),l(4718592),l(0),h(1),Array(32).fill(0),h(24),ct(65535)],[tr[i.track.source._codec](i),ae(i.info.decoderConfig.colorSpace)?Vt(i):null]),Vt=r=>b("colr",[y("nclx"),h(F[r.info.decoderConfig.colorSpace.primaries]),h(B[r.info.decoderConfig.colorSpace.transfer]),h(N[r.info.decoderConfig.colorSpace.matrix]),C((r.info.decoderConfig.colorSpace.fullRange?1:0)<<7)]),Mt=r=>r.info.decoderConfig&&b("avcC",[...I(r.info.decoderConfig.description)]),It=r=>r.info.decoderConfig&&b("hvcC",[...I(r.info.decoderConfig.description)]),Qe=r=>{if(!r.info.decoderConfig)return null;let i=r.info.decoderConfig;c(i.colorSpace);let e=i.codec.split("."),t=Number(e[1]),s=Number(e[2]),n=(Number(e[3])<<4)+(0<<1)+Number(i.colorSpace.fullRange);return T("vpcC",1,0,[C(t),C(s),C(n),C(2),C(2),C(2),h(0)])},zt=()=>{let e=(1<<7)+1;return b("av1C",[e,0,0,0])},Pt=(r,i)=>b(r,[Array(6).fill(0),h(1),h(0),h(0),l(0),h(i.info.numberOfChannels),h(16),h(0),h(0),v(i.info.sampleRate)],[ir[i.track.source._codec](i)]),Ut=r=>{let e=[...I(r.info.decoderConfig.description??new ArrayBuffer(0))];return e=[...C(64),...C(21),...$e(0),...l(0),...l(0),...C(5),...ye(e.length),...e],e=[...h(1),...C(0),...C(4),...ye(e.length),...e,...C(6),...C(1),...C(2)],e=[...C(3),...ye(e.length),...e],T("esds",0,0,e)},Dt=r=>{let i=3840,e=0,t=r.info.decoderConfig?.description;if(t){c(t.byteLength<18);let s=ArrayBuffer.isView(t)?new DataView(t.buffer,t.byteOffset,t.byteLength):new DataView(t);i=s.getUint16(10,!0),e=s.getInt16(14,!0)}return b("dOps",[C(0),C(r.info.numberOfChannels),h(i),l(r.info.sampleRate),xe(e),C(0)])},Wt=(r,i)=>b(r,[Array(6).fill(0),h(1)],[or[i.track.source._codec](i)]),Rt=r=>b("vttC",[...x.encode(r.info.config.description)]);var Ft=r=>T("stts",0,0,[l(r.timeToSampleTable.length),r.timeToSampleTable.map(i=>[l(i.sampleCount),l(i.sampleDelta)])]),Bt=r=>{if(r.samples.every(e=>e.type==="key"))return null;let i=[...r.samples.entries()].filter(([,e])=>e.type==="key");return T("stss",0,0,[l(i.length),i.map(([e])=>l(e+1))])},Nt=r=>T("stsc",0,0,[l(r.compactlyCodedChunkTable.length),r.compactlyCodedChunkTable.map(i=>[l(i.firstChunk),l(i.samplesPerChunk),l(1)])]),Ht=r=>T("stsz",0,0,[l(0),l(r.samples.length),r.samples.map(i=>l(i.size))]),Qt=r=>r.finalizedChunks.length>0&&R(r.finalizedChunks).offset>=2**32?T("co64",0,0,[l(r.finalizedChunks.length),r.finalizedChunks.map(i=>z(i.offset))]):T("stco",0,0,[l(r.finalizedChunks.length),r.finalizedChunks.map(i=>l(i.offset))]),$t=r=>T("ctts",0,0,[l(r.compositionTimeOffsetTable.length),r.compositionTimeOffsetTable.map(i=>[l(i.sampleCount),l(i.sampleCompositionTimeOffset)])]),jt=r=>b("mvex",void 0,r.map(qt)),qt=r=>T("trex",0,0,[l(r.track.id),l(1),l(0),l(0),l(0)]),ve=(r,i)=>b("moof",void 0,[Kt(r),...i.map(Lt)]),Kt=r=>T("mfhd",0,0,[l(r)]),Ye=r=>{let i=0,e=0,t=0,s=0,o=r.type==="delta";return e|=+o,o?i|=1:i|=2,i<<24|e<<16|t<<8|s},Lt=r=>b("traf",void 0,[Xt(r),Gt(r),Yt(r)]),Xt=r=>{c(r.currentChunk);let i=0;i|=8,i|=16,i|=32,i|=131072;let e=r.currentChunk.samples[1]??r.currentChunk.samples[0],t={duration:e.timescaleUnitsToNextSample,size:e.size,flags:Ye(e)};return T("tfhd",0,i,[l(r.track.id),l(t.duration),l(t.size),l(t.flags)])},Gt=r=>(c(r.currentChunk),T("tfdt",1,0,[z(S(r.currentChunk.startTimestamp,r.timescale))])),Yt=r=>{c(r.currentChunk);let i=r.currentChunk.samples.map(k=>k.timescaleUnitsToNextSample),e=r.currentChunk.samples.map(k=>k.size),t=r.currentChunk.samples.map(Ye),s=r.currentChunk.samples.map(k=>S(k.timestamp-k.decodeTimestamp,r.timescale)),o=new Set(i),a=new Set(e),n=new Set(t),u=new Set(s),d=n.size===2&&t[0]!==t[1],m=o.size>1,f=a.size>1,w=!d&&n.size>1,_=u.size>1||[...u].some(k=>k!==0),g=0;return g|=1,g|=4*+d,g|=256*+m,g|=512*+f,g|=1024*+w,g|=2048*+_,T("trun",1,g,[l(r.currentChunk.samples.length),l(r.currentChunk.offset-r.currentChunk.moofOffset||0),d?l(t[0]):[],r.currentChunk.samples.map((k,D)=>[m?l(i[D]):[],f?l(e[D]):[],w?l(t[D]):[],_?je(s[D]):[]])])},Ze=r=>b("mfra",void 0,[...r.map(Zt),Jt()]),Zt=(r,i)=>T("tfra",1,0,[l(r.track.id),l(63),l(r.finalizedChunks.length),r.finalizedChunks.map(t=>[z(S(t.startTimestamp,r.timescale)),z(t.moofOffset),l(i+1),l(1),l(1)])]),Jt=()=>T("mfro",0,0,[l(0)]),Je=()=>b("vtte"),et=(r,i,e,t,s)=>b("vttc",void 0,[s!==null?b("vsid",[je(s)]):null,e!==null?b("iden",[...x.encode(e)]):null,i!==null?b("ctim",[...x.encode(le(i))]):null,t!==null?b("sttg",[...x.encode(t)]):null,b("payl",[...x.encode(r)])]),tt=r=>b("vtta",[...x.encode(r)]),er={avc:"avc1",hevc:"hvc1",vp8:"vp08",vp9:"vp09",av1:"av01"},tr={avc:Mt,hevc:It,vp8:Qe,vp9:Qe,av1:zt},rr={aac:"mp4a",opus:"Opus"},ir={aac:Ut,opus:Dt},sr={webvtt:"wvtt"},or={webvtt:Rt};var Q=class{constructor(i){this.mutex=new W;this.trackTimestampInfo=new WeakMap;this.output=i}beforeTrackAdd(i){}onTrackClose(i){}validateAndNormalizeTimestamp(i,e,t){let s=e/1e6,o=this.trackTimestampInfo.get(i);if(!o){if(!t)throw new Error("First frame must be a key frame.");if(this.timestampsMustStartAtZero&&s>0)throw new Error(`Timestamps must start at zero (got ${s}s).`);o={timestampOffset:s,maxTimestamp:i.source._offsetTimestamps?0:s,lastKeyFrameTimestamp:i.source._offsetTimestamps?0:s},this.trackTimestampInfo.set(i,o)}if(i.source._offsetTimestamps&&(s-=o.timestampOffset),s<0)throw new Error(`Timestamps must be non-negative (got ${s}s).`);if(ss.start-o.start);e.push({start:t[0].start,size:t[0].data.byteLength});for(let s=1;sd.start<=t&&tnr){for(let d=0;d=e.written[a+1].start;)e.written[a].end=Math.max(e.written[a].end,e.written[a+1].end),e.written.splice(a+1,1)}createChunk(e){let s={start:Math.floor(e/this.chunkSize)*this.chunkSize,data:new Uint8Array(this.chunkSize),written:[],shouldFlush:!1};return this.chunks.push(s),this.chunks.sort((o,a)=>o.start-a.start),this.chunks.indexOf(s)}queueChunksForFlush(e=!1){c(this.writer);for(let t=0;t{if(r==="avc"){let t=100;i<=768&&e<=432?t=66:i<=1920&&e<=1080&&(t=77);let s=0,o=i>1920||e>1080?50:41,a=t.toString(16).padStart(2,"0"),n=s.toString(16).padStart(2,"0"),u=o.toString(16).padStart(2,"0");return`avc1.${a}${n}${u}`}else if(r==="hevc"){let t=0,s=1,o=Array(32).fill(0);o[s]=1;let a=parseInt(o.reverse().join(""),2).toString(16).replace(/^0+/,""),n="L",u=120;return i<=1280&&e<=720?u=93:i<=1920&&e<=1080?u=120:i<=3840&&e<=2160?u=150:(n="H",u=180),`hev1.${t===0?"":String.fromCharCode(65+t-1)}${s}.${a}.${n}${u}.B0`}else{if(r==="vp8")return"vp8";if(r==="vp9"){let t="00",s;return i<=854&&e<=480?s="21":i<=1280&&e<=720?s="31":i<=1920&&e<=1080?s="41":i<=3840&&e<=2160?s="51":s="61",`vp09.${t}.${s}.08`}else if(r==="av1"){let s;return i<=854&&e<=480?s="01":i<=1280&&e<=720?s="03":i<=1920&&e<=1080?s="04":i<=3840&&e<=2160?s="07":s="09",`av01.0.${s}M.08`}}throw new TypeError(`Unhandled codec '${r}'.`)},it=(r,i,e)=>{if(r==="aac")return i>=2&&e<=24e3?"mp4a.40.29":e<=24e3?"mp4a.40.5":"mp4a.40.2";if(r==="opus")return"opus";if(r==="vorbis")return"vorbis";throw new TypeError(`Unhandled codec '${r}'.`)},pe=r=>{if(!r)throw new TypeError("Video chunk metadata must be provided.");if(typeof r!="object")throw new TypeError("Video chunk metadata must be an object.");if(!r.decoderConfig)throw new TypeError("Video chunk metadata must include a decoder configuration.");if(typeof r.decoderConfig!="object")throw new TypeError("Video chunk metadata decoder configuration must be an object.");if(typeof r.decoderConfig.codec!="string")throw new TypeError("Video chunk metadata decoder configuration must specify a codec string.");if(!Number.isInteger(r.decoderConfig.codedWidth)||r.decoderConfig.codedWidth<=0)throw new TypeError("Video chunk metadata decoder configuration must specify a valid codedWidth (positive integer).");if(!Number.isInteger(r.decoderConfig.codedHeight)||r.decoderConfig.codedHeight<=0)throw new TypeError("Video chunk metadata decoder configuration must specify a valid codedHeight (positive integer).");if(r.decoderConfig.description!==void 0&&!ge(r.decoderConfig.description))throw new TypeError("Video chunk metadata decoder configuration description, when defined, must be an ArrayBuffer or an ArrayBuffer view.");if(r.decoderConfig.colorSpace!==void 0){let{colorSpace:i}=r.decoderConfig;if(typeof i!="object")throw new TypeError("Video chunk metadata decoder configuration colorSpace, when provided, must be an object.");let e=Object.keys(F);if(i.primaries!=null&&!e.includes(i.primaries))throw new TypeError(`Video chunk metadata decoder configuration colorSpace primaries, when defined, must be one of ${e.join(", ")}.`);let t=Object.keys(B);if(i.transfer!=null&&!t.includes(i.transfer))throw new TypeError(`Video chunk metadata decoder configuration colorSpace transfer, when defined, must be one of ${t.join(", ")}.`);let s=Object.keys(N);if(i.matrix!=null&&!s.includes(i.matrix))throw new TypeError(`Video chunk metadata decoder configuration colorSpace matrix, when defined, must be one of ${s.join(", ")}.`);if(i.fullRange!=null&&typeof i.fullRange!="boolean")throw new TypeError("Video chunk metadata decoder configuration colorSpace fullRange, when defined, must be a boolean.")}if((r.decoderConfig.codec.startsWith("avc1")||r.decoderConfig.codec.startsWith("avc3"))&&!r.decoderConfig.description)throw new TypeError("Video chunk metadata decoder configuration for AVC must include a description, which is expected to be an AVCDecoderConfigurationRecord as specified in ISO 14496-15.");if((r.decoderConfig.codec.startsWith("hev1")||r.decoderConfig.codec.startsWith("hvc1"))&&!r.decoderConfig.description)throw new TypeError("Video chunk metadata decoder configuration for HEVC must include a description, which is expected to be an HEVCDecoderConfigurationRecord as specified in ISO 14496-15.");if((r.decoderConfig.codec==="vp8"||r.decoderConfig.codec.startsWith("vp09"))&&r.decoderConfig.colorSpace===void 0)throw new TypeError("Video chunk metadata decoder configuration for VP8/VP9 must include a colorSpace.")},he=r=>{if(!r)throw new TypeError("Audio chunk metadata must be provided.");if(typeof r!="object")throw new TypeError("Audio chunk metadata must be an object.");if(!r.decoderConfig)throw new TypeError("Audio chunk metadata must include a decoder configuration.");if(typeof r.decoderConfig!="object")throw new TypeError("Audio chunk metadata decoder configuration must be an object.");if(typeof r.decoderConfig.codec!="string")throw new TypeError("Audio chunk metadata decoder configuration must specify a codec string.");if(!Number.isInteger(r.decoderConfig.sampleRate)||r.decoderConfig.sampleRate<=0)throw new TypeError("Audio chunk metadata decoder configuration must specify a valid sampleRate (positive integer).");if(!Number.isInteger(r.decoderConfig.numberOfChannels)||r.decoderConfig.numberOfChannels<=0)throw new TypeError("Audio chunk metadata decoder configuration must specify a valid numberOfChannels (positive integer).");if(r.decoderConfig.description!==void 0&&!ge(r.decoderConfig.description))throw new TypeError("Audio chunk metadata decoder configuration description, when defined, must be an ArrayBuffer or an ArrayBuffer view.");if(r.decoderConfig.codec==="opus"&&r.decoderConfig.description&&r.decoderConfig.description.byteLength<18)throw new TypeError("Invalid decoder description provided for Opus; must be at least 18 bytes long.")},be=r=>{if(!r)throw new TypeError("Subtitle metadata must be provided.");if(typeof r!="object")throw new TypeError("Subtitle metadata must be an object.");if(!r.config)throw new TypeError("Subtitle metadata must include a config object.");if(typeof r.config!="object")throw new TypeError("Subtitle metadata config must be an object.");if(typeof r.config.description!="string")throw new TypeError("Subtitle metadata config description must be a string.")};var ce=1e3,ur=2082844800,S=(r,i,e=!0)=>{let t=r*i;return e?Math.round(t):t},Te=class extends Q{constructor(e,t){super(e);this.timestampsMustStartAtZero=!0;this.auxTarget=new J;this.auxWriter=this.auxTarget._createWriter();this.auxBoxWriter=new G(this.auxWriter);this.ftypSize=null;this.mdat=null;this.trackDatas=[];this.creationTime=Math.floor(Date.now()/1e3)+ur;this.finalizedChunks=[];this.nextFragmentNumber=1;this.writer=e._writer,this.boxWriter=new G(this.writer),this.fastStart=t._options.fastStart??(this.writer instanceof $?"in-memory":!1),(this.fastStart==="in-memory"||this.fastStart==="fragmented")&&(this.writer.ensureMonotonicity=!0)}async start(){let e=await this.mutex.acquire(),t=this.output._tracks.some(s=>s.type==="video"&&s.source._codec==="avc");this.boxWriter.writeBox(Xe({holdsAvc:t,fragmented:this.fastStart==="fragmented"})),this.ftypSize=this.writer.getPos(),this.fastStart==="in-memory"?this.mdat=de(!1):this.fastStart==="fragmented"||(this.mdat=de(!0),this.boxWriter.writeBox(this.mdat)),await this.writer.flush(),e()}getVideoTrackData(e,t){let s=this.trackDatas.find(a=>a.track===e);if(s)return s;pe(t),c(t),c(t.decoderConfig),c(t.decoderConfig.codedWidth!==void 0),c(t.decoderConfig.codedHeight!==void 0);let o={track:e,type:"video",info:{width:t.decoderConfig.codedWidth,height:t.decoderConfig.codedHeight,decoderConfig:t.decoderConfig},timescale:e.metadata.frameRate??57600,samples:[],sampleQueue:[],timestampProcessingQueue:[],timeToSampleTable:[],compositionTimeOffsetTable:[],lastTimescaleUnits:null,lastSample:null,finalizedChunks:[],currentChunk:null,compactlyCodedChunkTable:[]};return this.trackDatas.push(o),this.trackDatas.sort((a,n)=>a.track.id-n.track.id),o}getAudioTrackData(e,t){let s=this.trackDatas.find(a=>a.track===e);if(s)return s;he(t),c(t),c(t.decoderConfig);let o={track:e,type:"audio",info:{numberOfChannels:t.decoderConfig.numberOfChannels,sampleRate:t.decoderConfig.sampleRate,decoderConfig:t.decoderConfig},timescale:t.decoderConfig.sampleRate,samples:[],sampleQueue:[],timestampProcessingQueue:[],timeToSampleTable:[],compositionTimeOffsetTable:[],lastTimescaleUnits:null,lastSample:null,finalizedChunks:[],currentChunk:null,compactlyCodedChunkTable:[]};return this.trackDatas.push(o),this.trackDatas.sort((a,n)=>a.track.id-n.track.id),o}getSubtitleTrackData(e,t){let s=this.trackDatas.find(a=>a.track===e);if(s)return s;be(t),c(t),c(t.config);let o={track:e,type:"subtitle",info:{config:t.config},timescale:1e3,samples:[],sampleQueue:[],timestampProcessingQueue:[],timeToSampleTable:[],compositionTimeOffsetTable:[],lastTimescaleUnits:null,lastSample:null,finalizedChunks:[],currentChunk:null,compactlyCodedChunkTable:[],lastCueEndTimestamp:0,cueQueue:[],nextSourceId:0,cueToSourceId:new WeakMap};return this.trackDatas.push(o),this.trackDatas.sort((a,n)=>a.track.id-n.track.id),this.validateAndNormalizeTimestamp(e,0,!0),o}async addEncodedVideoChunk(e,t,s){let o=await this.mutex.acquire();try{let a=this.getVideoTrackData(e,s),n=new Uint8Array(t.byteLength);t.copyTo(n);let u=this.validateAndNormalizeTimestamp(a.track,t.timestamp,t.type==="key"),d=this.createSampleForTrack(a,n,u,(t.duration??0)/1e6,t.type);await this.registerSample(a,d)}finally{o()}}async addEncodedAudioChunk(e,t,s){let o=await this.mutex.acquire();try{let a=this.getAudioTrackData(e,s),n=new Uint8Array(t.byteLength);t.copyTo(n);let u=this.validateAndNormalizeTimestamp(a.track,t.timestamp,t.type==="key"),d=this.createSampleForTrack(a,n,u,(t.duration??0)/1e6,t.type);await this.registerSample(a,d)}finally{o()}}async addSubtitleCue(e,t,s){let o=await this.mutex.acquire();try{let a=this.getSubtitleTrackData(e,s);this.validateAndNormalizeTimestamp(a.track,1e6*t.timestamp,!0),e.source._codec==="webvtt"&&(a.cueQueue.push(t),await this.processWebVTTCues(a,t.timestamp))}finally{o()}}async processWebVTTCues(e,t){for(;e.cueQueue.length>0;){let s=new Set([]);for(let m of e.cueQueue)c(m.timestamp<=t),c(e.lastCueEndTimestamp<=m.timestamp+m.duration),s.add(Math.max(m.timestamp,e.lastCueEndTimestamp)),s.add(m.timestamp+m.duration);let o=[...s].sort((m,f)=>m-f),a=o[0],n=o[1]??a;if(t=n)break;H.lastIndex=0;let w=H.test(f.text),_=f.timestamp+f.duration,g=e.cueToSourceId.get(f);if(g===void 0&&n<_&&(g=e.nextSourceId++,e.cueToSourceId.set(f,g)),f.notes){let D=tt(f.notes);this.auxBoxWriter.writeBox(D)}let k=et(f.text,w?a:null,f.identifier??null,f.settings??null,g??null);this.auxBoxWriter.writeBox(k),_===n&&e.cueQueue.splice(m--,1)}let u=this.auxWriter.getSlice(0,this.auxWriter.getPos()),d=this.createSampleForTrack(e,u,a,n-a,"key");await this.registerSample(e,d),e.lastCueEndTimestamp=n}}createSampleForTrack(e,t,s,o,a){return{timestamp:s,decodeTimestamp:s,duration:o,data:t,size:t.byteLength,type:a,timescaleUnitsToNextSample:S(o,e.timescale)}}processTimestamps(e){if(e.timestampProcessingQueue.length===0)return;let t=e.timestampProcessingQueue.map(s=>s.timestamp).sort((s,o)=>s-o);for(let s=0;s{if(e===n)return t.type==="key";let u=n.sampleQueue[0];return u&&u.type==="key"});o>=1&&a&&(s=!0,await this.finalizeFragment())}else s=o>=.5}s&&(e.currentChunk&&await this.finalizeCurrentChunk(e),e.currentChunk={startTimestamp:t.timestamp,samples:[],offset:null,moofOffset:null}),c(e.currentChunk),e.currentChunk.samples.push(t),e.timestampProcessingQueue.push(t)}async finalizeCurrentChunk(e){if(c(this.fastStart!=="fragmented"),!!e.currentChunk){if(e.finalizedChunks.push(e.currentChunk),this.finalizedChunks.push(e.currentChunk),(e.compactlyCodedChunkTable.length===0||R(e.compactlyCodedChunkTable).samplesPerChunk!==e.currentChunk.samples.length)&&e.compactlyCodedChunkTable.push({firstChunk:e.finalizedChunks.length,samplesPerChunk:e.currentChunk.samples.length}),this.fastStart==="in-memory"){e.currentChunk.offset=0;return}e.currentChunk.offset=this.writer.getPos();for(let t of e.currentChunk.samples)c(t.data),this.writer.write(t.data),t.data=null;await this.writer.flush()}}async interleaveSamples(){c(this.fastStart==="fragmented");for(let e of this.output._tracks)if(!e.source._closed&&!this.trackDatas.some(t=>t.track===e))return;e:for(;;){let e=null,t=1/0;for(let o of this.trackDatas){if(o.sampleQueue.length===0&&!o.track.source._closed)break e;o.sampleQueue.length>0&&o.sampleQueue[0].timestamp=2**32&&(u.largeSize=!0,m=this.boxWriter.measureBox(u)+d),u.size=m,this.boxWriter.writeBox(u)}for(let u of this.trackDatas){u.currentChunk.offset=this.writer.getPos(),u.currentChunk.moofOffset=s;for(let d of u.currentChunk.samples)this.writer.write(d.data),d.data=null}let a=this.writer.getPos();this.writer.seek(this.boxWriter.offsets.get(o));let n=ve(t,this.trackDatas);this.boxWriter.writeBox(n),this.writer.seek(a);for(let u of this.trackDatas)u.finalizedChunks.push(u.currentChunk),this.finalizedChunks.push(u.currentChunk),u.currentChunk=null;e&&await this.writer.flush()}async onTrackClose(e){let t=await this.mutex.acquire();if(e.type==="subtitle"&&e.source._codec==="webvtt"){let s=this.trackDatas.find(o=>o.track===e);s&&await this.processWebVTTCues(s,1/0)}this.fastStart==="fragmented"&&await this.interleaveSamples(),t()}async finalize(){let e=await this.mutex.acquire();for(let t of this.trackDatas)t.type==="subtitle"&&t.track.source._codec==="webvtt"&&await this.processWebVTTCues(t,1/0);if(this.fastStart==="fragmented"){for(let t of this.trackDatas){for(let s of t.sampleQueue)await this.addSampleToTrack(t,s);this.processTimestamps(t)}await this.finalizeFragment(!1)}else for(let t of this.trackDatas)this.processTimestamps(t),await this.finalizeCurrentChunk(t);if(this.fastStart==="in-memory"){c(this.mdat);let t;for(let o=0;o<2;o++){let a=Y(this.trackDatas,this.creationTime),n=this.boxWriter.measureBox(a);t=this.boxWriter.measureBox(this.mdat);let u=this.writer.getPos()+n+t;for(let d of this.finalizedChunks){d.offset=u;for(let{data:m}of d.samples)c(m),u+=m.byteLength,t+=m.byteLength}if(u<2**32)break;t>=2**32&&(this.mdat.largeSize=!0)}let s=Y(this.trackDatas,this.creationTime);this.boxWriter.writeBox(s),this.mdat.size=t,this.boxWriter.writeBox(this.mdat);for(let o of this.finalizedChunks)for(let a of o.samples)c(a.data),this.writer.write(a.data),a.data=null}else if(this.fastStart==="fragmented"){let t=this.writer.getPos(),s=Ze(this.trackDatas);this.boxWriter.writeBox(s);let o=this.writer.getPos()-t;this.writer.seek(this.writer.getPos()-4),this.boxWriter.writeU32(o)}else{c(this.mdat),c(this.ftypSize!==null);let t=this.boxWriter.offsets.get(this.mdat);c(t!==void 0);let s=this.writer.getPos()-t;this.mdat.size=s,this.mdat.largeSize=s>=2**32,this.boxWriter.patchBox(this.mdat);let o=Y(this.trackDatas,this.creationTime);if(typeof this.fastStart=="object"){this.writer.seek(this.ftypSize),this.boxWriter.writeBox(o);let a=t-this.writer.getPos();this.boxWriter.writeBox(Ge(a))}else this.boxWriter.writeBox(o)}e()}};var ee=class{constructor(i){this.value=i}},j=class{constructor(i){this.value=i}},te=class{constructor(i){this.value=i}};var _e=r=>r<256?1:r<65536?2:r<1<<24?3:r<2**32?4:r<2**40?5:6,Oe=r=>r>=-64&&r<64?1:r>=-8192&&r<8192?2:r>=-(1<<20)&&r<1<<20?3:r>=-(1<<27)&&r<1<<27?4:r>=-(2**34)&&r<2**34?5:6,st=r=>{if(r<127)return 1;if(r<16383)return 2;if(r<(1<<21)-1)return 3;if(r<(1<<28)-1)return 4;if(r<2**35-1)return 5;if(r<2**42-1)return 6;throw new Error("EBML VINT size not supported "+r)};var Ee=2**15,ot="https://github.com/Vanilagy/webm-muxer",at=6,nt=5,lr={avc:"V_MPEG4/ISO/AVC",hevc:"V_MPEGH/ISO/HEVC",vp8:"V_VP8",vp9:"V_VP9",av1:"V_AV1",aac:"A_AAC",opus:"A_OPUS",webvtt:"S_TEXT/WEBVTT"},cr={video:1,audio:2,subtitle:17},we=class extends Q{constructor(e,t){super(e);this.timestampsMustStartAtZero=!1;this.helper=new Uint8Array(8);this.helperView=new DataView(this.helper.buffer);this.offsets=new WeakMap;this.dataOffsets=new WeakMap;this.trackDatas=[];this.segment=null;this.segmentInfo=null;this.seekHead=null;this.tracksElement=null;this.segmentDuration=null;this.cues=null;this.currentCluster=null;this.currentClusterMsTimestamp=null;this.trackDatasInCurrentCluster=new Set;this.duration=0;this.writer=e._writer,this.format=t,this.format._options.streamable&&(this.writer.ensureMonotonicity=!0)}writeByte(e){this.helperView.setUint8(0,e),this.writer.write(this.helper.subarray(0,1))}writeFloat32(e){this.helperView.setFloat32(0,e,!1),this.writer.write(this.helper.subarray(0,4))}writeFloat64(e){this.helperView.setFloat64(0,e,!1),this.writer.write(this.helper)}writeUnsignedInt(e,t=_e(e)){let s=0;switch(t){case 6:this.helperView.setUint8(s++,e/2**40|0);case 5:this.helperView.setUint8(s++,e/2**32|0);case 4:this.helperView.setUint8(s++,e>>24);case 3:this.helperView.setUint8(s++,e>>16);case 2:this.helperView.setUint8(s++,e>>8);case 1:this.helperView.setUint8(s++,e);break;default:throw new Error("Bad UINT size "+t)}this.writer.write(this.helper.subarray(0,s))}writeSignedInt(e,t=Oe(e)){e<0&&(e+=2**(t*8)),this.writeUnsignedInt(e,t)}writeEBMLVarInt(e,t=st(e)){let s=0;switch(t){case 1:this.helperView.setUint8(s++,128|e);break;case 2:this.helperView.setUint8(s++,64|e>>8),this.helperView.setUint8(s++,e);break;case 3:this.helperView.setUint8(s++,32|e>>16),this.helperView.setUint8(s++,e>>8),this.helperView.setUint8(s++,e);break;case 4:this.helperView.setUint8(s++,16|e>>24),this.helperView.setUint8(s++,e>>16),this.helperView.setUint8(s++,e>>8),this.helperView.setUint8(s++,e);break;case 5:this.helperView.setUint8(s++,8|e/2**32&7),this.helperView.setUint8(s++,e>>24),this.helperView.setUint8(s++,e>>16),this.helperView.setUint8(s++,e>>8),this.helperView.setUint8(s++,e);break;case 6:this.helperView.setUint8(s++,4|e/2**40&3),this.helperView.setUint8(s++,e/2**32|0),this.helperView.setUint8(s++,e>>24),this.helperView.setUint8(s++,e>>16),this.helperView.setUint8(s++,e>>8),this.helperView.setUint8(s++,e);break;default:throw new Error("Bad EBML VINT size "+t)}this.writer.write(this.helper.subarray(0,s))}writeString(e){this.writer.write(new Uint8Array(e.split("").map(t=>t.charCodeAt(0))))}writeEBML(e){if(e!==null){if(e instanceof Uint8Array)this.writer.write(e);else if(Array.isArray(e))for(let t of e)this.writeEBML(t);else if(this.offsets.set(e,this.writer.getPos()),this.writeUnsignedInt(e.id),Array.isArray(e.data)){let t=this.writer.getPos(),s=e.size===-1?1:e.size??4;e.size===-1?this.writeByte(255):this.writer.seek(this.writer.getPos()+s);let o=this.writer.getPos();if(this.dataOffsets.set(e,o),this.writeEBML(e.data),e.size!==-1){let a=this.writer.getPos()-o,n=this.writer.getPos();this.writer.seek(t),this.writeEBMLVarInt(a,s),this.writer.seek(n)}}else if(typeof e.data=="number"){let t=e.size??_e(e.data);this.writeEBMLVarInt(t),this.writeUnsignedInt(e.data,t)}else if(typeof e.data=="string")this.writeEBMLVarInt(e.data.length),this.writeString(e.data);else if(e.data instanceof Uint8Array)this.writeEBMLVarInt(e.data.byteLength,e.size),this.writer.write(e.data);else if(e.data instanceof ee)this.writeEBMLVarInt(4),this.writeFloat32(e.data.value);else if(e.data instanceof j)this.writeEBMLVarInt(8),this.writeFloat64(e.data.value);else if(e.data instanceof te){let t=e.size??Oe(e.data.value);this.writeEBMLVarInt(t),this.writeSignedInt(e.data.value,t)}}}beforeTrackAdd(e){if(this.format instanceof q)if(e.type==="video"){if(!["vp8","vp9","av1"].includes(e.source._codec))throw new Error("WebM only supports VP8, VP9 and AV1 as video codecs. Switching to MKV removes this restriction.")}else if(e.type==="audio"){if(!["opus","vorbis"].includes(e.source._codec))throw new Error("WebM only supports Opus and Vorbis as audio codecs. Switching to MKV removes this restriction.")}else if(e.type==="subtitle"){if(e.source._codec!=="webvtt")throw new Error("WebM only supports WebVTT as subtitle codec. Switching to MKV removes this restriction.")}else throw new Error("WebM only supports video, audio and subtitle tracks. Switching to MKV removes this restriction.")}async start(){let e=await this.mutex.acquire();this.writeEBMLHeader(),this.format._options.streamable||this.createSeekHead(),this.createSegmentInfo(),this.createCues(),await this.writer.flush(),e()}writeEBMLHeader(){let e={id:440786851,data:[{id:17030,data:1},{id:17143,data:1},{id:17138,data:4},{id:17139,data:8},{id:17026,data:this.format instanceof q?"webm":"matroska"},{id:17031,data:2},{id:17029,data:2}]};this.writeEBML(e)}createSeekHead(){let e=new Uint8Array([28,83,187,107]),t=new Uint8Array([21,73,169,102]),s=new Uint8Array([22,84,174,107]),o={id:290298740,data:[{id:19899,data:[{id:21419,data:e},{id:21420,size:5,data:0}]},{id:19899,data:[{id:21419,data:t},{id:21420,size:5,data:0}]},{id:19899,data:[{id:21419,data:s},{id:21420,size:5,data:0}]}]};this.seekHead=o}createSegmentInfo(){let e={id:17545,data:new j(0)};this.segmentDuration=e;let t={id:357149030,data:[{id:2807729,data:1e6},{id:19840,data:ot},{id:22337,data:ot},this.format._options.streamable?null:e]};this.segmentInfo=t}createTracks(){let e={id:374648427,data:[]};this.tracksElement=e;for(let t of this.trackDatas)e.data.push({id:174,data:[{id:215,data:t.track.id},{id:29637,data:t.track.id},{id:131,data:cr[t.type]},{id:134,data:lr[t.track.source._codec]},...t.type==="video"?[t.info.decoderConfig.description?{id:25506,data:I(t.info.decoderConfig.description)}:null,t.track.metadata.frameRate?{id:2352003,data:1e9/t.track.metadata.frameRate}:null,{id:224,data:[{id:176,data:t.info.width},{id:186,data:t.info.height},(()=>{if(t.info.decoderConfig.colorSpace){let s=t.info.decoderConfig.colorSpace;return ae(s)?{id:21936,data:[{id:21937,data:N[s.matrix]},{id:21946,data:B[s.transfer]},{id:21947,data:F[s.primaries]},{id:21945,data:[1,2][Number(s.fullRange)]}]}:null}return null})()]}]:[],...t.type==="audio"?[t.info.decoderConfig.description?{id:25506,data:I(t.info.decoderConfig.description)}:null,{id:225,data:[{id:181,data:new ee(t.info.sampleRate)},{id:159,data:t.info.numberOfChannels}]}]:[],...t.type==="subtitle"?[{id:25506,data:x.encode(t.info.config.description)}]:[]]})}createSegment(){let e={id:408125543,size:this.format._options.streamable?-1:at,data:[this.format._options.streamable?null:this.seekHead,this.segmentInfo,this.tracksElement]};this.segment=e,this.writeEBML(e)}createCues(){this.cues={id:475249515,data:[]}}get segmentDataOffset(){return c(this.segment),this.dataOffsets.get(this.segment)}getVideoTrackData(e,t){let s=this.trackDatas.find(a=>a.track===e);if(s)return s;pe(t),c(t),c(t.decoderConfig),c(t.decoderConfig.codedWidth!==void 0),c(t.decoderConfig.codedHeight!==void 0);let o={track:e,type:"video",info:{width:t.decoderConfig.codedWidth,height:t.decoderConfig.codedHeight,decoderConfig:t.decoderConfig},chunkQueue:[],lastWrittenMsTimestamp:null};return this.trackDatas.push(o),this.trackDatas.sort((a,n)=>a.track.id-n.track.id),o}getAudioTrackData(e,t){let s=this.trackDatas.find(a=>a.track===e);if(s)return s;he(t),c(t),c(t.decoderConfig);let o={track:e,type:"audio",info:{numberOfChannels:t.decoderConfig.numberOfChannels,sampleRate:t.decoderConfig.sampleRate,decoderConfig:t.decoderConfig},chunkQueue:[],lastWrittenMsTimestamp:null};return this.trackDatas.push(o),this.trackDatas.sort((a,n)=>a.track.id-n.track.id),o}getSubtitleTrackData(e,t){let s=this.trackDatas.find(a=>a.track===e);if(s)return s;be(t),c(t),c(t.config);let o={track:e,type:"subtitle",info:{config:t.config},chunkQueue:[],lastWrittenMsTimestamp:null};return this.trackDatas.push(o),this.trackDatas.sort((a,n)=>a.track.id-n.track.id),o}async addEncodedVideoChunk(e,t,s){let o=await this.mutex.acquire();try{let a=this.getVideoTrackData(e,s),n=new Uint8Array(t.byteLength);t.copyTo(n);let u=this.validateAndNormalizeTimestamp(a.track,t.timestamp,t.type==="key"),d=this.createInternalChunk(n,u,(t.duration??0)/1e6,t.type);e.source._codec==="vp9"&&this.fixVP9ColorSpace(a,d),a.chunkQueue.push(d),await this.interleaveChunks()}finally{o()}}async addEncodedAudioChunk(e,t,s){let o=await this.mutex.acquire();try{let a=this.getAudioTrackData(e,s),n=new Uint8Array(t.byteLength);t.copyTo(n);let u=this.validateAndNormalizeTimestamp(a.track,t.timestamp,t.type==="key"),d=this.createInternalChunk(n,u,(t.duration??0)/1e6,t.type);a.chunkQueue.push(d),await this.interleaveChunks()}finally{o()}}async addSubtitleCue(e,t,s){let o=await this.mutex.acquire();try{let a=this.getSubtitleTrackData(e,s),n=this.validateAndNormalizeTimestamp(a.track,1e6*t.timestamp,!0),u=t.text,d=Math.floor(n*1e3);H.lastIndex=0,u=u.replace(H,_=>{let k=ue(_.slice(1,-1))-d;return`<${le(k)}>`});let m=x.encode(u),f=`${t.settings??""} +${t.identifier??""} +${t.notes??""}`,w=this.createInternalChunk(m,n,t.duration,"key",f.trim()?x.encode(f):null);a.chunkQueue.push(w),await this.interleaveChunks()}finally{o()}}async interleaveChunks(){for(let e of this.output._tracks)if(!e.source._closed&&!this.trackDatas.some(t=>t.track===e))return;e:for(;;){let e=null,t=1/0;for(let o of this.trackDatas){if(o.chunkQueue.length===0&&!o.track.source._closed)break e;o.chunkQueue.length>0&&o.chunkQueue[0].timestamp=2&&s++;let d={rgb:7,bt709:2,bt470bg:1,smpte170m:3}[e.info.decoderConfig.colorSpace.matrix];He(t.data,s+0,s+3,d)}createInternalChunk(e,t,s,o,a=null){return{data:e,type:o,timestamp:t,duration:s,additions:a}}writeBlock(e,t){this.segment||(this.createTracks(),this.createSegment());let s=Math.floor(1e3*t.timestamp),o=this.trackDatas.every(f=>{if(f.track.source._closed)return!0;if(e===f)return t.type==="key";let w=f.chunkQueue[0];return w&&w.type==="key"});(!this.currentCluster||o&&s-this.currentClusterMsTimestamp>=1e3)&&this.createNewCluster(s);let a=s-this.currentClusterMsTimestamp;if(a<0)return;if(a>=Ee)throw new Error(`Current Matroska cluster exceeded its maximum allowed length of ${Ee} milliseconds. In order to produce a correct WebM file, you must pass in a key frame at least every ${Ee} milliseconds.`);let u=new Uint8Array(4),d=new DataView(u.buffer);d.setUint8(0,128|e.track.id),d.setInt16(1,a,!1);let m=Math.floor(1e3*t.duration);if(m===0&&!t.additions){d.setUint8(3,+(t.type==="key")<<7);let f={id:163,data:[u,t.data]};this.writeEBML(f)}else{let f={id:160,data:[{id:161,data:[u,t.data]},t.type==="delta"?{id:251,data:new te(e.lastWrittenMsTimestamp-s)}:null,t.additions?{id:30113,data:[{id:166,data:[{id:165,data:t.additions},{id:238,data:1}]}]}:null,m>0?{id:155,data:m}:null]};this.writeEBML(f)}this.duration=Math.max(this.duration,s+m),e.lastWrittenMsTimestamp=s,this.trackDatasInCurrentCluster.add(e)}createNewCluster(e){this.currentCluster&&!this.format._options.streamable&&this.finalizeCurrentCluster(),this.currentCluster={id:524531317,size:this.format._options.streamable?-1:nt,data:[{id:231,data:e}]},this.writeEBML(this.currentCluster),this.currentClusterMsTimestamp=e,this.trackDatasInCurrentCluster.clear()}finalizeCurrentCluster(){c(this.currentCluster);let e=this.writer.getPos()-this.dataOffsets.get(this.currentCluster),t=this.writer.getPos();this.writer.seek(this.offsets.get(this.currentCluster)+4),this.writeEBMLVarInt(e,nt),this.writer.seek(t);let s=this.offsets.get(this.currentCluster)-this.segmentDataOffset;c(this.cues),this.cues.data.push({id:187,data:[{id:179,data:this.currentClusterMsTimestamp},...[...this.trackDatasInCurrentCluster].map(o=>({id:183,data:[{id:247,data:o.track.id},{id:241,data:s}]}))]})}async onTrackClose(){let e=await this.mutex.acquire();await this.interleaveChunks(),e()}async finalize(){let e=await this.mutex.acquire();this.segment||(this.createTracks(),this.createSegment());for(let t of this.trackDatas)for(;t.chunkQueue.length>0;)this.writeBlock(t,t.chunkQueue.shift());if(!this.format._options.streamable&&this.currentCluster&&this.finalizeCurrentCluster(),c(this.cues),this.writeEBML(this.cues),!this.format._options.streamable){let t=this.writer.getPos(),s=this.writer.getPos()-this.segmentDataOffset;this.writer.seek(this.offsets.get(this.segment)+4),this.writeEBMLVarInt(s,at),this.segmentDuration.data=new j(this.duration),this.writer.seek(this.offsets.get(this.segmentDuration)),this.writeEBML(this.segmentDuration),this.seekHead.data[0].data[1].data=this.offsets.get(this.cues)-this.segmentDataOffset,this.seekHead.data[1].data[1].data=this.offsets.get(this.segmentInfo)-this.segmentDataOffset,this.seekHead.data[2].data[1].data=this.offsets.get(this.tracksElement)-this.segmentDataOffset,this.writer.seek(this.offsets.get(this.seekHead)),this.writeEBML(this.seekHead),this.writer.seek(t)}e()}};var U=class{},Ve=class extends U{constructor(i={}){if(!i||typeof i!="object")throw new TypeError("options must be an object.");if(i.fastStart!==void 0&&![!1,"in-memory","fragmented"].includes(i.fastStart))throw new TypeError('options.fastStart, when provided, must be false, "in-memory", or "fragmented".');super(),this._options=i}_createMuxer(i){return new Te(i,this)}},Ce=class extends U{constructor(i={}){if(!i||typeof i!="object")throw new TypeError("options must be an object.");if(i.streamable!==void 0&&typeof i.streamable!="boolean")throw new TypeError("options.streamable, when provided, must be a boolean.");super(),this._options=i}_createMuxer(i){return new we(i,this)}},q=class extends Ce{};var re=["avc","hevc","vp8","vp9","av1"],ie=["aac","opus"],Me=["webvtt"],K=class{constructor(){this._connectedTrack=null;this._closed=!1;this._offsetTimestamps=!1}_ensureValidDigest(){if(!this._connectedTrack)throw new Error("Cannot call digest without connecting the source to an output track.");if(!this._connectedTrack.output._started)throw new Error("Cannot call digest before output has been started.");if(this._connectedTrack.output._finalizing)throw new Error("Cannot call digest after output has started finalizing.");if(this._closed)throw new Error("Cannot call digest after source has been closed.")}_start(){}async _flush(){}close(){if(this._closed)throw new Error("Source already closed.");if(!this._connectedTrack)throw new Error("Cannot call close without connecting the source to an output track.");if(!this._connectedTrack.output._started)throw new Error("Cannot call close before output has been started.");this._closed=!0,!this._connectedTrack.output._finalizing&&this._connectedTrack.output._muxer.onTrackClose(this._connectedTrack)}},O=class extends K{constructor(e){super();this._connectedTrack=null;if(!re.includes(e))throw new TypeError(`Invalid video codec '${e}'. Must be one of: ${re.join(", ")}.`);this._codec=e}},Ie=class extends O{constructor(i){super(i)}digest(i,e){if(!(i instanceof EncodedVideoChunk))throw new TypeError("chunk must be an EncodedVideoChunk.");return this._ensureValidDigest(),this._connectedTrack.output._muxer.addEncodedVideoChunk(this._connectedTrack,i,e)}},dr=5,mr=r=>{if(!r||typeof r!="object")throw new TypeError("Codec config must be an object.");if(!re.includes(r.codec))throw new TypeError(`Invalid video codec '${r.codec}'. Must be one of: ${re.join(", ")}.`);if(!Number.isInteger(r.bitrate)||r.bitrate<=0)throw new TypeError("config.bitrate must be a positive integer.");if(r.latencyMode!==void 0&&["quality","realtime"].includes(r.latencyMode))throw new TypeError("config.latencyMode, when provided, must be 'quality' or 'realtime'.")},se=class{constructor(i,e){this.source=i;this.codecConfig=e;this.encoder=null;this.muxer=null;this.lastMultipleOfKeyFrameInterval=-1;this.lastWidth=null;this.lastHeight=null;mr(e)}async digest(i){if(this.source._ensureValidDigest(),this.lastWidth!==null&&this.lastHeight!==null){if(i.codedWidth!==this.lastWidth||i.codedHeight!==this.lastHeight)throw new Error(`Video frame size must remain constant. Expected ${this.lastWidth}x${this.lastHeight}, got ${i.codedWidth}x${i.codedHeight}.`)}else this.lastWidth=i.codedWidth,this.lastHeight=i.codedHeight;this.ensureEncoder(i),c(this.encoder);let e=Math.floor(i.timestamp/1e6/dr);this.encoder.encode(i,{keyFrame:e!==this.lastMultipleOfKeyFrameInterval}),this.lastMultipleOfKeyFrameInterval=e,this.encoder.encodeQueueSize>=4&&await new Promise(t=>this.encoder.addEventListener("dequeue",t,{once:!0})),await this.muxer.mutex.currentPromise}ensureEncoder(i){this.encoder||(this.encoder=new VideoEncoder({output:(e,t)=>this.muxer.addEncodedVideoChunk(this.source._connectedTrack,e,t),error:e=>console.error("Video encode error:",e)}),this.encoder.configure({codec:rt(this.codecConfig.codec,i.codedWidth,i.codedHeight),width:i.codedWidth,height:i.codedHeight,bitrate:this.codecConfig.bitrate,framerate:this.source._connectedTrack?.metadata.frameRate,latencyMode:this.codecConfig.latencyMode}),c(this.source._connectedTrack),this.muxer=this.source._connectedTrack.output._muxer)}async flush(){this.encoder&&(await this.encoder.flush(),this.encoder.close())}},ze=class extends O{constructor(i){super(i.codec),this._encoder=new se(this,i)}digest(i){if(!(i instanceof VideoFrame))throw new TypeError("videoFrame must be a VideoFrame.");return this._encoder.digest(i)}_flush(){return this._encoder.flush()}},Pe=class extends O{constructor(i,e){if(!(i instanceof HTMLCanvasElement))throw new TypeError("canvas must be an HTMLCanvasElement.");super(e.codec),this._encoder=new se(this,e),this._canvas=i}digest(i,e=0){if(!Number.isFinite(i)||i<0)throw new TypeError("timestamp must be a non-negative number.");if(!Number.isFinite(e)||e<0)throw new TypeError("duration must be a non-negative number.");let t=new VideoFrame(this._canvas,{timestamp:Math.round(1e6*i),duration:Math.round(1e6*e),alpha:"discard"}),s=this._encoder.digest(t);return t.close(),s}_flush(){return this._encoder.flush()}},Ue=class extends O{constructor(e,t){if(!(e instanceof MediaStreamTrack)||e.kind!=="video")throw new TypeError("track must be a video MediaStreamTrack.");t={...t,latencyMode:"realtime"};super(t.codec);this._abortController=null;this._offsetTimestamps=!0;this._encoder=new se(this,t),this._track=e}_start(){this._abortController=new AbortController;let e=new MediaStreamTrackProcessor({track:this._track}),t=new WritableStream({write:s=>{this._encoder.digest(s),s.close()}});e.readable.pipeTo(t,{signal:this._abortController.signal}).catch(s=>{s instanceof DOMException&&s.name==="AbortError"||console.error("Pipe error:",s)})}async _flush(){this._abortController&&(this._abortController.abort(),this._abortController=null),await this._encoder.flush()}},E=class extends K{constructor(e){super();this._connectedTrack=null;if(!ie.includes(e))throw new TypeError(`Invalid audio codec '${e}'. Must be one of: ${ie.join(", ")}.`);this._codec=e}},De=class extends E{constructor(i){super(i)}digest(i,e){if(!(i instanceof EncodedAudioChunk))throw new TypeError("chunk must be an EncodedAudioChunk.");return this._ensureValidDigest(),this._connectedTrack.output._muxer.addEncodedAudioChunk(this._connectedTrack,i,e)}},fr=r=>{if(!r||typeof r!="object")throw new TypeError("Codec config must be an object.");if(!ie.includes(r.codec))throw new TypeError(`Invalid audio codec '${r.codec}'. Must be one of: ${ie.join(", ")}.`);if(!Number.isInteger(r.bitrate)||r.bitrate<=0)throw new TypeError("config.bitrate must be a positive integer.")},oe=class{constructor(i,e){this.source=i;this.codecConfig=e;this.encoder=null;this.muxer=null;this.lastNumberOfChannels=null;this.lastSampleRate=null;fr(e)}async digest(i){if(this.source._ensureValidDigest(),this.lastNumberOfChannels!==null&&this.lastSampleRate!==null){if(i.numberOfChannels!==this.lastNumberOfChannels||i.sampleRate!==this.lastSampleRate)throw new Error(`Audio parameters must remain constant. Expected ${this.lastNumberOfChannels} channels at ${this.lastSampleRate} Hz, got ${i.numberOfChannels} channels at ${i.sampleRate} Hz.`)}else this.lastNumberOfChannels=i.numberOfChannels,this.lastSampleRate=i.sampleRate;this.ensureEncoder(i),c(this.encoder),this.encoder.encode(i),this.encoder.encodeQueueSize>=4&&await new Promise(e=>this.encoder.addEventListener("dequeue",e,{once:!0})),await this.muxer.mutex.currentPromise}ensureEncoder(i){this.encoder||(this.encoder=new AudioEncoder({output:(e,t)=>this.muxer.addEncodedAudioChunk(this.source._connectedTrack,e,t),error:e=>console.error("Audio encode error:",e)}),this.encoder.configure({codec:it(this.codecConfig.codec,i.numberOfChannels,i.sampleRate),numberOfChannels:i.numberOfChannels,sampleRate:i.sampleRate,bitrate:this.codecConfig.bitrate}),c(this.source._connectedTrack),this.muxer=this.source._connectedTrack.output._muxer)}async flush(){this.encoder&&(await this.encoder.flush(),this.encoder.close())}},We=class extends E{constructor(i){super(i.codec),this._encoder=new oe(this,i)}digest(i){if(!(i instanceof AudioData))throw new TypeError("audioData must be an AudioData.");return this._encoder.digest(i)}_flush(){return this._encoder.flush()}},Re=class extends E{constructor(e){super(e.codec);this._accumulatedFrameCount=0;this._encoder=new oe(this,e)}digest(e){if(!(e instanceof AudioBuffer))throw new TypeError("audioBuffer must be an AudioBuffer.");let t=e.numberOfChannels,s=e.sampleRate,o=e.length,a=new Float32Array(t*o);for(let d=0;d{this._encoder.digest(s),s.close()}});e.readable.pipeTo(t,{signal:this._abortController.signal}).catch(s=>{s instanceof DOMException&&s.name==="AbortError"||console.error("Pipe error:",s)})}async _flush(){this._abortController&&(this._abortController.abort(),this._abortController=null),await this._encoder.flush()}},L=class extends K{constructor(e){super();this._connectedTrack=null;if(!Me.includes(e))throw new TypeError(`Invalid subtitle codec '${e}'. Must be one of: ${Me.join(", ")}.`);this._codec=e}},Be=class extends L{constructor(i){super(i),this._parser=new ne({codec:i,output:(e,t)=>this._connectedTrack?.output._muxer.addSubtitleCue(this._connectedTrack,e,t),error:e=>console.error("Subtitle parse error:",e)})}digest(i){if(typeof i!="string")throw new TypeError("text must be a string.");return this._ensureValidDigest(),this._parser.parse(i),this._connectedTrack.output._muxer.mutex.currentPromise}};var Ne=class{constructor(i){this._tracks=[];this._started=!1;this._finalizing=!1;this._mutex=new W;if(!i||typeof i!="object")throw new TypeError("options must be an object.");if(!(i.format instanceof U))throw new TypeError("options.format must be an OutputFormat.");if(!(i.target instanceof P))throw new TypeError("options.target must be a Target.");if(i.target._output)throw new Error("Target is already used for another output.");i.target._output=this,this._writer=i.target._createWriter(),this._muxer=i.format._createMuxer(this)}addVideoTrack(i,e={}){if(!(i instanceof O))throw new TypeError("source must be a VideoSource.");if(!e||typeof e!="object")throw new TypeError("metadata must be an object.");if(typeof e.rotation=="number"&&![0,90,180,270].includes(e.rotation))throw new TypeError(`Invalid video rotation: ${e.rotation}. Has to be 0, 90, 180 or 270.`);if(Array.isArray(e.rotation)&&(e.rotation.length!==9||e.rotation.some(t=>!Number.isFinite(t))))throw new TypeError(`Invalid video transformation matrix: ${e.rotation.join()}`);if(e.frameRate!==void 0&&(!Number.isInteger(e.frameRate)||e.frameRate<=0))throw new TypeError(`Invalid video frame rate: ${e.frameRate}. Must be a positive integer.`);this._addTrack("video",i,e)}addAudioTrack(i,e={}){if(!(i instanceof E))throw new TypeError("source must be an AudioSource.");if(!e||typeof e!="object")throw new TypeError("metadata must be an object.");this._addTrack("audio",i,e)}addSubtitleTrack(i,e={}){if(!(i instanceof L))throw new TypeError("source must be a SubtitleSource.");if(!e||typeof e!="object")throw new TypeError("metadata must be an object.");this._addTrack("subtitle",i,e)}_addTrack(i,e,t){if(this._started)throw new Error("Cannot add track after output has started.");if(e._connectedTrack)throw new Error("Source is already used for a track.");let s={id:this._tracks.length+1,output:this,type:i,source:e,metadata:t};this._muxer.beforeTrackAdd(s),this._tracks.push(s),e._connectedTrack=s}async start(){if(this._started)throw new Error("Output already started.");this._started=!0,this._writer.start();let i=await this._mutex.acquire();await this._muxer.start();for(let e of this._tracks)e.source._start();i()}async finalize(){if(!this._started)throw new Error("Cannot finalize before starting.");if(this._finalizing)throw new Error("Cannot call finalize twice.");this._finalizing=!0;let i=await this._mutex.acquire(),e=this._tracks.map(t=>t.source._flush());await Promise.all(e),await this._muxer.finalize(),await this._writer.flush(),await this._writer.finalize(),i()}};export{ie as AUDIO_CODECS,J as ArrayBufferTarget,Re as AudioBufferSource,We as AudioDataSource,E as AudioSource,Pe as CanvasSource,De as EncodedAudioChunkSource,Ie as EncodedVideoChunkSource,K as MediaSource,Fe as MediaStreamAudioTrackSource,Ue as MediaStreamVideoTrackSource,Ce as MkvOutputFormat,Ve as Mp4OutputFormat,Ne as Output,U as OutputFormat,Me as SUBTITLE_CODECS,Ae as StreamTarget,L as SubtitleSource,P as Target,Be as TextSubtitleSource,re as VIDEO_CODECS,ze as VideoFrameSource,O as VideoSource,q as WebMOutputFormat}; diff --git a/dist/metamuxer.mjs b/dist/metamuxer.mjs index f9716c6..c2d3332 100644 --- a/dist/metamuxer.mjs +++ b/dist/metamuxer.mjs @@ -72,6 +72,21 @@ var colorSpaceIsComplete = (colorSpace) => { var isAllowSharedBufferSource = (x) => { return x instanceof ArrayBuffer || typeof SharedArrayBuffer !== "undefined" && x instanceof SharedArrayBuffer || ArrayBuffer.isView(x) && !(x instanceof DataView); }; +var AsyncMutex = class { + constructor() { + this.currentPromise = Promise.resolve(); + } + async acquire() { + let resolver; + let nextPromise = new Promise((resolve) => { + resolver = resolve; + }); + let currentPromiseAlias = this.currentPromise; + this.currentPromise = nextPromise; + await currentPromiseAlias; + return resolver; + } +}; // src/subtitles.ts var cueBlockHeaderRegex = /(?:(.+?)\n)?((?:\d{2}:)?\d{2}:\d{2}.\d{3})\s+-->\s+((?:\d{2}:)?\d{2}:\d{2}.\d{3})/g; @@ -1057,6 +1072,7 @@ var SUBTITLE_CODEC_TO_CONFIGURATION_BOX = { // src/muxer.ts var Muxer = class { constructor(output) { + this.mutex = new AsyncMutex(); this.trackTimestampInfo = /* @__PURE__ */ new WeakMap(); this.output = output; } @@ -1101,82 +1117,16 @@ var Muxer = class { } }; -// src/target.ts -var Target = class { - constructor() { - this.output = null; - } -}; -var ArrayBufferTarget = class extends Target { - constructor() { - super(...arguments); - this.buffer = null; - } - /** @internal */ - _createWriter() { - return new ArrayBufferTargetWriter(this); - } -}; -var StreamTarget = class extends Target { - constructor(options) { - super(); - this.options = options; - if (typeof options !== "object") { - throw new TypeError("StreamTarget requires an options object to be passed to its constructor."); - } - if (options.onData) { - if (typeof options.onData !== "function") { - throw new TypeError("options.onData, when provided, must be a function."); - } - if (options.onData.length < 2) { - throw new TypeError( - "options.onData, when provided, must be a function that takes in at least two arguments (data and position). Ignoring the position argument, which specifies the byte offset at which the data is to be written, can lead to broken outputs." - ); - } - } - if (options.chunked !== void 0 && typeof options.chunked !== "boolean") { - throw new TypeError("options.chunked, when provided, must be a boolean."); - } - if (options.chunkSize !== void 0 && (!Number.isInteger(options.chunkSize) || options.chunkSize <= 0)) { - throw new TypeError("options.chunkSize, when provided, must be a positive integer."); - } - } - /** @internal */ - _createWriter() { - return this.options.chunked ? new ChunkedStreamTargetWriter(this) : new StreamTargetWriter(this); - } -}; -var FileSystemWritableFileStreamTarget = class extends Target { - constructor(stream, options) { - super(); - this.stream = stream; - this.options = options; - if (!(stream instanceof FileSystemWritableFileStream)) { - throw new TypeError("FileSystemWritableFileStreamTarget requires a FileSystemWritableFileStream instance."); - } - if (options !== void 0 && typeof options !== "object") { - throw new TypeError("FileSystemWritableFileStreamTarget's options, when provided, must be an object."); - } - if (options) { - if (options.chunkSize !== void 0 && (!Number.isInteger(options.chunkSize) || options.chunkSize <= 0)) { - throw new TypeError("options.chunkSize, when provided, must be a positive integer"); - } - } - } - /** @internal */ - _createWriter() { - return new FileSystemWritableFileStreamTargetWriter(this); - } -}; - // src/writer.ts -var Writer2 = class { +var Writer = class { constructor() { /** Setting this to true will cause the writer to ensure data is written in a strictly monotonic, streamable way. */ this.ensureMonotonicity = false; } + start() { + } }; -var ArrayBufferTargetWriter = class extends Writer2 { +var ArrayBufferTargetWriter = class extends Writer { constructor(target) { super(); this.pos = 0; @@ -1207,9 +1157,9 @@ var ArrayBufferTargetWriter = class extends Writer2 { getPos() { return this.pos; } - flush() { + async flush() { } - finalize() { + async finalize() { this.ensureSize(this.pos); this.target.buffer = this.buffer.slice(0, Math.max(this.maxPos, this.pos)); } @@ -1217,14 +1167,18 @@ var ArrayBufferTargetWriter = class extends Writer2 { return this.bytes.slice(start, end); } }; -var StreamTargetWriter = class extends Writer2 { +var StreamTargetWriter = class extends Writer { constructor(target) { super(); this.pos = 0; this.sections = []; this.lastFlushEnd = 0; + this.writer = null; this.target = target; } + start() { + this.writer = this.target._writable.getWriter(); + } write(data) { this.sections.push({ data: data.slice(), @@ -1238,7 +1192,8 @@ var StreamTargetWriter = class extends Writer2 { getPos() { return this.pos; } - flush() { + async flush() { + assert(this.writer); if (this.sections.length === 0) return; let chunks = []; let sorted = [...this.sections].sort((a, b) => a.start - b.start); @@ -1268,17 +1223,26 @@ var StreamTargetWriter = class extends Writer2 { if (this.ensureMonotonicity && chunk.start !== this.lastFlushEnd) { throw new Error("Internal error: Monotonicity violation."); } - this.target.options.onData?.(chunk.data, chunk.start); + if (this.writer.desiredSize !== null && this.writer.desiredSize <= 0) { + await this.writer.ready; + } + this.writer.write({ + type: "write", + data: chunk.data, + position: chunk.start + }); this.lastFlushEnd = chunk.start + chunk.data.byteLength; } this.sections.length = 0; } finalize() { + assert(this.writer); + return this.writer.close(); } }; var DEFAULT_CHUNK_SIZE = 2 ** 24; var MAX_CHUNKS_AT_ONCE = 2; -var ChunkedStreamTargetWriter = class extends Writer2 { +var ChunkedStreamTargetWriter = class extends Writer { constructor(target) { super(); this.pos = 0; @@ -1288,15 +1252,20 @@ var ChunkedStreamTargetWriter = class extends Writer2 { */ this.chunks = []; this.lastFlushEnd = 0; + this.writer = null; + this.flushedChunkQueue = []; this.target = target; - this.chunkSize = target.options?.chunkSize ?? DEFAULT_CHUNK_SIZE; + this.chunkSize = target._options?.chunkSize ?? DEFAULT_CHUNK_SIZE; if (!Number.isInteger(this.chunkSize) || this.chunkSize < 2 ** 10) { throw new Error("Invalid StreamTarget options: chunkSize must be an integer not smaller than 1024."); } } + start() { + this.writer = this.target._writable.getWriter(); + } write(data) { this.writeDataIntoChunks(data, this.pos); - this.flushChunks(); + this.queueChunksForFlush(); this.pos += data.byteLength; } seek(newPos) { @@ -1324,7 +1293,7 @@ var ChunkedStreamTargetWriter = class extends Writer2 { for (let i = 0; i < this.chunks.length - 1; i++) { this.chunks[i].shouldFlush = true; } - this.flushChunks(); + this.queueChunksForFlush(); } if (toWrite.byteLength < data.byteLength) { this.writeDataIntoChunks(data.subarray(toWrite.byteLength), position + toWrite.byteLength); @@ -1362,7 +1331,8 @@ var ChunkedStreamTargetWriter = class extends Writer2 { this.chunks.sort((a, b) => a.start - b.start); return this.chunks.indexOf(chunk); } - flushChunks(force = false) { + queueChunksForFlush(force = false) { + assert(this.writer); for (let i = 0; i < this.chunks.length; i++) { let chunk = this.chunks[i]; if (!chunk.shouldFlush && !force) continue; @@ -1370,31 +1340,73 @@ var ChunkedStreamTargetWriter = class extends Writer2 { if (this.ensureMonotonicity && chunk.start + section.start !== this.lastFlushEnd) { throw new Error("Internal error: Monotonicity violation."); } - this.target.options.onData?.( - chunk.data.subarray(section.start, section.end), - chunk.start + section.start - ); + this.flushedChunkQueue.push({ + type: "write", + data: chunk.data.subarray(section.start, section.end), + position: chunk.start + section.start + }); this.lastFlushEnd = chunk.start + section.end; } this.chunks.splice(i--, 1); } } - flush() { + async flush() { + assert(this.writer); + if (this.flushedChunkQueue.length === 0) return; + for (let chunk of this.flushedChunkQueue) { + if (this.writer.desiredSize !== null && this.writer.desiredSize <= 0) { + await this.writer.ready; + } + this.writer.write(chunk); + } + this.flushedChunkQueue.length = 0; } - finalize() { - this.flushChunks(true); + async finalize() { + assert(this.writer); + this.queueChunksForFlush(true); + await this.flush(); + return this.writer.close(); } }; -var FileSystemWritableFileStreamTargetWriter = class extends ChunkedStreamTargetWriter { - constructor(target) { - super(new StreamTarget({ - onData: (data, position) => target.stream.write({ - type: "write", - data, - position - }), - chunkSize: target.options?.chunkSize - })); + +// src/target.ts +var Target = class { + constructor() { + /** @internal */ + this._output = null; + } +}; +var ArrayBufferTarget = class extends Target { + constructor() { + super(...arguments); + this.buffer = null; + } + /** @internal */ + _createWriter() { + return new ArrayBufferTargetWriter(this); + } +}; +var StreamTarget = class extends Target { + constructor(writable, options = {}) { + super(); + if (!(writable instanceof WritableStream)) { + throw new TypeError("StreamTarget requires a WritableStream instance."); + } + if (options != null && typeof options !== "object") { + throw new TypeError("StreamTarget options, when provided, must be an object."); + } + if (options.chunked !== void 0 && typeof options.chunked !== "boolean") { + throw new TypeError("options.chunked, when provided, must be a boolean."); + } + if (options.chunkSize !== void 0 && (!Number.isInteger(options.chunkSize) || options.chunkSize <= 0)) { + throw new TypeError("options.chunkSize, when provided, must be a positive integer."); + } + this._writable = writable; + this._options = options; + } + /** @internal */ + _createWriter() { + return this._options.chunked ? new ChunkedStreamTargetWriter(this) : new StreamTargetWriter(this); } }; @@ -1617,12 +1629,13 @@ var IsobmffMuxer = class extends Muxer { this.nextFragmentNumber = 1; this.writer = output._writer; this.boxWriter = new IsobmffBoxWriter(this.writer); - this.fastStart = format.options.fastStart ?? (this.writer instanceof ArrayBufferTargetWriter ? "in-memory" : false); + this.fastStart = format._options.fastStart ?? (this.writer instanceof ArrayBufferTargetWriter ? "in-memory" : false); if (this.fastStart === "in-memory" || this.fastStart === "fragmented") { this.writer.ensureMonotonicity = true; } } - start() { + async start() { + const release = await this.mutex.acquire(); const holdsAvc = this.output._tracks.some((x) => x.type === "video" && x.source._codec === "avc"); this.boxWriter.writeBox(ftyp({ holdsAvc, @@ -1636,7 +1649,8 @@ var IsobmffMuxer = class extends Muxer { this.mdat = mdat(true); this.boxWriter.writeBox(this.mdat); } - this.writer.flush(); + await this.writer.flush(); + release(); } getVideoTrackData(track, meta) { const existingTrackData = this.trackDatas.find((x) => x.track === track); @@ -1740,32 +1754,47 @@ var IsobmffMuxer = class extends Muxer { this.validateAndNormalizeTimestamp(track, 0, true); return newTrackData; } - addEncodedVideoChunk(track, chunk, meta) { - const trackData = this.getVideoTrackData(track, meta); - let data = new Uint8Array(chunk.byteLength); - chunk.copyTo(data); - let timestamp = this.validateAndNormalizeTimestamp(trackData.track, chunk.timestamp, chunk.type === "key"); - let sample = this.createSampleForTrack(trackData, data, timestamp, (chunk.duration ?? 0) / 1e6, chunk.type); - this.registerSample(trackData, sample); - } - addEncodedAudioChunk(track, chunk, meta) { - const trackData = this.getAudioTrackData(track, meta); - let data = new Uint8Array(chunk.byteLength); - chunk.copyTo(data); - let timestamp = this.validateAndNormalizeTimestamp(trackData.track, chunk.timestamp, chunk.type === "key"); - let sample = this.createSampleForTrack(trackData, data, timestamp, (chunk.duration ?? 0) / 1e6, chunk.type); - this.registerSample(trackData, sample); - } - addSubtitleCue(track, cue, meta) { - const trackData = this.getSubtitleTrackData(track, meta); - this.validateAndNormalizeTimestamp(trackData.track, 1e6 * cue.timestamp, true); - if (track.source._codec === "webvtt") { - trackData.cueQueue.push(cue); - this.processWebVTTCues(trackData, cue.timestamp); - } else { + async addEncodedVideoChunk(track, chunk, meta) { + const release = await this.mutex.acquire(); + try { + const trackData = this.getVideoTrackData(track, meta); + let data = new Uint8Array(chunk.byteLength); + chunk.copyTo(data); + let timestamp = this.validateAndNormalizeTimestamp(trackData.track, chunk.timestamp, chunk.type === "key"); + let sample = this.createSampleForTrack(trackData, data, timestamp, (chunk.duration ?? 0) / 1e6, chunk.type); + await this.registerSample(trackData, sample); + } finally { + release(); } } - processWebVTTCues(trackData, until) { + async addEncodedAudioChunk(track, chunk, meta) { + const release = await this.mutex.acquire(); + try { + const trackData = this.getAudioTrackData(track, meta); + let data = new Uint8Array(chunk.byteLength); + chunk.copyTo(data); + let timestamp = this.validateAndNormalizeTimestamp(trackData.track, chunk.timestamp, chunk.type === "key"); + let sample = this.createSampleForTrack(trackData, data, timestamp, (chunk.duration ?? 0) / 1e6, chunk.type); + await this.registerSample(trackData, sample); + } finally { + release(); + } + } + async addSubtitleCue(track, cue, meta) { + const release = await this.mutex.acquire(); + try { + const trackData = this.getSubtitleTrackData(track, meta); + this.validateAndNormalizeTimestamp(trackData.track, 1e6 * cue.timestamp, true); + if (track.source._codec === "webvtt") { + trackData.cueQueue.push(cue); + await this.processWebVTTCues(trackData, cue.timestamp); + } else { + } + } finally { + release(); + } + } + async processWebVTTCues(trackData, until) { while (trackData.cueQueue.length > 0) { let timestamps = /* @__PURE__ */ new Set([]); for (let cue of trackData.cueQueue) { @@ -1786,7 +1815,7 @@ var IsobmffMuxer = class extends Muxer { this.auxBoxWriter.writeBox(box2); let body2 = this.auxWriter.getSlice(0, this.auxWriter.getPos()); let sample2 = this.createSampleForTrack(trackData, body2, trackData.lastCueEndTimestamp, sampleStart - trackData.lastCueEndTimestamp, "key"); - this.registerSample(trackData, sample2); + await this.registerSample(trackData, sample2); trackData.lastCueEndTimestamp = sampleStart; } this.auxWriter.seek(0); @@ -1815,7 +1844,7 @@ var IsobmffMuxer = class extends Muxer { } let body = this.auxWriter.getSlice(0, this.auxWriter.getPos()); let sample = this.createSampleForTrack(trackData, body, sampleStart, sampleEnd - sampleStart, "key"); - this.registerSample(trackData, sample); + await this.registerSample(trackData, sample); trackData.lastCueEndTimestamp = sampleEnd; } } @@ -1903,15 +1932,15 @@ var IsobmffMuxer = class extends Muxer { } trackData.timestampProcessingQueue.length = 0; } - registerSample(trackData, sample) { + async registerSample(trackData, sample) { if (this.fastStart === "fragmented") { trackData.sampleQueue.push(sample); - this.interleaveSamples(); + await this.interleaveSamples(); } else { - this.addSampleToTrack(trackData, sample); + await this.addSampleToTrack(trackData, sample); } } - addSampleToTrack(trackData, sample) { + async addSampleToTrack(trackData, sample) { if (sample.type === "key") { this.processTimestamps(trackData); } @@ -1933,7 +1962,7 @@ var IsobmffMuxer = class extends Muxer { }); if (currentChunkDuration >= 1 && keyFrameQueuedEverywhere) { beginNewChunk = true; - this.finalizeFragment(); + await this.finalizeFragment(); } } else { beginNewChunk = currentChunkDuration >= 0.5; @@ -1941,7 +1970,7 @@ var IsobmffMuxer = class extends Muxer { } if (beginNewChunk) { if (trackData.currentChunk) { - this.finalizeCurrentChunk(trackData); + await this.finalizeCurrentChunk(trackData); } trackData.currentChunk = { startTimestamp: sample.timestamp, @@ -1954,7 +1983,7 @@ var IsobmffMuxer = class extends Muxer { trackData.currentChunk.samples.push(sample); trackData.timestampProcessingQueue.push(sample); } - finalizeCurrentChunk(trackData) { + async finalizeCurrentChunk(trackData) { assert(this.fastStart !== "fragmented"); if (!trackData.currentChunk) return; trackData.finalizedChunks.push(trackData.currentChunk); @@ -1976,9 +2005,9 @@ var IsobmffMuxer = class extends Muxer { this.writer.write(sample.data); sample.data = null; } - this.writer.flush(); + await this.writer.flush(); } - interleaveSamples() { + async interleaveSamples() { assert(this.fastStart === "fragmented"); for (const track of this.output._tracks) { if (!track.source._closed && !this.trackDatas.some((x) => x.track === track)) { @@ -2002,10 +2031,10 @@ var IsobmffMuxer = class extends Muxer { break; } let sample = trackWithMinTimestamp.sampleQueue.shift(); - this.addSampleToTrack(trackWithMinTimestamp, sample); + await this.addSampleToTrack(trackWithMinTimestamp, sample); } } - finalizeFragment(flushWriter = true) { + async finalizeFragment(flushWriter = true) { assert(this.fastStart === "fragmented"); let fragmentNumber = this.nextFragmentNumber++; if (fragmentNumber === 1) { @@ -2051,39 +2080,42 @@ var IsobmffMuxer = class extends Muxer { trackData.currentChunk = null; } if (flushWriter) { - this.writer.flush(); + await this.writer.flush(); } } - onTrackClose(track) { + async onTrackClose(track) { + const release = await this.mutex.acquire(); if (track.type === "subtitle" && track.source._codec === "webvtt") { let trackData = this.trackDatas.find((x) => x.track === track); if (trackData) { - this.processWebVTTCues(trackData, Infinity); + await this.processWebVTTCues(trackData, Infinity); } } if (this.fastStart === "fragmented") { - this.interleaveSamples(); + await this.interleaveSamples(); } + release(); } /** Finalizes the file, making it ready for use. Must be called after all video and audio chunks have been added. */ - finalize() { + async finalize() { + const release = await this.mutex.acquire(); for (let trackData of this.trackDatas) { if (trackData.type === "subtitle" && trackData.track.source._codec === "webvtt") { - this.processWebVTTCues(trackData, Infinity); + await this.processWebVTTCues(trackData, Infinity); } } if (this.fastStart === "fragmented") { for (let trackData of this.trackDatas) { for (let sample of trackData.sampleQueue) { - this.addSampleToTrack(trackData, sample); + await this.addSampleToTrack(trackData, sample); } this.processTimestamps(trackData); } - this.finalizeFragment(false); + await this.finalizeFragment(false); } else { for (let trackData of this.trackDatas) { this.processTimestamps(trackData); - this.finalizeCurrentChunk(trackData); + await this.finalizeCurrentChunk(trackData); } } if (this.fastStart === "in-memory") { @@ -2142,6 +2174,7 @@ var IsobmffMuxer = class extends Muxer { this.boxWriter.writeBox(movieBox); } } + release(); } }; @@ -2255,7 +2288,7 @@ var MatroskaMuxer = class extends Muxer { this.duration = 0; this.writer = output._writer; this.format = format; - if (this.format.options.streamable) { + if (this.format._options.streamable) { this.writer.ensureMonotonicity = true; } } @@ -2415,14 +2448,16 @@ var MatroskaMuxer = class extends Muxer { throw new Error("WebM only supports video, audio and subtitle tracks. Switching to MKV removes this restriction."); } } - start() { + async start() { + const release = await this.mutex.acquire(); this.writeEBMLHeader(); - if (!this.format.options.streamable) { + if (!this.format._options.streamable) { this.createSeekHead(); } this.createSegmentInfo(); this.createCues(); - this.writer.flush(); + await this.writer.flush(); + release(); } writeEBMLHeader() { let ebmlHeader = { id: 440786851 /* EBML */, data: [ @@ -2467,7 +2502,7 @@ var MatroskaMuxer = class extends Muxer { { id: 2807729 /* TimestampScale */, data: 1e6 }, { id: 19840 /* MuxingApp */, data: APP_NAME }, { id: 22337 /* WritingApp */, data: APP_NAME }, - !this.format.options.streamable ? segmentDuration : null + !this.format._options.streamable ? segmentDuration : null ] }; this.segmentInfo = segmentInfo; } @@ -2520,9 +2555,9 @@ var MatroskaMuxer = class extends Muxer { createSegment() { let segment = { id: 408125543 /* Segment */, - size: this.format.options.streamable ? -1 : SEGMENT_SIZE_BYTES, + size: this.format._options.streamable ? -1 : SEGMENT_SIZE_BYTES, data: [ - !this.format.options.streamable ? this.seekHead : null, + !this.format._options.streamable ? this.seekHead : null, this.segmentInfo, this.tracksElement ] @@ -2606,45 +2641,60 @@ var MatroskaMuxer = class extends Muxer { this.trackDatas.sort((a, b) => a.track.id - b.track.id); return newTrackData; } - addEncodedVideoChunk(track, chunk, meta) { - const trackData = this.getVideoTrackData(track, meta); - let data = new Uint8Array(chunk.byteLength); - chunk.copyTo(data); - let timestamp = this.validateAndNormalizeTimestamp(trackData.track, chunk.timestamp, chunk.type === "key"); - let videoChunk = this.createInternalChunk(data, timestamp, (chunk.duration ?? 0) / 1e6, chunk.type); - if (track.source._codec === "vp9") this.fixVP9ColorSpace(trackData, videoChunk); - trackData.chunkQueue.push(videoChunk); - this.interleaveChunks(); + async addEncodedVideoChunk(track, chunk, meta) { + const release = await this.mutex.acquire(); + try { + const trackData = this.getVideoTrackData(track, meta); + let data = new Uint8Array(chunk.byteLength); + chunk.copyTo(data); + let timestamp = this.validateAndNormalizeTimestamp(trackData.track, chunk.timestamp, chunk.type === "key"); + let videoChunk = this.createInternalChunk(data, timestamp, (chunk.duration ?? 0) / 1e6, chunk.type); + if (track.source._codec === "vp9") this.fixVP9ColorSpace(trackData, videoChunk); + trackData.chunkQueue.push(videoChunk); + await this.interleaveChunks(); + } finally { + release(); + } } - addEncodedAudioChunk(track, chunk, meta) { - const trackData = this.getAudioTrackData(track, meta); - let data = new Uint8Array(chunk.byteLength); - chunk.copyTo(data); - let timestamp = this.validateAndNormalizeTimestamp(trackData.track, chunk.timestamp, chunk.type === "key"); - let audioChunk = this.createInternalChunk(data, timestamp, (chunk.duration ?? 0) / 1e6, chunk.type); - trackData.chunkQueue.push(audioChunk); - this.interleaveChunks(); + async addEncodedAudioChunk(track, chunk, meta) { + const release = await this.mutex.acquire(); + try { + const trackData = this.getAudioTrackData(track, meta); + let data = new Uint8Array(chunk.byteLength); + chunk.copyTo(data); + let timestamp = this.validateAndNormalizeTimestamp(trackData.track, chunk.timestamp, chunk.type === "key"); + let audioChunk = this.createInternalChunk(data, timestamp, (chunk.duration ?? 0) / 1e6, chunk.type); + trackData.chunkQueue.push(audioChunk); + await this.interleaveChunks(); + } finally { + release(); + } } - addSubtitleCue(track, cue, meta) { - const trackData = this.getSubtitleTrackData(track, meta); - const timestamp = this.validateAndNormalizeTimestamp(trackData.track, 1e6 * cue.timestamp, true); - let bodyText = cue.text; - const timestampMs = Math.floor(timestamp * 1e3); - inlineTimestampRegex.lastIndex = 0; - bodyText = bodyText.replace(inlineTimestampRegex, (match) => { - let time = parseSubtitleTimestamp(match.slice(1, -1)); - let offsetTime = time - timestampMs; - return `<${formatSubtitleTimestamp(offsetTime)}>`; - }); - const body = textEncoder.encode(bodyText); - const additions = `${cue.settings ?? ""} + async addSubtitleCue(track, cue, meta) { + const release = await this.mutex.acquire(); + try { + const trackData = this.getSubtitleTrackData(track, meta); + const timestamp = this.validateAndNormalizeTimestamp(trackData.track, 1e6 * cue.timestamp, true); + let bodyText = cue.text; + const timestampMs = Math.floor(timestamp * 1e3); + inlineTimestampRegex.lastIndex = 0; + bodyText = bodyText.replace(inlineTimestampRegex, (match) => { + let time = parseSubtitleTimestamp(match.slice(1, -1)); + let offsetTime = time - timestampMs; + return `<${formatSubtitleTimestamp(offsetTime)}>`; + }); + const body = textEncoder.encode(bodyText); + const additions = `${cue.settings ?? ""} ${cue.identifier ?? ""} ${cue.notes ?? ""}`; - let subtitleChunk = this.createInternalChunk(body, timestamp, cue.duration, "key", additions.trim() ? textEncoder.encode(additions) : null); - trackData.chunkQueue.push(subtitleChunk); - this.interleaveChunks(); + let subtitleChunk = this.createInternalChunk(body, timestamp, cue.duration, "key", additions.trim() ? textEncoder.encode(additions) : null); + trackData.chunkQueue.push(subtitleChunk); + await this.interleaveChunks(); + } finally { + release(); + } } - interleaveChunks() { + async interleaveChunks() { for (const track of this.output._tracks) { if (!track.source._closed && !this.trackDatas.some((x) => x.track === track)) { return; @@ -2669,7 +2719,7 @@ ${cue.notes ?? ""}`; let chunk = trackWithMinTimestamp.chunkQueue.shift(); this.writeBlock(trackWithMinTimestamp, chunk); } - this.writer.flush(); + await this.writer.flush(); } /** Due to [a bug in Chromium](https://bugs.chromium.org/p/chromium/issues/detail?id=1377842), VP9 streams often * lack color space information. This method patches in that information. */ @@ -2778,12 +2828,12 @@ ${cue.notes ?? ""}`; } /** Creates a new Cluster element to contain media chunks. */ createNewCluster(msTimestamp) { - if (this.currentCluster && !this.format.options.streamable) { + if (this.currentCluster && !this.format._options.streamable) { this.finalizeCurrentCluster(); } this.currentCluster = { id: 524531317 /* Cluster */, - size: this.format.options.streamable ? -1 : CLUSTER_SIZE_BYTES, + size: this.format._options.streamable ? -1 : CLUSTER_SIZE_BYTES, data: [ { id: 231 /* Timestamp */, data: msTimestamp } ] @@ -2812,11 +2862,14 @@ ${cue.notes ?? ""}`; }) ] }); } - onTrackClose() { - this.interleaveChunks(); + async onTrackClose() { + const release = await this.mutex.acquire(); + await this.interleaveChunks(); + release(); } /** Finalizes the file, making it ready for use. Must be called after all media chunks have been added. */ - finalize() { + async finalize() { + const release = await this.mutex.acquire(); if (!this.segment) { this.createTracks(); this.createSegment(); @@ -2826,12 +2879,12 @@ ${cue.notes ?? ""}`; this.writeBlock(trackData, trackData.chunkQueue.shift()); } } - if (!this.format.options.streamable && this.currentCluster) { + if (!this.format._options.streamable && this.currentCluster) { this.finalizeCurrentCluster(); } assert(this.cues); this.writeEBML(this.cues); - if (!this.format.options.streamable) { + if (!this.format._options.streamable) { let endPos = this.writer.getPos(); let segmentSize = this.writer.getPos() - this.segmentDataOffset; this.writer.seek(this.offsets.get(this.segment) + 4); @@ -2846,6 +2899,7 @@ ${cue.notes ?? ""}`; this.writeEBML(this.seekHead); this.writer.seek(endPos); } + release(); } }; @@ -2861,7 +2915,7 @@ var Mp4OutputFormat = class extends OutputFormat { throw new TypeError('options.fastStart, when provided, must be false, "in-memory", or "fragmented".'); } super(); - this.options = options; + this._options = options; } /** @internal */ _createMuxer(output) { @@ -2877,7 +2931,7 @@ var MkvOutputFormat2 = class extends OutputFormat { throw new TypeError("options.streamable, when provided, must be a boolean."); } super(); - this.options = options; + this._options = options; } /** @internal */ _createMuxer(output) { @@ -2958,7 +3012,7 @@ var EncodedVideoChunkSource = class extends VideoSource { throw new TypeError("chunk must be an EncodedVideoChunk."); } this._ensureValidDigest(); - this._connectedTrack?.output._muxer.addEncodedVideoChunk(this._connectedTrack, chunk, meta); + return this._connectedTrack.output._muxer.addEncodedVideoChunk(this._connectedTrack, chunk, meta); } }; var KEY_FRAME_INTERVAL = 5; @@ -2981,12 +3035,13 @@ var VideoEncoderWrapper = class { this.source = source; this.codecConfig = codecConfig; this.encoder = null; + this.muxer = null; this.lastMultipleOfKeyFrameInterval = -1; this.lastWidth = null; this.lastHeight = null; validateVideoCodecConfig(codecConfig); } - digest(videoFrame) { + async digest(videoFrame) { this.source._ensureValidDigest(); if (this.lastWidth !== null && this.lastHeight !== null) { if (videoFrame.codedWidth !== this.lastWidth || videoFrame.codedHeight !== this.lastHeight) { @@ -3001,13 +3056,17 @@ var VideoEncoderWrapper = class { const multipleOfKeyFrameInterval = Math.floor(videoFrame.timestamp / 1e6 / KEY_FRAME_INTERVAL); this.encoder.encode(videoFrame, { keyFrame: multipleOfKeyFrameInterval !== this.lastMultipleOfKeyFrameInterval }); this.lastMultipleOfKeyFrameInterval = multipleOfKeyFrameInterval; + if (this.encoder.encodeQueueSize >= 4) { + await new Promise((resolve) => this.encoder.addEventListener("dequeue", resolve, { once: true })); + } + await this.muxer.mutex.currentPromise; } ensureEncoder(videoFrame) { if (this.encoder) { return; } this.encoder = new VideoEncoder({ - output: (chunk, meta) => this.source._connectedTrack?.output._muxer.addEncodedVideoChunk(this.source._connectedTrack, chunk, meta), + output: (chunk, meta) => this.muxer.addEncodedVideoChunk(this.source._connectedTrack, chunk, meta), error: (error) => console.error("Video encode error:", error) }); this.encoder.configure({ @@ -3018,9 +3077,14 @@ var VideoEncoderWrapper = class { framerate: this.source._connectedTrack?.metadata.frameRate, latencyMode: this.codecConfig.latencyMode }); + assert(this.source._connectedTrack); + this.muxer = this.source._connectedTrack.output._muxer; } async flush() { - return this.encoder?.flush(); + if (this.encoder) { + await this.encoder.flush(); + this.encoder.close(); + } } }; var VideoFrameSource = class extends VideoSource { @@ -3032,7 +3096,7 @@ var VideoFrameSource = class extends VideoSource { if (!(videoFrame instanceof VideoFrame)) { throw new TypeError("videoFrame must be a VideoFrame."); } - this._encoder.digest(videoFrame); + return this._encoder.digest(videoFrame); } /** @internal */ _flush() { @@ -3060,8 +3124,9 @@ var CanvasSource = class extends VideoSource { duration: Math.round(1e6 * duration), alpha: "discard" }); - this._encoder.digest(frame); + const promise = this._encoder.digest(frame); frame.close(); + return promise; } /** @internal */ _flush() { @@ -3131,7 +3196,7 @@ var EncodedAudioChunkSource = class extends AudioSource { throw new TypeError("chunk must be an EncodedAudioChunk."); } this._ensureValidDigest(); - this._connectedTrack?.output._muxer.addEncodedAudioChunk(this._connectedTrack, chunk, meta); + return this._connectedTrack.output._muxer.addEncodedAudioChunk(this._connectedTrack, chunk, meta); } }; var validateAudioCodecConfig = (config) => { @@ -3150,11 +3215,12 @@ var AudioEncoderWrapper = class { this.source = source; this.codecConfig = codecConfig; this.encoder = null; + this.muxer = null; this.lastNumberOfChannels = null; this.lastSampleRate = null; validateAudioCodecConfig(codecConfig); } - digest(audioData) { + async digest(audioData) { this.source._ensureValidDigest(); if (this.lastNumberOfChannels !== null && this.lastSampleRate !== null) { if (audioData.numberOfChannels !== this.lastNumberOfChannels || audioData.sampleRate !== this.lastSampleRate) { @@ -3167,13 +3233,17 @@ var AudioEncoderWrapper = class { this.ensureEncoder(audioData); assert(this.encoder); this.encoder.encode(audioData); + if (this.encoder.encodeQueueSize >= 4) { + await new Promise((resolve) => this.encoder.addEventListener("dequeue", resolve, { once: true })); + } + await this.muxer.mutex.currentPromise; } ensureEncoder(audioData) { if (this.encoder) { return; } this.encoder = new AudioEncoder({ - output: (chunk, meta) => this.source._connectedTrack?.output._muxer.addEncodedAudioChunk(this.source._connectedTrack, chunk, meta), + output: (chunk, meta) => this.muxer.addEncodedAudioChunk(this.source._connectedTrack, chunk, meta), error: (error) => console.error("Audio encode error:", error) }); this.encoder.configure({ @@ -3182,9 +3252,14 @@ var AudioEncoderWrapper = class { sampleRate: audioData.sampleRate, bitrate: this.codecConfig.bitrate }); + assert(this.source._connectedTrack); + this.muxer = this.source._connectedTrack.output._muxer; } async flush() { - return this.encoder?.flush(); + if (this.encoder) { + await this.encoder.flush(); + this.encoder.close(); + } } }; var AudioDataSource = class extends AudioSource { @@ -3196,7 +3271,7 @@ var AudioDataSource = class extends AudioSource { if (!(audioData instanceof AudioData)) { throw new TypeError("audioData must be an AudioData."); } - this._encoder.digest(audioData); + return this._encoder.digest(audioData); } /** @internal */ _flush() { @@ -3230,9 +3305,10 @@ var AudioBufferSource = class extends AudioSource { timestamp: Math.round(1e6 * this._accumulatedFrameCount / sampleRate), data }); - this._encoder.digest(audioData); + const promise = this._encoder.digest(audioData); audioData.close(); this._accumulatedFrameCount += numberOfFrames; + return promise; } /** @internal */ _flush() { @@ -3247,6 +3323,7 @@ var MediaStreamAudioTrackSource = class extends AudioSource { super(codecConfig.codec); /** @internal */ this._abortController = null; + /** @internal */ this._offsetTimestamps = true; this._encoder = new AudioEncoderWrapper(this, codecConfig); this._track = track; @@ -3303,6 +3380,7 @@ var TextSubtitleSource = class extends SubtitleSource { } this._ensureValidDigest(); this._parser.parse(text); + return this._connectedTrack.output._muxer.mutex.currentPromise; } }; @@ -3315,6 +3393,8 @@ var Output = class { this._started = false; /** @internal */ this._finalizing = false; + /** @internal */ + this._mutex = new AsyncMutex(); if (!options || typeof options !== "object") { throw new TypeError("options must be an object."); } @@ -3324,10 +3404,10 @@ var Output = class { if (!(options.target instanceof Target)) { throw new TypeError("options.target must be a Target."); } - if (options.target.output) { + if (options.target._output) { throw new Error("Target is already used for another output."); } - options.target.output = this; + options.target._output = this; this._writer = options.target._createWriter(); this._muxer = options.format._createMuxer(this); } @@ -3387,26 +3467,34 @@ var Output = class { this._tracks.push(track); source._connectedTrack = track; } - start() { + async start() { if (this._started) { throw new Error("Output already started."); } this._started = true; - this._muxer.start(); + this._writer.start(); + const release = await this._mutex.acquire(); + await this._muxer.start(); for (const track of this._tracks) { track.source._start(); } + release(); } async finalize() { + if (!this._started) { + throw new Error("Cannot finalize before starting."); + } if (this._finalizing) { throw new Error("Cannot call finalize twice."); } this._finalizing = true; + const release = await this._mutex.acquire(); const promises = this._tracks.map((x) => x.source._flush()); await Promise.all(promises); - this._muxer.finalize(); - this._writer.flush(); - this._writer.finalize(); + await this._muxer.finalize(); + await this._writer.flush(); + await this._writer.finalize(); + release(); } }; export { @@ -3418,7 +3506,6 @@ export { CanvasSource, EncodedAudioChunkSource, EncodedVideoChunkSource, - FileSystemWritableFileStreamTarget, MediaSource, MediaStreamAudioTrackSource, MediaStreamVideoTrackSource, diff --git a/package-lock.json b/package-lock.json index 3d71e49..c778ffd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "MIT", "dependencies": { "@types/dom-mediacapture-transform": "^0.1.10", - "@types/dom-webcodecs": "^0.1.11" + "@types/dom-webcodecs": "^0.1.13" }, "devDependencies": { "@microsoft/api-extractor": "^7.48.0", @@ -563,9 +563,9 @@ } }, "node_modules/@types/dom-webcodecs": { - "version": "0.1.11", - "resolved": "https://registry.npmjs.org/@types/dom-webcodecs/-/dom-webcodecs-0.1.11.tgz", - "integrity": "sha512-yPEZ3z7EohrmOxbk/QTAa0yonMFkNkjnVXqbGb7D4rMr+F1dGQ8ZUFxXkyLLJuiICPejZ0AZE9Rrk9wUCczx4A==" + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/@types/dom-webcodecs/-/dom-webcodecs-0.1.13.tgz", + "integrity": "sha512-O5hkiFIcjjszPIYyUSyvScyvrBoV3NOEEZx/pMlsu44TKzWNkLVBBxnxJz42in5n3QIolYOcBYFCPZZ0h8SkwQ==" }, "node_modules/ajv": { "version": "8.12.0", diff --git a/package.json b/package.json index d4afe29..3182423 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "license": "MIT", "dependencies": { "@types/dom-mediacapture-transform": "^0.1.10", - "@types/dom-webcodecs": "^0.1.11" + "@types/dom-webcodecs": "^0.1.13" }, "devDependencies": { "@microsoft/api-extractor": "^7.48.0", diff --git a/src/index.ts b/src/index.ts index 4282401..c0c505a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,5 @@ export { Output, OutputOptions, VideoTrackMetadata, AudioTrackMetadata, SubtitleTrackMetadata } from './output'; -export { OutputFormat, Mp4OutputFormat, MkvOutputFormat, WebMOutputFormat } from './output-format'; +export { OutputFormat, Mp4OutputFormat, Mp4OutputFormatOptions, MkvOutputFormat, MkvOutputFormatOptions, WebMOutputFormat, WebMOutputFormatOptions as WebmOutputFormatOptions } from './output-format'; export { VIDEO_CODECS, VideoCodec, VideoCodecConfig, AUDIO_CODECS, AudioCodec, AudioCodecConfig, SUBTITLE_CODECS, SubtitleCodec, MediaSource, VideoSource, EncodedVideoChunkSource, VideoFrameSource, CanvasSource, MediaStreamVideoTrackSource, AudioSource, EncodedAudioChunkSource, AudioDataSource, AudioBufferSource, MediaStreamAudioTrackSource, SubtitleSource, TextSubtitleSource } from './source'; -export { Target, ArrayBufferTarget, StreamTarget, FileSystemWritableFileStreamTarget } from './target'; +export { Target, ArrayBufferTarget, StreamTarget, StreamTargetChunk, StreamTargetOptions } from './target'; export { TransformationMatrix } from './misc'; \ No newline at end of file diff --git a/src/isobmff/isobmff-muxer.ts b/src/isobmff/isobmff-muxer.ts index a3b3247..de85c7d 100644 --- a/src/isobmff/isobmff-muxer.ts +++ b/src/isobmff/isobmff-muxer.ts @@ -3,7 +3,7 @@ import { Muxer } from '../muxer'; import { Output, OutputAudioTrack, OutputSubtitleTrack, OutputTrack, OutputVideoTrack } from '../output'; import { ArrayBufferTargetWriter, Writer } from '../writer'; import { assert, last, TransformationMatrix } from '../misc'; -import { Mp4OutputFormat } from '../output-format'; +import { Mp4OutputFormat, Mp4OutputFormatOptions } from '../output-format'; import { inlineTimestampRegex, SubtitleConfig, SubtitleCue, SubtitleMetadata } from '../subtitles'; import { ArrayBufferTarget } from '../target'; import { validateAudioChunkMetadata, validateSubtitleMetadata, validateVideoChunkMetadata } from '../codec'; @@ -88,7 +88,7 @@ export class IsobmffMuxer extends Muxer { private writer: Writer; private boxWriter: IsobmffBoxWriter; - private fastStart: NonNullable; + private fastStart: NonNullable; private auxTarget = new ArrayBufferTarget(); private auxWriter = this.auxTarget._createWriter(); @@ -112,14 +112,16 @@ export class IsobmffMuxer extends Muxer { // If the fastStart option isn't defined, enable in-memory fast start if the target is an ArrayBuffer, as the // memory usage remains identical - this.fastStart = format.options.fastStart ?? (this.writer instanceof ArrayBufferTargetWriter ? 'in-memory' : false); + this.fastStart = format._options.fastStart ?? (this.writer instanceof ArrayBufferTargetWriter ? 'in-memory' : false); if (this.fastStart === 'in-memory' || this.fastStart === 'fragmented') { this.writer.ensureMonotonicity = true; } } - start() { + async start() { + const release = await this.mutex.acquire(); + const holdsAvc = this.output._tracks.some(x => x.type === 'video' && x.source._codec === 'avc'); // Write the header @@ -139,7 +141,9 @@ export class IsobmffMuxer extends Muxer { this.boxWriter.writeBox(this.mdat); } - this.writer.flush(); + await this.writer.flush(); + + release(); } private getVideoTrackData(track: OutputVideoTrack, meta?: EncodedVideoChunkMetadata) { @@ -264,44 +268,62 @@ export class IsobmffMuxer extends Muxer { return newTrackData; } - addEncodedVideoChunk(track: OutputVideoTrack, chunk: EncodedVideoChunk, meta?: EncodedVideoChunkMetadata) { - const trackData = this.getVideoTrackData(track, meta); + async addEncodedVideoChunk(track: OutputVideoTrack, chunk: EncodedVideoChunk, meta?: EncodedVideoChunkMetadata) { + const release = await this.mutex.acquire(); - let data = new Uint8Array(chunk.byteLength); - chunk.copyTo(data); - - let timestamp = this.validateAndNormalizeTimestamp(trackData.track, chunk.timestamp, chunk.type === 'key'); - let sample = this.createSampleForTrack(trackData, data, timestamp, (chunk.duration ?? 0) / 1e6, chunk.type); - - this.registerSample(trackData, sample); - } - - addEncodedAudioChunk(track: OutputAudioTrack, chunk: EncodedAudioChunk, meta?: EncodedAudioChunkMetadata) { - const trackData = this.getAudioTrackData(track, meta); - - let data = new Uint8Array(chunk.byteLength); - chunk.copyTo(data); - - let timestamp = this.validateAndNormalizeTimestamp(trackData.track, chunk.timestamp, chunk.type === 'key'); - let sample = this.createSampleForTrack(trackData, data, timestamp, (chunk.duration ?? 0) / 1e6, chunk.type); - - this.registerSample(trackData, sample); - } - - addSubtitleCue(track: OutputSubtitleTrack, cue: SubtitleCue, meta?: SubtitleMetadata) { - const trackData = this.getSubtitleTrackData(track, meta); - - this.validateAndNormalizeTimestamp(trackData.track, 1e6 * cue.timestamp, true); - - if (track.source._codec === 'webvtt') { - trackData.cueQueue.push(cue); - this.processWebVTTCues(trackData, cue.timestamp); - } else { - // TODO + try { + const trackData = this.getVideoTrackData(track, meta); + + let data = new Uint8Array(chunk.byteLength); + chunk.copyTo(data); + + let timestamp = this.validateAndNormalizeTimestamp(trackData.track, chunk.timestamp, chunk.type === 'key'); + let sample = this.createSampleForTrack(trackData, data, timestamp, (chunk.duration ?? 0) / 1e6, chunk.type); + + await this.registerSample(trackData, sample); + } finally { + release(); } } - private processWebVTTCues(trackData: IsobmffSubtitleTrackData, until: number) { + async addEncodedAudioChunk(track: OutputAudioTrack, chunk: EncodedAudioChunk, meta?: EncodedAudioChunkMetadata) { + const release = await this.mutex.acquire(); + + try { + const trackData = this.getAudioTrackData(track, meta); + + let data = new Uint8Array(chunk.byteLength); + chunk.copyTo(data); + + let timestamp = this.validateAndNormalizeTimestamp(trackData.track, chunk.timestamp, chunk.type === 'key'); + let sample = this.createSampleForTrack(trackData, data, timestamp, (chunk.duration ?? 0) / 1e6, chunk.type); + + await this.registerSample(trackData, sample); + } finally { + release(); + } + } + + async addSubtitleCue(track: OutputSubtitleTrack, cue: SubtitleCue, meta?: SubtitleMetadata) { + const release = await this.mutex.acquire(); + + try { + const trackData = this.getSubtitleTrackData(track, meta); + + this.validateAndNormalizeTimestamp(trackData.track, 1e6 * cue.timestamp, true); + + if (track.source._codec === 'webvtt') { + trackData.cueQueue.push(cue); + await this.processWebVTTCues(trackData, cue.timestamp); + } else { + // TODO + } + } finally { + release(); + } + } + + private async processWebVTTCues(trackData: IsobmffSubtitleTrackData, until: number) { // WebVTT cues need to undergo special processing as empty sections need to be padded out with samples, and // overlapping samples require special logic. The algorithm produces the format specified in ISO 14496-30. @@ -334,7 +356,7 @@ export class IsobmffMuxer extends Muxer { let body = this.auxWriter.getSlice(0, this.auxWriter.getPos()); let sample = this.createSampleForTrack(trackData, body, trackData.lastCueEndTimestamp, sampleStart - trackData.lastCueEndTimestamp, 'key'); - this.registerSample(trackData, sample); + await this.registerSample(trackData, sample); trackData.lastCueEndTimestamp = sampleStart; } @@ -377,7 +399,7 @@ export class IsobmffMuxer extends Muxer { let body = this.auxWriter.getSlice(0, this.auxWriter.getPos()); let sample = this.createSampleForTrack(trackData, body, sampleStart, sampleEnd - sampleStart, 'key'); - this.registerSample(trackData, sample); + await this.registerSample(trackData, sample); trackData.lastCueEndTimestamp = sampleEnd; } } @@ -501,16 +523,16 @@ export class IsobmffMuxer extends Muxer { trackData.timestampProcessingQueue.length = 0; } - private registerSample(trackData: IsobmffTrackData, sample: Sample) { + private async registerSample(trackData: IsobmffTrackData, sample: Sample) { if (this.fastStart === 'fragmented') { trackData.sampleQueue.push(sample); - this.interleaveSamples(); + await this.interleaveSamples(); } else { - this.addSampleToTrack(trackData, sample); + await this.addSampleToTrack(trackData, sample); } } - private addSampleToTrack(trackData: IsobmffTrackData, sample: Sample) { + private async addSampleToTrack(trackData: IsobmffTrackData, sample: Sample) { if (sample.type === 'key') { this.processTimestamps(trackData); } @@ -539,7 +561,7 @@ export class IsobmffMuxer extends Muxer { if (currentChunkDuration >= 1.0 && keyFrameQueuedEverywhere) { beginNewChunk = true; - this.finalizeFragment(); + await this.finalizeFragment(); } } else { beginNewChunk = currentChunkDuration >= 0.5; // Chunk is long enough, we need a new one @@ -548,7 +570,7 @@ export class IsobmffMuxer extends Muxer { if (beginNewChunk) { if (trackData.currentChunk) { - this.finalizeCurrentChunk(trackData); + await this.finalizeCurrentChunk(trackData); } trackData.currentChunk = { @@ -564,7 +586,7 @@ export class IsobmffMuxer extends Muxer { trackData.timestampProcessingQueue.push(sample); } - private finalizeCurrentChunk(trackData: IsobmffTrackData) { + private async finalizeCurrentChunk(trackData: IsobmffTrackData) { assert(this.fastStart !== 'fragmented'); if (!trackData.currentChunk) return; @@ -595,10 +617,10 @@ export class IsobmffMuxer extends Muxer { sample.data = null; // Can be GC'd } - this.writer.flush(); + await this.writer.flush(); } - private interleaveSamples() { + private async interleaveSamples() { assert(this.fastStart === 'fragmented'); for (const track of this.output._tracks) { @@ -628,11 +650,11 @@ export class IsobmffMuxer extends Muxer { } let sample = trackWithMinTimestamp.sampleQueue.shift()!; - this.addSampleToTrack(trackWithMinTimestamp, sample); + await this.addSampleToTrack(trackWithMinTimestamp, sample); } } - private finalizeFragment(flushWriter = true) { + private async finalizeFragment(flushWriter = true) { assert(this.fastStart === 'fragmented'); let fragmentNumber = this.nextFragmentNumber++; @@ -697,46 +719,52 @@ export class IsobmffMuxer extends Muxer { } if (flushWriter) { - this.writer.flush(); + await this.writer.flush(); } } - override onTrackClose(track: OutputTrack) { + override async onTrackClose(track: OutputTrack) { + const release = await this.mutex.acquire(); + if (track.type === 'subtitle' && track.source._codec === 'webvtt') { let trackData = this.trackDatas.find(x => x.track === track) as IsobmffSubtitleTrackData; if (trackData) { - this.processWebVTTCues(trackData, Infinity); + await this.processWebVTTCues(trackData, Infinity); } } if (this.fastStart === 'fragmented') { // Since a track is now closed, we may be able to write out chunks that were previously waiting - this.interleaveSamples(); + await this.interleaveSamples(); } + + release(); } /** Finalizes the file, making it ready for use. Must be called after all video and audio chunks have been added. */ - finalize() { + async finalize() { + const release = await this.mutex.acquire(); + for (let trackData of this.trackDatas) { if (trackData.type === 'subtitle' && trackData.track.source._codec === 'webvtt') { - this.processWebVTTCues(trackData, Infinity); + await this.processWebVTTCues(trackData, Infinity); } } if (this.fastStart === 'fragmented') { for (let trackData of this.trackDatas) { for (let sample of trackData.sampleQueue) { - this.addSampleToTrack(trackData, sample); + await this.addSampleToTrack(trackData, sample); } this.processTimestamps(trackData); } - this.finalizeFragment(false); // Don't flush the last fragment as we will flush it with the mfra box soon + await this.finalizeFragment(false); // Don't flush the last fragment as we will flush it with the mfra box soon } else { for (let trackData of this.trackDatas) { this.processTimestamps(trackData); - this.finalizeCurrentChunk(trackData); + await this.finalizeCurrentChunk(trackData); } } @@ -817,5 +845,7 @@ export class IsobmffMuxer extends Muxer { this.boxWriter.writeBox(movieBox); } } + + release(); } } diff --git a/src/matroska/matroska-muxer.ts b/src/matroska/matroska-muxer.ts index 8dd87b3..5750129 100644 --- a/src/matroska/matroska-muxer.ts +++ b/src/matroska/matroska-muxer.ts @@ -123,7 +123,7 @@ export class MatroskaMuxer extends Muxer { this.writer = output._writer; this.format = format; - if (this.format.options.streamable) { + if (this.format._options.streamable) { this.writer.ensureMonotonicity = true; } } @@ -314,17 +314,21 @@ export class MatroskaMuxer extends Muxer { } } - start() { + async start() { + const release = await this.mutex.acquire(); + this.writeEBMLHeader(); - if (!this.format.options.streamable) { + if (!this.format._options.streamable) { this.createSeekHead(); } this.createSegmentInfo(); this.createCues(); - this.writer.flush(); + await this.writer.flush(); + + release(); } private writeEBMLHeader() { @@ -374,7 +378,7 @@ export class MatroskaMuxer extends Muxer { { id: EBMLId.TimestampScale, data: 1e6 }, { id: EBMLId.MuxingApp, data: APP_NAME }, { id: EBMLId.WritingApp, data: APP_NAME }, - !this.format.options.streamable ? segmentDuration : null + !this.format._options.streamable ? segmentDuration : null ] }; this.segmentInfo = segmentInfo; } @@ -432,9 +436,9 @@ export class MatroskaMuxer extends Muxer { private createSegment() { let segment: EBML = { id: EBMLId.Segment, - size: this.format.options.streamable ? -1 : SEGMENT_SIZE_BYTES, + size: this.format._options.streamable ? -1 : SEGMENT_SIZE_BYTES, data: [ - !this.format.options.streamable ? this.seekHead as EBML : null, + !this.format._options.streamable ? this.seekHead as EBML : null, this.segmentInfo, this.tracksElement ] @@ -547,60 +551,78 @@ export class MatroskaMuxer extends Muxer { return newTrackData; } - addEncodedVideoChunk(track: OutputVideoTrack, chunk: EncodedVideoChunk, meta?: EncodedVideoChunkMetadata) { - const trackData = this.getVideoTrackData(track, meta); + async addEncodedVideoChunk(track: OutputVideoTrack, chunk: EncodedVideoChunk, meta?: EncodedVideoChunkMetadata) { + const release = await this.mutex.acquire(); - let data = new Uint8Array(chunk.byteLength); - chunk.copyTo(data); - - let timestamp = this.validateAndNormalizeTimestamp(trackData.track, chunk.timestamp, chunk.type === 'key'); - let videoChunk = this.createInternalChunk(data, timestamp, (chunk.duration ?? 0) / 1e6, chunk.type); - if (track.source._codec === 'vp9') this.fixVP9ColorSpace(trackData, videoChunk); - - trackData.chunkQueue.push(videoChunk); - this.interleaveChunks(); + try { + const trackData = this.getVideoTrackData(track, meta); + + let data = new Uint8Array(chunk.byteLength); + chunk.copyTo(data); + + let timestamp = this.validateAndNormalizeTimestamp(trackData.track, chunk.timestamp, chunk.type === 'key'); + let videoChunk = this.createInternalChunk(data, timestamp, (chunk.duration ?? 0) / 1e6, chunk.type); + if (track.source._codec === 'vp9') this.fixVP9ColorSpace(trackData, videoChunk); + + trackData.chunkQueue.push(videoChunk); + await this.interleaveChunks(); + } finally { + release(); + } } - addEncodedAudioChunk(track: OutputAudioTrack, chunk: EncodedAudioChunk, meta?: EncodedAudioChunkMetadata) { - const trackData = this.getAudioTrackData(track, meta); + async addEncodedAudioChunk(track: OutputAudioTrack, chunk: EncodedAudioChunk, meta?: EncodedAudioChunkMetadata) { + const release = await this.mutex.acquire(); - let data = new Uint8Array(chunk.byteLength); - chunk.copyTo(data); - - let timestamp = this.validateAndNormalizeTimestamp(trackData.track, chunk.timestamp, chunk.type === 'key'); - let audioChunk = this.createInternalChunk(data, timestamp, (chunk.duration ?? 0) / 1e6, chunk.type); - - trackData.chunkQueue.push(audioChunk); - this.interleaveChunks(); + try { + const trackData = this.getAudioTrackData(track, meta); + + let data = new Uint8Array(chunk.byteLength); + chunk.copyTo(data); + + let timestamp = this.validateAndNormalizeTimestamp(trackData.track, chunk.timestamp, chunk.type === 'key'); + let audioChunk = this.createInternalChunk(data, timestamp, (chunk.duration ?? 0) / 1e6, chunk.type); + + trackData.chunkQueue.push(audioChunk); + await this.interleaveChunks(); + } finally { + release(); + } } - addSubtitleCue(track: OutputSubtitleTrack, cue: SubtitleCue, meta?: SubtitleMetadata) { - const trackData = this.getSubtitleTrackData(track, meta); + async addSubtitleCue(track: OutputSubtitleTrack, cue: SubtitleCue, meta?: SubtitleMetadata) { + const release = await this.mutex.acquire(); - const timestamp = this.validateAndNormalizeTimestamp(trackData.track, 1e6 * cue.timestamp, true); - - let bodyText = cue.text; - const timestampMs = Math.floor(timestamp * 1000); - - // Replace in-body timestamps so that they're relative to the cue start time - inlineTimestampRegex.lastIndex = 0; - bodyText = bodyText.replace(inlineTimestampRegex, (match) => { - let time = parseSubtitleTimestamp(match.slice(1, -1)); - let offsetTime = time - timestampMs; - - return `<${formatSubtitleTimestamp(offsetTime)}>`; - }); - - const body = textEncoder.encode(bodyText); - const additions = `${cue.settings ?? ''}\n${cue.identifier ?? ''}\n${cue.notes ?? ''}`; - - let subtitleChunk = this.createInternalChunk(body, timestamp, cue.duration, 'key', additions.trim() ? textEncoder.encode(additions) : null); - - trackData.chunkQueue.push(subtitleChunk); - this.interleaveChunks(); + try { + const trackData = this.getSubtitleTrackData(track, meta); + + const timestamp = this.validateAndNormalizeTimestamp(trackData.track, 1e6 * cue.timestamp, true); + + let bodyText = cue.text; + const timestampMs = Math.floor(timestamp * 1000); + + // Replace in-body timestamps so that they're relative to the cue start time + inlineTimestampRegex.lastIndex = 0; + bodyText = bodyText.replace(inlineTimestampRegex, (match) => { + let time = parseSubtitleTimestamp(match.slice(1, -1)); + let offsetTime = time - timestampMs; + + return `<${formatSubtitleTimestamp(offsetTime)}>`; + }); + + const body = textEncoder.encode(bodyText); + const additions = `${cue.settings ?? ''}\n${cue.identifier ?? ''}\n${cue.notes ?? ''}`; + + let subtitleChunk = this.createInternalChunk(body, timestamp, cue.duration, 'key', additions.trim() ? textEncoder.encode(additions) : null); + + trackData.chunkQueue.push(subtitleChunk); + await this.interleaveChunks(); + } finally { + release(); + } } - private interleaveChunks() { + private async interleaveChunks() { for (const track of this.output._tracks) { if (!track.source._closed && !this.trackDatas.some(x => x.track === track)) { return; // We haven't seen a sample from this open track yet @@ -631,7 +653,7 @@ export class MatroskaMuxer extends Muxer { this.writeBlock(trackWithMinTimestamp, chunk); } - this.writer.flush(); + await this.writer.flush(); } /** Due to [a bug in Chromium](https://bugs.chromium.org/p/chromium/issues/detail?id=1377842), VP9 streams often @@ -778,7 +800,7 @@ export class MatroskaMuxer extends Muxer { /** Creates a new Cluster element to contain media chunks. */ private createNewCluster(msTimestamp: number) { - if (this.currentCluster && !this.format.options.streamable) { + if (this.currentCluster && !this.format._options.streamable) { this.finalizeCurrentCluster(); } @@ -790,7 +812,7 @@ export class MatroskaMuxer extends Muxer { this.currentCluster = { id: EBMLId.Cluster, - size: this.format.options.streamable ? -1 : CLUSTER_SIZE_BYTES, + size: this.format._options.streamable ? -1 : CLUSTER_SIZE_BYTES, data: [ { id: EBMLId.Timestamp, data: msTimestamp } ] @@ -836,13 +858,19 @@ export class MatroskaMuxer extends Muxer { ] }); } - override onTrackClose() { + override async onTrackClose() { + const release = await this.mutex.acquire(); + // Since a track is now closed, we may be able to write out chunks that were previously waiting - this.interleaveChunks(); + await this.interleaveChunks(); + + release(); } /** Finalizes the file, making it ready for use. Must be called after all media chunks have been added. */ - finalize() { + async finalize() { + const release = await this.mutex.acquire(); + if (!this.segment) { this.createTracks(); this.createSegment(); @@ -855,14 +883,14 @@ export class MatroskaMuxer extends Muxer { } } - if (!this.format.options.streamable && this.currentCluster) { + if (!this.format._options.streamable && this.currentCluster) { this.finalizeCurrentCluster(); } assert(this.cues); this.writeEBML(this.cues); - if (!this.format.options.streamable) { + if (!this.format._options.streamable) { let endPos = this.writer.getPos(); // Write the Segment size @@ -888,5 +916,7 @@ export class MatroskaMuxer extends Muxer { this.writer.seek(endPos); } + + release(); } } \ No newline at end of file diff --git a/src/misc.ts b/src/misc.ts index 890767e..a1c8ecd 100644 --- a/src/misc.ts +++ b/src/misc.ts @@ -79,4 +79,22 @@ export const colorSpaceIsComplete = (colorSpace: VideoColorSpaceInit | undefined export const isAllowSharedBufferSource = (x: unknown) => { // Quite a mouthful: return x instanceof ArrayBuffer || (typeof SharedArrayBuffer !== 'undefined' && x instanceof SharedArrayBuffer) || (ArrayBuffer.isView(x) && !(x instanceof DataView)); -}; \ No newline at end of file +}; + +export class AsyncMutex { + currentPromise = Promise.resolve(); + + async acquire() { + let resolver: () => void; + let nextPromise = new Promise(resolve => { + resolver = resolve; + }); + + let currentPromiseAlias = this.currentPromise; + this.currentPromise = nextPromise; + + await currentPromiseAlias; + + return resolver!; + } +} \ No newline at end of file diff --git a/src/muxer.ts b/src/muxer.ts index 8dd874f..70fc37c 100644 --- a/src/muxer.ts +++ b/src/muxer.ts @@ -1,18 +1,20 @@ +import { AsyncMutex } from "./misc"; import { Output, OutputAudioTrack, OutputSubtitleTrack, OutputTrack, OutputVideoTrack } from "./output"; import { SubtitleCue, SubtitleMetadata } from "./subtitles"; export abstract class Muxer { output: Output; + mutex = new AsyncMutex(); constructor(output: Output) { this.output = output; } - abstract start(): void; - abstract addEncodedVideoChunk(track: OutputVideoTrack, chunk: EncodedVideoChunk, meta?: EncodedVideoChunkMetadata): void; - abstract addEncodedAudioChunk(track: OutputAudioTrack, chunk: EncodedAudioChunk, meta?: EncodedAudioChunkMetadata): void; - abstract addSubtitleCue(track: OutputSubtitleTrack, cue: SubtitleCue, meta?: SubtitleMetadata): void; - abstract finalize(): void; + abstract start(): Promise; + abstract addEncodedVideoChunk(track: OutputVideoTrack, chunk: EncodedVideoChunk, meta?: EncodedVideoChunkMetadata): Promise; + abstract addEncodedAudioChunk(track: OutputAudioTrack, chunk: EncodedAudioChunk, meta?: EncodedAudioChunkMetadata): Promise; + abstract addSubtitleCue(track: OutputSubtitleTrack, cue: SubtitleCue, meta?: SubtitleMetadata): Promise; + abstract finalize(): Promise; beforeTrackAdd(track: OutputTrack) {} onTrackClose(track: OutputTrack) {} diff --git a/src/output-format.ts b/src/output-format.ts index 3d08dfe..957f1b7 100644 --- a/src/output-format.ts +++ b/src/output-format.ts @@ -9,11 +9,17 @@ export abstract class OutputFormat { abstract _createMuxer(output: Output): Muxer; } +/** @public */ +export type Mp4OutputFormatOptions = { + fastStart?: false | 'in-memory' | 'fragmented' +}; + /** @public */ export class Mp4OutputFormat extends OutputFormat { - constructor(public options: { - fastStart?: false | 'in-memory' | 'fragmented', - } = {}) { + /** @internal */ + _options: Mp4OutputFormatOptions; + + constructor(options: Mp4OutputFormatOptions = {}) { if (!options || typeof options !== 'object') { throw new TypeError('options must be an object.'); } @@ -22,6 +28,8 @@ export class Mp4OutputFormat extends OutputFormat { } super(); + + this._options = options; } /** @internal */ @@ -30,11 +38,17 @@ export class Mp4OutputFormat extends OutputFormat { } } +/** @public */ +export type MkvOutputFormatOptions = { + streamable?: boolean +}; + /** @public */ export class MkvOutputFormat extends OutputFormat { - constructor(public options: { - streamable?: boolean - } = {}) { + /** @internal */ + _options: MkvOutputFormatOptions; + + constructor(options: MkvOutputFormatOptions = {}) { if (!options || typeof options !== 'object') { throw new TypeError('options must be an object.'); } @@ -43,6 +57,8 @@ export class MkvOutputFormat extends OutputFormat { } super(); + + this._options = options; } /** @internal */ @@ -51,5 +67,8 @@ export class MkvOutputFormat extends OutputFormat { } } +/** @public */ +export type WebMOutputFormatOptions = MkvOutputFormatOptions; + /** @public */ export class WebMOutputFormat extends MkvOutputFormat {} \ No newline at end of file diff --git a/src/output.ts b/src/output.ts index 34fc415..106d6ae 100644 --- a/src/output.ts +++ b/src/output.ts @@ -1,4 +1,4 @@ -import { TransformationMatrix } from "./misc"; +import { AsyncMutex, TransformationMatrix } from "./misc"; import { Muxer } from "./muxer"; import { OutputFormat } from "./output-format"; import { AudioSource, MediaSource, SubtitleSource, VideoSource } from "./source"; @@ -54,6 +54,8 @@ export class Output { _started = false; /** @internal */ _finalizing = false; + /** @internal */ + _mutex = new AsyncMutex(); constructor(options: OutputOptions) { if (!options || typeof options !== 'object') { @@ -66,10 +68,10 @@ export class Output { throw new TypeError('options.target must be a Target.'); } - if (options.target.output) { + if (options.target._output) { throw new Error('Target is already used for another output.'); } - options.target.output = this; + options.target._output = this; this._writer = options.target._createWriter(); this._muxer = options.format._createMuxer(this); @@ -147,31 +149,44 @@ export class Output { source._connectedTrack = track; } - start() { + async start() { if (this._started) { throw new Error('Output already started.'); } this._started = true; - this._muxer.start(); + this._writer.start(); + + const release = await this._mutex.acquire(); + + await this._muxer.start(); for (const track of this._tracks) { track.source._start(); } + + release(); } async finalize() { + if (!this._started) { + throw new Error('Cannot finalize before starting.'); + } if (this._finalizing) { throw new Error('Cannot call finalize twice.'); } this._finalizing = true; + const release = await this._mutex.acquire(); + const promises = this._tracks.map(x => x.source._flush()); await Promise.all(promises); - this._muxer.finalize(); + await this._muxer.finalize(); - this._writer.flush(); - this._writer.finalize(); + await this._writer.flush(); + await this._writer.finalize(); + + release(); } } \ No newline at end of file diff --git a/src/source.ts b/src/source.ts index 4cd16f8..eecc5d3 100644 --- a/src/source.ts +++ b/src/source.ts @@ -1,5 +1,6 @@ import { buildAudioCodecString, buildVideoCodecString } from "./codec"; import { assert } from "./misc"; +import { Muxer } from "./muxer"; import { OutputAudioTrack, OutputSubtitleTrack, OutputTrack, OutputVideoTrack } from "./output"; import { SubtitleParser } from "./subtitles"; @@ -104,7 +105,7 @@ export class EncodedVideoChunkSource extends VideoSource { } this._ensureValidDigest(); - this._connectedTrack?.output._muxer.addEncodedVideoChunk(this._connectedTrack, chunk, meta); + return this._connectedTrack!.output._muxer.addEncodedVideoChunk(this._connectedTrack!, chunk, meta); } } @@ -134,6 +135,7 @@ const validateVideoCodecConfig = (config: VideoCodecConfig) => { class VideoEncoderWrapper { private encoder: VideoEncoder | null = null; + private muxer: Muxer | null = null; private lastMultipleOfKeyFrameInterval = -1; private lastWidth: number | null = null; private lastHeight: number | null = null; @@ -142,7 +144,7 @@ class VideoEncoderWrapper { validateVideoCodecConfig(codecConfig); } - digest(videoFrame: VideoFrame) { + async digest(videoFrame: VideoFrame) { this.source._ensureValidDigest(); // Ensure video frame size remains constant @@ -166,6 +168,13 @@ class VideoEncoderWrapper { this.encoder.encode(videoFrame, { keyFrame: multipleOfKeyFrameInterval !== this.lastMultipleOfKeyFrameInterval }); this.lastMultipleOfKeyFrameInterval = multipleOfKeyFrameInterval; + + // We need to do this after sending the frame to the encoder as the frame otherwise might be closed + if (this.encoder.encodeQueueSize >= 4) { + await new Promise(resolve => this.encoder!.addEventListener('dequeue', resolve, { once: true })); + } + + await this.muxer!.mutex.currentPromise; // Allow the writer to apply backpressure } private ensureEncoder(videoFrame: VideoFrame) { @@ -174,7 +183,7 @@ class VideoEncoderWrapper { } this.encoder = new VideoEncoder({ - output: (chunk, meta) => this.source._connectedTrack?.output._muxer.addEncodedVideoChunk(this.source._connectedTrack, chunk, meta), + output: (chunk, meta) => this.muxer!.addEncodedVideoChunk(this.source._connectedTrack!, chunk, meta), error: (error) => console.error('Video encode error:', error), }); @@ -186,10 +195,16 @@ class VideoEncoderWrapper { framerate: this.source._connectedTrack?.metadata.frameRate, latencyMode: this.codecConfig.latencyMode, }); + + assert(this.source._connectedTrack); + this.muxer = this.source._connectedTrack.output._muxer; } async flush() { - return this.encoder?.flush(); + if (this.encoder) { + await this.encoder.flush(); + this.encoder.close(); + } } } @@ -208,7 +223,7 @@ export class VideoFrameSource extends VideoSource { throw new TypeError('videoFrame must be a VideoFrame.'); } - this._encoder.digest(videoFrame); + return this._encoder.digest(videoFrame); } /** @internal */ @@ -248,8 +263,10 @@ export class CanvasSource extends VideoSource { alpha: 'discard', }); - this._encoder.digest(frame); + const promise = this._encoder.digest(frame); frame.close(); + + return promise; } /** @internal */ @@ -350,7 +367,7 @@ export class EncodedAudioChunkSource extends AudioSource { } this._ensureValidDigest(); - this._connectedTrack?.output._muxer.addEncodedAudioChunk(this._connectedTrack, chunk, meta); + return this._connectedTrack!.output._muxer.addEncodedAudioChunk(this._connectedTrack!, chunk, meta); } } /** @public */ @@ -373,6 +390,7 @@ const validateAudioCodecConfig = (config: AudioCodecConfig) => { class AudioEncoderWrapper { private encoder: AudioEncoder | null = null; + private muxer: Muxer | null = null; private lastNumberOfChannels: number | null = null; private lastSampleRate: number | null = null; @@ -380,7 +398,7 @@ class AudioEncoderWrapper { validateAudioCodecConfig(codecConfig); } - digest(audioData: AudioData) { + async digest(audioData: AudioData) { this.source._ensureValidDigest(); // Ensure audio parameters remain constant @@ -397,6 +415,12 @@ class AudioEncoderWrapper { assert(this.encoder); this.encoder.encode(audioData); + + if (this.encoder.encodeQueueSize >= 4) { + await new Promise(resolve => this.encoder!.addEventListener('dequeue', resolve, { once: true })); + } + + await this.muxer!.mutex.currentPromise; // Allow the writer to apply backpressure } private ensureEncoder(audioData: AudioData) { @@ -405,7 +429,7 @@ class AudioEncoderWrapper { } this.encoder = new AudioEncoder({ - output: (chunk, meta) => this.source._connectedTrack?.output._muxer.addEncodedAudioChunk(this.source._connectedTrack, chunk, meta), + output: (chunk, meta) => this.muxer!.addEncodedAudioChunk(this.source._connectedTrack!, chunk, meta), error: (error) => console.error('Audio encode error:', error), }); @@ -415,10 +439,16 @@ class AudioEncoderWrapper { sampleRate: audioData.sampleRate, bitrate: this.codecConfig.bitrate, }); + + assert(this.source._connectedTrack); + this.muxer = this.source._connectedTrack.output._muxer; } async flush() { - return this.encoder?.flush(); + if (this.encoder) { + await this.encoder.flush(); + this.encoder.close(); + } } } @@ -437,7 +467,7 @@ export class AudioDataSource extends AudioSource { throw new TypeError('audioData must be an AudioData.'); } - this._encoder.digest(audioData); + return this._encoder.digest(audioData); } /** @internal */ @@ -483,10 +513,12 @@ export class AudioBufferSource extends AudioSource { data: data }); - this._encoder.digest(audioData); + const promise = this._encoder.digest(audioData); audioData.close(); this._accumulatedFrameCount += numberOfFrames; + + return promise; } /** @internal */ @@ -504,6 +536,7 @@ export class MediaStreamAudioTrackSource extends AudioSource { /** @internal */ private _track: MediaStreamAudioTrack; + /** @internal */ override _offsetTimestamps = true; constructor(track: MediaStreamAudioTrack, codecConfig: AudioCodecConfig) { @@ -590,5 +623,7 @@ export class TextSubtitleSource extends SubtitleSource { this._ensureValidDigest(); this._parser.parse(text); + + return this._connectedTrack!.output._muxer.mutex.currentPromise; } } \ No newline at end of file diff --git a/src/target.ts b/src/target.ts index 022cf39..136aa49 100644 --- a/src/target.ts +++ b/src/target.ts @@ -1,15 +1,17 @@ import { Output } from "./output"; -import { ArrayBufferTargetWriter, ChunkedStreamTargetWriter, FileSystemWritableFileStreamTargetWriter, StreamTargetWriter, Writer } from "./writer"; +import { ArrayBufferTargetWriter, ChunkedStreamTargetWriter, StreamTargetWriter, Writer } from "./writer"; /** @public */ export abstract class Target { - output: Output | null = null; + /** @internal */ + _output: Output | null = null; /** @internal */ abstract _createWriter(): Writer; } /** @public */ +// TODO: Switch to Uint8ArrayTarget for efficiency? export class ArrayBufferTarget extends Target { buffer: ArrayBuffer | null = null; @@ -19,31 +21,37 @@ export class ArrayBufferTarget extends Target { } } +/** @public */ +export type StreamTargetChunk = { + type: 'write', // This ensures automatic compatibility with FileSystemWritableFileStream + data: Uint8Array, + position: number +}; + +/** @public */ +export type StreamTargetOptions = { + chunked?: boolean, + chunkSize?: number +}; + /** @public */ export class StreamTarget extends Target { - constructor(public options: { - onData?: (data: Uint8Array, position: number) => void, - chunked?: boolean, - chunkSize?: number - }) { + /** @internal */ + _writable: WritableStream; + /** @internal */ + _options: StreamTargetOptions; + + constructor( + writable: WritableStream, + options: StreamTargetOptions = {} + ) { super(); - if (typeof options !== 'object') { - throw new TypeError('StreamTarget requires an options object to be passed to its constructor.'); + if (!(writable instanceof WritableStream)) { + throw new TypeError('StreamTarget requires a WritableStream instance.'); } - if (options.onData) { - if (typeof options.onData !== 'function') { - throw new TypeError('options.onData, when provided, must be a function.'); - } - if (options.onData.length < 2) { - // Checking the amount of parameters here is an important validation step as it catches a common error - // where people do not respect the position argument. - throw new TypeError( - 'options.onData, when provided, must be a function that takes in at least two arguments (data and ' - + 'position). Ignoring the position argument, which specifies the byte offset at which the data is ' - + 'to be written, can lead to broken outputs.' - ); - } + if (options != null && typeof options !== 'object') { + throw new TypeError('StreamTarget options, when provided, must be an object.'); } if (options.chunked !== undefined && typeof options.chunked !== 'boolean') { throw new TypeError('options.chunked, when provided, must be a boolean.'); @@ -51,37 +59,13 @@ export class StreamTarget extends Target { if (options.chunkSize !== undefined && (!Number.isInteger(options.chunkSize) || options.chunkSize <= 0)) { throw new TypeError('options.chunkSize, when provided, must be a positive integer.'); } + + this._writable = writable; + this._options = options; } /** @internal */ _createWriter() { - return this.options.chunked ? new ChunkedStreamTargetWriter(this) : new StreamTargetWriter(this); + return this._options.chunked ? new ChunkedStreamTargetWriter(this) : new StreamTargetWriter(this); } -} - -/** @public */ -export class FileSystemWritableFileStreamTarget extends Target { - constructor( - public stream: FileSystemWritableFileStream, - public options?: { chunkSize?: number } - ) { - super(); - - if (!(stream instanceof FileSystemWritableFileStream)) { - throw new TypeError('FileSystemWritableFileStreamTarget requires a FileSystemWritableFileStream instance.'); - } - if (options !== undefined && typeof options !== 'object') { - throw new TypeError("FileSystemWritableFileStreamTarget's options, when provided, must be an object."); - } - if (options) { - if (options.chunkSize !== undefined && (!Number.isInteger(options.chunkSize) || options.chunkSize <= 0)) { - throw new TypeError('options.chunkSize, when provided, must be a positive integer'); - } - } - } - - /** @internal */ - _createWriter() { - return new FileSystemWritableFileStreamTargetWriter(this); - } -} +} \ No newline at end of file diff --git a/src/writer.ts b/src/writer.ts index 1cfe6ae..d30338f 100644 --- a/src/writer.ts +++ b/src/writer.ts @@ -1,19 +1,22 @@ -import { ArrayBufferTarget, FileSystemWritableFileStreamTarget, StreamTarget } from './target'; +import { assert } from './misc'; +import { ArrayBufferTarget, StreamTarget, StreamTargetChunk } from './target'; export abstract class Writer { /** Setting this to true will cause the writer to ensure data is written in a strictly monotonic, streamable way. */ ensureMonotonicity = false; + start() {} + /** Writes the given data to the target, at the current position. */ abstract write(data: Uint8Array): void; /** Sets the current position for future writes to a new one. */ abstract seek(newPos: number): void; /** Returns the current position. */ abstract getPos(): number; - /** Called after muxing has finished. */ - abstract finalize(): void; /** Signals to the writer that it may be time to flush. */ - abstract flush(): void; + abstract flush(): Promise; + /** Called after muxing has finished. */ + abstract finalize(): Promise; } /** @@ -64,9 +67,9 @@ export class ArrayBufferTargetWriter extends Writer { return this.pos; } - flush() {} + async flush() {} - finalize() { + async finalize() { this.ensureSize(this.pos); this.target.buffer = this.buffer.slice(0, Math.max(this.maxPos, this.pos)); } @@ -88,6 +91,7 @@ export class StreamTargetWriter extends Writer { start: number }[] = []; private lastFlushEnd = 0; + private writer: WritableStreamDefaultWriter | null = null; constructor(target: StreamTarget) { super(); @@ -95,6 +99,10 @@ export class StreamTargetWriter extends Writer { this.target = target; } + override start() { + this.writer = this.target._writable.getWriter(); + } + write(data: Uint8Array) { this.sections.push({ data: data.slice(), @@ -111,7 +119,8 @@ export class StreamTargetWriter extends Writer { return this.pos; } - flush() { + async flush() { + assert(this.writer); if (this.sections.length === 0) return; let chunks: { @@ -156,14 +165,25 @@ export class StreamTargetWriter extends Writer { throw new Error('Internal error: Monotonicity violation.'); } - this.target.options.onData?.(chunk.data, chunk.start); + if (this.writer.desiredSize !== null && this.writer.desiredSize <= 0) { + await this.writer.ready; // Allow the writer to apply backpressure + } + + this.writer.write({ + type: 'write', + data: chunk.data, + position: chunk.start + }); this.lastFlushEnd = chunk.start + chunk.data.byteLength; } this.sections.length = 0; } - finalize() {} + finalize() { + assert(this.writer); + return this.writer.close(); + } } const DEFAULT_CHUNK_SIZE = 2**24; @@ -195,21 +215,27 @@ export class ChunkedStreamTargetWriter extends Writer { */ private chunks: Chunk[] = []; private lastFlushEnd = 0; + private writer: WritableStreamDefaultWriter | null = null; + private flushedChunkQueue: StreamTargetChunk[] = []; constructor(target: StreamTarget) { super(); this.target = target; - this.chunkSize = target.options?.chunkSize ?? DEFAULT_CHUNK_SIZE; + this.chunkSize = target._options?.chunkSize ?? DEFAULT_CHUNK_SIZE; if (!Number.isInteger(this.chunkSize) || this.chunkSize < 2**10) { throw new Error('Invalid StreamTarget options: chunkSize must be an integer not smaller than 1024.'); } } + override start() { + this.writer = this.target._writable.getWriter(); + } + write(data: Uint8Array) { this.writeDataIntoChunks(data, this.pos); - this.flushChunks(); + this.queueChunksForFlush(); this.pos += data.byteLength; } @@ -251,7 +277,7 @@ export class ChunkedStreamTargetWriter extends Writer { for (let i = 0; i < this.chunks.length-1; i++) { this.chunks[i]!.shouldFlush = true; } - this.flushChunks(); + this.queueChunksForFlush(); } // If the data didn't fit in one chunk, recurse with the remaining datas @@ -302,7 +328,9 @@ export class ChunkedStreamTargetWriter extends Writer { return this.chunks.indexOf(chunk); } - private flushChunks(force = false) { + private queueChunksForFlush(force = false) { + assert(this.writer); + for (let i = 0; i < this.chunks.length; i++) { let chunk = this.chunks[i]!; if (!chunk.shouldFlush && !force) continue; @@ -312,38 +340,38 @@ export class ChunkedStreamTargetWriter extends Writer { throw new Error('Internal error: Monotonicity violation.'); } - this.target.options.onData?.( - chunk.data.subarray(section.start, section.end), - chunk.start + section.start - ); + this.flushedChunkQueue.push({ + type: 'write', + data: chunk.data.subarray(section.start, section.end), + position: chunk.start + section.start + }); this.lastFlushEnd = chunk.start + section.end; } this.chunks.splice(i--, 1); } } - flush() { - // Do nothing, we flush ourselves + async flush() { + assert(this.writer); + if (this.flushedChunkQueue.length === 0) return; + + for (let chunk of this.flushedChunkQueue) { + if (this.writer.desiredSize !== null && this.writer.desiredSize <= 0) { + await this.writer.ready; // Allow the writer to apply backpressure + } + + this.writer.write(chunk); + } + + this.flushedChunkQueue.length = 0; } - finalize() { - this.flushChunks(true); - } -} + async finalize() { + assert(this.writer); -/** - * Essentially a wrapper around ChunkedStreamTargetWriter, writing directly to disk using the File System Access API. - * This is useful for large files, as available RAM is no longer a bottleneck. - */ -export class FileSystemWritableFileStreamTargetWriter extends ChunkedStreamTargetWriter { - constructor(target: FileSystemWritableFileStreamTarget) { - super(new StreamTarget({ - onData: (data, position) => target.stream.write({ - type: 'write', - data, - position - }), - chunkSize: target.options?.chunkSize - })); + this.queueChunksForFlush(true); + await this.flush(); + + return this.writer.close(); } } \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 03263c0..4d884cf 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,7 +9,8 @@ "rootDir": "src", "outDir": "build", "declaration": true, - "stripInternal": true + "stripInternal": true, + "skipLibCheck": true }, "include": [ "src/**/*"