diff --git a/dev/demux.html b/dev/demux.html
new file mode 100644
index 0000000..d7a4f71
--- /dev/null
+++ b/dev/demux.html
@@ -0,0 +1,102 @@
+
+
+
\ No newline at end of file
diff --git a/dist/metamuxer.js b/dist/metamuxer.js
index 4ae4cce..c523ad2 100644
--- a/dist/metamuxer.js
+++ b/dist/metamuxer.js
@@ -21,14 +21,24 @@ var Metamuxer = (() => {
// src/index.ts
var src_exports = {};
__export(src_exports, {
+ ALL_FORMATS: () => ALL_FORMATS,
AUDIO_CODECS: () => AUDIO_CODECS,
+ ArrayBufferSource: () => ArrayBufferSource,
ArrayBufferTarget: () => ArrayBufferTarget,
AudioBufferSource: () => AudioBufferSource,
AudioDataSource: () => AudioDataSource,
AudioSource: () => AudioSource,
+ BlobSource: () => BlobSource,
CanvasSource: () => CanvasSource,
EncodedAudioChunkSource: () => EncodedAudioChunkSource,
+ EncodedVideoChunkDrain: () => EncodedVideoChunkDrain,
EncodedVideoChunkSource: () => EncodedVideoChunkSource,
+ ISOBMFF: () => ISOBMFF,
+ Input: () => Input,
+ MATROSKA: () => MATROSKA,
+ MKV: () => MKV,
+ MOV: () => MOV,
+ MP4: () => MP4,
MediaSource: () => MediaSource,
MediaStreamAudioTrackSource: () => MediaStreamAudioTrackSource,
MediaStreamVideoTrackSource: () => MediaStreamVideoTrackSource,
@@ -37,13 +47,16 @@ var Metamuxer = (() => {
Output: () => Output,
OutputFormat: () => OutputFormat,
SUBTITLE_CODECS: () => SUBTITLE_CODECS,
+ Source: () => Source,
StreamTarget: () => StreamTarget,
SubtitleSource: () => SubtitleSource,
Target: () => Target,
TextSubtitleSource: () => TextSubtitleSource,
VIDEO_CODECS: () => VIDEO_CODECS,
+ VideoFrameDrain: () => VideoFrameDrain,
VideoFrameSource: () => VideoFrameSource,
VideoSource: () => VideoSource,
+ WEBM: () => WEBM,
WebMOutputFormat: () => WebMOutputFormat
});
@@ -89,6 +102,9 @@ var Metamuxer = (() => {
}
};
var textEncoder = new TextEncoder();
+ var invertObject = (object) => {
+ return Object.fromEntries(Object.entries(object).map(([key, value]) => [value, key]));
+ };
var COLOR_PRIMARIES_MAP = {
bt709: 1,
// ITU-R BT.709
@@ -97,6 +113,7 @@ var Metamuxer = (() => {
smpte170m: 6
// ITU-R BT.601 525 - SMPTE 170M
};
+ var COLOR_PRIMARIES_MAP_INVERSE = invertObject(COLOR_PRIMARIES_MAP);
var TRANSFER_CHARACTERISTICS_MAP = {
"bt709": 1,
// ITU-R BT.709
@@ -105,6 +122,7 @@ var Metamuxer = (() => {
"iec61966-2-1": 13
// IEC 61966-2-1
};
+ var TRANSFER_CHARACTERISTICS_MAP_INVERSE = invertObject(TRANSFER_CHARACTERISTICS_MAP);
var MATRIX_COEFFICIENTS_MAP = {
rgb: 0,
// Identity
@@ -115,6 +133,7 @@ var Metamuxer = (() => {
smpte170m: 6
// SMPTE 170M
};
+ var MATRIX_COEFFICIENTS_MAP_INVERSE = invertObject(MATRIX_COEFFICIENTS_MAP);
var colorSpaceIsComplete = (colorSpace) => {
return !!colorSpace && !!colorSpace.primaries && !!colorSpace.transfer && !!colorSpace.matrix && colorSpace.fullRange !== void 0;
};
@@ -122,9 +141,7 @@ var Metamuxer = (() => {
return x instanceof ArrayBuffer || typeof SharedArrayBuffer !== "undefined" && x instanceof SharedArrayBuffer || ArrayBuffer.isView(x) && !(x instanceof DataView);
};
var AsyncMutex = class {
- constructor() {
- this.currentPromise = Promise.resolve();
- }
+ currentPromise = Promise.resolve();
async acquire() {
let resolver;
const nextPromise = new Promise((resolve) => {
@@ -136,15 +153,87 @@ var Metamuxer = (() => {
return resolver;
}
};
+ var rotationMatrix = (rotationInDegrees) => {
+ const theta = rotationInDegrees * (Math.PI / 180);
+ const cosTheta = Math.cos(theta);
+ const sinTheta = Math.sin(theta);
+ return [
+ cosTheta,
+ sinTheta,
+ 0,
+ -sinTheta,
+ cosTheta,
+ 0,
+ 0,
+ 0,
+ 1
+ ];
+ };
+ var IDENTITY_MATRIX = rotationMatrix(0);
+ var bytesToHexString = (bytes2) => {
+ return [...bytes2].map((x) => x.toString(16).padStart(2, "0")).join("");
+ };
+ var reverseBitsU32 = (x) => {
+ x = x >> 1 & 1431655765 | (x & 1431655765) << 1;
+ x = x >> 2 & 858993459 | (x & 858993459) << 2;
+ x = x >> 4 & 252645135 | (x & 252645135) << 4;
+ x = x >> 8 & 16711935 | (x & 16711935) << 8;
+ x = x >> 16 & 65535 | (x & 65535) << 16;
+ return x >>> 0;
+ };
+ var binarySearchExact = (arr, key, valueGetter) => {
+ let low = 0;
+ let high = arr.length - 1;
+ let res = -1;
+ while (low <= high) {
+ const mid = low + high >> 1;
+ const midVal = valueGetter(arr[mid]);
+ if (midVal === key) {
+ res = mid;
+ high = mid - 1;
+ } else if (midVal < key) {
+ low = mid + 1;
+ } else {
+ high = mid - 1;
+ }
+ }
+ return res;
+ };
+ var binarySearchLessOrEqual = (arr, key, valueGetter) => {
+ let ans = -1;
+ let low = 0;
+ let high = arr.length - 1;
+ while (low <= high) {
+ const mid = low + (high - low + 1) / 2 | 0;
+ const midVal = valueGetter(arr[mid]);
+ if (midVal <= key) {
+ ans = mid;
+ low = mid + 1;
+ } else {
+ high = mid - 1;
+ }
+ }
+ return ans;
+ };
+ var promiseWithResolvers = () => {
+ let resolve;
+ let reject;
+ const promise = new Promise((res, rej) => {
+ resolve = res;
+ reject = rej;
+ });
+ return { promise, resolve, reject };
+ };
// 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;
var preambleStartRegex = /^WEBVTT(.|\n)*?\n{2}/;
var inlineTimestampRegex = /<(?:(\d{2}):)?(\d{2}):(\d{2}).(\d{3})>/g;
var SubtitleParser = class {
+ options;
+ preambleText = null;
+ preambleEmitted = false;
constructor(options) {
- this.preambleText = null;
- this.preambleEmitted = false;
this.options = options;
}
parse(text) {
@@ -221,14 +310,14 @@ var Metamuxer = (() => {
var IsobmffBoxWriter = class {
constructor(writer) {
this.writer = writer;
- this.helper = new Uint8Array(8);
- this.helperView = new DataView(this.helper.buffer);
- /**
- * Stores the position from the start of the file to where boxes elements have been written. This is used to
- * rewrite/edit elements that were already added before, and to measure sizes of things.
- */
- this.offsets = /* @__PURE__ */ new WeakMap();
}
+ helper = new Uint8Array(8);
+ helperView = new DataView(this.helper.buffer);
+ /**
+ * Stores the position from the start of the file to where boxes elements have been written. This is used to
+ * rewrite/edit elements that were already added before, and to measure sizes of things.
+ */
+ offsets = /* @__PURE__ */ new WeakMap();
writeU32(value) {
this.helperView.setUint32(0, value, false);
this.writer.write(this.helper.subarray(0, 4));
@@ -368,23 +457,6 @@ var Metamuxer = (() => {
}
return result;
};
- var rotationMatrix = (rotationInDegrees) => {
- const theta = rotationInDegrees * (Math.PI / 180);
- const cosTheta = Math.cos(theta);
- const sinTheta = Math.sin(theta);
- return [
- cosTheta,
- sinTheta,
- 0,
- -sinTheta,
- cosTheta,
- 0,
- 0,
- 0,
- 1
- ];
- };
- var IDENTITY_MATRIX = rotationMatrix(0);
var matrixToBytes = (matrix) => {
return [
fixed_16_16(matrix[0]),
@@ -1120,9 +1192,9 @@ var Metamuxer = (() => {
// src/muxer.ts
var Muxer = class {
+ output;
+ mutex = new AsyncMutex();
constructor(output) {
- this.mutex = new AsyncMutex();
- this.trackTimestampInfo = /* @__PURE__ */ new WeakMap();
this.output = output;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -1131,6 +1203,7 @@ var Metamuxer = (() => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
onTrackClose(track) {
}
+ trackTimestampInfo = /* @__PURE__ */ new WeakMap();
validateAndNormalizeTimestamp(track, rawTimestampInUs, isKeyFrame) {
let timestampInSeconds = rawTimestampInUs / 1e6;
let timestampInfo = this.trackTimestampInfo.get(track);
@@ -1174,20 +1247,19 @@ var Metamuxer = (() => {
// src/writer.ts
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;
- }
+ /** Setting this to true will cause the writer to ensure data is written in a strictly monotonic, streamable way. */
+ ensureMonotonicity = false;
start() {
}
};
var ArrayBufferTargetWriter = class extends Writer {
+ pos = 0;
+ target;
+ buffer = new ArrayBuffer(2 ** 16);
+ bytes = new Uint8Array(this.buffer);
+ maxPos = 0;
constructor(target) {
super();
- this.pos = 0;
- this.buffer = new ArrayBuffer(2 ** 16);
- this.bytes = new Uint8Array(this.buffer);
- this.maxPos = 0;
this.target = target;
}
ensureSize(size) {
@@ -1214,7 +1286,6 @@ var Metamuxer = (() => {
}
async flush() {
}
- // eslint-disable-next-line @typescript-eslint/require-await
async finalize() {
this.ensureSize(this.pos);
this.target.buffer = this.buffer.slice(0, Math.max(this.maxPos, this.pos));
@@ -1224,12 +1295,13 @@ var Metamuxer = (() => {
}
};
var StreamTargetWriter = class extends Writer {
+ pos = 0;
+ target;
+ sections = [];
+ lastFlushEnd = 0;
+ writer = null;
constructor(target) {
super();
- this.pos = 0;
- this.sections = [];
- this.lastFlushEnd = 0;
- this.writer = null;
this.target = target;
}
start() {
@@ -1299,17 +1371,19 @@ var Metamuxer = (() => {
var DEFAULT_CHUNK_SIZE = 2 ** 24;
var MAX_CHUNKS_AT_ONCE = 2;
var ChunkedStreamTargetWriter = class extends Writer {
+ pos = 0;
+ target;
+ chunkSize;
+ /**
+ * The data is divided up into fixed-size chunks, whose contents are first filled in RAM and then flushed out.
+ * A chunk is flushed if all of its contents have been written.
+ */
+ chunks = [];
+ lastFlushEnd = 0;
+ writer = null;
+ flushedChunkQueue = [];
constructor(target) {
super();
- this.pos = 0;
- /**
- * The data is divided up into fixed-size chunks, whose contents are first filled in RAM and then flushed out.
- * A chunk is flushed if all of its contents have been written.
- */
- this.chunks = [];
- this.lastFlushEnd = 0;
- this.writer = null;
- this.flushedChunkQueue = [];
this.target = target;
this.chunkSize = target._options?.chunkSize ?? DEFAULT_CHUNK_SIZE;
if (!Number.isInteger(this.chunkSize) || this.chunkSize < 2 ** 10) {
@@ -1427,22 +1501,21 @@ var Metamuxer = (() => {
// src/target.ts
var Target = class {
- constructor() {
- /** @internal */
- this._output = null;
- }
+ /** @internal */
+ _output = null;
};
var ArrayBufferTarget = class extends Target {
- constructor() {
- super(...arguments);
- this.buffer = null;
- }
+ buffer = null;
/** @internal */
_createWriter() {
return new ArrayBufferTargetWriter(this);
}
};
var StreamTarget = class extends Target {
+ /** @internal */
+ _writable;
+ /** @internal */
+ _options;
constructor(writable, options = {}) {
super();
if (!(writable instanceof WritableStream)) {
@@ -1467,6 +1540,9 @@ var Metamuxer = (() => {
};
// src/codec.ts
+ var VIDEO_CODECS = ["avc", "hevc", "vp8", "vp9", "av1"];
+ var AUDIO_CODECS = ["aac", "opus"];
+ var SUBTITLE_CODECS = ["webvtt"];
var AVC_LEVEL_TABLE = [
{ maxMacroblocks: 99, maxBitrate: 64e3, level: 10 },
// Level 1
@@ -1674,6 +1750,43 @@ var Metamuxer = (() => {
}
throw new TypeError(`Unhandled codec '${codec}'.`);
};
+ var extractVideoCodecString = (codec, description) => {
+ if (codec === "avc") {
+ if (!description || description.byteLength < 4) {
+ throw new TypeError("AVC description must be at least 4 bytes long.");
+ }
+ return `avc1.${bytesToHexString(description.subarray(1, 4))}`;
+ } else if (codec === "hevc") {
+ if (!description) {
+ throw new TypeError("HEVC description must be provided.");
+ }
+ const view2 = new DataView(description.buffer, description.byteOffset, description.byteLength);
+ let codecString = "hev1.";
+ const generalProfileSpace = description[1] >> 6 & 3;
+ const generalProfileIdc = description[1] & 31;
+ codecString += ["", "A", "B", "C"][generalProfileSpace] + generalProfileIdc;
+ codecString += ".";
+ const compatibilityFlags = reverseBitsU32(view2.getUint32(2));
+ codecString += compatibilityFlags.toString(16);
+ codecString += ".";
+ const generalTierFlag = description[1] >> 5 & 1;
+ const generalLevelIdc = description[12];
+ codecString += generalTierFlag === 0 ? "L" : "H";
+ codecString += generalLevelIdc;
+ codecString += ".";
+ const constraintFlags = [];
+ for (let i = 0; i < 6; i++) {
+ const byte = description[i + 13];
+ constraintFlags.push(byte);
+ }
+ while (constraintFlags[constraintFlags.length - 1] === 0) {
+ constraintFlags.pop();
+ }
+ codecString += constraintFlags.map((x) => x.toString(16)).join(".");
+ return codecString;
+ }
+ throw new TypeError(`Unhandled codec '${codec}'.`);
+ };
var buildAudioCodecString = (codec, numberOfChannels, sampleRate) => {
if (codec === "aac") {
if (numberOfChannels >= 2 && sampleRate <= 24e3) {
@@ -1690,6 +1803,20 @@ var Metamuxer = (() => {
}
throw new TypeError(`Unhandled codec '${codec}'.`);
};
+ var extractAudioCodecString = (codec, description) => {
+ if (codec === "aac") {
+ if (!description || description.byteLength < 2) {
+ throw new TypeError("AAC description must be at least 2 bytes long.");
+ }
+ const mpeg4AudioObjectType = description[0] >> 3;
+ return `mp4a.40.${mpeg4AudioObjectType}`;
+ } else if (codec === "opus") {
+ return "opus";
+ } else if (codec === "vorbis") {
+ return "vorbis";
+ }
+ throw new TypeError(`Unhandled codec '${codec}'.`);
+ };
var getVideoEncoderConfigExtension = (codec) => {
if (codec === "avc") {
return {
@@ -1871,18 +1998,21 @@ var Metamuxer = (() => {
return round ? Math.round(value) : value;
};
var IsobmffMuxer = class extends Muxer {
+ timestampsMustStartAtZero = true;
+ writer;
+ boxWriter;
+ fastStart;
+ auxTarget = new ArrayBufferTarget();
+ auxWriter = this.auxTarget._createWriter();
+ auxBoxWriter = new IsobmffBoxWriter(this.auxWriter);
+ ftypSize = null;
+ mdat = null;
+ trackDatas = [];
+ creationTime = Math.floor(Date.now() / 1e3) + TIMESTAMP_OFFSET;
+ finalizedChunks = [];
+ nextFragmentNumber = 1;
constructor(output, format) {
super(output);
- this.timestampsMustStartAtZero = true;
- this.auxTarget = new ArrayBufferTarget();
- this.auxWriter = this.auxTarget._createWriter();
- this.auxBoxWriter = new IsobmffBoxWriter(this.auxWriter);
- this.ftypSize = null;
- this.mdat = null;
- this.trackDatas = [];
- this.creationTime = Math.floor(Date.now() / 1e3) + TIMESTAMP_OFFSET;
- this.finalizedChunks = [];
- this.nextFragmentNumber = 1;
this.writer = output._writer;
this.boxWriter = new IsobmffBoxWriter(this.writer);
const fastStartDefault = this.writer instanceof ArrayBufferTargetWriter ? "in-memory" : false;
@@ -2471,16 +2601,19 @@ var Metamuxer = (() => {
// src/matroska/ebml.ts
var EBMLFloat32 = class {
+ value;
constructor(value) {
this.value = value;
}
};
var EBMLFloat64 = class {
+ value;
constructor(value) {
this.value = value;
}
};
var EBMLSignedInt = class {
+ value;
constructor(value) {
this.value = value;
}
@@ -2554,29 +2687,31 @@ var Metamuxer = (() => {
subtitle: 17
};
var MatroskaMuxer = class extends Muxer {
+ timestampsMustStartAtZero = false;
+ writer;
+ format;
+ helper = new Uint8Array(8);
+ helperView = new DataView(this.helper.buffer);
+ /**
+ * Stores the position from the start of the file to where EBML elements have been written. This is used to
+ * rewrite/edit elements that were already added before, and to measure sizes of things.
+ */
+ offsets = /* @__PURE__ */ new WeakMap();
+ /** Same as offsets, but stores position where the element's data starts (after ID and size fields). */
+ dataOffsets = /* @__PURE__ */ new WeakMap();
+ trackDatas = [];
+ segment = null;
+ segmentInfo = null;
+ seekHead = null;
+ tracksElement = null;
+ segmentDuration = null;
+ cues = null;
+ currentCluster = null;
+ currentClusterMsTimestamp = null;
+ trackDatasInCurrentCluster = /* @__PURE__ */ new Set();
+ duration = 0;
constructor(output, format) {
super(output);
- this.timestampsMustStartAtZero = false;
- this.helper = new Uint8Array(8);
- this.helperView = new DataView(this.helper.buffer);
- /**
- * Stores the position from the start of the file to where EBML elements have been written. This is used to
- * rewrite/edit elements that were already added before, and to measure sizes of things.
- */
- this.offsets = /* @__PURE__ */ new WeakMap();
- /** Same as offsets, but stores position where the element's data starts (after ID and size fields). */
- this.dataOffsets = /* @__PURE__ */ 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 = /* @__PURE__ */ new Set();
- this.duration = 0;
this.writer = output._writer;
this.format = format;
if (this.format._options.streamable) {
@@ -3251,6 +3386,8 @@ ${cue.notes ?? ""}`;
var OutputFormat = class {
};
var Mp4OutputFormat = class extends OutputFormat {
+ /** @internal */
+ _options;
constructor(options = {}) {
if (!options || typeof options !== "object") {
throw new TypeError("options must be an object.");
@@ -3267,6 +3404,8 @@ ${cue.notes ?? ""}`;
}
};
var MkvOutputFormat2 = class extends OutputFormat {
+ /** @internal */
+ _options;
constructor(options = {}) {
if (!options || typeof options !== "object") {
throw new TypeError("options must be an object.");
@@ -3285,19 +3424,14 @@ ${cue.notes ?? ""}`;
var WebMOutputFormat = class extends MkvOutputFormat2 {
};
- // src/source.ts
- var VIDEO_CODECS = ["avc", "hevc", "vp8", "vp9", "av1"];
- var AUDIO_CODECS = ["aac", "opus"];
- var SUBTITLE_CODECS = ["webvtt"];
+ // src/media-source.ts
var MediaSource = class {
- constructor() {
- /** @internal */
- this._connectedTrack = null;
- /** @internal */
- this._closed = false;
- /** @internal */
- this._offsetTimestamps = false;
- }
+ /** @internal */
+ _connectedTrack = null;
+ /** @internal */
+ _closed = false;
+ /** @internal */
+ _offsetTimestamps = false;
/** @internal */
_ensureValidDigest() {
if (!this._connectedTrack) {
@@ -3337,10 +3471,12 @@ ${cue.notes ?? ""}`;
}
};
var VideoSource = class extends MediaSource {
+ /** @internal */
+ _connectedTrack = null;
+ /** @internal */
+ _codec;
constructor(codec) {
super();
- /** @internal */
- this._connectedTrack = null;
if (!VIDEO_CODECS.includes(codec)) {
throw new TypeError(`Invalid video codec '${codec}'. Must be one of: ${VIDEO_CODECS.join(", ")}.`);
}
@@ -3378,13 +3514,13 @@ ${cue.notes ?? ""}`;
constructor(source, codecConfig) {
this.source = source;
this.codecConfig = codecConfig;
- this.encoder = null;
- this.muxer = null;
- this.lastMultipleOfKeyFrameInterval = -1;
- this.lastWidth = null;
- this.lastHeight = null;
validateVideoCodecConfig(codecConfig);
}
+ encoder = null;
+ muxer = null;
+ lastMultipleOfKeyFrameInterval = -1;
+ lastWidth = null;
+ lastHeight = null;
async digest(videoFrame) {
this.source._ensureValidDigest();
if (this.lastWidth !== null && this.lastHeight !== null) {
@@ -3442,6 +3578,8 @@ ${cue.notes ?? ""}`;
}
};
var VideoFrameSource = class extends VideoSource {
+ /** @internal */
+ _encoder;
constructor(codecConfig) {
super(codecConfig.codec);
this._encoder = new VideoEncoderWrapper(this, codecConfig);
@@ -3458,6 +3596,10 @@ ${cue.notes ?? ""}`;
}
};
var CanvasSource = class extends VideoSource {
+ /** @internal */
+ _encoder;
+ /** @internal */
+ _canvas;
constructor(canvas, codecConfig) {
if (!(canvas instanceof HTMLCanvasElement)) {
throw new TypeError("canvas must be an HTMLCanvasElement.");
@@ -3488,6 +3630,14 @@ ${cue.notes ?? ""}`;
}
};
var MediaStreamVideoTrackSource = class extends VideoSource {
+ /** @internal */
+ _encoder;
+ /** @internal */
+ _abortController = null;
+ /** @internal */
+ _track;
+ /** @internal */
+ _offsetTimestamps = true;
constructor(track, codecConfig) {
if (!(track instanceof MediaStreamTrack) || track.kind !== "video") {
throw new TypeError("track must be a video MediaStreamTrack.");
@@ -3497,10 +3647,6 @@ ${cue.notes ?? ""}`;
latencyMode: "realtime"
};
super(codecConfig.codec);
- /** @internal */
- this._abortController = null;
- /** @internal */
- this._offsetTimestamps = true;
this._encoder = new VideoEncoderWrapper(this, codecConfig);
this._track = track;
}
@@ -3531,10 +3677,12 @@ ${cue.notes ?? ""}`;
}
};
var AudioSource = class extends MediaSource {
+ /** @internal */
+ _connectedTrack = null;
+ /** @internal */
+ _codec;
constructor(codec) {
super();
- /** @internal */
- this._connectedTrack = null;
if (!AUDIO_CODECS.includes(codec)) {
throw new TypeError(`Invalid audio codec '${codec}'. Must be one of: ${AUDIO_CODECS.join(", ")}.`);
}
@@ -3568,12 +3716,12 @@ ${cue.notes ?? ""}`;
constructor(source, codecConfig) {
this.source = source;
this.codecConfig = codecConfig;
- this.encoder = null;
- this.muxer = null;
- this.lastNumberOfChannels = null;
- this.lastSampleRate = null;
validateAudioCodecConfig(codecConfig);
}
+ encoder = null;
+ muxer = null;
+ lastNumberOfChannels = null;
+ lastSampleRate = null;
async digest(audioData) {
this.source._ensureValidDigest();
if (this.lastNumberOfChannels !== null && this.lastSampleRate !== null) {
@@ -3620,6 +3768,8 @@ ${cue.notes ?? ""}`;
}
};
var AudioDataSource = class extends AudioSource {
+ /** @internal */
+ _encoder;
constructor(codecConfig) {
super(codecConfig.codec);
this._encoder = new AudioEncoderWrapper(this, codecConfig);
@@ -3636,10 +3786,12 @@ ${cue.notes ?? ""}`;
}
};
var AudioBufferSource = class extends AudioSource {
+ /** @internal */
+ _encoder;
+ /** @internal */
+ _accumulatedFrameCount = 0;
constructor(codecConfig) {
super(codecConfig.codec);
- /** @internal */
- this._accumulatedFrameCount = 0;
this._encoder = new AudioEncoderWrapper(this, codecConfig);
}
digest(audioBuffer) {
@@ -3673,15 +3825,19 @@ ${cue.notes ?? ""}`;
}
};
var MediaStreamAudioTrackSource = class extends AudioSource {
+ /** @internal */
+ _encoder;
+ /** @internal */
+ _abortController = null;
+ /** @internal */
+ _track;
+ /** @internal */
+ _offsetTimestamps = true;
constructor(track, codecConfig) {
if (!(track instanceof MediaStreamTrack) || track.kind !== "audio") {
throw new TypeError("track must be an audio MediaStreamTrack.");
}
super(codecConfig.codec);
- /** @internal */
- this._abortController = null;
- /** @internal */
- this._offsetTimestamps = true;
this._encoder = new AudioEncoderWrapper(this, codecConfig);
this._track = track;
}
@@ -3712,10 +3868,12 @@ ${cue.notes ?? ""}`;
}
};
var SubtitleSource = class extends MediaSource {
+ /** @internal */
+ _connectedTrack = null;
+ /** @internal */
+ _codec;
constructor(codec) {
super();
- /** @internal */
- this._connectedTrack = null;
if (!SUBTITLE_CODECS.includes(codec)) {
throw new TypeError(`Invalid subtitle codec '${codec}'. Must be one of: ${SUBTITLE_CODECS.join(", ")}.`);
}
@@ -3723,6 +3881,8 @@ ${cue.notes ?? ""}`;
}
};
var TextSubtitleSource = class extends SubtitleSource {
+ /** @internal */
+ _parser;
constructor(codec) {
super(codec);
this._parser = new SubtitleParser({
@@ -3743,15 +3903,19 @@ ${cue.notes ?? ""}`;
// src/output.ts
var Output = class {
+ /** @internal */
+ _muxer;
+ /** @internal */
+ _writer;
+ /** @internal */
+ _tracks = [];
+ /** @internal */
+ _started = false;
+ /** @internal */
+ _finalizing = false;
+ /** @internal */
+ _mutex = new AsyncMutex();
constructor(options) {
- /** @internal */
- this._tracks = [];
- /** @internal */
- 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.");
}
@@ -3854,6 +4018,1302 @@ ${cue.notes ?? ""}`;
release();
}
};
+
+ // src/source.ts
+ var Source = class {
+ };
+ var ArrayBufferSource = class extends Source {
+ constructor(buffer) {
+ super();
+ this.buffer = buffer;
+ }
+ /** @internal */
+ async _read(start, end) {
+ return new Uint8Array(this.buffer, start, end - start);
+ }
+ /** @internal */
+ async _getSize() {
+ return this.buffer.byteLength;
+ }
+ };
+ var BlobSource = class extends Source {
+ constructor(blob) {
+ super();
+ this.blob = blob;
+ }
+ /** @internal */
+ async _read(start, end) {
+ const slice = this.blob.slice(start, end);
+ const buffer = await slice.arrayBuffer();
+ return new Uint8Array(buffer);
+ }
+ /** @internal */
+ async _getSize() {
+ return this.blob.size;
+ }
+ };
+
+ // src/demuxer.ts
+ var Demuxer = class {
+ input;
+ constructor(input) {
+ this.input = input;
+ }
+ };
+
+ // src/input-track.ts
+ var InputTrack = class {
+ /** @internal */
+ _backing;
+ /** @internal */
+ constructor(backing) {
+ this._backing = backing;
+ }
+ isVideoTrack() {
+ return this instanceof InputVideoTrack;
+ }
+ isAudioTrack() {
+ return this instanceof InputAudioTrack;
+ }
+ getDuration() {
+ return this._backing.getDuration();
+ }
+ };
+ var InputVideoTrack = class extends InputTrack {
+ /** @internal */
+ _backing;
+ /** @internal */
+ constructor(backing) {
+ super(backing);
+ this._backing = backing;
+ }
+ getCodec() {
+ return this._backing.getCodec();
+ }
+ getWidth() {
+ return this._backing.getWidth();
+ }
+ getHeight() {
+ return this._backing.getHeight();
+ }
+ getRotation() {
+ return this._backing.getRotation();
+ }
+ getDecoderConfig() {
+ return this._backing.getDecoderConfig();
+ }
+ async getCodecMimeType() {
+ const decoderConfig = await this.getDecoderConfig();
+ return decoderConfig.codec;
+ }
+ };
+ var InputAudioTrack = class extends InputTrack {
+ /** @internal */
+ _backing;
+ /** @internal */
+ constructor(backing) {
+ super(backing);
+ this._backing = backing;
+ }
+ getCodec() {
+ return this._backing.getCodec();
+ }
+ getNumberOfChannels() {
+ return this._backing.getNumberOfChannels();
+ }
+ getSampleRate() {
+ return this._backing.getSampleRate();
+ }
+ getDecoderConfig() {
+ return this._backing.getDecoderConfig();
+ }
+ async getCodecMimeType() {
+ const decoderConfig = await this.getDecoderConfig();
+ return decoderConfig.codec;
+ }
+ };
+
+ // src/isobmff/isobmff-reader.ts
+ var IsobmffReader = class {
+ constructor(reader) {
+ this.reader = reader;
+ }
+ pos = 0;
+ readRange(start, end) {
+ const { view: view2, offset } = this.reader.getViewAndOffset(start, end);
+ return new Uint8Array(view2.buffer, offset, end - start);
+ }
+ readU8() {
+ const { view: view2, offset } = this.reader.getViewAndOffset(this.pos, this.pos + 1);
+ this.pos++;
+ return view2.getUint8(offset);
+ }
+ readU16() {
+ const { view: view2, offset } = this.reader.getViewAndOffset(this.pos, this.pos + 2);
+ this.pos += 2;
+ return view2.getUint16(offset, false);
+ }
+ readU24() {
+ const { view: view2, offset } = this.reader.getViewAndOffset(this.pos, this.pos + 3);
+ this.pos += 3;
+ const high = view2.getUint16(offset, false);
+ const low = view2.getUint8(offset + 2);
+ return high * 256 + low;
+ }
+ readS32() {
+ const { view: view2, offset } = this.reader.getViewAndOffset(this.pos, this.pos + 4);
+ this.pos += 4;
+ return view2.getInt32(offset, false);
+ }
+ readU32() {
+ const { view: view2, offset } = this.reader.getViewAndOffset(this.pos, this.pos + 4);
+ this.pos += 4;
+ return view2.getUint32(offset, false);
+ }
+ readI32() {
+ const { view: view2, offset } = this.reader.getViewAndOffset(this.pos, this.pos + 4);
+ this.pos += 4;
+ return view2.getInt32(offset, false);
+ }
+ readU64() {
+ const high = this.readU32();
+ const low = this.readU32();
+ return high * 4294967296 + low;
+ }
+ readF64() {
+ const { view: view2, offset } = this.reader.getViewAndOffset(this.pos, this.pos + 8);
+ this.pos += 8;
+ return view2.getFloat64(offset, false);
+ }
+ readFixed_16_16() {
+ return this.readS32() / 65536;
+ }
+ readFixed_2_30() {
+ return this.readS32() / 1073741824;
+ }
+ readAscii(length) {
+ const { view: view2, offset } = this.reader.getViewAndOffset(this.pos, this.pos + length);
+ this.pos += length;
+ let str = "";
+ for (let i = 0; i < length; i++) {
+ str += String.fromCharCode(view2.getUint8(offset + i));
+ }
+ return str;
+ }
+ readIsomVariableInteger() {
+ let result = 0;
+ for (let i = 0; i < 4; i++) {
+ result <<= 7;
+ const nextByte = this.readU8();
+ result |= nextByte & 127;
+ if ((nextByte & 128) === 0) {
+ break;
+ }
+ }
+ return result;
+ }
+ readBoxHeader() {
+ let totalSize = this.readU32();
+ const name = this.readAscii(4);
+ let headerSize = 8;
+ const hasLargeSize = totalSize === 1;
+ if (hasLargeSize) {
+ totalSize = this.readU64();
+ headerSize = 16;
+ }
+ return { name, totalSize, headerSize, contentSize: totalSize - headerSize };
+ }
+ };
+
+ // src/isobmff/isobmff-demuxer.ts
+ var knownMatrixes = [rotationMatrix(0), rotationMatrix(90), rotationMatrix(180), rotationMatrix(270)];
+ var IsobmffDemuxer = class extends Demuxer {
+ isobmffReader;
+ currentTrack = null;
+ tracks = [];
+ metadataPromise = null;
+ movieTimescale = -1;
+ movieDurationInTimescale = -1;
+ constructor(input) {
+ super(input);
+ this.isobmffReader = new IsobmffReader(input._reader);
+ }
+ async getDuration() {
+ await this.readMetadata();
+ if (this.movieDurationInTimescale === -1) {
+ throw new Error("Could not read movie duration.");
+ }
+ return this.movieDurationInTimescale / this.movieTimescale;
+ }
+ async getTracks() {
+ await this.readMetadata();
+ return this.tracks.map((track) => track.inputTrack);
+ }
+ async getMimeType() {
+ await this.readMetadata();
+ let string = "video/mp4";
+ if (this.tracks.length > 0) {
+ const codecMimeTypes = await Promise.all(this.tracks.map((x) => x.inputTrack.getCodecMimeType()));
+ const uniqueCodecMimeTypes = [...new Set(codecMimeTypes)];
+ string += `; codecs="${uniqueCodecMimeTypes.join(", ")}"`;
+ }
+ return string;
+ }
+ readMetadata() {
+ return this.metadataPromise ??= (async () => {
+ const sourceSize = await this.isobmffReader.reader.getSourceSize();
+ while (this.isobmffReader.pos < sourceSize) {
+ await this.isobmffReader.reader.loadRange(this.isobmffReader.pos, this.isobmffReader.pos + 16);
+ const startPos = this.isobmffReader.pos;
+ const boxInfo = this.isobmffReader.readBoxHeader();
+ if (boxInfo.name === "moov") {
+ await this.isobmffReader.reader.loadRange(
+ this.isobmffReader.pos,
+ this.isobmffReader.pos + boxInfo.contentSize
+ );
+ this.readContiguousBoxes(boxInfo.contentSize);
+ return;
+ }
+ this.isobmffReader.pos = startPos + boxInfo.totalSize;
+ }
+ })();
+ }
+ getSampleTableForTrack(internalTrack) {
+ if (internalTrack.sampleTable) {
+ return internalTrack.sampleTable;
+ }
+ const sampleTable = {
+ sampleTimingEntries: [],
+ sampleCompositionTimeOffsets: [],
+ sampleSizes: [],
+ keySampleIndices: null,
+ chunkOffsets: [],
+ sampleToChunk: [],
+ presentationTimestamps: []
+ };
+ internalTrack.sampleTable = sampleTable;
+ this.isobmffReader.pos = internalTrack.sampleTableOffset;
+ this.currentTrack = internalTrack;
+ this.traverseBox();
+ this.currentTrack = null;
+ for (const entry of sampleTable.sampleTimingEntries) {
+ for (let i = 0; i < entry.count; i++) {
+ sampleTable.presentationTimestamps.push({
+ presentationTimestamp: entry.startDecodeTimestamp + i * entry.delta,
+ sampleIndex: entry.startIndex + i
+ });
+ }
+ }
+ for (const entry of sampleTable.sampleCompositionTimeOffsets) {
+ for (let i = 0; i < entry.count; i++) {
+ const sampleIndex = entry.startIndex + i;
+ const sample = sampleTable.presentationTimestamps[sampleIndex];
+ if (!sample) {
+ continue;
+ }
+ sample.presentationTimestamp += entry.offset;
+ }
+ }
+ sampleTable.presentationTimestamps.sort((a, b) => a.presentationTimestamp - b.presentationTimestamp);
+ return internalTrack.sampleTable;
+ }
+ readContiguousBoxes(totalSize) {
+ const startIndex = this.isobmffReader.pos;
+ while (this.isobmffReader.pos - startIndex < totalSize) {
+ this.traverseBox();
+ }
+ }
+ traverseBox() {
+ const startPos = this.isobmffReader.pos;
+ const boxInfo = this.isobmffReader.readBoxHeader();
+ const boxEndPos = startPos + boxInfo.totalSize;
+ switch (boxInfo.name) {
+ case "mdia":
+ case "minf":
+ case "dinf":
+ {
+ this.readContiguousBoxes(boxInfo.contentSize);
+ }
+ ;
+ break;
+ case "mvhd":
+ {
+ const version = this.isobmffReader.readU8();
+ this.isobmffReader.pos += 3;
+ if (version === 1) {
+ this.isobmffReader.pos += 8 + 8;
+ this.movieTimescale = this.isobmffReader.readU32();
+ this.movieDurationInTimescale = this.isobmffReader.readU64();
+ } else {
+ this.isobmffReader.pos += 4 + 4;
+ this.movieTimescale = this.isobmffReader.readU32();
+ this.movieDurationInTimescale = this.isobmffReader.readU32();
+ }
+ }
+ ;
+ break;
+ case "trak":
+ {
+ const track = {
+ id: -1,
+ demuxer: this,
+ inputTrack: null,
+ info: null,
+ timescale: -1,
+ durationInTimescale: -1,
+ rotation: 0,
+ sampleTableOffset: -1,
+ sampleTable: null
+ };
+ this.currentTrack = track;
+ this.readContiguousBoxes(boxInfo.contentSize);
+ if (track.id !== -1 && track.timescale !== -1 && track.info !== null) {
+ if (track.info.type === "video" && track.info.codec !== null) {
+ const videoTrack = track;
+ track.inputTrack = new InputVideoTrack(new IsobmffVideoTrackBacking(videoTrack));
+ this.tracks.push(track);
+ } else if (track.info.type === "audio" && track.info.codec !== null) {
+ const audioTrack = track;
+ track.inputTrack = new InputAudioTrack(new IsobmffAudioTrackBacking(audioTrack));
+ this.tracks.push(track);
+ }
+ }
+ this.currentTrack = null;
+ }
+ ;
+ break;
+ case "tkhd":
+ {
+ const track = this.currentTrack;
+ assert(track);
+ const version = this.isobmffReader.readU8();
+ const flags = this.isobmffReader.readU24();
+ const trackEnabled = (flags & 1) !== 0;
+ if (!trackEnabled) {
+ break;
+ }
+ if (version === 0) {
+ this.isobmffReader.pos += 8;
+ track.id = this.isobmffReader.readU32();
+ this.isobmffReader.pos += 8;
+ } else if (version === 1) {
+ this.isobmffReader.pos += 16;
+ track.id = this.isobmffReader.readU32();
+ this.isobmffReader.pos += 12;
+ } else {
+ throw new Error(`Incorrect track header version ${version}.`);
+ }
+ this.isobmffReader.pos += 2 * 4 + 2 + 2 + 2 + 2;
+ const rotationMatrix2 = [];
+ rotationMatrix2.push(this.isobmffReader.readFixed_16_16(), this.isobmffReader.readFixed_16_16());
+ this.isobmffReader.pos += 4;
+ rotationMatrix2.push(this.isobmffReader.readFixed_16_16(), this.isobmffReader.readFixed_16_16());
+ const matrixIndex = knownMatrixes.findIndex((x) => x.every((y, i) => y === rotationMatrix2[i]));
+ if (matrixIndex === -1) {
+ track.rotation = 0;
+ } else {
+ track.rotation = 90 * matrixIndex;
+ }
+ }
+ ;
+ break;
+ case "mdhd":
+ {
+ const track = this.currentTrack;
+ assert(track);
+ const version = this.isobmffReader.readU8();
+ this.isobmffReader.pos += 3;
+ if (version === 0) {
+ this.isobmffReader.pos += 8;
+ track.timescale = this.isobmffReader.readU32();
+ track.durationInTimescale = this.isobmffReader.readU32();
+ } else if (version === 1) {
+ this.isobmffReader.pos += 16;
+ track.timescale = this.isobmffReader.readU32();
+ track.durationInTimescale = this.isobmffReader.readU64();
+ }
+ }
+ ;
+ break;
+ case "hdlr":
+ {
+ const track = this.currentTrack;
+ assert(track);
+ this.isobmffReader.pos += 8;
+ const handlerType = this.isobmffReader.readAscii(4);
+ if (handlerType === "vide") {
+ track.info = {
+ type: "video",
+ width: -1,
+ height: -1,
+ codec: null,
+ codecDescription: null,
+ colorSpace: null
+ };
+ } else if (handlerType === "soun") {
+ track.info = {
+ type: "audio",
+ numberOfChannels: -1,
+ sampleRate: -1,
+ codec: null,
+ codecDescription: null
+ };
+ }
+ }
+ ;
+ break;
+ case "stbl":
+ {
+ const track = this.currentTrack;
+ assert(track);
+ track.sampleTableOffset = startPos;
+ this.readContiguousBoxes(boxInfo.contentSize);
+ }
+ ;
+ break;
+ case "stsd":
+ {
+ const track = this.currentTrack;
+ assert(track);
+ if (track.info === null || track.sampleTable) {
+ break;
+ }
+ const stsdVersion = this.isobmffReader.readU8();
+ this.isobmffReader.pos += 3;
+ const entries = this.isobmffReader.readU32();
+ for (let i = 0; i < entries; i++) {
+ const sampleBoxInfo = this.isobmffReader.readBoxHeader();
+ if (track.info.type === "video") {
+ if (sampleBoxInfo.name === "avc1") {
+ track.info.codec = "avc";
+ } else if (sampleBoxInfo.name === "hvc1" || sampleBoxInfo.name === "hev1") {
+ track.info.codec = "hevc";
+ } else {
+ console.warn(`Unsupported video sample entry type ${sampleBoxInfo.name}.`);
+ break;
+ }
+ this.isobmffReader.pos += 6 * 1 + 2 + 2 + 2 + 3 * 4;
+ track.info.width = this.isobmffReader.readU16();
+ track.info.height = this.isobmffReader.readU16();
+ this.isobmffReader.pos += 4 + 4 + 4 + 2 + 32 + 2 + 2;
+ this.readContiguousBoxes(startPos + sampleBoxInfo.totalSize - this.isobmffReader.pos);
+ } else {
+ if (sampleBoxInfo.name === "mp4a") {
+ track.info.codec = "aac";
+ } else if (sampleBoxInfo.name.toLowerCase() === "opus") {
+ track.info.codec = "opus";
+ } else {
+ console.warn(`Unsupported audio sample entry type ${sampleBoxInfo.name}.`);
+ break;
+ }
+ this.isobmffReader.pos += 6 * 1 + 2;
+ const version = this.isobmffReader.readU16();
+ this.isobmffReader.pos += 3 * 2;
+ let channelCount = this.isobmffReader.readU16();
+ this.isobmffReader.pos += 2 + 2 + 2;
+ let sampleRate = this.isobmffReader.readU32() / 65536;
+ if (stsdVersion === 0 && version > 0) {
+ if (version === 1) {
+ this.isobmffReader.pos += 4 * 4;
+ } else if (version === 2) {
+ this.isobmffReader.pos += 4;
+ sampleRate = this.isobmffReader.readF64();
+ channelCount = this.isobmffReader.readU32();
+ this.isobmffReader.pos += 4;
+ const sampleSize = this.isobmffReader.readU32();
+ const flags = this.isobmffReader.readU32();
+ const bytesPerFrame = this.isobmffReader.readU32();
+ const samplesPerFrame = this.isobmffReader.readU32();
+ }
+ }
+ track.info.numberOfChannels = channelCount;
+ track.info.sampleRate = sampleRate;
+ this.readContiguousBoxes(startPos + sampleBoxInfo.totalSize - this.isobmffReader.pos);
+ }
+ }
+ }
+ ;
+ break;
+ case "avcC":
+ {
+ const track = this.currentTrack;
+ assert(track && track.info);
+ track.info.codecDescription = this.isobmffReader.readRange(
+ this.isobmffReader.pos,
+ this.isobmffReader.pos + boxInfo.contentSize
+ );
+ }
+ ;
+ break;
+ case "hvcC":
+ {
+ const track = this.currentTrack;
+ assert(track && track.info);
+ track.info.codecDescription = this.isobmffReader.readRange(
+ this.isobmffReader.pos,
+ this.isobmffReader.pos + boxInfo.contentSize
+ );
+ }
+ ;
+ break;
+ case "colr":
+ {
+ const track = this.currentTrack;
+ assert(track && track.info?.type === "video");
+ const colourType = this.isobmffReader.readAscii(4);
+ if (colourType !== "nclx") {
+ break;
+ }
+ const colourPrimaries = this.isobmffReader.readU16();
+ const transferCharacteristics = this.isobmffReader.readU16();
+ const matrixCoefficients = this.isobmffReader.readU16();
+ const fullRangeFlag = Boolean(this.isobmffReader.readU8() & 128);
+ track.info.colorSpace = {
+ primaries: COLOR_PRIMARIES_MAP_INVERSE[colourPrimaries],
+ transfer: TRANSFER_CHARACTERISTICS_MAP_INVERSE[transferCharacteristics],
+ matrix: MATRIX_COEFFICIENTS_MAP_INVERSE[matrixCoefficients],
+ fullRange: fullRangeFlag
+ };
+ }
+ ;
+ break;
+ case "wave":
+ {
+ if (boxInfo.totalSize > 8) {
+ this.readContiguousBoxes(boxInfo.contentSize);
+ }
+ }
+ ;
+ break;
+ case "esds":
+ {
+ const track = this.currentTrack;
+ assert(track && track.info);
+ this.isobmffReader.pos += 4;
+ const tag = this.isobmffReader.readU8();
+ assert(tag === 3);
+ this.isobmffReader.readIsomVariableInteger();
+ this.isobmffReader.pos += 2;
+ const mixed = this.isobmffReader.readU8();
+ const streamDependenceFlag = (mixed & 128) !== 0;
+ const urlFlag = (mixed & 64) !== 0;
+ const ocrStreamFlag = (mixed & 32) !== 0;
+ if (streamDependenceFlag) {
+ this.isobmffReader.pos += 2;
+ }
+ if (urlFlag) {
+ const urlLength = this.isobmffReader.readU8();
+ this.isobmffReader.pos += urlLength;
+ }
+ if (ocrStreamFlag) {
+ this.isobmffReader.pos += 2;
+ }
+ const decoderConfigTag = this.isobmffReader.readU8();
+ assert(decoderConfigTag === 4);
+ this.isobmffReader.readIsomVariableInteger();
+ const objectTypeIndication = this.isobmffReader.readU8();
+ assert(objectTypeIndication === 64);
+ this.isobmffReader.pos += 1 + 3 + 4 + 4;
+ const decoderSpecificInfoTag = this.isobmffReader.readU8();
+ assert(decoderSpecificInfoTag === 5);
+ const decoderSpecificInfoLength = this.isobmffReader.readIsomVariableInteger();
+ track.info.codecDescription = this.isobmffReader.readRange(
+ this.isobmffReader.pos,
+ this.isobmffReader.pos + decoderSpecificInfoLength
+ );
+ }
+ ;
+ break;
+ case "stts":
+ {
+ const track = this.currentTrack;
+ assert(track);
+ if (!track.sampleTable) {
+ break;
+ }
+ this.isobmffReader.pos += 4;
+ const entryCount = this.isobmffReader.readU32();
+ let currentIndex = 0;
+ let currentTimestamp = 0;
+ for (let i = 0; i < entryCount; i++) {
+ const sampleCount = this.isobmffReader.readU32();
+ const sampleDelta = this.isobmffReader.readU32();
+ track.sampleTable.sampleTimingEntries.push({
+ startIndex: currentIndex,
+ startDecodeTimestamp: currentTimestamp,
+ count: sampleCount,
+ delta: sampleDelta
+ });
+ currentIndex += sampleCount;
+ currentTimestamp += sampleCount * sampleDelta;
+ }
+ }
+ ;
+ break;
+ case "ctts":
+ {
+ const track = this.currentTrack;
+ assert(track);
+ if (!track.sampleTable) {
+ break;
+ }
+ this.isobmffReader.pos += 1 + 3;
+ const entryCount = this.isobmffReader.readU32();
+ let sampleIndex = 0;
+ for (let i = 0; i < entryCount; i++) {
+ const sampleCount = this.isobmffReader.readU32();
+ const sampleOffset = this.isobmffReader.readI32();
+ track.sampleTable.sampleCompositionTimeOffsets.push({
+ startIndex: sampleIndex,
+ count: sampleCount,
+ offset: sampleOffset
+ });
+ sampleIndex += sampleCount;
+ }
+ }
+ ;
+ break;
+ case "stsz":
+ {
+ const track = this.currentTrack;
+ assert(track);
+ if (!track.sampleTable) {
+ break;
+ }
+ this.isobmffReader.pos += 4;
+ const sampleSize = this.isobmffReader.readU32();
+ const sampleCount = this.isobmffReader.readU32();
+ if (sampleSize === 0) {
+ for (let i = 0; i < sampleCount; i++) {
+ const sampleSize2 = this.isobmffReader.readU32();
+ track.sampleTable.sampleSizes.push(sampleSize2);
+ }
+ } else {
+ track.sampleTable.sampleSizes.push(sampleSize);
+ }
+ }
+ ;
+ break;
+ case "stz2":
+ {
+ throw new Error("Unsupported.");
+ }
+ ;
+ case "stss":
+ {
+ const track = this.currentTrack;
+ assert(track);
+ if (!track.sampleTable) {
+ break;
+ }
+ this.isobmffReader.pos += 4;
+ track.sampleTable.keySampleIndices = [];
+ const entryCount = this.isobmffReader.readU32();
+ for (let i = 0; i < entryCount; i++) {
+ const sampleIndex = this.isobmffReader.readU32() - 1;
+ track.sampleTable.keySampleIndices.push(sampleIndex);
+ }
+ }
+ ;
+ break;
+ case "stsc":
+ {
+ const track = this.currentTrack;
+ assert(track);
+ if (!track.sampleTable) {
+ break;
+ }
+ this.isobmffReader.pos += 4;
+ const entryCount = this.isobmffReader.readU32();
+ for (let i = 0; i < entryCount; i++) {
+ const startChunkIndex = this.isobmffReader.readU32() - 1;
+ const samplesPerChunk = this.isobmffReader.readU32();
+ const sampleDescriptionIndex = this.isobmffReader.readU32();
+ track.sampleTable.sampleToChunk.push({
+ startSampleIndex: -1,
+ startChunkIndex,
+ samplesPerChunk,
+ sampleDescriptionIndex
+ });
+ }
+ let startSampleIndex = 0;
+ for (let i = 0; i < track.sampleTable.sampleToChunk.length; i++) {
+ track.sampleTable.sampleToChunk[i].startSampleIndex = startSampleIndex;
+ if (i < track.sampleTable.sampleToChunk.length - 1) {
+ const nextChunk = track.sampleTable.sampleToChunk[i + 1];
+ const chunkCount = nextChunk.startChunkIndex - track.sampleTable.sampleToChunk[i].startChunkIndex;
+ startSampleIndex += chunkCount * track.sampleTable.sampleToChunk[i].samplesPerChunk;
+ }
+ }
+ }
+ ;
+ break;
+ case "stco":
+ {
+ const track = this.currentTrack;
+ assert(track);
+ if (!track.sampleTable) {
+ break;
+ }
+ this.isobmffReader.pos += 4;
+ const entryCount = this.isobmffReader.readU32();
+ for (let i = 0; i < entryCount; i++) {
+ const chunkOffset = this.isobmffReader.readU32();
+ track.sampleTable.chunkOffsets.push(chunkOffset);
+ }
+ }
+ ;
+ break;
+ case "co64":
+ {
+ const track = this.currentTrack;
+ assert(track);
+ if (!track.sampleTable) {
+ break;
+ }
+ this.isobmffReader.pos += 4;
+ const entryCount = this.isobmffReader.readU32();
+ for (let i = 0; i < entryCount; i++) {
+ const chunkOffset = this.isobmffReader.readU64();
+ track.sampleTable.chunkOffsets.push(chunkOffset);
+ }
+ }
+ ;
+ break;
+ }
+ this.isobmffReader.pos = boxEndPos;
+ }
+ };
+ var IsobmffTrackBacking = class {
+ constructor(internalTrack) {
+ this.internalTrack = internalTrack;
+ }
+ chunkToSampleIndex = /* @__PURE__ */ new WeakMap();
+ sampleIndexToChunk = /* @__PURE__ */ new Map();
+ getCodec() {
+ throw new Error("Not implemented on base class.");
+ }
+ async getDuration() {
+ return this.internalTrack.durationInTimescale / this.internalTrack.timescale;
+ }
+ };
+ var IsobmffVideoTrackBacking = class extends IsobmffTrackBacking {
+ internalTrack;
+ constructor(internalTrack) {
+ super(internalTrack);
+ this.internalTrack = internalTrack;
+ }
+ async getCodec() {
+ return this.internalTrack.info.codec;
+ }
+ async getWidth() {
+ return this.internalTrack.info.width;
+ }
+ async getHeight() {
+ return this.internalTrack.info.height;
+ }
+ async getRotation() {
+ return this.internalTrack.rotation;
+ }
+ async getDecoderConfig() {
+ return {
+ codec: extractVideoCodecString(this.internalTrack.info.codec, this.internalTrack.info.codecDescription),
+ codedWidth: this.internalTrack.info.width,
+ codedHeight: this.internalTrack.info.height,
+ description: this.internalTrack.info.codecDescription ?? void 0,
+ colorSpace: this.internalTrack.info.colorSpace ?? void 0
+ };
+ }
+ async fetchChunkForSampleIndex(sampleIndex) {
+ if (sampleIndex === -1) {
+ return null;
+ }
+ const existingChunk = this.sampleIndexToChunk.get(sampleIndex)?.deref();
+ if (existingChunk) {
+ return existingChunk;
+ }
+ const sampleTable = this.internalTrack.demuxer.getSampleTableForTrack(this.internalTrack);
+ const sampleInfo = getSampleInfo(sampleTable, sampleIndex);
+ if (!sampleInfo) {
+ return null;
+ }
+ const data = await this.internalTrack.demuxer.isobmffReader.reader.source._read(
+ sampleInfo.byteOffset,
+ sampleInfo.byteOffset + sampleInfo.byteSize
+ );
+ const chunk = new EncodedVideoChunk({
+ data,
+ timestamp: 1e6 * sampleInfo.presentationTimestamp / this.internalTrack.timescale,
+ duration: 1e6 * sampleInfo.duration / this.internalTrack.timescale,
+ type: sampleInfo.isKeyFrame ? "key" : "delta"
+ });
+ this.chunkToSampleIndex.set(chunk, sampleIndex);
+ this.sampleIndexToChunk.set(sampleIndex, new WeakRef(chunk));
+ return chunk;
+ }
+ async getFirstChunk() {
+ return this.fetchChunkForSampleIndex(0);
+ }
+ async getChunk(timestamp) {
+ const sampleTable = this.internalTrack.demuxer.getSampleTableForTrack(this.internalTrack);
+ const sampleIndex = getSampleIndexForTimestamp(sampleTable, timestamp * this.internalTrack.timescale);
+ return this.fetchChunkForSampleIndex(sampleIndex);
+ }
+ async getNextChunk(chunk) {
+ const sampleIndex = this.chunkToSampleIndex.get(chunk);
+ if (sampleIndex === void 0) {
+ throw new Error("Chunk was not created from this track.");
+ }
+ return this.fetchChunkForSampleIndex(sampleIndex + 1);
+ }
+ async getKeyChunk(timestamp) {
+ const sampleTable = this.internalTrack.demuxer.getSampleTableForTrack(this.internalTrack);
+ const sampleIndex = getSampleIndexForTimestamp(sampleTable, timestamp * this.internalTrack.timescale);
+ const keyFrameSampleIndex = sampleIndex === -1 ? -1 : getRelevantKeyframeIndexForSample(sampleTable, sampleIndex);
+ return this.fetchChunkForSampleIndex(keyFrameSampleIndex);
+ }
+ async getNextKeyChunk(chunk) {
+ const sampleIndex = this.chunkToSampleIndex.get(chunk);
+ if (sampleIndex === void 0) {
+ throw new Error("Chunk was not created from this track.");
+ }
+ const sampleTable = this.internalTrack.demuxer.getSampleTableForTrack(this.internalTrack);
+ const nextKeyFrameSampleIndex = getNextKeyframeIndexForSample(sampleTable, sampleIndex);
+ return this.fetchChunkForSampleIndex(nextKeyFrameSampleIndex);
+ }
+ };
+ var IsobmffAudioTrackBacking = class extends IsobmffTrackBacking {
+ internalTrack;
+ constructor(internalTrack) {
+ super(internalTrack);
+ this.internalTrack = internalTrack;
+ }
+ async getCodec() {
+ return this.internalTrack.info.codec;
+ }
+ async getNumberOfChannels() {
+ return this.internalTrack.info.numberOfChannels;
+ }
+ async getSampleRate() {
+ return this.internalTrack.info.sampleRate;
+ }
+ async getDecoderConfig() {
+ return {
+ codec: extractAudioCodecString(this.internalTrack.info.codec, this.internalTrack.info.codecDescription),
+ numberOfChannels: this.internalTrack.info.numberOfChannels,
+ sampleRate: this.internalTrack.info.sampleRate,
+ description: this.internalTrack.info.codecDescription ?? void 0
+ };
+ }
+ };
+ var getSampleIndexForTimestamp = (sampleTable, timescaleUnits) => {
+ const index = binarySearchLessOrEqual(
+ sampleTable.presentationTimestamps,
+ timescaleUnits,
+ (x) => x.presentationTimestamp
+ );
+ if (index === -1) {
+ return -1;
+ }
+ return sampleTable.presentationTimestamps[index].sampleIndex;
+ };
+ var getSampleInfo = (sampleTable, sampleIndex) => {
+ const timingEntryIndex = binarySearchLessOrEqual(sampleTable.sampleTimingEntries, sampleIndex, (x) => x.startIndex);
+ const timingEntry = sampleTable.sampleTimingEntries[timingEntryIndex];
+ if (!timingEntry || timingEntry.startIndex + timingEntry.count <= sampleIndex) {
+ return null;
+ }
+ const decodeTimestamp = timingEntry.startDecodeTimestamp + (sampleIndex - timingEntry.startIndex) * timingEntry.delta;
+ let presentationTimestamp = decodeTimestamp;
+ const offsetEntryIndex = binarySearchLessOrEqual(
+ sampleTable.sampleCompositionTimeOffsets,
+ sampleIndex,
+ (x) => x.startIndex
+ );
+ const offsetEntry = sampleTable.sampleCompositionTimeOffsets[offsetEntryIndex];
+ if (offsetEntry) {
+ presentationTimestamp += offsetEntry.offset;
+ }
+ const sampleSize = sampleTable.sampleSizes[Math.min(sampleIndex, sampleTable.sampleSizes.length - 1)];
+ const chunkEntryIndex = binarySearchLessOrEqual(sampleTable.sampleToChunk, sampleIndex, (x) => x.startSampleIndex);
+ const chunkEntry = sampleTable.sampleToChunk[chunkEntryIndex];
+ assert(chunkEntry);
+ const chunkIndex = chunkEntry.startChunkIndex + Math.floor((sampleIndex - chunkEntry.startSampleIndex) / chunkEntry.samplesPerChunk);
+ const chunkOffset = sampleTable.chunkOffsets[chunkIndex];
+ let sampleOffset = chunkOffset;
+ if (sampleTable.sampleSizes.length === 1) {
+ sampleOffset += sampleSize * (sampleIndex - chunkEntry.startSampleIndex);
+ } else {
+ const startSampleIndex = chunkEntry.startSampleIndex + (chunkIndex - chunkEntry.startChunkIndex) * chunkEntry.samplesPerChunk;
+ for (let i = startSampleIndex; i < sampleIndex; i++) {
+ sampleOffset += sampleTable.sampleSizes[i];
+ }
+ }
+ return {
+ presentationTimestamp,
+ duration: timingEntry.delta,
+ byteOffset: sampleOffset,
+ byteSize: sampleSize,
+ isKeyFrame: sampleTable.keySampleIndices ? binarySearchExact(sampleTable.keySampleIndices, sampleIndex, (x) => x) !== -1 : true
+ };
+ };
+ var getRelevantKeyframeIndexForSample = (sampleTable, sampleIndex) => {
+ if (!sampleTable.keySampleIndices) {
+ return sampleIndex;
+ }
+ const index = binarySearchLessOrEqual(sampleTable.keySampleIndices, sampleIndex, (x) => x);
+ return sampleTable.keySampleIndices[index] ?? -1;
+ };
+ var getNextKeyframeIndexForSample = (sampleTable, sampleIndex) => {
+ if (!sampleTable.keySampleIndices) {
+ return sampleIndex + 1;
+ }
+ const index = binarySearchLessOrEqual(sampleTable.keySampleIndices, sampleIndex, (x) => x);
+ return sampleTable.keySampleIndices[index + 1] ?? -1;
+ };
+
+ // src/matroska/matroska-demuxer.ts
+ var MatroskaDemuxer = class extends Demuxer {
+ };
+
+ // src/input-format.ts
+ var InputFormat = class {
+ };
+ var IsobmffInputFormat = class extends InputFormat {
+ /** @internal */
+ async _canReadInput(input) {
+ const sourceSize = await input._reader.getSourceSize();
+ if (sourceSize < 8) {
+ return false;
+ }
+ await input._reader.loadRange(4, 8);
+ const isobmffReader = new IsobmffReader(input._reader);
+ isobmffReader.pos = 4;
+ const fourCc = isobmffReader.readAscii(4);
+ return fourCc === "ftyp";
+ }
+ _createDemuxer(input) {
+ return new IsobmffDemuxer(input);
+ }
+ };
+ var MatroskaInputFormat = class extends InputFormat {
+ /** @internal */
+ async _canReadInput() {
+ return false;
+ }
+ _createDemuxer(input) {
+ return new MatroskaDemuxer(input);
+ }
+ };
+ var ISOBMFF = new IsobmffInputFormat();
+ var MP4 = ISOBMFF;
+ var MOV = ISOBMFF;
+ var MATROSKA = new MatroskaInputFormat();
+ var MKV = MATROSKA;
+ var WEBM = MATROSKA;
+ var ALL_FORMATS = [ISOBMFF, MKV];
+
+ // src/reader.ts
+ var PAGE_SIZE = 4096;
+ var Reader = class {
+ constructor(source) {
+ this.source = source;
+ }
+ loadedSegments = [];
+ sourceSizePromise = null;
+ getSourceSize() {
+ if (this.sourceSizePromise) {
+ return this.sourceSizePromise;
+ } else {
+ return this.sourceSizePromise = this.source._getSize();
+ }
+ }
+ async loadRange(start, end) {
+ let alignedStart = Math.floor(start / PAGE_SIZE) * PAGE_SIZE;
+ let alignedEnd = Math.ceil(end / PAGE_SIZE) * PAGE_SIZE;
+ alignedEnd = Math.min(alignedEnd, await this.getSourceSize());
+ const thing = this.loadedSegments.find((x) => x.start <= alignedStart);
+ if (thing) {
+ alignedStart = Math.max(alignedStart, thing.end);
+ }
+ const thing2 = this.loadedSegments.find((x) => x.end >= alignedEnd);
+ if (thing2) {
+ alignedEnd = Math.min(alignedEnd, thing2.start);
+ }
+ if (alignedStart >= alignedEnd) {
+ return;
+ }
+ const bytes2 = await this.source._read(alignedStart, alignedEnd);
+ this.insertIntoLoadedSegments(alignedStart, bytes2);
+ }
+ insertIntoLoadedSegments(start, bytes2) {
+ const segment = {
+ start,
+ end: start + bytes2.byteLength,
+ bytes: bytes2,
+ view: new DataView(bytes2.buffer)
+ };
+ let index = this.loadedSegments.findLastIndex((x) => x.start <= start);
+ this.loadedSegments.splice(index + 1, 0, segment);
+ if (index === -1 || this.loadedSegments[index].end < segment.start) {
+ index++;
+ }
+ const mergeSectionStartIndex = index;
+ const mergeSectionStart = this.loadedSegments[mergeSectionStartIndex].start;
+ let mergeSectionEndIndex = index;
+ let mergeSectionEnd = this.loadedSegments[mergeSectionEndIndex].end;
+ while (this.loadedSegments.length - 1 > mergeSectionEndIndex && this.loadedSegments[mergeSectionEndIndex + 1].start <= mergeSectionEnd) {
+ mergeSectionEndIndex++;
+ mergeSectionEnd = Math.max(mergeSectionEnd, this.loadedSegments[mergeSectionEndIndex].end);
+ }
+ if (mergeSectionStartIndex === mergeSectionEndIndex) {
+ return;
+ }
+ for (let i = mergeSectionStartIndex; i <= mergeSectionEndIndex; i++) {
+ const segment2 = this.loadedSegments[i];
+ const coversEntireMergeSection = segment2.start === mergeSectionStart && segment2.end === mergeSectionEnd;
+ if (coversEntireMergeSection) {
+ this.loadedSegments.splice(i + 1, mergeSectionEndIndex - i);
+ this.loadedSegments.splice(mergeSectionStartIndex, i - mergeSectionStartIndex);
+ return;
+ }
+ }
+ const unifiedBytes = new Uint8Array(mergeSectionEnd - mergeSectionStart);
+ for (let i = mergeSectionStartIndex; i <= mergeSectionEndIndex; i++) {
+ const segment2 = this.loadedSegments[i];
+ unifiedBytes.set(segment2.bytes, segment2.start - mergeSectionStart);
+ }
+ this.loadedSegments.splice(mergeSectionStartIndex + 1, mergeSectionEndIndex - mergeSectionStartIndex);
+ this.loadedSegments[mergeSectionStartIndex].end = mergeSectionEnd;
+ this.loadedSegments[mergeSectionStartIndex].bytes = unifiedBytes;
+ this.loadedSegments[mergeSectionStartIndex].view = new DataView(unifiedBytes.buffer);
+ }
+ getViewAndOffset(start, end) {
+ const segment = this.loadedSegments.find((x) => x.start <= start && end <= x.end);
+ if (!segment) {
+ throw new Error(`No segment loaded for range [${start}, ${end}).`);
+ }
+ return {
+ view: segment.view,
+ offset: segment.bytes.byteOffset + start - segment.start
+ };
+ }
+ };
+
+ // src/input.ts
+ var Input = class {
+ /** @internal */
+ _formats;
+ /** @internal */
+ _reader;
+ /** @internal */
+ _demuxerPromise = null;
+ /** @internal */
+ _format = null;
+ constructor(options) {
+ this._formats = options.formats;
+ this._reader = new Reader(options.source);
+ }
+ /** @internal */
+ _getDemuxer() {
+ return this._demuxerPromise ??= (async () => {
+ for (const format of this._formats) {
+ const canRead = await format._canReadInput(this);
+ if (canRead) {
+ this._format = format;
+ return format._createDemuxer(this);
+ }
+ }
+ throw new Error("Input has an unrecognizable format.");
+ })();
+ }
+ async getFormat() {
+ await this._getDemuxer();
+ assert(this._format);
+ return this._format;
+ }
+ async getDuration() {
+ const demuxer = await this._getDemuxer();
+ return demuxer.getDuration();
+ }
+ async getTracks() {
+ const demuxer = await this._getDemuxer();
+ return demuxer.getTracks();
+ }
+ async getVideoTracks() {
+ const tracks = await this.getTracks();
+ return tracks.filter((x) => x.isVideoTrack());
+ }
+ async getPrimaryVideoTrack() {
+ const tracks = await this.getTracks();
+ return tracks.find((x) => x.isVideoTrack()) ?? null;
+ }
+ async getAudioTracks() {
+ const tracks = await this.getTracks();
+ return tracks.filter((x) => x.isAudioTrack());
+ }
+ async getPrimaryAudioTrack() {
+ const tracks = await this.getTracks();
+ return tracks.find((x) => x.isAudioTrack()) ?? null;
+ }
+ async getMimeType() {
+ const demuxer = await this._getDemuxer();
+ return demuxer.getMimeType();
+ }
+ };
+
+ // src/media-drain.ts
+ var EncodedVideoChunkDrain = class {
+ constructor(videoTrack) {
+ this.videoTrack = videoTrack;
+ }
+ getFirstChunk() {
+ return this.videoTrack._backing.getFirstChunk();
+ }
+ getChunk(timestamp) {
+ return this.videoTrack._backing.getChunk(timestamp);
+ }
+ getNextChunk(chunk) {
+ return this.videoTrack._backing.getNextChunk(chunk);
+ }
+ getKeyChunk(timestamp) {
+ return this.videoTrack._backing.getKeyChunk(timestamp);
+ }
+ getNextKeyChunk(chunk) {
+ return this.videoTrack._backing.getNextKeyChunk(chunk);
+ }
+ async *chunks(startTimestamp = 0) {
+ let chunk = await this.getChunk(startTimestamp);
+ while (chunk) {
+ yield chunk;
+ chunk = await this.getNextChunk(chunk);
+ }
+ }
+ };
+ var VideoFrameDrain = class {
+ constructor(videoTrack) {
+ this.videoTrack = videoTrack;
+ }
+ decoderConfig = null;
+ async createDecoder(onFrame) {
+ if (!this.decoderConfig) {
+ this.decoderConfig = await this.videoTrack.getDecoderConfig();
+ }
+ const decoder = new VideoDecoder({
+ output: onFrame,
+ error: (error) => console.error(error)
+ });
+ decoder.configure(this.decoderConfig);
+ return decoder;
+ }
+ async getKeyFrame(timestamp) {
+ let result = null;
+ const decoder = await this.createDecoder((frame) => result = frame);
+ const chunk = await this.videoTrack._backing.getKeyChunk(timestamp);
+ if (!chunk) {
+ return null;
+ }
+ decoder.decode(chunk);
+ await decoder.flush();
+ decoder.close();
+ return result;
+ }
+ async getFrame(timestamp) {
+ let result = null;
+ const decoder = await this.createDecoder((frame) => {
+ if (frame.timestamp / 1e6 <= timestamp) {
+ result?.close();
+ result = frame;
+ } else {
+ frame.close();
+ }
+ });
+ const keyChunk = await this.videoTrack._backing.getKeyChunk(timestamp);
+ if (!keyChunk) {
+ return null;
+ }
+ const targetChunk = await this.videoTrack._backing.getChunk(timestamp);
+ assert(targetChunk);
+ decoder.decode(keyChunk);
+ let currentChunk = keyChunk;
+ while (currentChunk !== targetChunk) {
+ const nextChunk = await this.videoTrack._backing.getNextChunk(currentChunk);
+ assert(nextChunk);
+ currentChunk = nextChunk;
+ decoder.decode(nextChunk);
+ if (decoder.decodeQueueSize >= 10) {
+ await new Promise((resolve) => decoder.addEventListener("dequeue", resolve, { once: true }));
+ }
+ }
+ await decoder.flush();
+ decoder.close();
+ return result;
+ }
+ async *frames(startTimestamp = 0) {
+ const frameQueue = [];
+ let firstFrameQueued = false;
+ let lastFrame = null;
+ let { promise: queueNonEmpty, resolve: onQueueNotEmpty } = promiseWithResolvers();
+ let ended = false;
+ const decoder = await this.createDecoder((frame) => {
+ if (ended) {
+ frame.close();
+ return;
+ }
+ const frameTimestamp = frame.timestamp / 1e6;
+ if (lastFrame) {
+ if (frameTimestamp > startTimestamp) {
+ frameQueue.push(lastFrame);
+ firstFrameQueued = true;
+ } else {
+ lastFrame.close();
+ }
+ }
+ if (frameTimestamp >= startTimestamp) {
+ frameQueue.push(frame);
+ firstFrameQueued = true;
+ }
+ lastFrame = firstFrameQueued ? null : frame;
+ if (frameQueue.length > 0) {
+ onQueueNotEmpty();
+ ({ promise: queueNonEmpty, resolve: onQueueNotEmpty } = promiseWithResolvers());
+ }
+ });
+ const keyChunk = await this.videoTrack._backing.getKeyChunk(startTimestamp);
+ if (!keyChunk) {
+ return;
+ }
+ let decoderIsFlushed = false;
+ void (async () => {
+ let currentChunk = keyChunk;
+ while (currentChunk && !ended) {
+ decoder.decode(currentChunk);
+ if (decoder.decodeQueueSize >= 10) {
+ await new Promise((resolve) => decoder.addEventListener("dequeue", resolve, { once: true }));
+ }
+ const nextChunk = await this.videoTrack._backing.getNextChunk(currentChunk);
+ currentChunk = nextChunk;
+ }
+ await decoder.flush();
+ decoder.close();
+ decoderIsFlushed = true;
+ onQueueNotEmpty();
+ })();
+ try {
+ while (true) {
+ if (frameQueue.length > 0) {
+ yield frameQueue.shift();
+ } else if (!decoderIsFlushed) {
+ await queueNonEmpty;
+ } else {
+ break;
+ }
+ }
+ } finally {
+ ended = true;
+ }
+ }
+ };
return __toCommonJS(src_exports);
})();
if (typeof module === "object" && typeof module.exports === "object") Object.assign(module.exports, Metamuxer)
diff --git a/dist/metamuxer.min.js b/dist/metamuxer.min.js
index 9f2561c..67d7f32 100644
--- a/dist/metamuxer.min.js
+++ b/dist/metamuxer.min.js
@@ -1,10 +1,10 @@
-"use strict";var Metamuxer=(()=>{var Pe=Object.defineProperty;var ht=Object.getOwnPropertyDescriptor;var bt=Object.getOwnPropertyNames;var xt=Object.prototype.hasOwnProperty;var Tt=(t,i)=>{for(var e in i)Pe(t,e,{get:i[e],enumerable:!0})},wt=(t,i,e,r)=>{if(i&&typeof i=="object"||typeof i=="function")for(let s of bt(i))!xt.call(t,s)&&s!==e&&Pe(t,s,{get:()=>i[s],enumerable:!(r=ht(i,s))||r.enumerable});return t};var Ct=t=>wt(Pe({},"__esModule",{value:!0}),t);var _r={};Tt(_r,{AUDIO_CODECS:()=>Y,ArrayBufferTarget:()=>K,AudioBufferSource:()=>Oe,AudioDataSource:()=>Ee,AudioSource:()=>O,CanvasSource:()=>ve,EncodedAudioChunkSource:()=>_e,EncodedVideoChunkSource:()=>Se,MediaSource:()=>W,MediaStreamAudioTrackSource:()=>Me,MediaStreamVideoTrackSource:()=>Ae,MkvOutputFormat:()=>se,Mp4OutputFormat:()=>ke,Output:()=>ze,OutputFormat:()=>z,SUBTITLE_CODECS:()=>ge,StreamTarget:()=>he,SubtitleSource:()=>R,Target:()=>V,TextSubtitleSource:()=>Ve,VIDEO_CODECS:()=>G,VideoFrameSource:()=>ye,VideoSource:()=>E,WebMOutputFormat:()=>D});function d(t){if(!t)throw new Error("Assertion failed.")}var S=t=>t&&t[t.length-1],P=t=>t>=0&&t<2**32,B=(t,i,e)=>{let r=0;for(let s=i;s>a;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),y=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},ae=t=>!!t&&!!t.primaries&&!!t.transfer&&!!t.matrix&&t.fullRange!==void 0,Be=t=>t instanceof ArrayBuffer||typeof SharedArrayBuffer<"u"&&t instanceof SharedArrayBuffer||ArrayBuffer.isView(t)&&!(t instanceof DataView),N=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,kt=/^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
+"use strict";var Metamuxer=(()=>{var et=Object.defineProperty;var tr=Object.getOwnPropertyDescriptor;var rr=Object.getOwnPropertyNames;var ir=Object.prototype.hasOwnProperty;var sr=(r,e)=>{for(var t in e)et(r,t,{get:e[t],enumerable:!0})},or=(r,e,t,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of rr(e))!ir.call(r,s)&&s!==t&&et(r,s,{get:()=>e[s],enumerable:!(i=tr(e,s))||i.enumerable});return r};var nr=r=>or(et({},"__esModule",{value:!0}),r);var ki={};sr(ki,{ALL_FORMATS:()=>er,AUDIO_CODECS:()=>L,ArrayBufferSource:()=>Ne,ArrayBufferTarget:()=>Y,AudioBufferSource:()=>Ue,AudioDataSource:()=>Be,AudioSource:()=>O,BlobSource:()=>He,CanvasSource:()=>Re,EncodedAudioChunkSource:()=>ze,EncodedVideoChunkDrain:()=>Ze,EncodedVideoChunkSource:()=>Ve,ISOBMFF:()=>he,Input:()=>Ye,MATROSKA:()=>qe,MKV:()=>kt,MOV:()=>Zt,MP4:()=>Yt,MediaSource:()=>Q,MediaStreamAudioTrackSource:()=>De,MediaStreamVideoTrackSource:()=>Pe,MkvOutputFormat:()=>ue,Mp4OutputFormat:()=>Oe,Output:()=>We,OutputFormat:()=>R,SUBTITLE_CODECS:()=>ne,Source:()=>J,StreamTarget:()=>ye,SubtitleSource:()=>j,Target:()=>M,TextSubtitleSource:()=>Fe,VIDEO_CODECS:()=>H,VideoFrameDrain:()=>Je,VideoFrameSource:()=>Me,VideoSource:()=>I,WEBM:()=>Jt,WebMOutputFormat:()=>$});function l(r){if(!r)throw new Error("Assertion failed.")}var y=r=>r&&r[r.length-1],P=r=>r>=0&&r<2**32,z=(r,e,t)=>{let i=0;for(let s=e;s>a;i<<=1,i|=c}return i},gt=(r,e,t,i)=>{for(let s=e;s>t-s-1<r instanceof ArrayBuffer?new Uint8Array(r):new Uint8Array(r.buffer,r.byteOffset,r.byteLength),v=new TextEncoder,tt=r=>Object.fromEntries(Object.entries(r).map(([e,t])=>[t,e])),U={bt709:1,bt470bg:5,smpte170m:6},Tt=tt(U),D={bt709:1,smpte170m:6,"iec61966-2-1":13},xt=tt(D),F={rgb:0,bt709:1,bt470bg:5,smpte170m:6},Ct=tt(F),be=r=>!!r&&!!r.primaries&&!!r.transfer&&!!r.matrix&&r.fullRange!==void 0,rt=r=>r instanceof ArrayBuffer||typeof SharedArrayBuffer<"u"&&r instanceof SharedArrayBuffer||ArrayBuffer.isView(r)&&!(r instanceof DataView),K=class{currentPromise=Promise.resolve();async acquire(){let e,t=new Promise(s=>{e=s}),i=this.currentPromise;return this.currentPromise=t,await i,e}},V=r=>{let e=r*(Math.PI/180),t=Math.cos(e),i=Math.sin(e);return[t,i,0,-i,t,0,0,0,1]},it=V(0),wt=r=>[...r].map(e=>e.toString(16).padStart(2,"0")).join(""),St=r=>(r=r>>1&1431655765|(r&1431655765)<<1,r=r>>2&858993459|(r&858993459)<<2,r=r>>4&252645135|(r&252645135)<<4,r=r>>8&16711935|(r&16711935)<<8,r=r>>16&65535|(r&65535)<<16,r>>>0),yt=(r,e,t)=>{let i=0,s=r.length-1,o=-1;for(;i<=s;){let n=i+s>>1,a=t(r[n]);a===e?(o=n,s=n-1):a{let i=-1,s=0,o=r.length-1;for(;s<=o;){let n=s+(o-s+1)/2|0;t(r[n])<=e?(i=n,s=n+1):o=n-1}return i},st=()=>{let r,e;return{promise:new Promise((i,s)=>{r=i,e=s}),resolve:r,reject:e}};var re=/(?:(.+?)\n)?((?:\d{2}:)?\d{2}:\d{2}.\d{3})\s+-->\s+((?:\d{2}:)?\d{2}:\d{2}.\d{3})/g,ar=/^WEBVTT(.|\n)*?\n{2}/,q=/<(?:(\d{2}):)?(\d{2}):(\d{2}).(\d{3})>/g,ke=class{options;preambleText=null;preambleEmitted=!1;constructor(e){this.options=e}parse(e){e=e.replaceAll(`\r
`,`
`).replaceAll("\r",`
-`),Z.lastIndex=0;let e;if(!this.preambleText){if(!kt.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(`
+`),re.lastIndex=0;let t;if(!this.preambleText){if(!ar.test(e)){let s=new Error("WebVTT preamble incorrect.");throw this.options.error(s),s}t=re.exec(e);let i=e.slice(0,t?.index??e.length).trimEnd();if(!i){let s=new Error("No WebVTT preamble provided.");throw this.options.error(s),s}this.preambleText=i,t&&(e=e.slice(t.index),re.lastIndex=0)}for(;t=re.exec(e);){let i=e.slice(0,t.index),s=t[1],o=t.index+t[0].length,n=e.indexOf(`
+`,o)+1,a=e.slice(o,n).trim(),c=e.indexOf(`
-`,o);u===-1&&(u=i.length);let m=ce(e[2]),f=ce(e[3])-m,T=i.slice(n,u).trim();i=i.slice(u).trimStart(),Z.lastIndex=0;let M={timestamp:m/1e3,duration:f/1e3,text:T,identifier:s,settings:a,notes:r},C={};this.preambleEmitted||(C.config={description:this.preambleText},this.preambleEmitted=!0),this.options.output(M,C)}}},gt=/(?:(\d{2}):)?(\d{2}):(\d{2}).(\d{3})/,ce=t=>{let i=gt.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])},le=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=>(_.setUint16(0,t,!1),[p[0],p[1]]),St=t=>(_.setInt16(0,t,!1),[p[0],p[1]]),je=t=>(_.setUint32(0,t,!1),[p[1],p[2],p[3]]),c=t=>(_.setUint32(0,t,!1),[p[0],p[1],p[2],p[3]]),Le=t=>(_.setInt32(0,t,!1),[p[0],p[1],p[2],p[3]]),U=t=>(_.setUint32(0,Math.floor(t/2**32),!1),_.setUint32(4,t,!1),[p[0],p[1],p[2],p[3],p[4],p[5],p[6],p[7]]),De=t=>(_.setInt16(0,2**8*t,!1),[p[0],p[1]]),A=t=>(_.setInt32(0,2**16*t,!1),[p[0],p[1],p[2],p[3]]),Ie=t=>(_.setInt32(0,2**30*t,!1),[p[0],p[1],p[2],p[3]]),Ue=(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()},g=(t,i=!1)=>{let e=Array(t.length).fill(null).map((r,s)=>t.charCodeAt(s));return i&&e.push(0),e},We=t=>{let i=null;for(let e of t)(!i||e.timestamp>i.timestamp)&&(i=e);return i},qe=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]},Ke=qe(0),Xe=t=>[A(t[0]),A(t[1]),Ie(t[2]),A(t[3]),A(t[4]),Ie(t[5]),A(t[6]),A(t[7]),Ie(t[8])],b=(t,i,e)=>({type:t,contents:i&&new Uint8Array(i.flat(10)),children:e}),x=(t,i,e,r,s)=>b(t,[w(i),je(e),r??[]],s),Ge=t=>t.fragmented?b("ftyp",[g("iso5"),c(512),g("iso5"),g("iso6"),g("mp41")]):b("ftyp",[g("isom"),c(512),g("isom"),t.holdsAvc?g("avc1"):[],g("mp41")]),me=t=>({type:"mdat",largeSize:t}),Ye=t=>({type:"free",size:t}),ee=(t,i,e=!1)=>b("moov",void 0,[yt(i,t),...t.map(r=>vt(r,i)),e?sr(t):null]),yt=(t,i)=>{let e=v(Math.max(0,...i.filter(n=>n.samples.length>0).map(n=>{let a=We(n.samples);return a.timestamp+a.duration})),de),r=Math.max(0,...i.map(n=>n.track.id))+1,s=!P(t)||!P(e),o=s?U:c;return x("mvhd",+s,0,[o(t),o(t),c(de),o(e),A(1),De(1),Array(10).fill(0),Xe(Ke),Array(24).fill(0),c(r)])},vt=(t,i)=>b("trak",void 0,[At(t,i),_t(t,i)]),At=(t,i)=>{let e=We(t.samples),r=v(e?e.timestamp+e.duration:0,de),s=!P(i)||!P(r),o=s?U:c,n;if(t.type==="video"){let a=t.track.metadata.rotation;n=a===void 0||typeof a=="number"?qe(a??0):a}else n=Ke;return x("tkhd",+s,3,[o(i),o(i),c(t.track.id),c(0),o(r),Array(8).fill(0),h(0),h(t.track.id),De(t.type==="audio"?1:0),h(0),Xe(n),A(t.type==="video"?t.info.width:0),A(t.type==="video"?t.info.height:0)])},_t=(t,i)=>b("mdia",void 0,[Et(t,i),Vt(t),zt(t)]),Et=(t,i)=>{let e=We(t.samples),r=v(e?e.timestamp+e.duration:0,t.timescale),s=!P(i)||!P(r),o=s?U:c;return x("mdhd",+s,0,[o(i),o(i),c(t.timescale),o(r),h(21956),h(0)])},Ot={video:"vide",audio:"soun",subtitle:"text"},Mt={video:"VideoHandler",audio:"SoundHandler",subtitle:"TextHandler"},Vt=t=>x("hdlr",0,0,[g("mhlr"),g(Ot[t.type]),c(0),c(0),c(0),g(Mt[t.type],!0)]),zt=t=>b("minf",void 0,[Ut[t.type](),Dt(),Ft(t)]),Pt=()=>x("vmhd",0,1,[h(0),h(0),h(0),h(0)]),Bt=()=>x("smhd",0,0,[h(0),h(0)]),It=()=>x("nmhd",0,0),Ut={video:Pt,audio:Bt,subtitle:It},Dt=()=>b("dinf",void 0,[Wt()]),Wt=()=>x("dref",0,0,[c(1)],[Rt()]),Rt=()=>x("url ",0,1),Ft=t=>{let i=t.compositionTimeOffsetTable.length>1||t.compositionTimeOffsetTable.some(e=>e.sampleCompositionTimeOffset!==0);return b("stbl",void 0,[Nt(t),Zt(t),Jt(t),er(t),tr(t),rr(t),i?ir(t):null])},Nt=t=>{let i;return t.type==="video"?i=Ht(fr[t.track.source._codec],t):t.type==="audio"?i=qt(hr[t.track.source._codec],t):t.type==="subtitle"&&(i=Gt(xr[t.track.source._codec],t)),d(i),x("stsd",0,0,[c(1)],[i])},Ht=(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),c(4718592),c(4718592),c(0),h(1),Array(32).fill(0),h(24),St(65535)],[pr[i.track.source._codec](i),ae(i.info.decoderConfig.colorSpace)?Qt(i):null]),Qt=t=>b("colr",[g("nclx"),h(H[t.info.decoderConfig.colorSpace.primaries]),h(Q[t.info.decoderConfig.colorSpace.transfer]),h($[t.info.decoderConfig.colorSpace.matrix]),w((t.info.decoderConfig.colorSpace.fullRange?1:0)<<7)]),$t=t=>t.info.decoderConfig&&b("avcC",[...I(t.info.decoderConfig.description)]),jt=t=>t.info.decoderConfig&&b("hvcC",[...I(t.info.decoderConfig.description)]),$e=t=>{if(!t.info.decoderConfig)return null;let i=t.info.decoderConfig;d(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 x("vpcC",1,0,[w(r),w(s),w(a),w(2),w(2),w(2),h(0)])},Lt=()=>b("av1C",[129,0,0,0]),qt=(t,i)=>b(t,[Array(6).fill(0),h(1),h(0),h(0),c(0),h(i.info.numberOfChannels),h(16),h(0),h(0),A(i.info.sampleRate)],[br[i.track.source._codec](i)]),Kt=t=>{let e=[...I(t.info.decoderConfig.description??new ArrayBuffer(0))];return e=[...w(64),...w(21),...je(0),...c(0),...c(0),...w(5),...Ue(e.length),...e],e=[...h(1),...w(0),...w(4),...Ue(e.length),...e,...w(6),...w(1),...w(2)],e=[...w(3),...Ue(e.length),...e],x("esds",0,0,e)},Xt=t=>{let i=3840,e=0,r=t.info.decoderConfig?.description;if(r){d(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",[w(0),w(t.info.numberOfChannels),h(i),c(t.info.sampleRate),De(e),w(0)])},Gt=(t,i)=>b(t,[Array(6).fill(0),h(1)],[Tr[i.track.source._codec](i)]),Yt=t=>b("vttC",[...y.encode(t.info.config.description)]);var Zt=t=>x("stts",0,0,[c(t.timeToSampleTable.length),t.timeToSampleTable.map(i=>[c(i.sampleCount),c(i.sampleDelta)])]),Jt=t=>{if(t.samples.every(e=>e.type==="key"))return null;let i=[...t.samples.entries()].filter(([,e])=>e.type==="key");return x("stss",0,0,[c(i.length),i.map(([e])=>c(e+1))])},er=t=>x("stsc",0,0,[c(t.compactlyCodedChunkTable.length),t.compactlyCodedChunkTable.map(i=>[c(i.firstChunk),c(i.samplesPerChunk),c(1)])]),tr=t=>x("stsz",0,0,[c(0),c(t.samples.length),t.samples.map(i=>c(i.size))]),rr=t=>t.finalizedChunks.length>0&&S(t.finalizedChunks).offset>=2**32?x("co64",0,0,[c(t.finalizedChunks.length),t.finalizedChunks.map(i=>U(i.offset))]):x("stco",0,0,[c(t.finalizedChunks.length),t.finalizedChunks.map(i=>c(i.offset))]),ir=t=>x("ctts",0,0,[c(t.compositionTimeOffsetTable.length),t.compositionTimeOffsetTable.map(i=>[c(i.sampleCount),c(i.sampleCompositionTimeOffset)])]),sr=t=>b("mvex",void 0,t.map(or)),or=t=>x("trex",0,0,[c(t.track.id),c(1),c(0),c(0),c(0)]),Re=(t,i)=>b("moof",void 0,[nr(t),...i.map(ar)]),nr=t=>x("mfhd",0,0,[c(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},ar=t=>b("traf",void 0,[ur(t),cr(t),lr(t)]),ur=t=>{d(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 x("tfhd",0,i,[c(t.track.id),c(r.duration),c(r.size),c(r.flags)])},cr=t=>(d(t.currentChunk),x("tfdt",1,0,[U(v(t.currentChunk.startTimestamp,t.timescale))])),lr=t=>{d(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=>v(k.timestamp-k.decodeTimestamp,t.timescale)),o=new Set(i),n=new Set(e),a=new Set(r),u=new Set(s),m=a.size===2&&r[0]!==r[1],l=o.size>1,f=n.size>1,T=!m&&a.size>1,M=u.size>1||[...u].some(k=>k!==0),C=0;return C|=1,C|=4*+m,C|=256*+l,C|=512*+f,C|=1024*+T,C|=2048*+M,x("trun",1,C,[c(t.currentChunk.samples.length),c(t.currentChunk.offset-t.currentChunk.moofOffset||0),m?c(r[0]):[],t.currentChunk.samples.map((k,F)=>[l?c(i[F]):[],f?c(e[F]):[],T?c(r[F]):[],M?Le(s[F]):[]])])},Je=t=>b("mfra",void 0,[...t.map(dr),mr()]),dr=(t,i)=>x("tfra",1,0,[c(t.track.id),c(63),c(t.finalizedChunks.length),t.finalizedChunks.map(r=>[U(v(r.startTimestamp,t.timescale)),U(r.moofOffset),c(i+1),c(1),c(1)])]),mr=()=>x("mfro",0,0,[c(0)]),et=()=>b("vtte"),tt=(t,i,e,r,s)=>b("vttc",void 0,[s!==null?b("vsid",[Le(s)]):null,e!==null?b("iden",[...y.encode(e)]):null,i!==null?b("ctim",[...y.encode(le(i))]):null,r!==null?b("sttg",[...y.encode(r)]):null,b("payl",[...y.encode(t)])]),rt=t=>b("vtta",[...y.encode(t)]),fr={avc:"avc1",hevc:"hvc1",vp8:"vp08",vp9:"vp09",av1:"av01"},pr={avc:$t,hevc:jt,vp8:$e,vp9:$e,av1:Lt},hr={aac:"mp4a",opus:"Opus"},br={aac:Kt,opus:Xt},xr={webvtt:"wvtt"},Tr={webvtt:Yt};var L=class{constructor(i){this.mutex=new N;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;sm.start<=r&&rCr){for(let m=0;m=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)}queueChunksForFlush(e=!1){d(this.writer);for(let r=0;r{if(t==="avc"){let o=Math.ceil(i/16)*Math.ceil(e/16),n=it.find(f=>o<=f.maxMacroblocks&&r<=f.maxBitrate)??S(it),a=n?n.level:0,u="64".padStart(2,"0"),m="00",l=a.toString(16).padStart(2,"0");return`avc1.${u}${m}${l}`}else if(t==="hevc"){let s="",n="6",a=i*e,u=st.find(l=>a<=l.maxPictureSize&&r<=l.maxBitrate)??S(st);return`hev1.${s}1.${n}.${u.tier}${u.level}.B0`}else{if(t==="vp8")return"vp8";if(t==="vp9"){let s="00",o=i*e,n=ot.find(u=>o<=u.maxPictureSize&&r<=u.maxBitrate)??S(ot);return`vp09.${s}.${n.level}.08`}else if(t==="av1"){let o=i*e,n=nt.find(u=>o<=u.maxPictureSize&&r<=u.maxBitrate)??S(nt);return`av01.0.${n.level.toString().padStart(2,"0")}${n.tier}.08`}}throw new TypeError(`Unhandled codec '${t}'.`)},ut=(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}'.`)},ct=t=>t==="avc"?{avc:{format:"avc"}}:t==="hevc"?{hevc:{format:"hevc"}}:{},lt=t=>t==="aac"?{aac:{format:"aac"}}:t==="opus"?{opus:{format:"opus"}}:{},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&&!Be(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.")},xe=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&&!Be(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.startsWith("mp4a")&&!t.decoderConfig.description)throw new TypeError("Audio chunk metadata decoder configuration for AAC must include a description, which is expected to be an AudioSpecificConfig as specified in ISO 14496-3.");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,kr=2082844800,v=(t,i,e=!0)=>{let r=t*i;return e?Math.round(r):r},we=class extends L{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)+kr;this.finalizedChunks=[];this.nextFragmentNumber=1;this.writer=e._writer,this.boxWriter=new J(this.writer);let s=this.writer instanceof q?"in-memory":!1;this.fastStart=r._options.fastStart??s,(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(n=>n.track===e);if(s)return s;be(r),d(r),d(r.decoderConfig),d(r.decoderConfig.codedWidth!==void 0),d(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;xe(r),d(r),d(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),d(r),d(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}async addEncodedVideoChunk(e,r,s){let o=await this.mutex.acquire();try{let n=this.getVideoTrackData(e,s),a=new Uint8Array(r.byteLength);r.copyTo(a);let u=this.validateAndNormalizeTimestamp(n.track,r.timestamp,r.type==="key"),m=this.createSampleForTrack(n,a,u,(r.duration??0)/1e6,r.type);await this.registerSample(n,m)}finally{o()}}async addEncodedAudioChunk(e,r,s){let o=await this.mutex.acquire();try{let n=this.getAudioTrackData(e,s),a=new Uint8Array(r.byteLength);r.copyTo(a);let u=r.type,m=this.validateAndNormalizeTimestamp(n.track,r.timestamp,u==="key"),l=this.createSampleForTrack(n,a,m,(r.duration??0)/1e6,u);await this.registerSample(n,l)}finally{o()}}async addSubtitleCue(e,r,s){let o=await this.mutex.acquire();try{let n=this.getSubtitleTrackData(e,s);this.validateAndNormalizeTimestamp(n.track,1e6*r.timestamp,!0),e.source._codec==="webvtt"&&(n.cueQueue.push(r),await this.processWebVTTCues(n,r.timestamp))}finally{o()}}async processWebVTTCues(e,r){for(;e.cueQueue.length>0;){let s=new Set([]);for(let l of e.cueQueue)d(l.timestamp<=r),d(e.lastCueEndTimestamp<=l.timestamp+l.duration),s.add(Math.max(l.timestamp,e.lastCueEndTimestamp)),s.add(l.timestamp+l.duration);let o=[...s].sort((l,f)=>l-f),n=o[0],a=o[1]??n;if(r=a)break;j.lastIndex=0;let T=j.test(f.text),M=f.timestamp+f.duration,C=e.cueToSourceId.get(f);if(C===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,await this.finalizeFragment())}else s=o>=.5}s&&(e.currentChunk&&await this.finalizeCurrentChunk(e),e.currentChunk={startTimestamp:r.timestamp,samples:[],offset:null,moofOffset:null}),d(e.currentChunk),e.currentChunk.samples.push(r),e.timestampProcessingQueue.push(r)}async finalizeCurrentChunk(e){if(d(this.fastStart!=="fragmented"),!!e.currentChunk){if(e.finalizedChunks.push(e.currentChunk),this.finalizedChunks.push(e.currentChunk),(e.compactlyCodedChunkTable.length===0||S(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)d(r.data),this.writer.write(r.data),r.data=null;await this.writer.flush()}}async interleaveSamples(){d(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,l=this.boxWriter.measureBox(u)+m),u.size=l,this.boxWriter.writeBox(u)}for(let u of this.trackDatas){u.currentChunk.offset=this.writer.getPos(),u.currentChunk.moofOffset=s;for(let m of u.currentChunk.samples)this.writer.write(m.data),m.data=null}let n=this.writer.getPos();this.writer.seek(this.boxWriter.offsets.get(o));let a=Re(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&&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"){d(this.mdat);let r;for(let o=0;o<2;o++){let n=ee(this.trackDatas,this.creationTime),a=this.boxWriter.measureBox(n);r=this.boxWriter.measureBox(this.mdat);let u=this.writer.getPos()+a+r;for(let m of this.finalizedChunks){m.offset=u;for(let{data:l}of m.samples)d(l),u+=l.byteLength,r+=l.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 n of o.samples)d(n.data),this.writer.write(n.data),n.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{d(this.mdat),d(this.ftypSize!==null);let r=this.boxWriter.offsets.get(this.mdat);d(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 n=r-this.writer.getPos();this.boxWriter.writeBox(Ye(n))}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 Fe=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,dt=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,mt="https://github.com/Vanilagy/webm-muxer",ft=6,pt=5,gr={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"},Sr={video:1,audio:2,subtitle:17},Ce=class extends L{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=Fe(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=dt(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??Fe(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:mt},{id:22337,data:mt},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:Sr[r.type]},{id:134,data:gr[r.track.source._codec]},r.type==="video"?this.videoSpecificTrackInfo(r):null,r.type==="audio"?this.audioSpecificTrackInfo(r):null,r.type==="subtitle"?this.subtitleSpecificTrackInfo(r):null]})}videoSpecificTrackInfo(e){let r=[e.info.decoderConfig.description?{id:25506,data:I(e.info.decoderConfig.description)}:null,e.track.metadata.frameRate?{id:2352003,data:1e9/e.track.metadata.frameRate}:null],s=e.info.decoderConfig.colorSpace,o={id:224,data:[{id:176,data:e.info.width},{id:186,data:e.info.height},ae(s)?{id:21936,data:[{id:21937,data:$[s.matrix]},{id:21946,data:Q[s.transfer]},{id:21947,data:H[s.primaries]},{id:21945,data:s.fullRange?2:1}]}:null]};return r.push(o),r}audioSpecificTrackInfo(e){return[e.info.decoderConfig.description?{id:25506,data:I(e.info.decoderConfig.description)}:null,{id:225,data:[{id:181,data:new re(e.info.sampleRate)},{id:159,data:e.info.numberOfChannels}]}]}subtitleSpecificTrackInfo(e){return[{id:25506,data:y.encode(e.info.config.description)}]}createSegment(){let e={id:408125543,size:this.format._options.streamable?-1:ft,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 d(this.segment),this.dataOffsets.get(this.segment)}getVideoTrackData(e,r){let s=this.trackDatas.find(n=>n.track===e);if(s)return s;be(r),d(r),d(r.decoderConfig),d(r.decoderConfig.codedWidth!==void 0),d(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;xe(r),d(r),d(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),d(r),d(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}async addEncodedVideoChunk(e,r,s){let o=await this.mutex.acquire();try{let n=this.getVideoTrackData(e,s),a=new Uint8Array(r.byteLength);r.copyTo(a);let u=r.type==="key",m=this.validateAndNormalizeTimestamp(n.track,r.timestamp,u),l=this.createInternalChunk(a,m,(r.duration??0)/1e6,r.type);e.source._codec==="vp9"&&this.fixVP9ColorSpace(n,l),n.chunkQueue.push(l),await this.interleaveChunks()}finally{o()}}async addEncodedAudioChunk(e,r,s){let o=await this.mutex.acquire();try{let n=this.getAudioTrackData(e,s),a=new Uint8Array(r.byteLength);r.copyTo(a);let u=r.type,m=u==="key",l=this.validateAndNormalizeTimestamp(n.track,r.timestamp,m),f=this.createInternalChunk(a,l,(r.duration??0)/1e6,u);n.chunkQueue.push(f),await this.interleaveChunks()}finally{o()}}async addSubtitleCue(e,r,s){let o=await this.mutex.acquire();try{let n=this.getSubtitleTrackData(e,s),a=this.validateAndNormalizeTimestamp(n.track,1e6*r.timestamp,!0),u=r.text,m=Math.floor(a*1e3);j.lastIndex=0,u=u.replace(j,M=>{let k=ce(M.slice(1,-1))-m;return`<${le(k)}>`});let l=y.encode(u),f=`${r.settings??""}
-${r.identifier??""}
-${r.notes??""}`,T=this.createInternalChunk(l,a,r.duration,"key",f.trim()?y.encode(f):null);n.chunkQueue.push(T),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 m={rgb:7,bt709:2,bt470bg:1,smpte170m:3}[e.info.decoderConfig.colorSpace.matrix];Qe(r.data,s+0,s+3,m)}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(f=>{if(f.track.source._closed)return!0;if(e===f)return r.type==="key";let T=f.chunkQueue[0];return T&&T.type==="key"});(!this.currentCluster||o&&s-this.currentClusterMsTimestamp>=1e3)&&this.createNewCluster(s);let n=s-this.currentClusterMsTimestamp;if(n<0)return;if(n>=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),m=new DataView(u.buffer);m.setUint8(0,128|e.track.id),m.setInt16(1,n,!1);let l=Math.floor(1e3*r.duration);if(l===0&&!r.additions){m.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,l>0?{id:155,data:l}:null]};this.writeEBML(f)}this.duration=Math.max(this.duration,s+l),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:pt,data:[{id:231,data:e}]},this.writeEBML(this.currentCluster),this.currentClusterMsTimestamp=e,this.trackDatasInCurrentCluster.clear()}finalizeCurrentCluster(){d(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,pt),this.writer.seek(r);let s=this.offsets.get(this.currentCluster)-this.segmentDataOffset;d(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(),d(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,ft),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 z=class{},ke=class extends z{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 we(i,this)}},se=class extends z{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 Ce(i,this)}},D=class extends se{};var G=["avc","hevc","vp8","vp9","av1"],Y=["aac","opus"],ge=["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)}},E=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}},Se=class extends E{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)}},yr=5,vr=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;vr(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),d(this.encoder);let e=Math.floor(i.timestamp/1e6/yr);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)=>void this.muxer.addEncodedVideoChunk(this.source._connectedTrack,e,r),error:e=>console.error("Video encode error:",e)}),this.encoder.configure({codec:at(this.codecConfig.codec,i.codedWidth,i.codedHeight,this.codecConfig.bitrate),width:i.codedWidth,height:i.codedHeight,bitrate:this.codecConfig.bitrate,framerate:this.source._connectedTrack?.metadata.frameRate,latencyMode:this.codecConfig.latencyMode,...ct(this.codecConfig.codec)}),d(this.source._connectedTrack),this.muxer=this.source._connectedTrack.output._muxer)}async flush(){this.encoder&&(await this.encoder.flush(),this.encoder.close())}},ye=class extends E{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 E{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 E{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)}},Ar=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.")},ne=class{constructor(i,e){this.source=i;this.codecConfig=e;this.encoder=null;this.muxer=null;this.lastNumberOfChannels=null;this.lastSampleRate=null;Ar(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),d(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)=>void this.muxer.addEncodedAudioChunk(this.source._connectedTrack,e,r),error:e=>console.error("Audio encode error:",e)}),this.encoder.configure({codec:ut(this.codecConfig.codec,i.numberOfChannels,i.sampleRate),numberOfChannels:i.numberOfChannels,sampleRate:i.sampleRate,bitrate:this.codecConfig.bitrate,...lt(this.codecConfig.codec)}),d(this.source._connectedTrack),this.muxer=this.source._connectedTrack.output._muxer)}async flush(){this.encoder&&(await this.encoder.flush(),this.encoder.close())}},Ee=class extends O{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.");return this._encoder.digest(i)}_flush(){return this._encoder.flush()}},Oe=class extends O{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 m=0;m{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(!ge.includes(e))throw new TypeError(`Invalid subtitle codec '${e}'. Must be one of: ${ge.join(", ")}.`);this._codec=e}},Ve=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 ze=class{constructor(i){this._tracks=[];this._started=!1;this._finalizing=!1;this._mutex=new N;if(!i||typeof i!="object")throw new TypeError("options must be an object.");if(!(i.format instanceof z))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 E))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 Ct(_r);})();
+`,o);c===-1&&(c=e.length);let u=ge(t[2]),f=ge(t[3])-u,T=e.slice(n,c).trim();e=e.slice(c).trimStart(),re.lastIndex=0;let C={timestamp:u/1e3,duration:f/1e3,text:T,identifier:s,settings:a,notes:i},k={};this.preambleEmitted||(k.config={description:this.preambleText},this.preambleEmitted=!0),this.options.output(C,k)}}},cr=/(?:(\d{2}):)?(\d{2}):(\d{2}).(\d{3})/,ge=r=>{let e=cr.exec(r);if(!e)throw new Error("Expected match.");return 60*60*1e3*Number(e[1]||"0")+60*1e3*Number(e[2])+1e3*Number(e[3])+Number(e[4])},Te=r=>{let e=Math.floor(r/36e5),t=Math.floor(r%(60*60*1e3)/(60*1e3)),i=Math.floor(r%(60*1e3)/1e3),s=r%1e3;return e.toString().padStart(2,"0")+":"+t.toString().padStart(2,"0")+":"+i.toString().padStart(2,"0")+"."+s.toString().padStart(3,"0")};var ie=class{constructor(e){this.writer=e}helper=new Uint8Array(8);helperView=new DataView(this.helper.buffer);offsets=new WeakMap;writeU32(e){this.helperView.setUint32(0,e,!1),this.writer.write(this.helper.subarray(0,4))}writeU64(e){this.helperView.setUint32(0,Math.floor(e/2**32),!1),this.helperView.setUint32(4,e,!1),this.writer.write(this.helper.subarray(0,8))}writeAscii(e){for(let t=0;t[(r%256+256)%256],p=r=>(E.setUint16(0,r,!1),[h[0],h[1]]),ur=r=>(E.setInt16(0,r,!1),[h[0],h[1]]),_t=r=>(E.setUint32(0,r,!1),[h[1],h[2],h[3]]),m=r=>(E.setUint32(0,r,!1),[h[0],h[1],h[2],h[3]]),At=r=>(E.setInt32(0,r,!1),[h[0],h[1],h[2],h[3]]),N=r=>(E.setUint32(0,Math.floor(r/2**32),!1),E.setUint32(4,r,!1),[h[0],h[1],h[2],h[3],h[4],h[5],h[6],h[7]]),at=r=>(E.setInt16(0,2**8*r,!1),[h[0],h[1]]),A=r=>(E.setInt32(0,2**16*r,!1),[h[0],h[1],h[2],h[3]]),ot=r=>(E.setInt32(0,2**30*r,!1),[h[0],h[1],h[2],h[3]]),nt=(r,e)=>{let t=[],i=r;do{let s=i&127;i>>=7,t.length>0&&(s|=128),t.push(s),e!==void 0&&e--}while(i>0||e);return t.reverse()},S=(r,e=!1)=>{let t=Array(r.length).fill(null).map((i,s)=>r.charCodeAt(s));return e&&t.push(0),t},ct=r=>{let e=null;for(let t of r)(!e||t.timestamp>e.timestamp)&&(e=t);return e},Et=r=>[A(r[0]),A(r[1]),ot(r[2]),A(r[3]),A(r[4]),ot(r[5]),A(r[6]),A(r[7]),ot(r[8])],b=(r,e,t)=>({type:r,contents:e&&new Uint8Array(e.flat(10)),children:t}),g=(r,e,t,i,s)=>b(r,[x(e),_t(t),i??[]],s),It=r=>r.fragmented?b("ftyp",[S("iso5"),m(512),S("iso5"),S("iso6"),S("mp41")]):b("ftyp",[S("isom"),m(512),S("isom"),r.holdsAvc?S("avc1"):[],S("mp41")]),Ce=r=>({type:"mdat",largeSize:r}),Ot=r=>({type:"free",size:r}),se=(r,e,t=!1)=>b("moov",void 0,[dr(e,r),...r.map(i=>lr(i,e)),t?$r(r):null]),dr=(r,e)=>{let t=_(Math.max(0,...e.filter(n=>n.samples.length>0).map(n=>{let a=ct(n.samples);return a.timestamp+a.duration})),xe),i=Math.max(0,...e.map(n=>n.track.id))+1,s=!P(r)||!P(t),o=s?N:m;return g("mvhd",+s,0,[o(r),o(r),m(xe),o(t),A(1),at(1),Array(10).fill(0),Et(it),Array(24).fill(0),m(i)])},lr=(r,e)=>b("trak",void 0,[mr(r,e),fr(r,e)]),mr=(r,e)=>{let t=ct(r.samples),i=_(t?t.timestamp+t.duration:0,xe),s=!P(e)||!P(i),o=s?N:m,n;if(r.type==="video"){let a=r.track.metadata.rotation;n=a===void 0||typeof a=="number"?V(a??0):a}else n=it;return g("tkhd",+s,3,[o(e),o(e),m(r.track.id),m(0),o(i),Array(8).fill(0),p(0),p(r.track.id),at(r.type==="audio"?1:0),p(0),Et(n),A(r.type==="video"?r.info.width:0),A(r.type==="video"?r.info.height:0)])},fr=(r,e)=>b("mdia",void 0,[hr(r,e),kr(r),gr(r)]),hr=(r,e)=>{let t=ct(r.samples),i=_(t?t.timestamp+t.duration:0,r.timescale),s=!P(e)||!P(i),o=s?N:m;return g("mdhd",+s,0,[o(e),o(e),m(r.timescale),o(i),p(21956),p(0)])},pr={video:"vide",audio:"soun",subtitle:"text"},br={video:"VideoHandler",audio:"SoundHandler",subtitle:"TextHandler"},kr=r=>g("hdlr",0,0,[S("mhlr"),S(pr[r.type]),m(0),m(0),m(0),S(br[r.type],!0)]),gr=r=>b("minf",void 0,[wr[r.type](),Sr(),_r(r)]),Tr=()=>g("vmhd",0,1,[p(0),p(0),p(0),p(0)]),xr=()=>g("smhd",0,0,[p(0),p(0)]),Cr=()=>g("nmhd",0,0),wr={video:Tr,audio:xr,subtitle:Cr},Sr=()=>b("dinf",void 0,[yr()]),yr=()=>g("dref",0,0,[m(1)],[vr()]),vr=()=>g("url ",0,1),_r=r=>{let e=r.compositionTimeOffsetTable.length>1||r.compositionTimeOffsetTable.some(t=>t.sampleCompositionTimeOffset!==0);return b("stbl",void 0,[Ar(r),Dr(r),Fr(r),Wr(r),Nr(r),Hr(r),e?Lr(r):null])},Ar=r=>{let e;return r.type==="video"?e=Er(Jr[r.track.source._codec],r):r.type==="audio"?e=Rr(ti[r.track.source._codec],r):r.type==="subtitle"&&(e=Br(ii[r.track.source._codec],r)),l(e),g("stsd",0,0,[m(1)],[e])},Er=(r,e)=>b(r,[Array(6).fill(0),p(1),p(0),p(0),Array(12).fill(0),p(e.info.width),p(e.info.height),m(4718592),m(4718592),m(0),p(1),Array(32).fill(0),p(24),ur(65535)],[ei[e.track.source._codec](e),be(e.info.decoderConfig.colorSpace)?Ir(e):null]),Ir=r=>b("colr",[S("nclx"),p(U[r.info.decoderConfig.colorSpace.primaries]),p(D[r.info.decoderConfig.colorSpace.transfer]),p(F[r.info.decoderConfig.colorSpace.matrix]),x((r.info.decoderConfig.colorSpace.fullRange?1:0)<<7)]),Or=r=>r.info.decoderConfig&&b("avcC",[...B(r.info.decoderConfig.description)]),Vr=r=>r.info.decoderConfig&&b("hvcC",[...B(r.info.decoderConfig.description)]),vt=r=>{if(!r.info.decoderConfig)return null;let e=r.info.decoderConfig;l(e.colorSpace);let t=e.codec.split("."),i=Number(t[1]),s=Number(t[2]),a=(Number(t[3])<<4)+(0<<1)+Number(e.colorSpace.fullRange);return g("vpcC",1,0,[x(i),x(s),x(a),x(2),x(2),x(2),p(0)])},Mr=()=>b("av1C",[129,0,0,0]),Rr=(r,e)=>b(r,[Array(6).fill(0),p(1),p(0),p(0),m(0),p(e.info.numberOfChannels),p(16),p(0),p(0),A(e.info.sampleRate)],[ri[e.track.source._codec](e)]),Pr=r=>{let t=[...B(r.info.decoderConfig.description??new ArrayBuffer(0))];return t=[...x(64),...x(21),..._t(0),...m(0),...m(0),...x(5),...nt(t.length),...t],t=[...p(1),...x(0),...x(4),...nt(t.length),...t,...x(6),...x(1),...x(2)],t=[...x(3),...nt(t.length),...t],g("esds",0,0,t)},zr=r=>{let e=3840,t=0,i=r.info.decoderConfig?.description;if(i){l(i.byteLength>=18);let s=ArrayBuffer.isView(i)?new DataView(i.buffer,i.byteOffset,i.byteLength):new DataView(i);e=s.getUint16(10,!0),t=s.getInt16(14,!0)}return b("dOps",[x(0),x(r.info.numberOfChannels),p(e),m(r.info.sampleRate),at(t),x(0)])},Br=(r,e)=>b(r,[Array(6).fill(0),p(1)],[si[e.track.source._codec](e)]),Ur=r=>b("vttC",[...v.encode(r.info.config.description)]);var Dr=r=>g("stts",0,0,[m(r.timeToSampleTable.length),r.timeToSampleTable.map(e=>[m(e.sampleCount),m(e.sampleDelta)])]),Fr=r=>{if(r.samples.every(t=>t.type==="key"))return null;let e=[...r.samples.entries()].filter(([,t])=>t.type==="key");return g("stss",0,0,[m(e.length),e.map(([t])=>m(t+1))])},Wr=r=>g("stsc",0,0,[m(r.compactlyCodedChunkTable.length),r.compactlyCodedChunkTable.map(e=>[m(e.firstChunk),m(e.samplesPerChunk),m(1)])]),Nr=r=>g("stsz",0,0,[m(0),m(r.samples.length),r.samples.map(e=>m(e.size))]),Hr=r=>r.finalizedChunks.length>0&&y(r.finalizedChunks).offset>=2**32?g("co64",0,0,[m(r.finalizedChunks.length),r.finalizedChunks.map(e=>N(e.offset))]):g("stco",0,0,[m(r.finalizedChunks.length),r.finalizedChunks.map(e=>m(e.offset))]),Lr=r=>g("ctts",0,0,[m(r.compositionTimeOffsetTable.length),r.compositionTimeOffsetTable.map(e=>[m(e.sampleCount),m(e.sampleCompositionTimeOffset)])]),$r=r=>b("mvex",void 0,r.map(Qr)),Qr=r=>g("trex",0,0,[m(r.track.id),m(1),m(0),m(0),m(0)]),ut=(r,e)=>b("moof",void 0,[jr(r),...e.map(Kr)]),jr=r=>g("mfhd",0,0,[m(r)]),Vt=r=>{let e=0,t=0,i=0,s=0,o=r.type==="delta";return t|=+o,o?e|=1:e|=2,e<<24|t<<16|i<<8|s},Kr=r=>b("traf",void 0,[qr(r),Xr(r),Gr(r)]),qr=r=>{l(r.currentChunk);let e=0;e|=8,e|=16,e|=32,e|=131072;let t=r.currentChunk.samples[1]??r.currentChunk.samples[0],i={duration:t.timescaleUnitsToNextSample,size:t.size,flags:Vt(t)};return g("tfhd",0,e,[m(r.track.id),m(i.duration),m(i.size),m(i.flags)])},Xr=r=>(l(r.currentChunk),g("tfdt",1,0,[N(_(r.currentChunk.startTimestamp,r.timescale))])),Gr=r=>{l(r.currentChunk);let e=r.currentChunk.samples.map(w=>w.timescaleUnitsToNextSample),t=r.currentChunk.samples.map(w=>w.size),i=r.currentChunk.samples.map(Vt),s=r.currentChunk.samples.map(w=>_(w.timestamp-w.decodeTimestamp,r.timescale)),o=new Set(e),n=new Set(t),a=new Set(i),c=new Set(s),u=a.size===2&&i[0]!==i[1],d=o.size>1,f=n.size>1,T=!u&&a.size>1,C=c.size>1||[...c].some(w=>w!==0),k=0;return k|=1,k|=4*+u,k|=256*+d,k|=512*+f,k|=1024*+T,k|=2048*+C,g("trun",1,k,[m(r.currentChunk.samples.length),m(r.currentChunk.offset-r.currentChunk.moofOffset||0),u?m(i[0]):[],r.currentChunk.samples.map((w,pe)=>[d?m(e[pe]):[],f?m(t[pe]):[],T?m(i[pe]):[],C?At(s[pe]):[]])])},Mt=r=>b("mfra",void 0,[...r.map(Yr),Zr()]),Yr=(r,e)=>g("tfra",1,0,[m(r.track.id),m(63),m(r.finalizedChunks.length),r.finalizedChunks.map(i=>[N(_(i.startTimestamp,r.timescale)),N(i.moofOffset),m(e+1),m(1),m(1)])]),Zr=()=>g("mfro",0,0,[m(0)]),Rt=()=>b("vtte"),Pt=(r,e,t,i,s)=>b("vttc",void 0,[s!==null?b("vsid",[At(s)]):null,t!==null?b("iden",[...v.encode(t)]):null,e!==null?b("ctim",[...v.encode(Te(e))]):null,i!==null?b("sttg",[...v.encode(i)]):null,b("payl",[...v.encode(r)])]),zt=r=>b("vtta",[...v.encode(r)]),Jr={avc:"avc1",hevc:"hvc1",vp8:"vp08",vp9:"vp09",av1:"av01"},ei={avc:Or,hevc:Vr,vp8:vt,vp9:vt,av1:Mr},ti={aac:"mp4a",opus:"Opus"},ri={aac:Pr,opus:zr},ii={webvtt:"wvtt"},si={webvtt:Ur};var X=class{output;mutex=new K;constructor(e){this.output=e}beforeTrackAdd(e){}onTrackClose(e){}trackTimestampInfo=new WeakMap;validateAndNormalizeTimestamp(e,t,i){let s=t/1e6,o=this.trackTimestampInfo.get(e);if(!o){if(!i)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:e.source._offsetTimestamps?0:s,lastKeyFrameTimestamp:e.source._offsetTimestamps?0:s},this.trackTimestampInfo.set(e,o)}if(e.source._offsetTimestamps&&(s-=o.timestampOffset),s<0)throw new Error(`Timestamps must be non-negative (got ${s}s).`);if(si.start-s.start);e.push({start:t[0].start,size:t[0].data.byteLength});for(let i=1;ic.start<=t&&tni){for(let c=0;c=e.written[o+1].start;)e.written[o].end=Math.max(e.written[o].end,e.written[o+1].end),e.written.splice(o+1,1)}createChunk(e){let i={start:Math.floor(e/this.chunkSize)*this.chunkSize,data:new Uint8Array(this.chunkSize),written:[],shouldFlush:!1};return this.chunks.push(i),this.chunks.sort((s,o)=>s.start-o.start),this.chunks.indexOf(i)}queueChunksForFlush(e=!1){l(this.writer);for(let t=0;t{if(r==="avc"){let o=Math.ceil(e/16)*Math.ceil(t/16),n=Bt.find(f=>o<=f.maxMacroblocks&&i<=f.maxBitrate)??y(Bt),a=n?n.level:0,c="64".padStart(2,"0"),u="00",d=a.toString(16).padStart(2,"0");return`avc1.${c}${u}${d}`}else if(r==="hevc"){let s="",n="6",a=e*t,c=Ut.find(d=>a<=d.maxPictureSize&&i<=d.maxBitrate)??y(Ut);return`hev1.${s}1.${n}.${c.tier}${c.level}.B0`}else{if(r==="vp8")return"vp8";if(r==="vp9"){let s="00",o=e*t,n=Dt.find(c=>o<=c.maxPictureSize&&i<=c.maxBitrate)??y(Dt);return`vp09.${s}.${n.level}.08`}else if(r==="av1"){let o=e*t,n=Ft.find(c=>o<=c.maxPictureSize&&i<=c.maxBitrate)??y(Ft);return`av01.0.${n.level.toString().padStart(2,"0")}${n.tier}.08`}}throw new TypeError(`Unhandled codec '${r}'.`)},Nt=(r,e)=>{if(r==="avc"){if(!e||e.byteLength<4)throw new TypeError("AVC description must be at least 4 bytes long.");return`avc1.${wt(e.subarray(1,4))}`}else if(r==="hevc"){if(!e)throw new TypeError("HEVC description must be provided.");let t=new DataView(e.buffer,e.byteOffset,e.byteLength),i="hev1.",s=e[1]>>6&3,o=e[1]&31;i+=["","A","B","C"][s]+o,i+=".";let n=St(t.getUint32(2));i+=n.toString(16),i+=".";let a=e[1]>>5&1,c=e[12];i+=a===0?"L":"H",i+=c,i+=".";let u=[];for(let d=0;d<6;d++){let f=e[d+13];u.push(f)}for(;u[u.length-1]===0;)u.pop();return i+=u.map(d=>d.toString(16)).join("."),i}throw new TypeError(`Unhandled codec '${r}'.`)},Ht=(r,e,t)=>{if(r==="aac")return e>=2&&t<=24e3?"mp4a.40.29":t<=24e3?"mp4a.40.5":"mp4a.40.2";if(r==="opus")return"opus";if(r==="vorbis")return"vorbis";throw new TypeError(`Unhandled codec '${r}'.`)},Lt=(r,e)=>{if(r==="aac"){if(!e||e.byteLength<2)throw new TypeError("AAC description must be at least 2 bytes long.");return`mp4a.40.${e[0]>>3}`}else{if(r==="opus")return"opus";if(r==="vorbis")return"vorbis"}throw new TypeError(`Unhandled codec '${r}'.`)},$t=r=>r==="avc"?{avc:{format:"avc"}}:r==="hevc"?{hevc:{format:"hevc"}}:{},Qt=r=>r==="aac"?{aac:{format:"aac"}}:r==="opus"?{opus:{format:"opus"}}:{},ve=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&&!rt(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:e}=r.decoderConfig;if(typeof e!="object")throw new TypeError("Video chunk metadata decoder configuration colorSpace, when provided, must be an object.");let t=Object.keys(U);if(e.primaries!=null&&!t.includes(e.primaries))throw new TypeError(`Video chunk metadata decoder configuration colorSpace primaries, when defined, must be one of ${t.join(", ")}.`);let i=Object.keys(D);if(e.transfer!=null&&!i.includes(e.transfer))throw new TypeError(`Video chunk metadata decoder configuration colorSpace transfer, when defined, must be one of ${i.join(", ")}.`);let s=Object.keys(F);if(e.matrix!=null&&!s.includes(e.matrix))throw new TypeError(`Video chunk metadata decoder configuration colorSpace matrix, when defined, must be one of ${s.join(", ")}.`);if(e.fullRange!=null&&typeof e.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.")},_e=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&&!rt(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.startsWith("mp4a")&&!r.decoderConfig.description)throw new TypeError("Audio chunk metadata decoder configuration for AAC must include a description, which is expected to be an AudioSpecificConfig as specified in ISO 14496-3.");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.")},Ae=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 xe=1e3,ai=2082844800,_=(r,e,t=!0)=>{let i=r*e;return t?Math.round(i):i},Ee=class extends X{timestampsMustStartAtZero=!0;writer;boxWriter;fastStart;auxTarget=new Y;auxWriter=this.auxTarget._createWriter();auxBoxWriter=new ie(this.auxWriter);ftypSize=null;mdat=null;trackDatas=[];creationTime=Math.floor(Date.now()/1e3)+ai;finalizedChunks=[];nextFragmentNumber=1;constructor(e,t){super(e),this.writer=e._writer,this.boxWriter=new ie(this.writer);let i=this.writer instanceof G?"in-memory":!1;this.fastStart=t._options.fastStart??i,(this.fastStart==="in-memory"||this.fastStart==="fragmented")&&(this.writer.ensureMonotonicity=!0)}async start(){let e=await this.mutex.acquire(),t=this.output._tracks.some(i=>i.type==="video"&&i.source._codec==="avc");this.boxWriter.writeBox(It({holdsAvc:t,fragmented:this.fastStart==="fragmented"})),this.ftypSize=this.writer.getPos(),this.fastStart==="in-memory"?this.mdat=Ce(!1):this.fastStart==="fragmented"||(this.mdat=Ce(!0),this.boxWriter.writeBox(this.mdat)),await this.writer.flush(),e()}getVideoTrackData(e,t){let i=this.trackDatas.find(o=>o.track===e);if(i)return i;ve(t),l(t),l(t.decoderConfig),l(t.decoderConfig.codedWidth!==void 0),l(t.decoderConfig.codedHeight!==void 0);let s={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(s),this.trackDatas.sort((o,n)=>o.track.id-n.track.id),s}getAudioTrackData(e,t){let i=this.trackDatas.find(o=>o.track===e);if(i)return i;_e(t),l(t),l(t.decoderConfig);let s={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(s),this.trackDatas.sort((o,n)=>o.track.id-n.track.id),s}getSubtitleTrackData(e,t){let i=this.trackDatas.find(o=>o.track===e);if(i)return i;Ae(t),l(t),l(t.config);let s={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(s),this.trackDatas.sort((o,n)=>o.track.id-n.track.id),this.validateAndNormalizeTimestamp(e,0,!0),s}async addEncodedVideoChunk(e,t,i){let s=await this.mutex.acquire();try{let o=this.getVideoTrackData(e,i),n=new Uint8Array(t.byteLength);t.copyTo(n);let a=this.validateAndNormalizeTimestamp(o.track,t.timestamp,t.type==="key"),c=this.createSampleForTrack(o,n,a,(t.duration??0)/1e6,t.type);await this.registerSample(o,c)}finally{s()}}async addEncodedAudioChunk(e,t,i){let s=await this.mutex.acquire();try{let o=this.getAudioTrackData(e,i),n=new Uint8Array(t.byteLength);t.copyTo(n);let a=t.type,c=this.validateAndNormalizeTimestamp(o.track,t.timestamp,a==="key"),u=this.createSampleForTrack(o,n,c,(t.duration??0)/1e6,a);await this.registerSample(o,u)}finally{s()}}async addSubtitleCue(e,t,i){let s=await this.mutex.acquire();try{let o=this.getSubtitleTrackData(e,i);this.validateAndNormalizeTimestamp(o.track,1e6*t.timestamp,!0),e.source._codec==="webvtt"&&(o.cueQueue.push(t),await this.processWebVTTCues(o,t.timestamp))}finally{s()}}async processWebVTTCues(e,t){for(;e.cueQueue.length>0;){let i=new Set([]);for(let u of e.cueQueue)l(u.timestamp<=t),l(e.lastCueEndTimestamp<=u.timestamp+u.duration),i.add(Math.max(u.timestamp,e.lastCueEndTimestamp)),i.add(u.timestamp+u.duration);let s=[...i].sort((u,d)=>u-d),o=s[0],n=s[1]??o;if(t=n)break;q.lastIndex=0;let f=q.test(d.text),T=d.timestamp+d.duration,C=e.cueToSourceId.get(d);if(C===void 0&&ni.timestamp).sort((i,s)=>i-s);for(let i=0;i{if(e===n)return t.type==="key";let a=n.sampleQueue[0];return a&&a.type==="key"});s>=1&&o&&(i=!0,await this.finalizeFragment())}else i=s>=.5}i&&(e.currentChunk&&await this.finalizeCurrentChunk(e),e.currentChunk={startTimestamp:t.timestamp,samples:[],offset:null,moofOffset:null}),l(e.currentChunk),e.currentChunk.samples.push(t),e.timestampProcessingQueue.push(t)}async finalizeCurrentChunk(e){if(l(this.fastStart!=="fragmented"),!!e.currentChunk){if(e.finalizedChunks.push(e.currentChunk),this.finalizedChunks.push(e.currentChunk),(e.compactlyCodedChunkTable.length===0||y(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)l(t.data),this.writer.write(t.data),t.data=null;await this.writer.flush()}}async interleaveSamples(){l(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 s of this.trackDatas){if(s.sampleQueue.length===0&&!s.track.source._closed)break e;s.sampleQueue.length>0&&s.sampleQueue[0].timestamp=2**32&&(a.largeSize=!0,u=this.boxWriter.measureBox(a)+c),a.size=u,this.boxWriter.writeBox(a)}for(let a of this.trackDatas){a.currentChunk.offset=this.writer.getPos(),a.currentChunk.moofOffset=i;for(let c of a.currentChunk.samples)this.writer.write(c.data),c.data=null}let o=this.writer.getPos();this.writer.seek(this.boxWriter.offsets.get(s));let n=ut(t,this.trackDatas);this.boxWriter.writeBox(n),this.writer.seek(o);for(let a of this.trackDatas)a.finalizedChunks.push(a.currentChunk),this.finalizedChunks.push(a.currentChunk),a.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 i=this.trackDatas.find(s=>s.track===e);i&&await this.processWebVTTCues(i,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 i of t.sampleQueue)await this.addSampleToTrack(t,i);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"){l(this.mdat);let t;for(let s=0;s<2;s++){let o=se(this.trackDatas,this.creationTime),n=this.boxWriter.measureBox(o);t=this.boxWriter.measureBox(this.mdat);let a=this.writer.getPos()+n+t;for(let c of this.finalizedChunks){c.offset=a;for(let{data:u}of c.samples)l(u),a+=u.byteLength,t+=u.byteLength}if(a<2**32)break;t>=2**32&&(this.mdat.largeSize=!0)}let i=se(this.trackDatas,this.creationTime);this.boxWriter.writeBox(i),this.mdat.size=t,this.boxWriter.writeBox(this.mdat);for(let s of this.finalizedChunks)for(let o of s.samples)l(o.data),this.writer.write(o.data),o.data=null}else if(this.fastStart==="fragmented"){let t=this.writer.getPos(),i=Mt(this.trackDatas);this.boxWriter.writeBox(i);let s=this.writer.getPos()-t;this.writer.seek(this.writer.getPos()-4),this.boxWriter.writeU32(s)}else{l(this.mdat),l(this.ftypSize!==null);let t=this.boxWriter.offsets.get(this.mdat);l(t!==void 0);let i=this.writer.getPos()-t;this.mdat.size=i,this.mdat.largeSize=i>=2**32,this.boxWriter.patchBox(this.mdat);let s=se(this.trackDatas,this.creationTime);if(typeof this.fastStart=="object"){this.writer.seek(this.ftypSize),this.boxWriter.writeBox(s);let o=t-this.writer.getPos();this.boxWriter.writeBox(Ot(o))}else this.boxWriter.writeBox(s)}e()}};var ae=class{value;constructor(e){this.value=e}},Z=class{value;constructor(e){this.value=e}},ce=class{value;constructor(e){this.value=e}};var dt=r=>r<256?1:r<65536?2:r<1<<24?3:r<2**32?4:r<2**40?5:6,lt=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,jt=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 mt=2**15,Kt="https://github.com/Vanilagy/webm-muxer",qt=6,Xt=5,ci={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"},ui={video:1,audio:2,subtitle:17},Ie=class extends X{timestampsMustStartAtZero=!1;writer;format;helper=new Uint8Array(8);helperView=new DataView(this.helper.buffer);offsets=new WeakMap;dataOffsets=new WeakMap;trackDatas=[];segment=null;segmentInfo=null;seekHead=null;tracksElement=null;segmentDuration=null;cues=null;currentCluster=null;currentClusterMsTimestamp=null;trackDatasInCurrentCluster=new Set;duration=0;constructor(e,t){super(e),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=dt(e)){let i=0;switch(t){case 6:this.helperView.setUint8(i++,e/2**40|0);case 5:this.helperView.setUint8(i++,e/2**32|0);case 4:this.helperView.setUint8(i++,e>>24);case 3:this.helperView.setUint8(i++,e>>16);case 2:this.helperView.setUint8(i++,e>>8);case 1:this.helperView.setUint8(i++,e);break;default:throw new Error("Bad UINT size "+t)}this.writer.write(this.helper.subarray(0,i))}writeSignedInt(e,t=lt(e)){e<0&&(e+=2**(t*8)),this.writeUnsignedInt(e,t)}writeEBMLVarInt(e,t=jt(e)){let i=0;switch(t){case 1:this.helperView.setUint8(i++,128|e);break;case 2:this.helperView.setUint8(i++,64|e>>8),this.helperView.setUint8(i++,e);break;case 3:this.helperView.setUint8(i++,32|e>>16),this.helperView.setUint8(i++,e>>8),this.helperView.setUint8(i++,e);break;case 4:this.helperView.setUint8(i++,16|e>>24),this.helperView.setUint8(i++,e>>16),this.helperView.setUint8(i++,e>>8),this.helperView.setUint8(i++,e);break;case 5:this.helperView.setUint8(i++,8|e/2**32&7),this.helperView.setUint8(i++,e>>24),this.helperView.setUint8(i++,e>>16),this.helperView.setUint8(i++,e>>8),this.helperView.setUint8(i++,e);break;case 6:this.helperView.setUint8(i++,4|e/2**40&3),this.helperView.setUint8(i++,e/2**32|0),this.helperView.setUint8(i++,e>>24),this.helperView.setUint8(i++,e>>16),this.helperView.setUint8(i++,e>>8),this.helperView.setUint8(i++,e);break;default:throw new Error("Bad EBML VINT size "+t)}this.writer.write(this.helper.subarray(0,i))}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(),i=e.size===-1?1:e.size??4;e.size===-1?this.writeByte(255):this.writer.seek(this.writer.getPos()+i);let s=this.writer.getPos();if(this.dataOffsets.set(e,s),this.writeEBML(e.data),e.size!==-1){let o=this.writer.getPos()-s,n=this.writer.getPos();this.writer.seek(t),this.writeEBMLVarInt(o,i),this.writer.seek(n)}}else if(typeof e.data=="number"){let t=e.size??dt(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 ae)this.writeEBMLVarInt(4),this.writeFloat32(e.data.value);else if(e.data instanceof Z)this.writeEBMLVarInt(8),this.writeFloat64(e.data.value);else if(e.data instanceof ce){let t=e.size??lt(e.data.value);this.writeEBMLVarInt(t),this.writeSignedInt(e.data.value,t)}}}beforeTrackAdd(e){if(this.format instanceof $)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 $?"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]),i=new Uint8Array([22,84,174,107]),s={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:i},{id:21420,size:5,data:0}]}]};this.seekHead=s}createSegmentInfo(){let e={id:17545,data:new Z(0)};this.segmentDuration=e;let t={id:357149030,data:[{id:2807729,data:1e6},{id:19840,data:Kt},{id:22337,data:Kt},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:ui[t.type]},{id:134,data:ci[t.track.source._codec]},t.type==="video"?this.videoSpecificTrackInfo(t):null,t.type==="audio"?this.audioSpecificTrackInfo(t):null,t.type==="subtitle"?this.subtitleSpecificTrackInfo(t):null]})}videoSpecificTrackInfo(e){let t=[e.info.decoderConfig.description?{id:25506,data:B(e.info.decoderConfig.description)}:null,e.track.metadata.frameRate?{id:2352003,data:1e9/e.track.metadata.frameRate}:null],i=e.info.decoderConfig.colorSpace,s={id:224,data:[{id:176,data:e.info.width},{id:186,data:e.info.height},be(i)?{id:21936,data:[{id:21937,data:F[i.matrix]},{id:21946,data:D[i.transfer]},{id:21947,data:U[i.primaries]},{id:21945,data:i.fullRange?2:1}]}:null]};return t.push(s),t}audioSpecificTrackInfo(e){return[e.info.decoderConfig.description?{id:25506,data:B(e.info.decoderConfig.description)}:null,{id:225,data:[{id:181,data:new ae(e.info.sampleRate)},{id:159,data:e.info.numberOfChannels}]}]}subtitleSpecificTrackInfo(e){return[{id:25506,data:v.encode(e.info.config.description)}]}createSegment(){let e={id:408125543,size:this.format._options.streamable?-1:qt,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 l(this.segment),this.dataOffsets.get(this.segment)}getVideoTrackData(e,t){let i=this.trackDatas.find(o=>o.track===e);if(i)return i;ve(t),l(t),l(t.decoderConfig),l(t.decoderConfig.codedWidth!==void 0),l(t.decoderConfig.codedHeight!==void 0);let s={track:e,type:"video",info:{width:t.decoderConfig.codedWidth,height:t.decoderConfig.codedHeight,decoderConfig:t.decoderConfig},chunkQueue:[],lastWrittenMsTimestamp:null};return this.trackDatas.push(s),this.trackDatas.sort((o,n)=>o.track.id-n.track.id),s}getAudioTrackData(e,t){let i=this.trackDatas.find(o=>o.track===e);if(i)return i;_e(t),l(t),l(t.decoderConfig);let s={track:e,type:"audio",info:{numberOfChannels:t.decoderConfig.numberOfChannels,sampleRate:t.decoderConfig.sampleRate,decoderConfig:t.decoderConfig},chunkQueue:[],lastWrittenMsTimestamp:null};return this.trackDatas.push(s),this.trackDatas.sort((o,n)=>o.track.id-n.track.id),s}getSubtitleTrackData(e,t){let i=this.trackDatas.find(o=>o.track===e);if(i)return i;Ae(t),l(t),l(t.config);let s={track:e,type:"subtitle",info:{config:t.config},chunkQueue:[],lastWrittenMsTimestamp:null};return this.trackDatas.push(s),this.trackDatas.sort((o,n)=>o.track.id-n.track.id),s}async addEncodedVideoChunk(e,t,i){let s=await this.mutex.acquire();try{let o=this.getVideoTrackData(e,i),n=new Uint8Array(t.byteLength);t.copyTo(n);let a=t.type==="key",c=this.validateAndNormalizeTimestamp(o.track,t.timestamp,a),u=this.createInternalChunk(n,c,(t.duration??0)/1e6,t.type);e.source._codec==="vp9"&&this.fixVP9ColorSpace(o,u),o.chunkQueue.push(u),await this.interleaveChunks()}finally{s()}}async addEncodedAudioChunk(e,t,i){let s=await this.mutex.acquire();try{let o=this.getAudioTrackData(e,i),n=new Uint8Array(t.byteLength);t.copyTo(n);let a=t.type,c=a==="key",u=this.validateAndNormalizeTimestamp(o.track,t.timestamp,c),d=this.createInternalChunk(n,u,(t.duration??0)/1e6,a);o.chunkQueue.push(d),await this.interleaveChunks()}finally{s()}}async addSubtitleCue(e,t,i){let s=await this.mutex.acquire();try{let o=this.getSubtitleTrackData(e,i),n=this.validateAndNormalizeTimestamp(o.track,1e6*t.timestamp,!0),a=t.text,c=Math.floor(n*1e3);q.lastIndex=0,a=a.replace(q,T=>{let k=ge(T.slice(1,-1))-c;return`<${Te(k)}>`});let u=v.encode(a),d=`${t.settings??""}
+${t.identifier??""}
+${t.notes??""}`,f=this.createInternalChunk(u,n,t.duration,"key",d.trim()?v.encode(d):null);o.chunkQueue.push(f),await this.interleaveChunks()}finally{s()}}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 s of this.trackDatas){if(s.chunkQueue.length===0&&!s.track.source._closed)break e;s.chunkQueue.length>0&&s.chunkQueue[0].timestamp=2&&i++;let c={rgb:7,bt709:2,bt470bg:1,smpte170m:3}[e.info.decoderConfig.colorSpace.matrix];gt(t.data,i+0,i+3,c)}createInternalChunk(e,t,i,s,o=null){return{data:e,type:s,timestamp:t,duration:i,additions:o}}writeBlock(e,t){this.segment||(this.createTracks(),this.createSegment());let i=Math.floor(1e3*t.timestamp),s=this.trackDatas.every(d=>{if(d.track.source._closed)return!0;if(e===d)return t.type==="key";let f=d.chunkQueue[0];return f&&f.type==="key"});(!this.currentCluster||s&&i-this.currentClusterMsTimestamp>=1e3)&&this.createNewCluster(i);let o=i-this.currentClusterMsTimestamp;if(o<0)return;if(o>=mt)throw new Error(`Current Matroska cluster exceeded its maximum allowed length of ${mt} milliseconds. In order to produce a correct WebM file, you must pass in a key frame at least every ${mt} milliseconds.`);let a=new Uint8Array(4),c=new DataView(a.buffer);c.setUint8(0,128|e.track.id),c.setInt16(1,o,!1);let u=Math.floor(1e3*t.duration);if(u===0&&!t.additions){c.setUint8(3,+(t.type==="key")<<7);let d={id:163,data:[a,t.data]};this.writeEBML(d)}else{let d={id:160,data:[{id:161,data:[a,t.data]},t.type==="delta"?{id:251,data:new ce(e.lastWrittenMsTimestamp-i)}:null,t.additions?{id:30113,data:[{id:166,data:[{id:165,data:t.additions},{id:238,data:1}]}]}:null,u>0?{id:155,data:u}:null]};this.writeEBML(d)}this.duration=Math.max(this.duration,i+u),e.lastWrittenMsTimestamp=i,this.trackDatasInCurrentCluster.add(e)}createNewCluster(e){this.currentCluster&&!this.format._options.streamable&&this.finalizeCurrentCluster(),this.currentCluster={id:524531317,size:this.format._options.streamable?-1:Xt,data:[{id:231,data:e}]},this.writeEBML(this.currentCluster),this.currentClusterMsTimestamp=e,this.trackDatasInCurrentCluster.clear()}finalizeCurrentCluster(){l(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,Xt),this.writer.seek(t);let i=this.offsets.get(this.currentCluster)-this.segmentDataOffset;l(this.cues),this.cues.data.push({id:187,data:[{id:179,data:this.currentClusterMsTimestamp},...[...this.trackDatasInCurrentCluster].map(s=>({id:183,data:[{id:247,data:s.track.id},{id:241,data:i}]}))]})}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(),l(this.cues),this.writeEBML(this.cues),!this.format._options.streamable){let t=this.writer.getPos(),i=this.writer.getPos()-this.segmentDataOffset;this.writer.seek(this.offsets.get(this.segment)+4),this.writeEBMLVarInt(i,qt),this.segmentDuration.data=new Z(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 R=class{},Oe=class extends R{_options;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 Ee(e,this)}},ue=class extends R{_options;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 Ie(e,this)}},$=class extends ue{};var Q=class{_connectedTrack=null;_closed=!1;_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)}},I=class extends Q{_connectedTrack=null;_codec;constructor(e){if(super(),!H.includes(e))throw new TypeError(`Invalid video codec '${e}'. Must be one of: ${H.join(", ")}.`);this._codec=e}},Ve=class extends I{constructor(e){super(e)}digest(e,t){if(!(e instanceof EncodedVideoChunk))throw new TypeError("chunk must be an EncodedVideoChunk.");return this._ensureValidDigest(),this._connectedTrack.output._muxer.addEncodedVideoChunk(this._connectedTrack,e,t)}},di=5,li=r=>{if(!r||typeof r!="object")throw new TypeError("Codec config must be an object.");if(!H.includes(r.codec))throw new TypeError(`Invalid video codec '${r.codec}'. Must be one of: ${H.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'.")},de=class{constructor(e,t){this.source=e;this.codecConfig=t;li(t)}encoder=null;muxer=null;lastMultipleOfKeyFrameInterval=-1;lastWidth=null;lastHeight=null;async digest(e){if(this.source._ensureValidDigest(),this.lastWidth!==null&&this.lastHeight!==null){if(e.codedWidth!==this.lastWidth||e.codedHeight!==this.lastHeight)throw new Error(`Video frame size must remain constant. Expected ${this.lastWidth}x${this.lastHeight}, got ${e.codedWidth}x${e.codedHeight}.`)}else this.lastWidth=e.codedWidth,this.lastHeight=e.codedHeight;this.ensureEncoder(e),l(this.encoder);let t=Math.floor(e.timestamp/1e6/di);this.encoder.encode(e,{keyFrame:t!==this.lastMultipleOfKeyFrameInterval}),this.lastMultipleOfKeyFrameInterval=t,this.encoder.encodeQueueSize>=4&&await new Promise(i=>this.encoder.addEventListener("dequeue",i,{once:!0})),await this.muxer.mutex.currentPromise}ensureEncoder(e){this.encoder||(this.encoder=new VideoEncoder({output:(t,i)=>void this.muxer.addEncodedVideoChunk(this.source._connectedTrack,t,i),error:t=>console.error("Video encode error:",t)}),this.encoder.configure({codec:Wt(this.codecConfig.codec,e.codedWidth,e.codedHeight,this.codecConfig.bitrate),width:e.codedWidth,height:e.codedHeight,bitrate:this.codecConfig.bitrate,framerate:this.source._connectedTrack?.metadata.frameRate,latencyMode:this.codecConfig.latencyMode,...$t(this.codecConfig.codec)}),l(this.source._connectedTrack),this.muxer=this.source._connectedTrack.output._muxer)}async flush(){this.encoder&&(await this.encoder.flush(),this.encoder.close())}},Me=class extends I{_encoder;constructor(e){super(e.codec),this._encoder=new de(this,e)}digest(e){if(!(e instanceof VideoFrame))throw new TypeError("videoFrame must be a VideoFrame.");return this._encoder.digest(e)}_flush(){return this._encoder.flush()}},Re=class extends I{_encoder;_canvas;constructor(e,t){if(!(e instanceof HTMLCanvasElement))throw new TypeError("canvas must be an HTMLCanvasElement.");super(t.codec),this._encoder=new de(this,t),this._canvas=e}digest(e,t=0){if(!Number.isFinite(e)||e<0)throw new TypeError("timestamp must be a non-negative number.");if(!Number.isFinite(t)||t<0)throw new TypeError("duration must be a non-negative number.");let i=new VideoFrame(this._canvas,{timestamp:Math.round(1e6*e),duration:Math.round(1e6*t),alpha:"discard"}),s=this._encoder.digest(i);return i.close(),s}_flush(){return this._encoder.flush()}},Pe=class extends I{_encoder;_abortController=null;_track;_offsetTimestamps=!0;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._encoder=new de(this,t),this._track=e}_start(){this._abortController=new AbortController;let e=new MediaStreamTrackProcessor({track:this._track}),t=new WritableStream({write:i=>{this._encoder.digest(i),i.close()}});e.readable.pipeTo(t,{signal:this._abortController.signal}).catch(i=>{i instanceof DOMException&&i.name==="AbortError"||console.error("Pipe error:",i)})}async _flush(){this._abortController&&(this._abortController.abort(),this._abortController=null),await this._encoder.flush()}},O=class extends Q{_connectedTrack=null;_codec;constructor(e){if(super(),!L.includes(e))throw new TypeError(`Invalid audio codec '${e}'. Must be one of: ${L.join(", ")}.`);this._codec=e}},ze=class extends O{constructor(e){super(e)}digest(e,t){if(!(e instanceof EncodedAudioChunk))throw new TypeError("chunk must be an EncodedAudioChunk.");return this._ensureValidDigest(),this._connectedTrack.output._muxer.addEncodedAudioChunk(this._connectedTrack,e,t)}},mi=r=>{if(!r||typeof r!="object")throw new TypeError("Codec config must be an object.");if(!L.includes(r.codec))throw new TypeError(`Invalid audio codec '${r.codec}'. Must be one of: ${L.join(", ")}.`);if(!Number.isInteger(r.bitrate)||r.bitrate<=0)throw new TypeError("config.bitrate must be a positive integer.")},le=class{constructor(e,t){this.source=e;this.codecConfig=t;mi(t)}encoder=null;muxer=null;lastNumberOfChannels=null;lastSampleRate=null;async digest(e){if(this.source._ensureValidDigest(),this.lastNumberOfChannels!==null&&this.lastSampleRate!==null){if(e.numberOfChannels!==this.lastNumberOfChannels||e.sampleRate!==this.lastSampleRate)throw new Error(`Audio parameters must remain constant. Expected ${this.lastNumberOfChannels} channels at ${this.lastSampleRate} Hz, got ${e.numberOfChannels} channels at ${e.sampleRate} Hz.`)}else this.lastNumberOfChannels=e.numberOfChannels,this.lastSampleRate=e.sampleRate;this.ensureEncoder(e),l(this.encoder),this.encoder.encode(e),this.encoder.encodeQueueSize>=4&&await new Promise(t=>this.encoder.addEventListener("dequeue",t,{once:!0})),await this.muxer.mutex.currentPromise}ensureEncoder(e){this.encoder||(this.encoder=new AudioEncoder({output:(t,i)=>void this.muxer.addEncodedAudioChunk(this.source._connectedTrack,t,i),error:t=>console.error("Audio encode error:",t)}),this.encoder.configure({codec:Ht(this.codecConfig.codec,e.numberOfChannels,e.sampleRate),numberOfChannels:e.numberOfChannels,sampleRate:e.sampleRate,bitrate:this.codecConfig.bitrate,...Qt(this.codecConfig.codec)}),l(this.source._connectedTrack),this.muxer=this.source._connectedTrack.output._muxer)}async flush(){this.encoder&&(await this.encoder.flush(),this.encoder.close())}},Be=class extends O{_encoder;constructor(e){super(e.codec),this._encoder=new le(this,e)}digest(e){if(!(e instanceof AudioData))throw new TypeError("audioData must be an AudioData.");return this._encoder.digest(e)}_flush(){return this._encoder.flush()}},Ue=class extends O{_encoder;_accumulatedFrameCount=0;constructor(e){super(e.codec),this._encoder=new le(this,e)}digest(e){if(!(e instanceof AudioBuffer))throw new TypeError("audioBuffer must be an AudioBuffer.");let t=e.numberOfChannels,i=e.sampleRate,s=e.length,o=new Float32Array(t*s);for(let c=0;c{this._encoder.digest(i),i.close()}});e.readable.pipeTo(t,{signal:this._abortController.signal}).catch(i=>{i instanceof DOMException&&i.name==="AbortError"||console.error("Pipe error:",i)})}async _flush(){this._abortController&&(this._abortController.abort(),this._abortController=null),await this._encoder.flush()}},j=class extends Q{_connectedTrack=null;_codec;constructor(e){if(super(),!ne.includes(e))throw new TypeError(`Invalid subtitle codec '${e}'. Must be one of: ${ne.join(", ")}.`);this._codec=e}},Fe=class extends j{_parser;constructor(e){super(e),this._parser=new ke({codec:e,output:(t,i)=>this._connectedTrack?.output._muxer.addSubtitleCue(this._connectedTrack,t,i),error:t=>console.error("Subtitle parse error:",t)})}digest(e){if(typeof e!="string")throw new TypeError("text must be a string.");return this._ensureValidDigest(),this._parser.parse(e),this._connectedTrack.output._muxer.mutex.currentPromise}};var We=class{_muxer;_writer;_tracks=[];_started=!1;_finalizing=!1;_mutex=new K;constructor(e){if(!e||typeof e!="object")throw new TypeError("options must be an object.");if(!(e.format instanceof R))throw new TypeError("options.format must be an OutputFormat.");if(!(e.target instanceof M))throw new TypeError("options.target must be a Target.");if(e.target._output)throw new Error("Target is already used for another output.");e.target._output=this,this._writer=e.target._createWriter(),this._muxer=e.format._createMuxer(this)}addVideoTrack(e,t={}){if(!(e instanceof I))throw new TypeError("source must be a VideoSource.");if(!t||typeof t!="object")throw new TypeError("metadata must be an object.");if(typeof t.rotation=="number"&&![0,90,180,270].includes(t.rotation))throw new TypeError(`Invalid video rotation: ${t.rotation}. Has to be 0, 90, 180 or 270.`);if(Array.isArray(t.rotation)&&(t.rotation.length!==9||t.rotation.some(i=>!Number.isFinite(i))))throw new TypeError(`Invalid video transformation matrix: ${t.rotation.join()}`);if(t.frameRate!==void 0&&(!Number.isInteger(t.frameRate)||t.frameRate<=0))throw new TypeError(`Invalid video frame rate: ${t.frameRate}. Must be a positive integer.`);this._addTrack("video",e,t)}addAudioTrack(e,t={}){if(!(e instanceof O))throw new TypeError("source must be an AudioSource.");if(!t||typeof t!="object")throw new TypeError("metadata must be an object.");this._addTrack("audio",e,t)}addSubtitleTrack(e,t={}){if(!(e instanceof j))throw new TypeError("source must be a SubtitleSource.");if(!t||typeof t!="object")throw new TypeError("metadata must be an object.");this._addTrack("subtitle",e,t)}_addTrack(e,t,i){if(this._started)throw new Error("Cannot add track after output has started.");if(t._connectedTrack)throw new Error("Source is already used for a track.");let s={id:this._tracks.length+1,output:this,type:e,source:t,metadata:i};this._muxer.beforeTrackAdd(s),this._tracks.push(s),t._connectedTrack=s}async start(){if(this._started)throw new Error("Output already started.");this._started=!0,this._writer.start();let e=await this._mutex.acquire();await this._muxer.start();for(let t of this._tracks)t.source._start();e()}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 e=await this._mutex.acquire(),t=this._tracks.map(i=>i.source._flush());await Promise.all(t),await this._muxer.finalize(),await this._writer.flush(),await this._writer.finalize(),e()}};var J=class{},Ne=class extends J{constructor(t){super();this.buffer=t}async _read(t,i){return new Uint8Array(this.buffer,t,i-t)}async _getSize(){return this.buffer.byteLength}},He=class extends J{constructor(t){super();this.blob=t}async _read(t,i){let o=await this.blob.slice(t,i).arrayBuffer();return new Uint8Array(o)}async _getSize(){return this.blob.size}};var ee=class{input;constructor(e){this.input=e}};var Le=class{_backing;constructor(e){this._backing=e}isVideoTrack(){return this instanceof me}isAudioTrack(){return this instanceof fe}getDuration(){return this._backing.getDuration()}},me=class extends Le{_backing;constructor(e){super(e),this._backing=e}getCodec(){return this._backing.getCodec()}getWidth(){return this._backing.getWidth()}getHeight(){return this._backing.getHeight()}getRotation(){return this._backing.getRotation()}getDecoderConfig(){return this._backing.getDecoderConfig()}async getCodecMimeType(){return(await this.getDecoderConfig()).codec}},fe=class extends Le{_backing;constructor(e){super(e),this._backing=e}getCodec(){return this._backing.getCodec()}getNumberOfChannels(){return this._backing.getNumberOfChannels()}getSampleRate(){return this._backing.getSampleRate()}getDecoderConfig(){return this._backing.getDecoderConfig()}async getCodecMimeType(){return(await this.getDecoderConfig()).codec}};var te=class{constructor(e){this.reader=e}pos=0;readRange(e,t){let{view:i,offset:s}=this.reader.getViewAndOffset(e,t);return new Uint8Array(i.buffer,s,t-e)}readU8(){let{view:e,offset:t}=this.reader.getViewAndOffset(this.pos,this.pos+1);return this.pos++,e.getUint8(t)}readU16(){let{view:e,offset:t}=this.reader.getViewAndOffset(this.pos,this.pos+2);return this.pos+=2,e.getUint16(t,!1)}readU24(){let{view:e,offset:t}=this.reader.getViewAndOffset(this.pos,this.pos+3);this.pos+=3;let i=e.getUint16(t,!1),s=e.getUint8(t+2);return i*256+s}readS32(){let{view:e,offset:t}=this.reader.getViewAndOffset(this.pos,this.pos+4);return this.pos+=4,e.getInt32(t,!1)}readU32(){let{view:e,offset:t}=this.reader.getViewAndOffset(this.pos,this.pos+4);return this.pos+=4,e.getUint32(t,!1)}readI32(){let{view:e,offset:t}=this.reader.getViewAndOffset(this.pos,this.pos+4);return this.pos+=4,e.getInt32(t,!1)}readU64(){let e=this.readU32(),t=this.readU32();return e*4294967296+t}readF64(){let{view:e,offset:t}=this.reader.getViewAndOffset(this.pos,this.pos+8);return this.pos+=8,e.getFloat64(t,!1)}readFixed_16_16(){return this.readS32()/65536}readFixed_2_30(){return this.readS32()/1073741824}readAscii(e){let{view:t,offset:i}=this.reader.getViewAndOffset(this.pos,this.pos+e);this.pos+=e;let s="";for(let o=0;oe.inputTrack)}async getMimeType(){await this.readMetadata();let e="video/mp4";if(this.tracks.length>0){let t=await Promise.all(this.tracks.map(s=>s.inputTrack.getCodecMimeType())),i=[...new Set(t)];e+=`; codecs="${i.join(", ")}"`}return e}readMetadata(){return this.metadataPromise??=(async()=>{let e=await this.isobmffReader.reader.getSourceSize();for(;this.isobmffReader.posi.presentationTimestamp-s.presentationTimestamp),e.sampleTable}readContiguousBoxes(e){let t=this.isobmffReader.pos;for(;this.isobmffReader.pos-td.every((f,T)=>f===c[T]));u===-1?s.rotation=0:s.rotation=90*u}break;case"mdhd":{let s=this.currentTrack;l(s);let o=this.isobmffReader.readU8();this.isobmffReader.pos+=3,o===0?(this.isobmffReader.pos+=8,s.timescale=this.isobmffReader.readU32(),s.durationInTimescale=this.isobmffReader.readU32()):o===1&&(this.isobmffReader.pos+=16,s.timescale=this.isobmffReader.readU32(),s.durationInTimescale=this.isobmffReader.readU64())}break;case"hdlr":{let s=this.currentTrack;l(s),this.isobmffReader.pos+=8;let o=this.isobmffReader.readAscii(4);o==="vide"?s.info={type:"video",width:-1,height:-1,codec:null,codecDescription:null,colorSpace:null}:o==="soun"&&(s.info={type:"audio",numberOfChannels:-1,sampleRate:-1,codec:null,codecDescription:null})}break;case"stbl":{let s=this.currentTrack;l(s),s.sampleTableOffset=e,this.readContiguousBoxes(t.contentSize)}break;case"stsd":{let s=this.currentTrack;if(l(s),s.info===null||s.sampleTable)break;let o=this.isobmffReader.readU8();this.isobmffReader.pos+=3;let n=this.isobmffReader.readU32();for(let a=0;a0){if(u===1)this.isobmffReader.pos+=4*4;else if(u===2){this.isobmffReader.pos+=4,f=this.isobmffReader.readF64(),d=this.isobmffReader.readU32(),this.isobmffReader.pos+=4;let T=this.isobmffReader.readU32(),C=this.isobmffReader.readU32(),k=this.isobmffReader.readU32(),w=this.isobmffReader.readU32()}}s.info.numberOfChannels=d,s.info.sampleRate=f,this.readContiguousBoxes(e+c.totalSize-this.isobmffReader.pos)}}}break;case"avcC":{let s=this.currentTrack;l(s&&s.info),s.info.codecDescription=this.isobmffReader.readRange(this.isobmffReader.pos,this.isobmffReader.pos+t.contentSize)}break;case"hvcC":{let s=this.currentTrack;l(s&&s.info),s.info.codecDescription=this.isobmffReader.readRange(this.isobmffReader.pos,this.isobmffReader.pos+t.contentSize)}break;case"colr":{let s=this.currentTrack;if(l(s&&s.info?.type==="video"),this.isobmffReader.readAscii(4)!=="nclx")break;let n=this.isobmffReader.readU16(),a=this.isobmffReader.readU16(),c=this.isobmffReader.readU16(),u=!!(this.isobmffReader.readU8()&128);s.info.colorSpace={primaries:Tt[n],transfer:xt[a],matrix:Ct[c],fullRange:u}}break;case"wave":t.totalSize>8&&this.readContiguousBoxes(t.contentSize);break;case"esds":{let s=this.currentTrack;l(s&&s.info),this.isobmffReader.pos+=4;let o=this.isobmffReader.readU8();l(o===3),this.isobmffReader.readIsomVariableInteger(),this.isobmffReader.pos+=2;let n=this.isobmffReader.readU8(),a=(n&128)!==0,c=(n&64)!==0,u=(n&32)!==0;if(a&&(this.isobmffReader.pos+=2),c){let k=this.isobmffReader.readU8();this.isobmffReader.pos+=k}u&&(this.isobmffReader.pos+=2);let d=this.isobmffReader.readU8();l(d===4),this.isobmffReader.readIsomVariableInteger();let f=this.isobmffReader.readU8();l(f===64),this.isobmffReader.pos+=12;let T=this.isobmffReader.readU8();l(T===5);let C=this.isobmffReader.readIsomVariableInteger();s.info.codecDescription=this.isobmffReader.readRange(this.isobmffReader.pos,this.isobmffReader.pos+C)}break;case"stts":{let s=this.currentTrack;if(l(s),!s.sampleTable)break;this.isobmffReader.pos+=4;let o=this.isobmffReader.readU32(),n=0,a=0;for(let c=0;c{let t=W(r.presentationTimestamps,e,i=>i.presentationTimestamp);return t===-1?-1:r.presentationTimestamps[t].sampleIndex},hi=(r,e)=>{let t=W(r.sampleTimingEntries,e,k=>k.startIndex),i=r.sampleTimingEntries[t];if(!i||i.startIndex+i.count<=e)return null;let o=i.startDecodeTimestamp+(e-i.startIndex)*i.delta,n=W(r.sampleCompositionTimeOffsets,e,k=>k.startIndex),a=r.sampleCompositionTimeOffsets[n];a&&(o+=a.offset);let c=r.sampleSizes[Math.min(e,r.sampleSizes.length-1)],u=W(r.sampleToChunk,e,k=>k.startSampleIndex),d=r.sampleToChunk[u];l(d);let f=d.startChunkIndex+Math.floor((e-d.startSampleIndex)/d.samplesPerChunk),C=r.chunkOffsets[f];if(r.sampleSizes.length===1)C+=c*(e-d.startSampleIndex);else{let k=d.startSampleIndex+(f-d.startChunkIndex)*d.samplesPerChunk;for(let w=k;wk)!==-1:!0}},pi=(r,e)=>{if(!r.keySampleIndices)return e;let t=W(r.keySampleIndices,e,i=>i);return r.keySampleIndices[t]??-1},bi=(r,e)=>{if(!r.keySampleIndices)return e+1;let t=W(r.keySampleIndices,e,i=>i);return r.keySampleIndices[t+1]??-1};var je=class extends ee{};var Ke=class{},pt=class extends Ke{async _canReadInput(e){if(await e._reader.getSourceSize()<8)return!1;await e._reader.loadRange(4,8);let i=new te(e._reader);return i.pos=4,i.readAscii(4)==="ftyp"}_createDemuxer(e){return new $e(e)}},bt=class extends Ke{async _canReadInput(){return!1}_createDemuxer(e){return new je(e)}},he=new pt,Yt=he,Zt=he,qe=new bt,kt=qe,Jt=qe,er=[he,kt];var Xe=4096,Ge=class{constructor(e){this.source=e}loadedSegments=[];sourceSizePromise=null;getSourceSize(){return this.sourceSizePromise?this.sourceSizePromise:this.sourceSizePromise=this.source._getSize()}async loadRange(e,t){let i=Math.floor(e/Xe)*Xe,s=Math.ceil(t/Xe)*Xe;s=Math.min(s,await this.getSourceSize());let o=this.loadedSegments.find(c=>c.start<=i);o&&(i=Math.max(i,o.end));let n=this.loadedSegments.find(c=>c.end>=s);if(n&&(s=Math.min(s,n.start)),i>=s)return;let a=await this.source._read(i,s);this.insertIntoLoadedSegments(i,a)}insertIntoLoadedSegments(e,t){let i={start:e,end:e+t.byteLength,bytes:t,view:new DataView(t.buffer)},s=this.loadedSegments.findLastIndex(d=>d.start<=e);this.loadedSegments.splice(s+1,0,i),(s===-1||this.loadedSegments[s].enda&&this.loadedSegments[a+1].start<=c;)a++,c=Math.max(c,this.loadedSegments[a].end);if(o===a)return;for(let d=o;d<=a;d++){let f=this.loadedSegments[d];if(f.start===n&&f.end===c){this.loadedSegments.splice(d+1,a-d),this.loadedSegments.splice(o,d-o);return}}let u=new Uint8Array(c-n);for(let d=o;d<=a;d++){let f=this.loadedSegments[d];u.set(f.bytes,f.start-n)}this.loadedSegments.splice(o+1,a-o),this.loadedSegments[o].end=c,this.loadedSegments[o].bytes=u,this.loadedSegments[o].view=new DataView(u.buffer)}getViewAndOffset(e,t){let i=this.loadedSegments.find(s=>s.start<=e&&t<=s.end);if(!i)throw new Error(`No segment loaded for range [${e}, ${t}).`);return{view:i.view,offset:i.bytes.byteOffset+e-i.start}}};var Ye=class{_formats;_reader;_demuxerPromise=null;_format=null;constructor(e){this._formats=e.formats,this._reader=new Ge(e.source)}_getDemuxer(){return this._demuxerPromise??=(async()=>{for(let e of this._formats)if(await e._canReadInput(this))return this._format=e,e._createDemuxer(this);throw new Error("Input has an unrecognizable format.")})()}async getFormat(){return await this._getDemuxer(),l(this._format),this._format}async getDuration(){return(await this._getDemuxer()).getDuration()}async getTracks(){return(await this._getDemuxer()).getTracks()}async getVideoTracks(){return(await this.getTracks()).filter(t=>t.isVideoTrack())}async getPrimaryVideoTrack(){return(await this.getTracks()).find(t=>t.isVideoTrack())??null}async getAudioTracks(){return(await this.getTracks()).filter(t=>t.isAudioTrack())}async getPrimaryAudioTrack(){return(await this.getTracks()).find(t=>t.isAudioTrack())??null}async getMimeType(){return(await this._getDemuxer()).getMimeType()}};var Ze=class{constructor(e){this.videoTrack=e}getFirstChunk(){return this.videoTrack._backing.getFirstChunk()}getChunk(e){return this.videoTrack._backing.getChunk(e)}getNextChunk(e){return this.videoTrack._backing.getNextChunk(e)}getKeyChunk(e){return this.videoTrack._backing.getKeyChunk(e)}getNextKeyChunk(e){return this.videoTrack._backing.getNextKeyChunk(e)}async*chunks(e=0){let t=await this.getChunk(e);for(;t;)yield t,t=await this.getNextChunk(t)}},Je=class{constructor(e){this.videoTrack=e}decoderConfig=null;async createDecoder(e){this.decoderConfig||(this.decoderConfig=await this.videoTrack.getDecoderConfig());let t=new VideoDecoder({output:e,error:i=>console.error(i)});return t.configure(this.decoderConfig),t}async getKeyFrame(e){let t=null,i=await this.createDecoder(o=>t=o),s=await this.videoTrack._backing.getKeyChunk(e);return s?(i.decode(s),await i.flush(),i.close(),t):null}async getFrame(e){let t=null,i=await this.createDecoder(a=>{a.timestamp/1e6<=e?(t?.close(),t=a):a.close()}),s=await this.videoTrack._backing.getKeyChunk(e);if(!s)return null;let o=await this.videoTrack._backing.getChunk(e);l(o),i.decode(s);let n=s;for(;n!==o;){let a=await this.videoTrack._backing.getNextChunk(n);l(a),n=a,i.decode(a),i.decodeQueueSize>=10&&await new Promise(c=>i.addEventListener("dequeue",c,{once:!0}))}return await i.flush(),i.close(),t}async*frames(e=0){let t=[],i=!1,s=null,{promise:o,resolve:n}=st(),a=!1,c=await this.createDecoder(f=>{if(a){f.close();return}let T=f.timestamp/1e6;s&&(T>e?(t.push(s),i=!0):s.close()),T>=e&&(t.push(f),i=!0),s=i?null:f,t.length>0&&(n(),{promise:o,resolve:n}=st())}),u=await this.videoTrack._backing.getKeyChunk(e);if(!u)return;let d=!1;(async()=>{let f=u;for(;f&&!a;)c.decode(f),c.decodeQueueSize>=10&&await new Promise(C=>c.addEventListener("dequeue",C,{once:!0})),f=await this.videoTrack._backing.getNextChunk(f);await c.flush(),c.close(),d=!0,n()})();try{for(;;)if(t.length>0)yield t.shift();else if(!d)await o;else break}finally{a=!0}}};return nr(ki);})();
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 c0a015d..edaf1a2 100644
--- a/dist/metamuxer.min.mjs
+++ b/dist/metamuxer.min.mjs
@@ -1,9 +1,9 @@
-function d(t){if(!t)throw new Error("Assertion failed.")}var S=t=>t&&t[t.length-1],V=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},He=(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),y=new TextEncoder,R={bt709:1,bt470bg:5,smpte170m:6},F={bt709:1,smpte170m:6,"iec61966-2-1":13},N={rgb:0,bt709:1,bt470bg:5,smpte170m:6},ne=t=>!!t&&!!t.primaries&&!!t.transfer&&!!t.matrix&&t.fullRange!==void 0,Ce=t=>t instanceof ArrayBuffer||typeof SharedArrayBuffer<"u"&&t instanceof SharedArrayBuffer||ArrayBuffer.isView(t)&&!(t instanceof DataView),W=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 X=/(?:(.+?)\n)?((?:\d{2}:)?\d{2}:\d{2}.\d{3})\s+-->\s+((?:\d{2}:)?\d{2}:\d{2}.\d{3})/g,pt=/^WEBVTT(.|\n)*?\n{2}/,H=/<(?:(\d{2}):)?(\d{2}):(\d{2}).(\d{3})>/g,ae=class{constructor(i){this.preambleText=null;this.preambleEmitted=!1;this.options=i}parse(i){i=i.replaceAll(`\r
+function l(r){if(!r)throw new Error("Assertion failed.")}var y=r=>r&&r[r.length-1],M=r=>r>=0&&r<2**32,R=(r,e,t)=>{let i=0;for(let s=e;s>a;i<<=1,i|=c}return i},bt=(r,e,t,i)=>{for(let s=e;s>t-s-1<r instanceof ArrayBuffer?new Uint8Array(r):new Uint8Array(r.buffer,r.byteOffset,r.byteLength),v=new TextEncoder,Be=r=>Object.fromEntries(Object.entries(r).map(([e,t])=>[t,e])),z={bt709:1,bt470bg:5,smpte170m:6},kt=Be(z),B={bt709:1,smpte170m:6,"iec61966-2-1":13},gt=Be(B),U={rgb:0,bt709:1,bt470bg:5,smpte170m:6},Tt=Be(U),fe=r=>!!r&&!!r.primaries&&!!r.transfer&&!!r.matrix&&r.fullRange!==void 0,Ue=r=>r instanceof ArrayBuffer||typeof SharedArrayBuffer<"u"&&r instanceof SharedArrayBuffer||ArrayBuffer.isView(r)&&!(r instanceof DataView),H=class{currentPromise=Promise.resolve();async acquire(){let e,t=new Promise(s=>{e=s}),i=this.currentPromise;return this.currentPromise=t,await i,e}},V=r=>{let e=r*(Math.PI/180),t=Math.cos(e),i=Math.sin(e);return[t,i,0,-i,t,0,0,0,1]},De=V(0),xt=r=>[...r].map(e=>e.toString(16).padStart(2,"0")).join(""),Ct=r=>(r=r>>1&1431655765|(r&1431655765)<<1,r=r>>2&858993459|(r&858993459)<<2,r=r>>4&252645135|(r&252645135)<<4,r=r>>8&16711935|(r&16711935)<<8,r=r>>16&65535|(r&65535)<<16,r>>>0),wt=(r,e,t)=>{let i=0,s=r.length-1,o=-1;for(;i<=s;){let n=i+s>>1,a=t(r[n]);a===e?(o=n,s=n-1):a{let i=-1,s=0,o=r.length-1;for(;s<=o;){let n=s+(o-s+1)/2|0;t(r[n])<=e?(i=n,s=n+1):o=n-1}return i},Fe=()=>{let r,e;return{promise:new Promise((i,s)=>{r=i,e=s}),resolve:r,reject:e}};var ee=/(?:(.+?)\n)?((?:\d{2}:)?\d{2}:\d{2}.\d{3})\s+-->\s+((?:\d{2}:)?\d{2}:\d{2}.\d{3})/g,Gt=/^WEBVTT(.|\n)*?\n{2}/,L=/<(?:(\d{2}):)?(\d{2}):(\d{2}).(\d{3})>/g,he=class{options;preambleText=null;preambleEmitted=!1;constructor(e){this.options=e}parse(e){e=e.replaceAll(`\r
`,`
`).replaceAll("\r",`
-`),X.lastIndex=0;let e;if(!this.preambleText){if(!pt.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(`
+`),ee.lastIndex=0;let t;if(!this.preambleText){if(!Gt.test(e)){let s=new Error("WebVTT preamble incorrect.");throw this.options.error(s),s}t=ee.exec(e);let i=e.slice(0,t?.index??e.length).trimEnd();if(!i){let s=new Error("No WebVTT preamble provided.");throw this.options.error(s),s}this.preambleText=i,t&&(e=e.slice(t.index),ee.lastIndex=0)}for(;t=ee.exec(e);){let i=e.slice(0,t.index),s=t[1],o=t.index+t[0].length,n=e.indexOf(`
+`,o)+1,a=e.slice(o,n).trim(),c=e.indexOf(`
-`,o);u===-1&&(u=i.length);let m=ue(e[2]),f=ue(e[3])-m,T=i.slice(n,u).trim();i=i.slice(u).trimStart(),X.lastIndex=0;let E={timestamp:m/1e3,duration:f/1e3,text:T,identifier:s,settings:a,notes:r},C={};this.preambleEmitted||(C.config={description:this.preambleText},this.preambleEmitted=!0),this.options.output(E,C)}}},ht=/(?:(\d{2}):)?(\d{2}):(\d{2}).(\d{3})/,ue=t=>{let i=ht.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=>(_.setUint16(0,t,!1),[p[0],p[1]]),bt=t=>(_.setInt16(0,t,!1),[p[0],p[1]]),$e=t=>(_.setUint32(0,t,!1),[p[1],p[2],p[3]]),c=t=>(_.setUint32(0,t,!1),[p[0],p[1],p[2],p[3]]),je=t=>(_.setInt32(0,t,!1),[p[0],p[1],p[2],p[3]]),B=t=>(_.setUint32(0,Math.floor(t/2**32),!1),_.setUint32(4,t,!1),[p[0],p[1],p[2],p[3],p[4],p[5],p[6],p[7]]),Se=t=>(_.setInt16(0,2**8*t,!1),[p[0],p[1]]),A=t=>(_.setInt32(0,2**16*t,!1),[p[0],p[1],p[2],p[3]]),ke=t=>(_.setInt32(0,2**30*t,!1),[p[0],p[1],p[2],p[3]]),ge=(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()},g=(t,i=!1)=>{let e=Array(t.length).fill(null).map((r,s)=>t.charCodeAt(s));return i&&e.push(0),e},ye=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]},qe=Le(0),Ke=t=>[A(t[0]),A(t[1]),ke(t[2]),A(t[3]),A(t[4]),ke(t[5]),A(t[6]),A(t[7]),ke(t[8])],b=(t,i,e)=>({type:t,contents:i&&new Uint8Array(i.flat(10)),children:e}),x=(t,i,e,r,s)=>b(t,[w(i),$e(e),r??[]],s),Xe=t=>t.fragmented?b("ftyp",[g("iso5"),c(512),g("iso5"),g("iso6"),g("mp41")]):b("ftyp",[g("isom"),c(512),g("isom"),t.holdsAvc?g("avc1"):[],g("mp41")]),de=t=>({type:"mdat",largeSize:t}),Ge=t=>({type:"free",size:t}),Y=(t,i,e=!1)=>b("moov",void 0,[xt(i,t),...t.map(r=>Tt(r,i)),e?Yt(t):null]),xt=(t,i)=>{let e=v(Math.max(0,...i.filter(n=>n.samples.length>0).map(n=>{let a=ye(n.samples);return a.timestamp+a.duration})),le),r=Math.max(0,...i.map(n=>n.track.id))+1,s=!V(t)||!V(e),o=s?B:c;return x("mvhd",+s,0,[o(t),o(t),c(le),o(e),A(1),Se(1),Array(10).fill(0),Ke(qe),Array(24).fill(0),c(r)])},Tt=(t,i)=>b("trak",void 0,[wt(t,i),Ct(t,i)]),wt=(t,i)=>{let e=ye(t.samples),r=v(e?e.timestamp+e.duration:0,le),s=!V(i)||!V(r),o=s?B:c,n;if(t.type==="video"){let a=t.track.metadata.rotation;n=a===void 0||typeof a=="number"?Le(a??0):a}else n=qe;return x("tkhd",+s,3,[o(i),o(i),c(t.track.id),c(0),o(r),Array(8).fill(0),h(0),h(t.track.id),Se(t.type==="audio"?1:0),h(0),Ke(n),A(t.type==="video"?t.info.width:0),A(t.type==="video"?t.info.height:0)])},Ct=(t,i)=>b("mdia",void 0,[kt(t,i),yt(t),vt(t)]),kt=(t,i)=>{let e=ye(t.samples),r=v(e?e.timestamp+e.duration:0,t.timescale),s=!V(i)||!V(r),o=s?B:c;return x("mdhd",+s,0,[o(i),o(i),c(t.timescale),o(r),h(21956),h(0)])},gt={video:"vide",audio:"soun",subtitle:"text"},St={video:"VideoHandler",audio:"SoundHandler",subtitle:"TextHandler"},yt=t=>x("hdlr",0,0,[g("mhlr"),g(gt[t.type]),c(0),c(0),c(0),g(St[t.type],!0)]),vt=t=>b("minf",void 0,[Ot[t.type](),Mt(),Pt(t)]),At=()=>x("vmhd",0,1,[h(0),h(0),h(0),h(0)]),_t=()=>x("smhd",0,0,[h(0),h(0)]),Et=()=>x("nmhd",0,0),Ot={video:At,audio:_t,subtitle:Et},Mt=()=>b("dinf",void 0,[Vt()]),Vt=()=>x("dref",0,0,[c(1)],[zt()]),zt=()=>x("url ",0,1),Pt=t=>{let i=t.compositionTimeOffsetTable.length>1||t.compositionTimeOffsetTable.some(e=>e.sampleCompositionTimeOffset!==0);return b("stbl",void 0,[Bt(t),jt(t),Lt(t),qt(t),Kt(t),Xt(t),i?Gt(t):null])},Bt=t=>{let i;return t.type==="video"?i=It(nr[t.track.source._codec],t):t.type==="audio"?i=Ft(ur[t.track.source._codec],t):t.type==="subtitle"&&(i=Qt(lr[t.track.source._codec],t)),d(i),x("stsd",0,0,[c(1)],[i])},It=(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),c(4718592),c(4718592),c(0),h(1),Array(32).fill(0),h(24),bt(65535)],[ar[i.track.source._codec](i),ne(i.info.decoderConfig.colorSpace)?Ut(i):null]),Ut=t=>b("colr",[g("nclx"),h(R[t.info.decoderConfig.colorSpace.primaries]),h(F[t.info.decoderConfig.colorSpace.transfer]),h(N[t.info.decoderConfig.colorSpace.matrix]),w((t.info.decoderConfig.colorSpace.fullRange?1:0)<<7)]),Dt=t=>t.info.decoderConfig&&b("avcC",[...P(t.info.decoderConfig.description)]),Wt=t=>t.info.decoderConfig&&b("hvcC",[...P(t.info.decoderConfig.description)]),Qe=t=>{if(!t.info.decoderConfig)return null;let i=t.info.decoderConfig;d(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 x("vpcC",1,0,[w(r),w(s),w(a),w(2),w(2),w(2),h(0)])},Rt=()=>b("av1C",[129,0,0,0]),Ft=(t,i)=>b(t,[Array(6).fill(0),h(1),h(0),h(0),c(0),h(i.info.numberOfChannels),h(16),h(0),h(0),A(i.info.sampleRate)],[cr[i.track.source._codec](i)]),Nt=t=>{let e=[...P(t.info.decoderConfig.description??new ArrayBuffer(0))];return e=[...w(64),...w(21),...$e(0),...c(0),...c(0),...w(5),...ge(e.length),...e],e=[...h(1),...w(0),...w(4),...ge(e.length),...e,...w(6),...w(1),...w(2)],e=[...w(3),...ge(e.length),...e],x("esds",0,0,e)},Ht=t=>{let i=3840,e=0,r=t.info.decoderConfig?.description;if(r){d(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",[w(0),w(t.info.numberOfChannels),h(i),c(t.info.sampleRate),Se(e),w(0)])},Qt=(t,i)=>b(t,[Array(6).fill(0),h(1)],[dr[i.track.source._codec](i)]),$t=t=>b("vttC",[...y.encode(t.info.config.description)]);var jt=t=>x("stts",0,0,[c(t.timeToSampleTable.length),t.timeToSampleTable.map(i=>[c(i.sampleCount),c(i.sampleDelta)])]),Lt=t=>{if(t.samples.every(e=>e.type==="key"))return null;let i=[...t.samples.entries()].filter(([,e])=>e.type==="key");return x("stss",0,0,[c(i.length),i.map(([e])=>c(e+1))])},qt=t=>x("stsc",0,0,[c(t.compactlyCodedChunkTable.length),t.compactlyCodedChunkTable.map(i=>[c(i.firstChunk),c(i.samplesPerChunk),c(1)])]),Kt=t=>x("stsz",0,0,[c(0),c(t.samples.length),t.samples.map(i=>c(i.size))]),Xt=t=>t.finalizedChunks.length>0&&S(t.finalizedChunks).offset>=2**32?x("co64",0,0,[c(t.finalizedChunks.length),t.finalizedChunks.map(i=>B(i.offset))]):x("stco",0,0,[c(t.finalizedChunks.length),t.finalizedChunks.map(i=>c(i.offset))]),Gt=t=>x("ctts",0,0,[c(t.compositionTimeOffsetTable.length),t.compositionTimeOffsetTable.map(i=>[c(i.sampleCount),c(i.sampleCompositionTimeOffset)])]),Yt=t=>b("mvex",void 0,t.map(Zt)),Zt=t=>x("trex",0,0,[c(t.track.id),c(1),c(0),c(0),c(0)]),ve=(t,i)=>b("moof",void 0,[Jt(t),...i.map(er)]),Jt=t=>x("mfhd",0,0,[c(t)]),Ye=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},er=t=>b("traf",void 0,[tr(t),rr(t),ir(t)]),tr=t=>{d(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:Ye(e)};return x("tfhd",0,i,[c(t.track.id),c(r.duration),c(r.size),c(r.flags)])},rr=t=>(d(t.currentChunk),x("tfdt",1,0,[B(v(t.currentChunk.startTimestamp,t.timescale))])),ir=t=>{d(t.currentChunk);let i=t.currentChunk.samples.map(k=>k.timescaleUnitsToNextSample),e=t.currentChunk.samples.map(k=>k.size),r=t.currentChunk.samples.map(Ye),s=t.currentChunk.samples.map(k=>v(k.timestamp-k.decodeTimestamp,t.timescale)),o=new Set(i),n=new Set(e),a=new Set(r),u=new Set(s),m=a.size===2&&r[0]!==r[1],l=o.size>1,f=n.size>1,T=!m&&a.size>1,E=u.size>1||[...u].some(k=>k!==0),C=0;return C|=1,C|=4*+m,C|=256*+l,C|=512*+f,C|=1024*+T,C|=2048*+E,x("trun",1,C,[c(t.currentChunk.samples.length),c(t.currentChunk.offset-t.currentChunk.moofOffset||0),m?c(r[0]):[],t.currentChunk.samples.map((k,D)=>[l?c(i[D]):[],f?c(e[D]):[],T?c(r[D]):[],E?je(s[D]):[]])])},Ze=t=>b("mfra",void 0,[...t.map(sr),or()]),sr=(t,i)=>x("tfra",1,0,[c(t.track.id),c(63),c(t.finalizedChunks.length),t.finalizedChunks.map(r=>[B(v(r.startTimestamp,t.timescale)),B(r.moofOffset),c(i+1),c(1),c(1)])]),or=()=>x("mfro",0,0,[c(0)]),Je=()=>b("vtte"),et=(t,i,e,r,s)=>b("vttc",void 0,[s!==null?b("vsid",[je(s)]):null,e!==null?b("iden",[...y.encode(e)]):null,i!==null?b("ctim",[...y.encode(ce(i))]):null,r!==null?b("sttg",[...y.encode(r)]):null,b("payl",[...y.encode(t)])]),tt=t=>b("vtta",[...y.encode(t)]),nr={avc:"avc1",hevc:"hvc1",vp8:"vp08",vp9:"vp09",av1:"av01"},ar={avc:Dt,hevc:Wt,vp8:Qe,vp9:Qe,av1:Rt},ur={aac:"mp4a",opus:"Opus"},cr={aac:Nt,opus:Ht},lr={webvtt:"wvtt"},dr={webvtt:$t};var Q=class{constructor(i){this.mutex=new W;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;sm.start<=r&&rfr){for(let m=0;m=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)}queueChunksForFlush(e=!1){d(this.writer);for(let r=0;r{if(t==="avc"){let o=Math.ceil(i/16)*Math.ceil(e/16),n=rt.find(f=>o<=f.maxMacroblocks&&r<=f.maxBitrate)??S(rt),a=n?n.level:0,u="64".padStart(2,"0"),m="00",l=a.toString(16).padStart(2,"0");return`avc1.${u}${m}${l}`}else if(t==="hevc"){let s="",n="6",a=i*e,u=it.find(l=>a<=l.maxPictureSize&&r<=l.maxBitrate)??S(it);return`hev1.${s}1.${n}.${u.tier}${u.level}.B0`}else{if(t==="vp8")return"vp8";if(t==="vp9"){let s="00",o=i*e,n=st.find(u=>o<=u.maxPictureSize&&r<=u.maxBitrate)??S(st);return`vp09.${s}.${n.level}.08`}else if(t==="av1"){let o=i*e,n=ot.find(u=>o<=u.maxPictureSize&&r<=u.maxBitrate)??S(ot);return`av01.0.${n.level.toString().padStart(2,"0")}${n.tier}.08`}}throw new TypeError(`Unhandled codec '${t}'.`)},at=(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}'.`)},ut=t=>t==="avc"?{avc:{format:"avc"}}:t==="hevc"?{hevc:{format:"hevc"}}:{},ct=t=>t==="aac"?{aac:{format:"aac"}}:t==="opus"?{opus:{format:"opus"}}:{},pe=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&&!Ce(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(F);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(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((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.")},he=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&&!Ce(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.startsWith("mp4a")&&!t.decoderConfig.description)throw new TypeError("Audio chunk metadata decoder configuration for AAC must include a description, which is expected to be an AudioSpecificConfig as specified in ISO 14496-3.");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.")},be=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 le=1e3,pr=2082844800,v=(t,i,e=!0)=>{let r=t*i;return e?Math.round(r):r},xe=class extends Q{constructor(e,r){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)+pr;this.finalizedChunks=[];this.nextFragmentNumber=1;this.writer=e._writer,this.boxWriter=new G(this.writer);let s=this.writer instanceof $?"in-memory":!1;this.fastStart=r._options.fastStart??s,(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(Xe({holdsAvc:r,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,r){let s=this.trackDatas.find(n=>n.track===e);if(s)return s;pe(r),d(r),d(r.decoderConfig),d(r.decoderConfig.codedWidth!==void 0),d(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;he(r),d(r),d(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;be(r),d(r),d(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}async addEncodedVideoChunk(e,r,s){let o=await this.mutex.acquire();try{let n=this.getVideoTrackData(e,s),a=new Uint8Array(r.byteLength);r.copyTo(a);let u=this.validateAndNormalizeTimestamp(n.track,r.timestamp,r.type==="key"),m=this.createSampleForTrack(n,a,u,(r.duration??0)/1e6,r.type);await this.registerSample(n,m)}finally{o()}}async addEncodedAudioChunk(e,r,s){let o=await this.mutex.acquire();try{let n=this.getAudioTrackData(e,s),a=new Uint8Array(r.byteLength);r.copyTo(a);let u=r.type,m=this.validateAndNormalizeTimestamp(n.track,r.timestamp,u==="key"),l=this.createSampleForTrack(n,a,m,(r.duration??0)/1e6,u);await this.registerSample(n,l)}finally{o()}}async addSubtitleCue(e,r,s){let o=await this.mutex.acquire();try{let n=this.getSubtitleTrackData(e,s);this.validateAndNormalizeTimestamp(n.track,1e6*r.timestamp,!0),e.source._codec==="webvtt"&&(n.cueQueue.push(r),await this.processWebVTTCues(n,r.timestamp))}finally{o()}}async processWebVTTCues(e,r){for(;e.cueQueue.length>0;){let s=new Set([]);for(let l of e.cueQueue)d(l.timestamp<=r),d(e.lastCueEndTimestamp<=l.timestamp+l.duration),s.add(Math.max(l.timestamp,e.lastCueEndTimestamp)),s.add(l.timestamp+l.duration);let o=[...s].sort((l,f)=>l-f),n=o[0],a=o[1]??n;if(r=a)break;H.lastIndex=0;let T=H.test(f.text),E=f.timestamp+f.duration,C=e.cueToSourceId.get(f);if(C===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,await this.finalizeFragment())}else s=o>=.5}s&&(e.currentChunk&&await this.finalizeCurrentChunk(e),e.currentChunk={startTimestamp:r.timestamp,samples:[],offset:null,moofOffset:null}),d(e.currentChunk),e.currentChunk.samples.push(r),e.timestampProcessingQueue.push(r)}async finalizeCurrentChunk(e){if(d(this.fastStart!=="fragmented"),!!e.currentChunk){if(e.finalizedChunks.push(e.currentChunk),this.finalizedChunks.push(e.currentChunk),(e.compactlyCodedChunkTable.length===0||S(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)d(r.data),this.writer.write(r.data),r.data=null;await this.writer.flush()}}async interleaveSamples(){d(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,l=this.boxWriter.measureBox(u)+m),u.size=l,this.boxWriter.writeBox(u)}for(let u of this.trackDatas){u.currentChunk.offset=this.writer.getPos(),u.currentChunk.moofOffset=s;for(let m of u.currentChunk.samples)this.writer.write(m.data),m.data=null}let n=this.writer.getPos();this.writer.seek(this.boxWriter.offsets.get(o));let a=ve(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&&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"){d(this.mdat);let r;for(let o=0;o<2;o++){let n=Y(this.trackDatas,this.creationTime),a=this.boxWriter.measureBox(n);r=this.boxWriter.measureBox(this.mdat);let u=this.writer.getPos()+a+r;for(let m of this.finalizedChunks){m.offset=u;for(let{data:l}of m.samples)d(l),u+=l.byteLength,r+=l.byteLength}if(u<2**32)break;r>=2**32&&(this.mdat.largeSize=!0)}let s=Y(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 n of o.samples)d(n.data),this.writer.write(n.data),n.data=null}else if(this.fastStart==="fragmented"){let r=this.writer.getPos(),s=Ze(this.trackDatas);this.boxWriter.writeBox(s);let o=this.writer.getPos()-r;this.writer.seek(this.writer.getPos()-4),this.boxWriter.writeU32(o)}else{d(this.mdat),d(this.ftypSize!==null);let r=this.boxWriter.offsets.get(this.mdat);d(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=Y(this.trackDatas,this.creationTime);if(typeof this.fastStart=="object"){this.writer.seek(this.ftypSize),this.boxWriter.writeBox(o);let n=r-this.writer.getPos();this.boxWriter.writeBox(Ge(n))}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=t=>t<256?1:t<65536?2:t<1<<24?3:t<2**32?4:t<2**40?5:6,Ee=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,lt=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 Oe=2**15,dt="https://github.com/Vanilagy/webm-muxer",mt=6,ft=5,hr={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"},br={video:1,audio:2,subtitle:17},Te=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=_e(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=Ee(e)){e<0&&(e+=2**(r*8)),this.writeUnsignedInt(e,r)}writeEBMLVarInt(e,r=lt(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??_e(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 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 r=e.size??Ee(e.data.value);this.writeEBMLVarInt(r),this.writeSignedInt(e.data.value,r)}}}beforeTrackAdd(e){if(this.format instanceof L)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 L?"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:dt},{id:22337,data:dt},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:br[r.type]},{id:134,data:hr[r.track.source._codec]},r.type==="video"?this.videoSpecificTrackInfo(r):null,r.type==="audio"?this.audioSpecificTrackInfo(r):null,r.type==="subtitle"?this.subtitleSpecificTrackInfo(r):null]})}videoSpecificTrackInfo(e){let r=[e.info.decoderConfig.description?{id:25506,data:P(e.info.decoderConfig.description)}:null,e.track.metadata.frameRate?{id:2352003,data:1e9/e.track.metadata.frameRate}:null],s=e.info.decoderConfig.colorSpace,o={id:224,data:[{id:176,data:e.info.width},{id:186,data:e.info.height},ne(s)?{id:21936,data:[{id:21937,data:N[s.matrix]},{id:21946,data:F[s.transfer]},{id:21947,data:R[s.primaries]},{id:21945,data:s.fullRange?2:1}]}:null]};return r.push(o),r}audioSpecificTrackInfo(e){return[e.info.decoderConfig.description?{id:25506,data:P(e.info.decoderConfig.description)}:null,{id:225,data:[{id:181,data:new ee(e.info.sampleRate)},{id:159,data:e.info.numberOfChannels}]}]}subtitleSpecificTrackInfo(e){return[{id:25506,data:y.encode(e.info.config.description)}]}createSegment(){let e={id:408125543,size:this.format._options.streamable?-1:mt,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 d(this.segment),this.dataOffsets.get(this.segment)}getVideoTrackData(e,r){let s=this.trackDatas.find(n=>n.track===e);if(s)return s;pe(r),d(r),d(r.decoderConfig),d(r.decoderConfig.codedWidth!==void 0),d(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;he(r),d(r),d(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;be(r),d(r),d(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}async addEncodedVideoChunk(e,r,s){let o=await this.mutex.acquire();try{let n=this.getVideoTrackData(e,s),a=new Uint8Array(r.byteLength);r.copyTo(a);let u=r.type==="key",m=this.validateAndNormalizeTimestamp(n.track,r.timestamp,u),l=this.createInternalChunk(a,m,(r.duration??0)/1e6,r.type);e.source._codec==="vp9"&&this.fixVP9ColorSpace(n,l),n.chunkQueue.push(l),await this.interleaveChunks()}finally{o()}}async addEncodedAudioChunk(e,r,s){let o=await this.mutex.acquire();try{let n=this.getAudioTrackData(e,s),a=new Uint8Array(r.byteLength);r.copyTo(a);let u=r.type,m=u==="key",l=this.validateAndNormalizeTimestamp(n.track,r.timestamp,m),f=this.createInternalChunk(a,l,(r.duration??0)/1e6,u);n.chunkQueue.push(f),await this.interleaveChunks()}finally{o()}}async addSubtitleCue(e,r,s){let o=await this.mutex.acquire();try{let n=this.getSubtitleTrackData(e,s),a=this.validateAndNormalizeTimestamp(n.track,1e6*r.timestamp,!0),u=r.text,m=Math.floor(a*1e3);H.lastIndex=0,u=u.replace(H,E=>{let k=ue(E.slice(1,-1))-m;return`<${ce(k)}>`});let l=y.encode(u),f=`${r.settings??""}
-${r.identifier??""}
-${r.notes??""}`,T=this.createInternalChunk(l,a,r.duration,"key",f.trim()?y.encode(f):null);n.chunkQueue.push(T),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 m={rgb:7,bt709:2,bt470bg:1,smpte170m:3}[e.info.decoderConfig.colorSpace.matrix];He(r.data,s+0,s+3,m)}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(f=>{if(f.track.source._closed)return!0;if(e===f)return r.type==="key";let T=f.chunkQueue[0];return T&&T.type==="key"});(!this.currentCluster||o&&s-this.currentClusterMsTimestamp>=1e3)&&this.createNewCluster(s);let n=s-this.currentClusterMsTimestamp;if(n<0)return;if(n>=Oe)throw new Error(`Current Matroska cluster exceeded its maximum allowed length of ${Oe} milliseconds. In order to produce a correct WebM file, you must pass in a key frame at least every ${Oe} milliseconds.`);let u=new Uint8Array(4),m=new DataView(u.buffer);m.setUint8(0,128|e.track.id),m.setInt16(1,n,!1);let l=Math.floor(1e3*r.duration);if(l===0&&!r.additions){m.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 te(e.lastWrittenMsTimestamp-s)}:null,r.additions?{id:30113,data:[{id:166,data:[{id:165,data:r.additions},{id:238,data:1}]}]}:null,l>0?{id:155,data:l}:null]};this.writeEBML(f)}this.duration=Math.max(this.duration,s+l),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:ft,data:[{id:231,data:e}]},this.writeEBML(this.currentCluster),this.currentClusterMsTimestamp=e,this.trackDatasInCurrentCluster.clear()}finalizeCurrentCluster(){d(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,ft),this.writer.seek(r);let s=this.offsets.get(this.currentCluster)-this.segmentDataOffset;d(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(),d(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,mt),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(r)}e()}};var U=class{},Me=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 xe(i,this)}},we=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 Te(i,this)}},L=class extends we{};var re=["avc","hevc","vp8","vp9","av1"],ie=["aac","opus"],Ve=["webvtt"],q=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 q{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}},ze=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)}},xr=5,Tr=t=>{if(!t||typeof t!="object")throw new TypeError("Codec config must be an object.");if(!re.includes(t.codec))throw new TypeError(`Invalid video codec '${t.codec}'. Must be one of: ${re.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'.")},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;Tr(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),d(this.encoder);let e=Math.floor(i.timestamp/1e6/xr);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)=>void this.muxer.addEncodedVideoChunk(this.source._connectedTrack,e,r),error:e=>console.error("Video encode error:",e)}),this.encoder.configure({codec:nt(this.codecConfig.codec,i.codedWidth,i.codedHeight,this.codecConfig.bitrate),width:i.codedWidth,height:i.codedHeight,bitrate:this.codecConfig.bitrate,framerate:this.source._connectedTrack?.metadata.frameRate,latencyMode:this.codecConfig.latencyMode,...ut(this.codecConfig.codec)}),d(this.source._connectedTrack),this.muxer=this.source._connectedTrack.output._muxer)}async flush(){this.encoder&&(await this.encoder.flush(),this.encoder.close())}},Pe=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()}},Be=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 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()}},Ie=class extends O{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 se(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()}},M=class extends q{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}},Ue=class extends M{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)}},wr=t=>{if(!t||typeof t!="object")throw new TypeError("Codec config must be an object.");if(!ie.includes(t.codec))throw new TypeError(`Invalid audio 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.")},oe=class{constructor(i,e){this.source=i;this.codecConfig=e;this.encoder=null;this.muxer=null;this.lastNumberOfChannels=null;this.lastSampleRate=null;wr(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),d(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)=>void this.muxer.addEncodedAudioChunk(this.source._connectedTrack,e,r),error:e=>console.error("Audio encode error:",e)}),this.encoder.configure({codec:at(this.codecConfig.codec,i.numberOfChannels,i.sampleRate),numberOfChannels:i.numberOfChannels,sampleRate:i.sampleRate,bitrate:this.codecConfig.bitrate,...ct(this.codecConfig.codec)}),d(this.source._connectedTrack),this.muxer=this.source._connectedTrack.output._muxer)}async flush(){this.encoder&&(await this.encoder.flush(),this.encoder.close())}},De=class extends M{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()}},We=class extends M{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 r=e.numberOfChannels,s=e.sampleRate,o=e.length,n=new Float32Array(r*o);for(let m=0;m{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()}},K=class extends q{constructor(e){super();this._connectedTrack=null;if(!Ve.includes(e))throw new TypeError(`Invalid subtitle codec '${e}'. Must be one of: ${Ve.join(", ")}.`);this._codec=e}},Fe=class extends K{constructor(i){super(i),this._parser=new ae({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 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 I))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(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 M))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 K))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()}};export{ie as AUDIO_CODECS,J as ArrayBufferTarget,We as AudioBufferSource,De as AudioDataSource,M as AudioSource,Be as CanvasSource,Ue as EncodedAudioChunkSource,ze as EncodedVideoChunkSource,q as MediaSource,Re as MediaStreamAudioTrackSource,Ie as MediaStreamVideoTrackSource,we as MkvOutputFormat,Me as Mp4OutputFormat,Ne as Output,U as OutputFormat,Ve as SUBTITLE_CODECS,Ae as StreamTarget,K as SubtitleSource,I as Target,Fe as TextSubtitleSource,re as VIDEO_CODECS,Pe as VideoFrameSource,O as VideoSource,L as WebMOutputFormat};
+`,o);c===-1&&(c=e.length);let u=pe(t[2]),f=pe(t[3])-u,T=e.slice(n,c).trim();e=e.slice(c).trimStart(),ee.lastIndex=0;let C={timestamp:u/1e3,duration:f/1e3,text:T,identifier:s,settings:a,notes:i},k={};this.preambleEmitted||(k.config={description:this.preambleText},this.preambleEmitted=!0),this.options.output(C,k)}}},Yt=/(?:(\d{2}):)?(\d{2}):(\d{2}).(\d{3})/,pe=r=>{let e=Yt.exec(r);if(!e)throw new Error("Expected match.");return 60*60*1e3*Number(e[1]||"0")+60*1e3*Number(e[2])+1e3*Number(e[3])+Number(e[4])},be=r=>{let e=Math.floor(r/36e5),t=Math.floor(r%(60*60*1e3)/(60*1e3)),i=Math.floor(r%(60*1e3)/1e3),s=r%1e3;return e.toString().padStart(2,"0")+":"+t.toString().padStart(2,"0")+":"+i.toString().padStart(2,"0")+"."+s.toString().padStart(3,"0")};var te=class{constructor(e){this.writer=e}helper=new Uint8Array(8);helperView=new DataView(this.helper.buffer);offsets=new WeakMap;writeU32(e){this.helperView.setUint32(0,e,!1),this.writer.write(this.helper.subarray(0,4))}writeU64(e){this.helperView.setUint32(0,Math.floor(e/2**32),!1),this.helperView.setUint32(4,e,!1),this.writer.write(this.helper.subarray(0,8))}writeAscii(e){for(let t=0;t[(r%256+256)%256],p=r=>(E.setUint16(0,r,!1),[h[0],h[1]]),Zt=r=>(E.setInt16(0,r,!1),[h[0],h[1]]),yt=r=>(E.setUint32(0,r,!1),[h[1],h[2],h[3]]),m=r=>(E.setUint32(0,r,!1),[h[0],h[1],h[2],h[3]]),vt=r=>(E.setInt32(0,r,!1),[h[0],h[1],h[2],h[3]]),F=r=>(E.setUint32(0,Math.floor(r/2**32),!1),E.setUint32(4,r,!1),[h[0],h[1],h[2],h[3],h[4],h[5],h[6],h[7]]),He=r=>(E.setInt16(0,2**8*r,!1),[h[0],h[1]]),A=r=>(E.setInt32(0,2**16*r,!1),[h[0],h[1],h[2],h[3]]),We=r=>(E.setInt32(0,2**30*r,!1),[h[0],h[1],h[2],h[3]]),Ne=(r,e)=>{let t=[],i=r;do{let s=i&127;i>>=7,t.length>0&&(s|=128),t.push(s),e!==void 0&&e--}while(i>0||e);return t.reverse()},S=(r,e=!1)=>{let t=Array(r.length).fill(null).map((i,s)=>r.charCodeAt(s));return e&&t.push(0),t},Le=r=>{let e=null;for(let t of r)(!e||t.timestamp>e.timestamp)&&(e=t);return e},_t=r=>[A(r[0]),A(r[1]),We(r[2]),A(r[3]),A(r[4]),We(r[5]),A(r[6]),A(r[7]),We(r[8])],b=(r,e,t)=>({type:r,contents:e&&new Uint8Array(e.flat(10)),children:t}),g=(r,e,t,i,s)=>b(r,[x(e),yt(t),i??[]],s),At=r=>r.fragmented?b("ftyp",[S("iso5"),m(512),S("iso5"),S("iso6"),S("mp41")]):b("ftyp",[S("isom"),m(512),S("isom"),r.holdsAvc?S("avc1"):[],S("mp41")]),ge=r=>({type:"mdat",largeSize:r}),Et=r=>({type:"free",size:r}),re=(r,e,t=!1)=>b("moov",void 0,[Jt(e,r),...r.map(i=>er(i,e)),t?Rr(r):null]),Jt=(r,e)=>{let t=_(Math.max(0,...e.filter(n=>n.samples.length>0).map(n=>{let a=Le(n.samples);return a.timestamp+a.duration})),ke),i=Math.max(0,...e.map(n=>n.track.id))+1,s=!M(r)||!M(t),o=s?F:m;return g("mvhd",+s,0,[o(r),o(r),m(ke),o(t),A(1),He(1),Array(10).fill(0),_t(De),Array(24).fill(0),m(i)])},er=(r,e)=>b("trak",void 0,[tr(r,e),rr(r,e)]),tr=(r,e)=>{let t=Le(r.samples),i=_(t?t.timestamp+t.duration:0,ke),s=!M(e)||!M(i),o=s?F:m,n;if(r.type==="video"){let a=r.track.metadata.rotation;n=a===void 0||typeof a=="number"?V(a??0):a}else n=De;return g("tkhd",+s,3,[o(e),o(e),m(r.track.id),m(0),o(i),Array(8).fill(0),p(0),p(r.track.id),He(r.type==="audio"?1:0),p(0),_t(n),A(r.type==="video"?r.info.width:0),A(r.type==="video"?r.info.height:0)])},rr=(r,e)=>b("mdia",void 0,[ir(r,e),nr(r),ar(r)]),ir=(r,e)=>{let t=Le(r.samples),i=_(t?t.timestamp+t.duration:0,r.timescale),s=!M(e)||!M(i),o=s?F:m;return g("mdhd",+s,0,[o(e),o(e),m(r.timescale),o(i),p(21956),p(0)])},sr={video:"vide",audio:"soun",subtitle:"text"},or={video:"VideoHandler",audio:"SoundHandler",subtitle:"TextHandler"},nr=r=>g("hdlr",0,0,[S("mhlr"),S(sr[r.type]),m(0),m(0),m(0),S(or[r.type],!0)]),ar=r=>b("minf",void 0,[lr[r.type](),mr(),pr(r)]),cr=()=>g("vmhd",0,1,[p(0),p(0),p(0),p(0)]),ur=()=>g("smhd",0,0,[p(0),p(0)]),dr=()=>g("nmhd",0,0),lr={video:cr,audio:ur,subtitle:dr},mr=()=>b("dinf",void 0,[fr()]),fr=()=>g("dref",0,0,[m(1)],[hr()]),hr=()=>g("url ",0,1),pr=r=>{let e=r.compositionTimeOffsetTable.length>1||r.compositionTimeOffsetTable.some(t=>t.sampleCompositionTimeOffset!==0);return b("stbl",void 0,[br(r),Ar(r),Er(r),Ir(r),Or(r),Vr(r),e?Mr(r):null])},br=r=>{let e;return r.type==="video"?e=kr(Hr[r.track.source._codec],r):r.type==="audio"?e=wr($r[r.track.source._codec],r):r.type==="subtitle"&&(e=vr(jr[r.track.source._codec],r)),l(e),g("stsd",0,0,[m(1)],[e])},kr=(r,e)=>b(r,[Array(6).fill(0),p(1),p(0),p(0),Array(12).fill(0),p(e.info.width),p(e.info.height),m(4718592),m(4718592),m(0),p(1),Array(32).fill(0),p(24),Zt(65535)],[Lr[e.track.source._codec](e),fe(e.info.decoderConfig.colorSpace)?gr(e):null]),gr=r=>b("colr",[S("nclx"),p(z[r.info.decoderConfig.colorSpace.primaries]),p(B[r.info.decoderConfig.colorSpace.transfer]),p(U[r.info.decoderConfig.colorSpace.matrix]),x((r.info.decoderConfig.colorSpace.fullRange?1:0)<<7)]),Tr=r=>r.info.decoderConfig&&b("avcC",[...P(r.info.decoderConfig.description)]),xr=r=>r.info.decoderConfig&&b("hvcC",[...P(r.info.decoderConfig.description)]),St=r=>{if(!r.info.decoderConfig)return null;let e=r.info.decoderConfig;l(e.colorSpace);let t=e.codec.split("."),i=Number(t[1]),s=Number(t[2]),a=(Number(t[3])<<4)+(0<<1)+Number(e.colorSpace.fullRange);return g("vpcC",1,0,[x(i),x(s),x(a),x(2),x(2),x(2),p(0)])},Cr=()=>b("av1C",[129,0,0,0]),wr=(r,e)=>b(r,[Array(6).fill(0),p(1),p(0),p(0),m(0),p(e.info.numberOfChannels),p(16),p(0),p(0),A(e.info.sampleRate)],[Qr[e.track.source._codec](e)]),Sr=r=>{let t=[...P(r.info.decoderConfig.description??new ArrayBuffer(0))];return t=[...x(64),...x(21),...yt(0),...m(0),...m(0),...x(5),...Ne(t.length),...t],t=[...p(1),...x(0),...x(4),...Ne(t.length),...t,...x(6),...x(1),...x(2)],t=[...x(3),...Ne(t.length),...t],g("esds",0,0,t)},yr=r=>{let e=3840,t=0,i=r.info.decoderConfig?.description;if(i){l(i.byteLength>=18);let s=ArrayBuffer.isView(i)?new DataView(i.buffer,i.byteOffset,i.byteLength):new DataView(i);e=s.getUint16(10,!0),t=s.getInt16(14,!0)}return b("dOps",[x(0),x(r.info.numberOfChannels),p(e),m(r.info.sampleRate),He(t),x(0)])},vr=(r,e)=>b(r,[Array(6).fill(0),p(1)],[Kr[e.track.source._codec](e)]),_r=r=>b("vttC",[...v.encode(r.info.config.description)]);var Ar=r=>g("stts",0,0,[m(r.timeToSampleTable.length),r.timeToSampleTable.map(e=>[m(e.sampleCount),m(e.sampleDelta)])]),Er=r=>{if(r.samples.every(t=>t.type==="key"))return null;let e=[...r.samples.entries()].filter(([,t])=>t.type==="key");return g("stss",0,0,[m(e.length),e.map(([t])=>m(t+1))])},Ir=r=>g("stsc",0,0,[m(r.compactlyCodedChunkTable.length),r.compactlyCodedChunkTable.map(e=>[m(e.firstChunk),m(e.samplesPerChunk),m(1)])]),Or=r=>g("stsz",0,0,[m(0),m(r.samples.length),r.samples.map(e=>m(e.size))]),Vr=r=>r.finalizedChunks.length>0&&y(r.finalizedChunks).offset>=2**32?g("co64",0,0,[m(r.finalizedChunks.length),r.finalizedChunks.map(e=>F(e.offset))]):g("stco",0,0,[m(r.finalizedChunks.length),r.finalizedChunks.map(e=>m(e.offset))]),Mr=r=>g("ctts",0,0,[m(r.compositionTimeOffsetTable.length),r.compositionTimeOffsetTable.map(e=>[m(e.sampleCount),m(e.sampleCompositionTimeOffset)])]),Rr=r=>b("mvex",void 0,r.map(Pr)),Pr=r=>g("trex",0,0,[m(r.track.id),m(1),m(0),m(0),m(0)]),$e=(r,e)=>b("moof",void 0,[zr(r),...e.map(Br)]),zr=r=>g("mfhd",0,0,[m(r)]),It=r=>{let e=0,t=0,i=0,s=0,o=r.type==="delta";return t|=+o,o?e|=1:e|=2,e<<24|t<<16|i<<8|s},Br=r=>b("traf",void 0,[Ur(r),Dr(r),Fr(r)]),Ur=r=>{l(r.currentChunk);let e=0;e|=8,e|=16,e|=32,e|=131072;let t=r.currentChunk.samples[1]??r.currentChunk.samples[0],i={duration:t.timescaleUnitsToNextSample,size:t.size,flags:It(t)};return g("tfhd",0,e,[m(r.track.id),m(i.duration),m(i.size),m(i.flags)])},Dr=r=>(l(r.currentChunk),g("tfdt",1,0,[F(_(r.currentChunk.startTimestamp,r.timescale))])),Fr=r=>{l(r.currentChunk);let e=r.currentChunk.samples.map(w=>w.timescaleUnitsToNextSample),t=r.currentChunk.samples.map(w=>w.size),i=r.currentChunk.samples.map(It),s=r.currentChunk.samples.map(w=>_(w.timestamp-w.decodeTimestamp,r.timescale)),o=new Set(e),n=new Set(t),a=new Set(i),c=new Set(s),u=a.size===2&&i[0]!==i[1],d=o.size>1,f=n.size>1,T=!u&&a.size>1,C=c.size>1||[...c].some(w=>w!==0),k=0;return k|=1,k|=4*+u,k|=256*+d,k|=512*+f,k|=1024*+T,k|=2048*+C,g("trun",1,k,[m(r.currentChunk.samples.length),m(r.currentChunk.offset-r.currentChunk.moofOffset||0),u?m(i[0]):[],r.currentChunk.samples.map((w,me)=>[d?m(e[me]):[],f?m(t[me]):[],T?m(i[me]):[],C?vt(s[me]):[]])])},Ot=r=>b("mfra",void 0,[...r.map(Wr),Nr()]),Wr=(r,e)=>g("tfra",1,0,[m(r.track.id),m(63),m(r.finalizedChunks.length),r.finalizedChunks.map(i=>[F(_(i.startTimestamp,r.timescale)),F(i.moofOffset),m(e+1),m(1),m(1)])]),Nr=()=>g("mfro",0,0,[m(0)]),Vt=()=>b("vtte"),Mt=(r,e,t,i,s)=>b("vttc",void 0,[s!==null?b("vsid",[vt(s)]):null,t!==null?b("iden",[...v.encode(t)]):null,e!==null?b("ctim",[...v.encode(be(e))]):null,i!==null?b("sttg",[...v.encode(i)]):null,b("payl",[...v.encode(r)])]),Rt=r=>b("vtta",[...v.encode(r)]),Hr={avc:"avc1",hevc:"hvc1",vp8:"vp08",vp9:"vp09",av1:"av01"},Lr={avc:Tr,hevc:xr,vp8:St,vp9:St,av1:Cr},$r={aac:"mp4a",opus:"Opus"},Qr={aac:Sr,opus:yr},jr={webvtt:"wvtt"},Kr={webvtt:_r};var $=class{output;mutex=new H;constructor(e){this.output=e}beforeTrackAdd(e){}onTrackClose(e){}trackTimestampInfo=new WeakMap;validateAndNormalizeTimestamp(e,t,i){let s=t/1e6,o=this.trackTimestampInfo.get(e);if(!o){if(!i)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:e.source._offsetTimestamps?0:s,lastKeyFrameTimestamp:e.source._offsetTimestamps?0:s},this.trackTimestampInfo.set(e,o)}if(e.source._offsetTimestamps&&(s-=o.timestampOffset),s<0)throw new Error(`Timestamps must be non-negative (got ${s}s).`);if(si.start-s.start);e.push({start:t[0].start,size:t[0].data.byteLength});for(let i=1;ic.start<=t&&tXr){for(let c=0;c=e.written[o+1].start;)e.written[o].end=Math.max(e.written[o].end,e.written[o+1].end),e.written.splice(o+1,1)}createChunk(e){let i={start:Math.floor(e/this.chunkSize)*this.chunkSize,data:new Uint8Array(this.chunkSize),written:[],shouldFlush:!1};return this.chunks.push(i),this.chunks.sort((s,o)=>s.start-o.start),this.chunks.indexOf(i)}queueChunksForFlush(e=!1){l(this.writer);for(let t=0;t{if(r==="avc"){let o=Math.ceil(e/16)*Math.ceil(t/16),n=Pt.find(f=>o<=f.maxMacroblocks&&i<=f.maxBitrate)??y(Pt),a=n?n.level:0,c="64".padStart(2,"0"),u="00",d=a.toString(16).padStart(2,"0");return`avc1.${c}${u}${d}`}else if(r==="hevc"){let s="",n="6",a=e*t,c=zt.find(d=>a<=d.maxPictureSize&&i<=d.maxBitrate)??y(zt);return`hev1.${s}1.${n}.${c.tier}${c.level}.B0`}else{if(r==="vp8")return"vp8";if(r==="vp9"){let s="00",o=e*t,n=Bt.find(c=>o<=c.maxPictureSize&&i<=c.maxBitrate)??y(Bt);return`vp09.${s}.${n.level}.08`}else if(r==="av1"){let o=e*t,n=Ut.find(c=>o<=c.maxPictureSize&&i<=c.maxBitrate)??y(Ut);return`av01.0.${n.level.toString().padStart(2,"0")}${n.tier}.08`}}throw new TypeError(`Unhandled codec '${r}'.`)},Ft=(r,e)=>{if(r==="avc"){if(!e||e.byteLength<4)throw new TypeError("AVC description must be at least 4 bytes long.");return`avc1.${xt(e.subarray(1,4))}`}else if(r==="hevc"){if(!e)throw new TypeError("HEVC description must be provided.");let t=new DataView(e.buffer,e.byteOffset,e.byteLength),i="hev1.",s=e[1]>>6&3,o=e[1]&31;i+=["","A","B","C"][s]+o,i+=".";let n=Ct(t.getUint32(2));i+=n.toString(16),i+=".";let a=e[1]>>5&1,c=e[12];i+=a===0?"L":"H",i+=c,i+=".";let u=[];for(let d=0;d<6;d++){let f=e[d+13];u.push(f)}for(;u[u.length-1]===0;)u.pop();return i+=u.map(d=>d.toString(16)).join("."),i}throw new TypeError(`Unhandled codec '${r}'.`)},Wt=(r,e,t)=>{if(r==="aac")return e>=2&&t<=24e3?"mp4a.40.29":t<=24e3?"mp4a.40.5":"mp4a.40.2";if(r==="opus")return"opus";if(r==="vorbis")return"vorbis";throw new TypeError(`Unhandled codec '${r}'.`)},Nt=(r,e)=>{if(r==="aac"){if(!e||e.byteLength<2)throw new TypeError("AAC description must be at least 2 bytes long.");return`mp4a.40.${e[0]>>3}`}else{if(r==="opus")return"opus";if(r==="vorbis")return"vorbis"}throw new TypeError(`Unhandled codec '${r}'.`)},Ht=r=>r==="avc"?{avc:{format:"avc"}}:r==="hevc"?{hevc:{format:"hevc"}}:{},Lt=r=>r==="aac"?{aac:{format:"aac"}}:r==="opus"?{opus:{format:"opus"}}:{},we=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&&!Ue(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:e}=r.decoderConfig;if(typeof e!="object")throw new TypeError("Video chunk metadata decoder configuration colorSpace, when provided, must be an object.");let t=Object.keys(z);if(e.primaries!=null&&!t.includes(e.primaries))throw new TypeError(`Video chunk metadata decoder configuration colorSpace primaries, when defined, must be one of ${t.join(", ")}.`);let i=Object.keys(B);if(e.transfer!=null&&!i.includes(e.transfer))throw new TypeError(`Video chunk metadata decoder configuration colorSpace transfer, when defined, must be one of ${i.join(", ")}.`);let s=Object.keys(U);if(e.matrix!=null&&!s.includes(e.matrix))throw new TypeError(`Video chunk metadata decoder configuration colorSpace matrix, when defined, must be one of ${s.join(", ")}.`);if(e.fullRange!=null&&typeof e.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.")},Se=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&&!Ue(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.startsWith("mp4a")&&!r.decoderConfig.description)throw new TypeError("Audio chunk metadata decoder configuration for AAC must include a description, which is expected to be an AudioSpecificConfig as specified in ISO 14496-3.");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.")},ye=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 ke=1e3,Gr=2082844800,_=(r,e,t=!0)=>{let i=r*e;return t?Math.round(i):i},ve=class extends ${timestampsMustStartAtZero=!0;writer;boxWriter;fastStart;auxTarget=new se;auxWriter=this.auxTarget._createWriter();auxBoxWriter=new te(this.auxWriter);ftypSize=null;mdat=null;trackDatas=[];creationTime=Math.floor(Date.now()/1e3)+Gr;finalizedChunks=[];nextFragmentNumber=1;constructor(e,t){super(e),this.writer=e._writer,this.boxWriter=new te(this.writer);let i=this.writer instanceof Q?"in-memory":!1;this.fastStart=t._options.fastStart??i,(this.fastStart==="in-memory"||this.fastStart==="fragmented")&&(this.writer.ensureMonotonicity=!0)}async start(){let e=await this.mutex.acquire(),t=this.output._tracks.some(i=>i.type==="video"&&i.source._codec==="avc");this.boxWriter.writeBox(At({holdsAvc:t,fragmented:this.fastStart==="fragmented"})),this.ftypSize=this.writer.getPos(),this.fastStart==="in-memory"?this.mdat=ge(!1):this.fastStart==="fragmented"||(this.mdat=ge(!0),this.boxWriter.writeBox(this.mdat)),await this.writer.flush(),e()}getVideoTrackData(e,t){let i=this.trackDatas.find(o=>o.track===e);if(i)return i;we(t),l(t),l(t.decoderConfig),l(t.decoderConfig.codedWidth!==void 0),l(t.decoderConfig.codedHeight!==void 0);let s={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(s),this.trackDatas.sort((o,n)=>o.track.id-n.track.id),s}getAudioTrackData(e,t){let i=this.trackDatas.find(o=>o.track===e);if(i)return i;Se(t),l(t),l(t.decoderConfig);let s={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(s),this.trackDatas.sort((o,n)=>o.track.id-n.track.id),s}getSubtitleTrackData(e,t){let i=this.trackDatas.find(o=>o.track===e);if(i)return i;ye(t),l(t),l(t.config);let s={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(s),this.trackDatas.sort((o,n)=>o.track.id-n.track.id),this.validateAndNormalizeTimestamp(e,0,!0),s}async addEncodedVideoChunk(e,t,i){let s=await this.mutex.acquire();try{let o=this.getVideoTrackData(e,i),n=new Uint8Array(t.byteLength);t.copyTo(n);let a=this.validateAndNormalizeTimestamp(o.track,t.timestamp,t.type==="key"),c=this.createSampleForTrack(o,n,a,(t.duration??0)/1e6,t.type);await this.registerSample(o,c)}finally{s()}}async addEncodedAudioChunk(e,t,i){let s=await this.mutex.acquire();try{let o=this.getAudioTrackData(e,i),n=new Uint8Array(t.byteLength);t.copyTo(n);let a=t.type,c=this.validateAndNormalizeTimestamp(o.track,t.timestamp,a==="key"),u=this.createSampleForTrack(o,n,c,(t.duration??0)/1e6,a);await this.registerSample(o,u)}finally{s()}}async addSubtitleCue(e,t,i){let s=await this.mutex.acquire();try{let o=this.getSubtitleTrackData(e,i);this.validateAndNormalizeTimestamp(o.track,1e6*t.timestamp,!0),e.source._codec==="webvtt"&&(o.cueQueue.push(t),await this.processWebVTTCues(o,t.timestamp))}finally{s()}}async processWebVTTCues(e,t){for(;e.cueQueue.length>0;){let i=new Set([]);for(let u of e.cueQueue)l(u.timestamp<=t),l(e.lastCueEndTimestamp<=u.timestamp+u.duration),i.add(Math.max(u.timestamp,e.lastCueEndTimestamp)),i.add(u.timestamp+u.duration);let s=[...i].sort((u,d)=>u-d),o=s[0],n=s[1]??o;if(t=n)break;L.lastIndex=0;let f=L.test(d.text),T=d.timestamp+d.duration,C=e.cueToSourceId.get(d);if(C===void 0&&ni.timestamp).sort((i,s)=>i-s);for(let i=0;i{if(e===n)return t.type==="key";let a=n.sampleQueue[0];return a&&a.type==="key"});s>=1&&o&&(i=!0,await this.finalizeFragment())}else i=s>=.5}i&&(e.currentChunk&&await this.finalizeCurrentChunk(e),e.currentChunk={startTimestamp:t.timestamp,samples:[],offset:null,moofOffset:null}),l(e.currentChunk),e.currentChunk.samples.push(t),e.timestampProcessingQueue.push(t)}async finalizeCurrentChunk(e){if(l(this.fastStart!=="fragmented"),!!e.currentChunk){if(e.finalizedChunks.push(e.currentChunk),this.finalizedChunks.push(e.currentChunk),(e.compactlyCodedChunkTable.length===0||y(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)l(t.data),this.writer.write(t.data),t.data=null;await this.writer.flush()}}async interleaveSamples(){l(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 s of this.trackDatas){if(s.sampleQueue.length===0&&!s.track.source._closed)break e;s.sampleQueue.length>0&&s.sampleQueue[0].timestamp=2**32&&(a.largeSize=!0,u=this.boxWriter.measureBox(a)+c),a.size=u,this.boxWriter.writeBox(a)}for(let a of this.trackDatas){a.currentChunk.offset=this.writer.getPos(),a.currentChunk.moofOffset=i;for(let c of a.currentChunk.samples)this.writer.write(c.data),c.data=null}let o=this.writer.getPos();this.writer.seek(this.boxWriter.offsets.get(s));let n=$e(t,this.trackDatas);this.boxWriter.writeBox(n),this.writer.seek(o);for(let a of this.trackDatas)a.finalizedChunks.push(a.currentChunk),this.finalizedChunks.push(a.currentChunk),a.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 i=this.trackDatas.find(s=>s.track===e);i&&await this.processWebVTTCues(i,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 i of t.sampleQueue)await this.addSampleToTrack(t,i);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"){l(this.mdat);let t;for(let s=0;s<2;s++){let o=re(this.trackDatas,this.creationTime),n=this.boxWriter.measureBox(o);t=this.boxWriter.measureBox(this.mdat);let a=this.writer.getPos()+n+t;for(let c of this.finalizedChunks){c.offset=a;for(let{data:u}of c.samples)l(u),a+=u.byteLength,t+=u.byteLength}if(a<2**32)break;t>=2**32&&(this.mdat.largeSize=!0)}let i=re(this.trackDatas,this.creationTime);this.boxWriter.writeBox(i),this.mdat.size=t,this.boxWriter.writeBox(this.mdat);for(let s of this.finalizedChunks)for(let o of s.samples)l(o.data),this.writer.write(o.data),o.data=null}else if(this.fastStart==="fragmented"){let t=this.writer.getPos(),i=Ot(this.trackDatas);this.boxWriter.writeBox(i);let s=this.writer.getPos()-t;this.writer.seek(this.writer.getPos()-4),this.boxWriter.writeU32(s)}else{l(this.mdat),l(this.ftypSize!==null);let t=this.boxWriter.offsets.get(this.mdat);l(t!==void 0);let i=this.writer.getPos()-t;this.mdat.size=i,this.mdat.largeSize=i>=2**32,this.boxWriter.patchBox(this.mdat);let s=re(this.trackDatas,this.creationTime);if(typeof this.fastStart=="object"){this.writer.seek(this.ftypSize),this.boxWriter.writeBox(s);let o=t-this.writer.getPos();this.boxWriter.writeBox(Et(o))}else this.boxWriter.writeBox(s)}e()}};var oe=class{value;constructor(e){this.value=e}},q=class{value;constructor(e){this.value=e}},ne=class{value;constructor(e){this.value=e}};var je=r=>r<256?1:r<65536?2:r<1<<24?3:r<2**32?4:r<2**40?5:6,Ke=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,$t=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 qe=2**15,Qt="https://github.com/Vanilagy/webm-muxer",jt=6,Kt=5,Yr={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"},Zr={video:1,audio:2,subtitle:17},_e=class extends ${timestampsMustStartAtZero=!1;writer;format;helper=new Uint8Array(8);helperView=new DataView(this.helper.buffer);offsets=new WeakMap;dataOffsets=new WeakMap;trackDatas=[];segment=null;segmentInfo=null;seekHead=null;tracksElement=null;segmentDuration=null;cues=null;currentCluster=null;currentClusterMsTimestamp=null;trackDatasInCurrentCluster=new Set;duration=0;constructor(e,t){super(e),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=je(e)){let i=0;switch(t){case 6:this.helperView.setUint8(i++,e/2**40|0);case 5:this.helperView.setUint8(i++,e/2**32|0);case 4:this.helperView.setUint8(i++,e>>24);case 3:this.helperView.setUint8(i++,e>>16);case 2:this.helperView.setUint8(i++,e>>8);case 1:this.helperView.setUint8(i++,e);break;default:throw new Error("Bad UINT size "+t)}this.writer.write(this.helper.subarray(0,i))}writeSignedInt(e,t=Ke(e)){e<0&&(e+=2**(t*8)),this.writeUnsignedInt(e,t)}writeEBMLVarInt(e,t=$t(e)){let i=0;switch(t){case 1:this.helperView.setUint8(i++,128|e);break;case 2:this.helperView.setUint8(i++,64|e>>8),this.helperView.setUint8(i++,e);break;case 3:this.helperView.setUint8(i++,32|e>>16),this.helperView.setUint8(i++,e>>8),this.helperView.setUint8(i++,e);break;case 4:this.helperView.setUint8(i++,16|e>>24),this.helperView.setUint8(i++,e>>16),this.helperView.setUint8(i++,e>>8),this.helperView.setUint8(i++,e);break;case 5:this.helperView.setUint8(i++,8|e/2**32&7),this.helperView.setUint8(i++,e>>24),this.helperView.setUint8(i++,e>>16),this.helperView.setUint8(i++,e>>8),this.helperView.setUint8(i++,e);break;case 6:this.helperView.setUint8(i++,4|e/2**40&3),this.helperView.setUint8(i++,e/2**32|0),this.helperView.setUint8(i++,e>>24),this.helperView.setUint8(i++,e>>16),this.helperView.setUint8(i++,e>>8),this.helperView.setUint8(i++,e);break;default:throw new Error("Bad EBML VINT size "+t)}this.writer.write(this.helper.subarray(0,i))}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(),i=e.size===-1?1:e.size??4;e.size===-1?this.writeByte(255):this.writer.seek(this.writer.getPos()+i);let s=this.writer.getPos();if(this.dataOffsets.set(e,s),this.writeEBML(e.data),e.size!==-1){let o=this.writer.getPos()-s,n=this.writer.getPos();this.writer.seek(t),this.writeEBMLVarInt(o,i),this.writer.seek(n)}}else if(typeof e.data=="number"){let t=e.size??je(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 oe)this.writeEBMLVarInt(4),this.writeFloat32(e.data.value);else if(e.data instanceof q)this.writeEBMLVarInt(8),this.writeFloat64(e.data.value);else if(e.data instanceof ne){let t=e.size??Ke(e.data.value);this.writeEBMLVarInt(t),this.writeSignedInt(e.data.value,t)}}}beforeTrackAdd(e){if(this.format instanceof X)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 X?"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]),i=new Uint8Array([22,84,174,107]),s={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:i},{id:21420,size:5,data:0}]}]};this.seekHead=s}createSegmentInfo(){let e={id:17545,data:new q(0)};this.segmentDuration=e;let t={id:357149030,data:[{id:2807729,data:1e6},{id:19840,data:Qt},{id:22337,data:Qt},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:Zr[t.type]},{id:134,data:Yr[t.track.source._codec]},t.type==="video"?this.videoSpecificTrackInfo(t):null,t.type==="audio"?this.audioSpecificTrackInfo(t):null,t.type==="subtitle"?this.subtitleSpecificTrackInfo(t):null]})}videoSpecificTrackInfo(e){let t=[e.info.decoderConfig.description?{id:25506,data:P(e.info.decoderConfig.description)}:null,e.track.metadata.frameRate?{id:2352003,data:1e9/e.track.metadata.frameRate}:null],i=e.info.decoderConfig.colorSpace,s={id:224,data:[{id:176,data:e.info.width},{id:186,data:e.info.height},fe(i)?{id:21936,data:[{id:21937,data:U[i.matrix]},{id:21946,data:B[i.transfer]},{id:21947,data:z[i.primaries]},{id:21945,data:i.fullRange?2:1}]}:null]};return t.push(s),t}audioSpecificTrackInfo(e){return[e.info.decoderConfig.description?{id:25506,data:P(e.info.decoderConfig.description)}:null,{id:225,data:[{id:181,data:new oe(e.info.sampleRate)},{id:159,data:e.info.numberOfChannels}]}]}subtitleSpecificTrackInfo(e){return[{id:25506,data:v.encode(e.info.config.description)}]}createSegment(){let e={id:408125543,size:this.format._options.streamable?-1:jt,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 l(this.segment),this.dataOffsets.get(this.segment)}getVideoTrackData(e,t){let i=this.trackDatas.find(o=>o.track===e);if(i)return i;we(t),l(t),l(t.decoderConfig),l(t.decoderConfig.codedWidth!==void 0),l(t.decoderConfig.codedHeight!==void 0);let s={track:e,type:"video",info:{width:t.decoderConfig.codedWidth,height:t.decoderConfig.codedHeight,decoderConfig:t.decoderConfig},chunkQueue:[],lastWrittenMsTimestamp:null};return this.trackDatas.push(s),this.trackDatas.sort((o,n)=>o.track.id-n.track.id),s}getAudioTrackData(e,t){let i=this.trackDatas.find(o=>o.track===e);if(i)return i;Se(t),l(t),l(t.decoderConfig);let s={track:e,type:"audio",info:{numberOfChannels:t.decoderConfig.numberOfChannels,sampleRate:t.decoderConfig.sampleRate,decoderConfig:t.decoderConfig},chunkQueue:[],lastWrittenMsTimestamp:null};return this.trackDatas.push(s),this.trackDatas.sort((o,n)=>o.track.id-n.track.id),s}getSubtitleTrackData(e,t){let i=this.trackDatas.find(o=>o.track===e);if(i)return i;ye(t),l(t),l(t.config);let s={track:e,type:"subtitle",info:{config:t.config},chunkQueue:[],lastWrittenMsTimestamp:null};return this.trackDatas.push(s),this.trackDatas.sort((o,n)=>o.track.id-n.track.id),s}async addEncodedVideoChunk(e,t,i){let s=await this.mutex.acquire();try{let o=this.getVideoTrackData(e,i),n=new Uint8Array(t.byteLength);t.copyTo(n);let a=t.type==="key",c=this.validateAndNormalizeTimestamp(o.track,t.timestamp,a),u=this.createInternalChunk(n,c,(t.duration??0)/1e6,t.type);e.source._codec==="vp9"&&this.fixVP9ColorSpace(o,u),o.chunkQueue.push(u),await this.interleaveChunks()}finally{s()}}async addEncodedAudioChunk(e,t,i){let s=await this.mutex.acquire();try{let o=this.getAudioTrackData(e,i),n=new Uint8Array(t.byteLength);t.copyTo(n);let a=t.type,c=a==="key",u=this.validateAndNormalizeTimestamp(o.track,t.timestamp,c),d=this.createInternalChunk(n,u,(t.duration??0)/1e6,a);o.chunkQueue.push(d),await this.interleaveChunks()}finally{s()}}async addSubtitleCue(e,t,i){let s=await this.mutex.acquire();try{let o=this.getSubtitleTrackData(e,i),n=this.validateAndNormalizeTimestamp(o.track,1e6*t.timestamp,!0),a=t.text,c=Math.floor(n*1e3);L.lastIndex=0,a=a.replace(L,T=>{let k=pe(T.slice(1,-1))-c;return`<${be(k)}>`});let u=v.encode(a),d=`${t.settings??""}
+${t.identifier??""}
+${t.notes??""}`,f=this.createInternalChunk(u,n,t.duration,"key",d.trim()?v.encode(d):null);o.chunkQueue.push(f),await this.interleaveChunks()}finally{s()}}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 s of this.trackDatas){if(s.chunkQueue.length===0&&!s.track.source._closed)break e;s.chunkQueue.length>0&&s.chunkQueue[0].timestamp=2&&i++;let c={rgb:7,bt709:2,bt470bg:1,smpte170m:3}[e.info.decoderConfig.colorSpace.matrix];bt(t.data,i+0,i+3,c)}createInternalChunk(e,t,i,s,o=null){return{data:e,type:s,timestamp:t,duration:i,additions:o}}writeBlock(e,t){this.segment||(this.createTracks(),this.createSegment());let i=Math.floor(1e3*t.timestamp),s=this.trackDatas.every(d=>{if(d.track.source._closed)return!0;if(e===d)return t.type==="key";let f=d.chunkQueue[0];return f&&f.type==="key"});(!this.currentCluster||s&&i-this.currentClusterMsTimestamp>=1e3)&&this.createNewCluster(i);let o=i-this.currentClusterMsTimestamp;if(o<0)return;if(o>=qe)throw new Error(`Current Matroska cluster exceeded its maximum allowed length of ${qe} milliseconds. In order to produce a correct WebM file, you must pass in a key frame at least every ${qe} milliseconds.`);let a=new Uint8Array(4),c=new DataView(a.buffer);c.setUint8(0,128|e.track.id),c.setInt16(1,o,!1);let u=Math.floor(1e3*t.duration);if(u===0&&!t.additions){c.setUint8(3,+(t.type==="key")<<7);let d={id:163,data:[a,t.data]};this.writeEBML(d)}else{let d={id:160,data:[{id:161,data:[a,t.data]},t.type==="delta"?{id:251,data:new ne(e.lastWrittenMsTimestamp-i)}:null,t.additions?{id:30113,data:[{id:166,data:[{id:165,data:t.additions},{id:238,data:1}]}]}:null,u>0?{id:155,data:u}:null]};this.writeEBML(d)}this.duration=Math.max(this.duration,i+u),e.lastWrittenMsTimestamp=i,this.trackDatasInCurrentCluster.add(e)}createNewCluster(e){this.currentCluster&&!this.format._options.streamable&&this.finalizeCurrentCluster(),this.currentCluster={id:524531317,size:this.format._options.streamable?-1:Kt,data:[{id:231,data:e}]},this.writeEBML(this.currentCluster),this.currentClusterMsTimestamp=e,this.trackDatasInCurrentCluster.clear()}finalizeCurrentCluster(){l(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,Kt),this.writer.seek(t);let i=this.offsets.get(this.currentCluster)-this.segmentDataOffset;l(this.cues),this.cues.data.push({id:187,data:[{id:179,data:this.currentClusterMsTimestamp},...[...this.trackDatasInCurrentCluster].map(s=>({id:183,data:[{id:247,data:s.track.id},{id:241,data:i}]}))]})}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(),l(this.cues),this.writeEBML(this.cues),!this.format._options.streamable){let t=this.writer.getPos(),i=this.writer.getPos()-this.segmentDataOffset;this.writer.seek(this.offsets.get(this.segment)+4),this.writeEBMLVarInt(i,jt),this.segmentDuration.data=new q(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 N=class{},Xe=class extends N{_options;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 ve(e,this)}},Ae=class extends N{_options;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 _e(e,this)}},X=class extends Ae{};var G=class{_connectedTrack=null;_closed=!1;_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)}},I=class extends G{_connectedTrack=null;_codec;constructor(e){if(super(),!j.includes(e))throw new TypeError(`Invalid video codec '${e}'. Must be one of: ${j.join(", ")}.`);this._codec=e}},Ge=class extends I{constructor(e){super(e)}digest(e,t){if(!(e instanceof EncodedVideoChunk))throw new TypeError("chunk must be an EncodedVideoChunk.");return this._ensureValidDigest(),this._connectedTrack.output._muxer.addEncodedVideoChunk(this._connectedTrack,e,t)}},Jr=5,ei=r=>{if(!r||typeof r!="object")throw new TypeError("Codec config must be an object.");if(!j.includes(r.codec))throw new TypeError(`Invalid video codec '${r.codec}'. Must be one of: ${j.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'.")},ae=class{constructor(e,t){this.source=e;this.codecConfig=t;ei(t)}encoder=null;muxer=null;lastMultipleOfKeyFrameInterval=-1;lastWidth=null;lastHeight=null;async digest(e){if(this.source._ensureValidDigest(),this.lastWidth!==null&&this.lastHeight!==null){if(e.codedWidth!==this.lastWidth||e.codedHeight!==this.lastHeight)throw new Error(`Video frame size must remain constant. Expected ${this.lastWidth}x${this.lastHeight}, got ${e.codedWidth}x${e.codedHeight}.`)}else this.lastWidth=e.codedWidth,this.lastHeight=e.codedHeight;this.ensureEncoder(e),l(this.encoder);let t=Math.floor(e.timestamp/1e6/Jr);this.encoder.encode(e,{keyFrame:t!==this.lastMultipleOfKeyFrameInterval}),this.lastMultipleOfKeyFrameInterval=t,this.encoder.encodeQueueSize>=4&&await new Promise(i=>this.encoder.addEventListener("dequeue",i,{once:!0})),await this.muxer.mutex.currentPromise}ensureEncoder(e){this.encoder||(this.encoder=new VideoEncoder({output:(t,i)=>void this.muxer.addEncodedVideoChunk(this.source._connectedTrack,t,i),error:t=>console.error("Video encode error:",t)}),this.encoder.configure({codec:Dt(this.codecConfig.codec,e.codedWidth,e.codedHeight,this.codecConfig.bitrate),width:e.codedWidth,height:e.codedHeight,bitrate:this.codecConfig.bitrate,framerate:this.source._connectedTrack?.metadata.frameRate,latencyMode:this.codecConfig.latencyMode,...Ht(this.codecConfig.codec)}),l(this.source._connectedTrack),this.muxer=this.source._connectedTrack.output._muxer)}async flush(){this.encoder&&(await this.encoder.flush(),this.encoder.close())}},Ye=class extends I{_encoder;constructor(e){super(e.codec),this._encoder=new ae(this,e)}digest(e){if(!(e instanceof VideoFrame))throw new TypeError("videoFrame must be a VideoFrame.");return this._encoder.digest(e)}_flush(){return this._encoder.flush()}},Ze=class extends I{_encoder;_canvas;constructor(e,t){if(!(e instanceof HTMLCanvasElement))throw new TypeError("canvas must be an HTMLCanvasElement.");super(t.codec),this._encoder=new ae(this,t),this._canvas=e}digest(e,t=0){if(!Number.isFinite(e)||e<0)throw new TypeError("timestamp must be a non-negative number.");if(!Number.isFinite(t)||t<0)throw new TypeError("duration must be a non-negative number.");let i=new VideoFrame(this._canvas,{timestamp:Math.round(1e6*e),duration:Math.round(1e6*t),alpha:"discard"}),s=this._encoder.digest(i);return i.close(),s}_flush(){return this._encoder.flush()}},Je=class extends I{_encoder;_abortController=null;_track;_offsetTimestamps=!0;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._encoder=new ae(this,t),this._track=e}_start(){this._abortController=new AbortController;let e=new MediaStreamTrackProcessor({track:this._track}),t=new WritableStream({write:i=>{this._encoder.digest(i),i.close()}});e.readable.pipeTo(t,{signal:this._abortController.signal}).catch(i=>{i instanceof DOMException&&i.name==="AbortError"||console.error("Pipe error:",i)})}async _flush(){this._abortController&&(this._abortController.abort(),this._abortController=null),await this._encoder.flush()}},O=class extends G{_connectedTrack=null;_codec;constructor(e){if(super(),!K.includes(e))throw new TypeError(`Invalid audio codec '${e}'. Must be one of: ${K.join(", ")}.`);this._codec=e}},et=class extends O{constructor(e){super(e)}digest(e,t){if(!(e instanceof EncodedAudioChunk))throw new TypeError("chunk must be an EncodedAudioChunk.");return this._ensureValidDigest(),this._connectedTrack.output._muxer.addEncodedAudioChunk(this._connectedTrack,e,t)}},ti=r=>{if(!r||typeof r!="object")throw new TypeError("Codec config must be an object.");if(!K.includes(r.codec))throw new TypeError(`Invalid audio codec '${r.codec}'. Must be one of: ${K.join(", ")}.`);if(!Number.isInteger(r.bitrate)||r.bitrate<=0)throw new TypeError("config.bitrate must be a positive integer.")},ce=class{constructor(e,t){this.source=e;this.codecConfig=t;ti(t)}encoder=null;muxer=null;lastNumberOfChannels=null;lastSampleRate=null;async digest(e){if(this.source._ensureValidDigest(),this.lastNumberOfChannels!==null&&this.lastSampleRate!==null){if(e.numberOfChannels!==this.lastNumberOfChannels||e.sampleRate!==this.lastSampleRate)throw new Error(`Audio parameters must remain constant. Expected ${this.lastNumberOfChannels} channels at ${this.lastSampleRate} Hz, got ${e.numberOfChannels} channels at ${e.sampleRate} Hz.`)}else this.lastNumberOfChannels=e.numberOfChannels,this.lastSampleRate=e.sampleRate;this.ensureEncoder(e),l(this.encoder),this.encoder.encode(e),this.encoder.encodeQueueSize>=4&&await new Promise(t=>this.encoder.addEventListener("dequeue",t,{once:!0})),await this.muxer.mutex.currentPromise}ensureEncoder(e){this.encoder||(this.encoder=new AudioEncoder({output:(t,i)=>void this.muxer.addEncodedAudioChunk(this.source._connectedTrack,t,i),error:t=>console.error("Audio encode error:",t)}),this.encoder.configure({codec:Wt(this.codecConfig.codec,e.numberOfChannels,e.sampleRate),numberOfChannels:e.numberOfChannels,sampleRate:e.sampleRate,bitrate:this.codecConfig.bitrate,...Lt(this.codecConfig.codec)}),l(this.source._connectedTrack),this.muxer=this.source._connectedTrack.output._muxer)}async flush(){this.encoder&&(await this.encoder.flush(),this.encoder.close())}},tt=class extends O{_encoder;constructor(e){super(e.codec),this._encoder=new ce(this,e)}digest(e){if(!(e instanceof AudioData))throw new TypeError("audioData must be an AudioData.");return this._encoder.digest(e)}_flush(){return this._encoder.flush()}},rt=class extends O{_encoder;_accumulatedFrameCount=0;constructor(e){super(e.codec),this._encoder=new ce(this,e)}digest(e){if(!(e instanceof AudioBuffer))throw new TypeError("audioBuffer must be an AudioBuffer.");let t=e.numberOfChannels,i=e.sampleRate,s=e.length,o=new Float32Array(t*s);for(let c=0;c{this._encoder.digest(i),i.close()}});e.readable.pipeTo(t,{signal:this._abortController.signal}).catch(i=>{i instanceof DOMException&&i.name==="AbortError"||console.error("Pipe error:",i)})}async _flush(){this._abortController&&(this._abortController.abort(),this._abortController=null),await this._encoder.flush()}},Y=class extends G{_connectedTrack=null;_codec;constructor(e){if(super(),!Ce.includes(e))throw new TypeError(`Invalid subtitle codec '${e}'. Must be one of: ${Ce.join(", ")}.`);this._codec=e}},st=class extends Y{_parser;constructor(e){super(e),this._parser=new he({codec:e,output:(t,i)=>this._connectedTrack?.output._muxer.addSubtitleCue(this._connectedTrack,t,i),error:t=>console.error("Subtitle parse error:",t)})}digest(e){if(typeof e!="string")throw new TypeError("text must be a string.");return this._ensureValidDigest(),this._parser.parse(e),this._connectedTrack.output._muxer.mutex.currentPromise}};var ot=class{_muxer;_writer;_tracks=[];_started=!1;_finalizing=!1;_mutex=new H;constructor(e){if(!e||typeof e!="object")throw new TypeError("options must be an object.");if(!(e.format instanceof N))throw new TypeError("options.format must be an OutputFormat.");if(!(e.target instanceof W))throw new TypeError("options.target must be a Target.");if(e.target._output)throw new Error("Target is already used for another output.");e.target._output=this,this._writer=e.target._createWriter(),this._muxer=e.format._createMuxer(this)}addVideoTrack(e,t={}){if(!(e instanceof I))throw new TypeError("source must be a VideoSource.");if(!t||typeof t!="object")throw new TypeError("metadata must be an object.");if(typeof t.rotation=="number"&&![0,90,180,270].includes(t.rotation))throw new TypeError(`Invalid video rotation: ${t.rotation}. Has to be 0, 90, 180 or 270.`);if(Array.isArray(t.rotation)&&(t.rotation.length!==9||t.rotation.some(i=>!Number.isFinite(i))))throw new TypeError(`Invalid video transformation matrix: ${t.rotation.join()}`);if(t.frameRate!==void 0&&(!Number.isInteger(t.frameRate)||t.frameRate<=0))throw new TypeError(`Invalid video frame rate: ${t.frameRate}. Must be a positive integer.`);this._addTrack("video",e,t)}addAudioTrack(e,t={}){if(!(e instanceof O))throw new TypeError("source must be an AudioSource.");if(!t||typeof t!="object")throw new TypeError("metadata must be an object.");this._addTrack("audio",e,t)}addSubtitleTrack(e,t={}){if(!(e instanceof Y))throw new TypeError("source must be a SubtitleSource.");if(!t||typeof t!="object")throw new TypeError("metadata must be an object.");this._addTrack("subtitle",e,t)}_addTrack(e,t,i){if(this._started)throw new Error("Cannot add track after output has started.");if(t._connectedTrack)throw new Error("Source is already used for a track.");let s={id:this._tracks.length+1,output:this,type:e,source:t,metadata:i};this._muxer.beforeTrackAdd(s),this._tracks.push(s),t._connectedTrack=s}async start(){if(this._started)throw new Error("Output already started.");this._started=!0,this._writer.start();let e=await this._mutex.acquire();await this._muxer.start();for(let t of this._tracks)t.source._start();e()}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 e=await this._mutex.acquire(),t=this._tracks.map(i=>i.source._flush());await Promise.all(t),await this._muxer.finalize(),await this._writer.flush(),await this._writer.finalize(),e()}};var ue=class{},nt=class extends ue{constructor(t){super();this.buffer=t}async _read(t,i){return new Uint8Array(this.buffer,t,i-t)}async _getSize(){return this.buffer.byteLength}},at=class extends ue{constructor(t){super();this.blob=t}async _read(t,i){let o=await this.blob.slice(t,i).arrayBuffer();return new Uint8Array(o)}async _getSize(){return this.blob.size}};var Z=class{input;constructor(e){this.input=e}};var Ee=class{_backing;constructor(e){this._backing=e}isVideoTrack(){return this instanceof de}isAudioTrack(){return this instanceof le}getDuration(){return this._backing.getDuration()}},de=class extends Ee{_backing;constructor(e){super(e),this._backing=e}getCodec(){return this._backing.getCodec()}getWidth(){return this._backing.getWidth()}getHeight(){return this._backing.getHeight()}getRotation(){return this._backing.getRotation()}getDecoderConfig(){return this._backing.getDecoderConfig()}async getCodecMimeType(){return(await this.getDecoderConfig()).codec}},le=class extends Ee{_backing;constructor(e){super(e),this._backing=e}getCodec(){return this._backing.getCodec()}getNumberOfChannels(){return this._backing.getNumberOfChannels()}getSampleRate(){return this._backing.getSampleRate()}getDecoderConfig(){return this._backing.getDecoderConfig()}async getCodecMimeType(){return(await this.getDecoderConfig()).codec}};var J=class{constructor(e){this.reader=e}pos=0;readRange(e,t){let{view:i,offset:s}=this.reader.getViewAndOffset(e,t);return new Uint8Array(i.buffer,s,t-e)}readU8(){let{view:e,offset:t}=this.reader.getViewAndOffset(this.pos,this.pos+1);return this.pos++,e.getUint8(t)}readU16(){let{view:e,offset:t}=this.reader.getViewAndOffset(this.pos,this.pos+2);return this.pos+=2,e.getUint16(t,!1)}readU24(){let{view:e,offset:t}=this.reader.getViewAndOffset(this.pos,this.pos+3);this.pos+=3;let i=e.getUint16(t,!1),s=e.getUint8(t+2);return i*256+s}readS32(){let{view:e,offset:t}=this.reader.getViewAndOffset(this.pos,this.pos+4);return this.pos+=4,e.getInt32(t,!1)}readU32(){let{view:e,offset:t}=this.reader.getViewAndOffset(this.pos,this.pos+4);return this.pos+=4,e.getUint32(t,!1)}readI32(){let{view:e,offset:t}=this.reader.getViewAndOffset(this.pos,this.pos+4);return this.pos+=4,e.getInt32(t,!1)}readU64(){let e=this.readU32(),t=this.readU32();return e*4294967296+t}readF64(){let{view:e,offset:t}=this.reader.getViewAndOffset(this.pos,this.pos+8);return this.pos+=8,e.getFloat64(t,!1)}readFixed_16_16(){return this.readS32()/65536}readFixed_2_30(){return this.readS32()/1073741824}readAscii(e){let{view:t,offset:i}=this.reader.getViewAndOffset(this.pos,this.pos+e);this.pos+=e;let s="";for(let o=0;oe.inputTrack)}async getMimeType(){await this.readMetadata();let e="video/mp4";if(this.tracks.length>0){let t=await Promise.all(this.tracks.map(s=>s.inputTrack.getCodecMimeType())),i=[...new Set(t)];e+=`; codecs="${i.join(", ")}"`}return e}readMetadata(){return this.metadataPromise??=(async()=>{let e=await this.isobmffReader.reader.getSourceSize();for(;this.isobmffReader.posi.presentationTimestamp-s.presentationTimestamp),e.sampleTable}readContiguousBoxes(e){let t=this.isobmffReader.pos;for(;this.isobmffReader.pos-td.every((f,T)=>f===c[T]));u===-1?s.rotation=0:s.rotation=90*u}break;case"mdhd":{let s=this.currentTrack;l(s);let o=this.isobmffReader.readU8();this.isobmffReader.pos+=3,o===0?(this.isobmffReader.pos+=8,s.timescale=this.isobmffReader.readU32(),s.durationInTimescale=this.isobmffReader.readU32()):o===1&&(this.isobmffReader.pos+=16,s.timescale=this.isobmffReader.readU32(),s.durationInTimescale=this.isobmffReader.readU64())}break;case"hdlr":{let s=this.currentTrack;l(s),this.isobmffReader.pos+=8;let o=this.isobmffReader.readAscii(4);o==="vide"?s.info={type:"video",width:-1,height:-1,codec:null,codecDescription:null,colorSpace:null}:o==="soun"&&(s.info={type:"audio",numberOfChannels:-1,sampleRate:-1,codec:null,codecDescription:null})}break;case"stbl":{let s=this.currentTrack;l(s),s.sampleTableOffset=e,this.readContiguousBoxes(t.contentSize)}break;case"stsd":{let s=this.currentTrack;if(l(s),s.info===null||s.sampleTable)break;let o=this.isobmffReader.readU8();this.isobmffReader.pos+=3;let n=this.isobmffReader.readU32();for(let a=0;a0){if(u===1)this.isobmffReader.pos+=4*4;else if(u===2){this.isobmffReader.pos+=4,f=this.isobmffReader.readF64(),d=this.isobmffReader.readU32(),this.isobmffReader.pos+=4;let T=this.isobmffReader.readU32(),C=this.isobmffReader.readU32(),k=this.isobmffReader.readU32(),w=this.isobmffReader.readU32()}}s.info.numberOfChannels=d,s.info.sampleRate=f,this.readContiguousBoxes(e+c.totalSize-this.isobmffReader.pos)}}}break;case"avcC":{let s=this.currentTrack;l(s&&s.info),s.info.codecDescription=this.isobmffReader.readRange(this.isobmffReader.pos,this.isobmffReader.pos+t.contentSize)}break;case"hvcC":{let s=this.currentTrack;l(s&&s.info),s.info.codecDescription=this.isobmffReader.readRange(this.isobmffReader.pos,this.isobmffReader.pos+t.contentSize)}break;case"colr":{let s=this.currentTrack;if(l(s&&s.info?.type==="video"),this.isobmffReader.readAscii(4)!=="nclx")break;let n=this.isobmffReader.readU16(),a=this.isobmffReader.readU16(),c=this.isobmffReader.readU16(),u=!!(this.isobmffReader.readU8()&128);s.info.colorSpace={primaries:kt[n],transfer:gt[a],matrix:Tt[c],fullRange:u}}break;case"wave":t.totalSize>8&&this.readContiguousBoxes(t.contentSize);break;case"esds":{let s=this.currentTrack;l(s&&s.info),this.isobmffReader.pos+=4;let o=this.isobmffReader.readU8();l(o===3),this.isobmffReader.readIsomVariableInteger(),this.isobmffReader.pos+=2;let n=this.isobmffReader.readU8(),a=(n&128)!==0,c=(n&64)!==0,u=(n&32)!==0;if(a&&(this.isobmffReader.pos+=2),c){let k=this.isobmffReader.readU8();this.isobmffReader.pos+=k}u&&(this.isobmffReader.pos+=2);let d=this.isobmffReader.readU8();l(d===4),this.isobmffReader.readIsomVariableInteger();let f=this.isobmffReader.readU8();l(f===64),this.isobmffReader.pos+=12;let T=this.isobmffReader.readU8();l(T===5);let C=this.isobmffReader.readIsomVariableInteger();s.info.codecDescription=this.isobmffReader.readRange(this.isobmffReader.pos,this.isobmffReader.pos+C)}break;case"stts":{let s=this.currentTrack;if(l(s),!s.sampleTable)break;this.isobmffReader.pos+=4;let o=this.isobmffReader.readU32(),n=0,a=0;for(let c=0;c{let t=D(r.presentationTimestamps,e,i=>i.presentationTimestamp);return t===-1?-1:r.presentationTimestamps[t].sampleIndex},ii=(r,e)=>{let t=D(r.sampleTimingEntries,e,k=>k.startIndex),i=r.sampleTimingEntries[t];if(!i||i.startIndex+i.count<=e)return null;let o=i.startDecodeTimestamp+(e-i.startIndex)*i.delta,n=D(r.sampleCompositionTimeOffsets,e,k=>k.startIndex),a=r.sampleCompositionTimeOffsets[n];a&&(o+=a.offset);let c=r.sampleSizes[Math.min(e,r.sampleSizes.length-1)],u=D(r.sampleToChunk,e,k=>k.startSampleIndex),d=r.sampleToChunk[u];l(d);let f=d.startChunkIndex+Math.floor((e-d.startSampleIndex)/d.samplesPerChunk),C=r.chunkOffsets[f];if(r.sampleSizes.length===1)C+=c*(e-d.startSampleIndex);else{let k=d.startSampleIndex+(f-d.startChunkIndex)*d.samplesPerChunk;for(let w=k;wk)!==-1:!0}},si=(r,e)=>{if(!r.keySampleIndices)return e;let t=D(r.keySampleIndices,e,i=>i);return r.keySampleIndices[t]??-1},oi=(r,e)=>{if(!r.keySampleIndices)return e+1;let t=D(r.keySampleIndices,e,i=>i);return r.keySampleIndices[t+1]??-1};var Ve=class extends Z{};var Me=class{},dt=class extends Me{async _canReadInput(e){if(await e._reader.getSourceSize()<8)return!1;await e._reader.loadRange(4,8);let i=new J(e._reader);return i.pos=4,i.readAscii(4)==="ftyp"}_createDemuxer(e){return new Ie(e)}},lt=class extends Me{async _canReadInput(){return!1}_createDemuxer(e){return new Ve(e)}},Re=new dt,ni=Re,ai=Re,mt=new lt,Xt=mt,ci=mt,ui=[Re,Xt];var Pe=4096,ze=class{constructor(e){this.source=e}loadedSegments=[];sourceSizePromise=null;getSourceSize(){return this.sourceSizePromise?this.sourceSizePromise:this.sourceSizePromise=this.source._getSize()}async loadRange(e,t){let i=Math.floor(e/Pe)*Pe,s=Math.ceil(t/Pe)*Pe;s=Math.min(s,await this.getSourceSize());let o=this.loadedSegments.find(c=>c.start<=i);o&&(i=Math.max(i,o.end));let n=this.loadedSegments.find(c=>c.end>=s);if(n&&(s=Math.min(s,n.start)),i>=s)return;let a=await this.source._read(i,s);this.insertIntoLoadedSegments(i,a)}insertIntoLoadedSegments(e,t){let i={start:e,end:e+t.byteLength,bytes:t,view:new DataView(t.buffer)},s=this.loadedSegments.findLastIndex(d=>d.start<=e);this.loadedSegments.splice(s+1,0,i),(s===-1||this.loadedSegments[s].enda&&this.loadedSegments[a+1].start<=c;)a++,c=Math.max(c,this.loadedSegments[a].end);if(o===a)return;for(let d=o;d<=a;d++){let f=this.loadedSegments[d];if(f.start===n&&f.end===c){this.loadedSegments.splice(d+1,a-d),this.loadedSegments.splice(o,d-o);return}}let u=new Uint8Array(c-n);for(let d=o;d<=a;d++){let f=this.loadedSegments[d];u.set(f.bytes,f.start-n)}this.loadedSegments.splice(o+1,a-o),this.loadedSegments[o].end=c,this.loadedSegments[o].bytes=u,this.loadedSegments[o].view=new DataView(u.buffer)}getViewAndOffset(e,t){let i=this.loadedSegments.find(s=>s.start<=e&&t<=s.end);if(!i)throw new Error(`No segment loaded for range [${e}, ${t}).`);return{view:i.view,offset:i.bytes.byteOffset+e-i.start}}};var ft=class{_formats;_reader;_demuxerPromise=null;_format=null;constructor(e){this._formats=e.formats,this._reader=new ze(e.source)}_getDemuxer(){return this._demuxerPromise??=(async()=>{for(let e of this._formats)if(await e._canReadInput(this))return this._format=e,e._createDemuxer(this);throw new Error("Input has an unrecognizable format.")})()}async getFormat(){return await this._getDemuxer(),l(this._format),this._format}async getDuration(){return(await this._getDemuxer()).getDuration()}async getTracks(){return(await this._getDemuxer()).getTracks()}async getVideoTracks(){return(await this.getTracks()).filter(t=>t.isVideoTrack())}async getPrimaryVideoTrack(){return(await this.getTracks()).find(t=>t.isVideoTrack())??null}async getAudioTracks(){return(await this.getTracks()).filter(t=>t.isAudioTrack())}async getPrimaryAudioTrack(){return(await this.getTracks()).find(t=>t.isAudioTrack())??null}async getMimeType(){return(await this._getDemuxer()).getMimeType()}};var ht=class{constructor(e){this.videoTrack=e}getFirstChunk(){return this.videoTrack._backing.getFirstChunk()}getChunk(e){return this.videoTrack._backing.getChunk(e)}getNextChunk(e){return this.videoTrack._backing.getNextChunk(e)}getKeyChunk(e){return this.videoTrack._backing.getKeyChunk(e)}getNextKeyChunk(e){return this.videoTrack._backing.getNextKeyChunk(e)}async*chunks(e=0){let t=await this.getChunk(e);for(;t;)yield t,t=await this.getNextChunk(t)}},pt=class{constructor(e){this.videoTrack=e}decoderConfig=null;async createDecoder(e){this.decoderConfig||(this.decoderConfig=await this.videoTrack.getDecoderConfig());let t=new VideoDecoder({output:e,error:i=>console.error(i)});return t.configure(this.decoderConfig),t}async getKeyFrame(e){let t=null,i=await this.createDecoder(o=>t=o),s=await this.videoTrack._backing.getKeyChunk(e);return s?(i.decode(s),await i.flush(),i.close(),t):null}async getFrame(e){let t=null,i=await this.createDecoder(a=>{a.timestamp/1e6<=e?(t?.close(),t=a):a.close()}),s=await this.videoTrack._backing.getKeyChunk(e);if(!s)return null;let o=await this.videoTrack._backing.getChunk(e);l(o),i.decode(s);let n=s;for(;n!==o;){let a=await this.videoTrack._backing.getNextChunk(n);l(a),n=a,i.decode(a),i.decodeQueueSize>=10&&await new Promise(c=>i.addEventListener("dequeue",c,{once:!0}))}return await i.flush(),i.close(),t}async*frames(e=0){let t=[],i=!1,s=null,{promise:o,resolve:n}=Fe(),a=!1,c=await this.createDecoder(f=>{if(a){f.close();return}let T=f.timestamp/1e6;s&&(T>e?(t.push(s),i=!0):s.close()),T>=e&&(t.push(f),i=!0),s=i?null:f,t.length>0&&(n(),{promise:o,resolve:n}=Fe())}),u=await this.videoTrack._backing.getKeyChunk(e);if(!u)return;let d=!1;(async()=>{let f=u;for(;f&&!a;)c.decode(f),c.decodeQueueSize>=10&&await new Promise(C=>c.addEventListener("dequeue",C,{once:!0})),f=await this.videoTrack._backing.getNextChunk(f);await c.flush(),c.close(),d=!0,n()})();try{for(;;)if(t.length>0)yield t.shift();else if(!d)await o;else break}finally{a=!0}}};export{ui as ALL_FORMATS,K as AUDIO_CODECS,nt as ArrayBufferSource,se as ArrayBufferTarget,rt as AudioBufferSource,tt as AudioDataSource,O as AudioSource,at as BlobSource,Ze as CanvasSource,et as EncodedAudioChunkSource,ht as EncodedVideoChunkDrain,Ge as EncodedVideoChunkSource,Re as ISOBMFF,ft as Input,mt as MATROSKA,Xt as MKV,ai as MOV,ni as MP4,G as MediaSource,it as MediaStreamAudioTrackSource,Je as MediaStreamVideoTrackSource,Ae as MkvOutputFormat,Xe as Mp4OutputFormat,ot as Output,N as OutputFormat,Ce as SUBTITLE_CODECS,ue as Source,Qe as StreamTarget,Y as SubtitleSource,W as Target,st as TextSubtitleSource,j as VIDEO_CODECS,pt as VideoFrameDrain,Ye as VideoFrameSource,I as VideoSource,ci as WEBM,X as WebMOutputFormat};
diff --git a/dist/metamuxer.mjs b/dist/metamuxer.mjs
index 13fbb5f..8f131ef 100644
--- a/dist/metamuxer.mjs
+++ b/dist/metamuxer.mjs
@@ -40,6 +40,9 @@ var toUint8Array = (source) => {
}
};
var textEncoder = new TextEncoder();
+var invertObject = (object) => {
+ return Object.fromEntries(Object.entries(object).map(([key, value]) => [value, key]));
+};
var COLOR_PRIMARIES_MAP = {
bt709: 1,
// ITU-R BT.709
@@ -48,6 +51,7 @@ var COLOR_PRIMARIES_MAP = {
smpte170m: 6
// ITU-R BT.601 525 - SMPTE 170M
};
+var COLOR_PRIMARIES_MAP_INVERSE = invertObject(COLOR_PRIMARIES_MAP);
var TRANSFER_CHARACTERISTICS_MAP = {
"bt709": 1,
// ITU-R BT.709
@@ -56,6 +60,7 @@ var TRANSFER_CHARACTERISTICS_MAP = {
"iec61966-2-1": 13
// IEC 61966-2-1
};
+var TRANSFER_CHARACTERISTICS_MAP_INVERSE = invertObject(TRANSFER_CHARACTERISTICS_MAP);
var MATRIX_COEFFICIENTS_MAP = {
rgb: 0,
// Identity
@@ -66,6 +71,7 @@ var MATRIX_COEFFICIENTS_MAP = {
smpte170m: 6
// SMPTE 170M
};
+var MATRIX_COEFFICIENTS_MAP_INVERSE = invertObject(MATRIX_COEFFICIENTS_MAP);
var colorSpaceIsComplete = (colorSpace) => {
return !!colorSpace && !!colorSpace.primaries && !!colorSpace.transfer && !!colorSpace.matrix && colorSpace.fullRange !== void 0;
};
@@ -73,9 +79,7 @@ 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();
- }
+ currentPromise = Promise.resolve();
async acquire() {
let resolver;
const nextPromise = new Promise((resolve) => {
@@ -87,15 +91,87 @@ var AsyncMutex = class {
return resolver;
}
};
+var rotationMatrix = (rotationInDegrees) => {
+ const theta = rotationInDegrees * (Math.PI / 180);
+ const cosTheta = Math.cos(theta);
+ const sinTheta = Math.sin(theta);
+ return [
+ cosTheta,
+ sinTheta,
+ 0,
+ -sinTheta,
+ cosTheta,
+ 0,
+ 0,
+ 0,
+ 1
+ ];
+};
+var IDENTITY_MATRIX = rotationMatrix(0);
+var bytesToHexString = (bytes2) => {
+ return [...bytes2].map((x) => x.toString(16).padStart(2, "0")).join("");
+};
+var reverseBitsU32 = (x) => {
+ x = x >> 1 & 1431655765 | (x & 1431655765) << 1;
+ x = x >> 2 & 858993459 | (x & 858993459) << 2;
+ x = x >> 4 & 252645135 | (x & 252645135) << 4;
+ x = x >> 8 & 16711935 | (x & 16711935) << 8;
+ x = x >> 16 & 65535 | (x & 65535) << 16;
+ return x >>> 0;
+};
+var binarySearchExact = (arr, key, valueGetter) => {
+ let low = 0;
+ let high = arr.length - 1;
+ let res = -1;
+ while (low <= high) {
+ const mid = low + high >> 1;
+ const midVal = valueGetter(arr[mid]);
+ if (midVal === key) {
+ res = mid;
+ high = mid - 1;
+ } else if (midVal < key) {
+ low = mid + 1;
+ } else {
+ high = mid - 1;
+ }
+ }
+ return res;
+};
+var binarySearchLessOrEqual = (arr, key, valueGetter) => {
+ let ans = -1;
+ let low = 0;
+ let high = arr.length - 1;
+ while (low <= high) {
+ const mid = low + (high - low + 1) / 2 | 0;
+ const midVal = valueGetter(arr[mid]);
+ if (midVal <= key) {
+ ans = mid;
+ low = mid + 1;
+ } else {
+ high = mid - 1;
+ }
+ }
+ return ans;
+};
+var promiseWithResolvers = () => {
+ let resolve;
+ let reject;
+ const promise = new Promise((res, rej) => {
+ resolve = res;
+ reject = rej;
+ });
+ return { promise, resolve, reject };
+};
// 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;
var preambleStartRegex = /^WEBVTT(.|\n)*?\n{2}/;
var inlineTimestampRegex = /<(?:(\d{2}):)?(\d{2}):(\d{2}).(\d{3})>/g;
var SubtitleParser = class {
+ options;
+ preambleText = null;
+ preambleEmitted = false;
constructor(options) {
- this.preambleText = null;
- this.preambleEmitted = false;
this.options = options;
}
parse(text) {
@@ -172,14 +248,14 @@ var formatSubtitleTimestamp = (timestamp) => {
var IsobmffBoxWriter = class {
constructor(writer) {
this.writer = writer;
- this.helper = new Uint8Array(8);
- this.helperView = new DataView(this.helper.buffer);
- /**
- * Stores the position from the start of the file to where boxes elements have been written. This is used to
- * rewrite/edit elements that were already added before, and to measure sizes of things.
- */
- this.offsets = /* @__PURE__ */ new WeakMap();
}
+ helper = new Uint8Array(8);
+ helperView = new DataView(this.helper.buffer);
+ /**
+ * Stores the position from the start of the file to where boxes elements have been written. This is used to
+ * rewrite/edit elements that were already added before, and to measure sizes of things.
+ */
+ offsets = /* @__PURE__ */ new WeakMap();
writeU32(value) {
this.helperView.setUint32(0, value, false);
this.writer.write(this.helper.subarray(0, 4));
@@ -319,23 +395,6 @@ var lastPresentedSample = (samples) => {
}
return result;
};
-var rotationMatrix = (rotationInDegrees) => {
- const theta = rotationInDegrees * (Math.PI / 180);
- const cosTheta = Math.cos(theta);
- const sinTheta = Math.sin(theta);
- return [
- cosTheta,
- sinTheta,
- 0,
- -sinTheta,
- cosTheta,
- 0,
- 0,
- 0,
- 1
- ];
-};
-var IDENTITY_MATRIX = rotationMatrix(0);
var matrixToBytes = (matrix) => {
return [
fixed_16_16(matrix[0]),
@@ -1071,9 +1130,9 @@ var SUBTITLE_CODEC_TO_CONFIGURATION_BOX = {
// src/muxer.ts
var Muxer = class {
+ output;
+ mutex = new AsyncMutex();
constructor(output) {
- this.mutex = new AsyncMutex();
- this.trackTimestampInfo = /* @__PURE__ */ new WeakMap();
this.output = output;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -1082,6 +1141,7 @@ var Muxer = class {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
onTrackClose(track) {
}
+ trackTimestampInfo = /* @__PURE__ */ new WeakMap();
validateAndNormalizeTimestamp(track, rawTimestampInUs, isKeyFrame) {
let timestampInSeconds = rawTimestampInUs / 1e6;
let timestampInfo = this.trackTimestampInfo.get(track);
@@ -1125,20 +1185,19 @@ var Muxer = class {
// src/writer.ts
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;
- }
+ /** Setting this to true will cause the writer to ensure data is written in a strictly monotonic, streamable way. */
+ ensureMonotonicity = false;
start() {
}
};
var ArrayBufferTargetWriter = class extends Writer {
+ pos = 0;
+ target;
+ buffer = new ArrayBuffer(2 ** 16);
+ bytes = new Uint8Array(this.buffer);
+ maxPos = 0;
constructor(target) {
super();
- this.pos = 0;
- this.buffer = new ArrayBuffer(2 ** 16);
- this.bytes = new Uint8Array(this.buffer);
- this.maxPos = 0;
this.target = target;
}
ensureSize(size) {
@@ -1165,7 +1224,6 @@ var ArrayBufferTargetWriter = class extends Writer {
}
async flush() {
}
- // eslint-disable-next-line @typescript-eslint/require-await
async finalize() {
this.ensureSize(this.pos);
this.target.buffer = this.buffer.slice(0, Math.max(this.maxPos, this.pos));
@@ -1175,12 +1233,13 @@ var ArrayBufferTargetWriter = class extends Writer {
}
};
var StreamTargetWriter = class extends Writer {
+ pos = 0;
+ target;
+ sections = [];
+ lastFlushEnd = 0;
+ writer = null;
constructor(target) {
super();
- this.pos = 0;
- this.sections = [];
- this.lastFlushEnd = 0;
- this.writer = null;
this.target = target;
}
start() {
@@ -1250,17 +1309,19 @@ var StreamTargetWriter = class extends Writer {
var DEFAULT_CHUNK_SIZE = 2 ** 24;
var MAX_CHUNKS_AT_ONCE = 2;
var ChunkedStreamTargetWriter = class extends Writer {
+ pos = 0;
+ target;
+ chunkSize;
+ /**
+ * The data is divided up into fixed-size chunks, whose contents are first filled in RAM and then flushed out.
+ * A chunk is flushed if all of its contents have been written.
+ */
+ chunks = [];
+ lastFlushEnd = 0;
+ writer = null;
+ flushedChunkQueue = [];
constructor(target) {
super();
- this.pos = 0;
- /**
- * The data is divided up into fixed-size chunks, whose contents are first filled in RAM and then flushed out.
- * A chunk is flushed if all of its contents have been written.
- */
- this.chunks = [];
- this.lastFlushEnd = 0;
- this.writer = null;
- this.flushedChunkQueue = [];
this.target = target;
this.chunkSize = target._options?.chunkSize ?? DEFAULT_CHUNK_SIZE;
if (!Number.isInteger(this.chunkSize) || this.chunkSize < 2 ** 10) {
@@ -1378,22 +1439,21 @@ var ChunkedStreamTargetWriter = class extends Writer {
// src/target.ts
var Target = class {
- constructor() {
- /** @internal */
- this._output = null;
- }
+ /** @internal */
+ _output = null;
};
var ArrayBufferTarget = class extends Target {
- constructor() {
- super(...arguments);
- this.buffer = null;
- }
+ buffer = null;
/** @internal */
_createWriter() {
return new ArrayBufferTargetWriter(this);
}
};
var StreamTarget = class extends Target {
+ /** @internal */
+ _writable;
+ /** @internal */
+ _options;
constructor(writable, options = {}) {
super();
if (!(writable instanceof WritableStream)) {
@@ -1418,6 +1478,9 @@ var StreamTarget = class extends Target {
};
// src/codec.ts
+var VIDEO_CODECS = ["avc", "hevc", "vp8", "vp9", "av1"];
+var AUDIO_CODECS = ["aac", "opus"];
+var SUBTITLE_CODECS = ["webvtt"];
var AVC_LEVEL_TABLE = [
{ maxMacroblocks: 99, maxBitrate: 64e3, level: 10 },
// Level 1
@@ -1625,6 +1688,43 @@ var buildVideoCodecString = (codec, width, height, bitrate) => {
}
throw new TypeError(`Unhandled codec '${codec}'.`);
};
+var extractVideoCodecString = (codec, description) => {
+ if (codec === "avc") {
+ if (!description || description.byteLength < 4) {
+ throw new TypeError("AVC description must be at least 4 bytes long.");
+ }
+ return `avc1.${bytesToHexString(description.subarray(1, 4))}`;
+ } else if (codec === "hevc") {
+ if (!description) {
+ throw new TypeError("HEVC description must be provided.");
+ }
+ const view2 = new DataView(description.buffer, description.byteOffset, description.byteLength);
+ let codecString = "hev1.";
+ const generalProfileSpace = description[1] >> 6 & 3;
+ const generalProfileIdc = description[1] & 31;
+ codecString += ["", "A", "B", "C"][generalProfileSpace] + generalProfileIdc;
+ codecString += ".";
+ const compatibilityFlags = reverseBitsU32(view2.getUint32(2));
+ codecString += compatibilityFlags.toString(16);
+ codecString += ".";
+ const generalTierFlag = description[1] >> 5 & 1;
+ const generalLevelIdc = description[12];
+ codecString += generalTierFlag === 0 ? "L" : "H";
+ codecString += generalLevelIdc;
+ codecString += ".";
+ const constraintFlags = [];
+ for (let i = 0; i < 6; i++) {
+ const byte = description[i + 13];
+ constraintFlags.push(byte);
+ }
+ while (constraintFlags[constraintFlags.length - 1] === 0) {
+ constraintFlags.pop();
+ }
+ codecString += constraintFlags.map((x) => x.toString(16)).join(".");
+ return codecString;
+ }
+ throw new TypeError(`Unhandled codec '${codec}'.`);
+};
var buildAudioCodecString = (codec, numberOfChannels, sampleRate) => {
if (codec === "aac") {
if (numberOfChannels >= 2 && sampleRate <= 24e3) {
@@ -1641,6 +1741,20 @@ var buildAudioCodecString = (codec, numberOfChannels, sampleRate) => {
}
throw new TypeError(`Unhandled codec '${codec}'.`);
};
+var extractAudioCodecString = (codec, description) => {
+ if (codec === "aac") {
+ if (!description || description.byteLength < 2) {
+ throw new TypeError("AAC description must be at least 2 bytes long.");
+ }
+ const mpeg4AudioObjectType = description[0] >> 3;
+ return `mp4a.40.${mpeg4AudioObjectType}`;
+ } else if (codec === "opus") {
+ return "opus";
+ } else if (codec === "vorbis") {
+ return "vorbis";
+ }
+ throw new TypeError(`Unhandled codec '${codec}'.`);
+};
var getVideoEncoderConfigExtension = (codec) => {
if (codec === "avc") {
return {
@@ -1822,18 +1936,21 @@ var intoTimescale = (timeInSeconds, timescale, round = true) => {
return round ? Math.round(value) : value;
};
var IsobmffMuxer = class extends Muxer {
+ timestampsMustStartAtZero = true;
+ writer;
+ boxWriter;
+ fastStart;
+ auxTarget = new ArrayBufferTarget();
+ auxWriter = this.auxTarget._createWriter();
+ auxBoxWriter = new IsobmffBoxWriter(this.auxWriter);
+ ftypSize = null;
+ mdat = null;
+ trackDatas = [];
+ creationTime = Math.floor(Date.now() / 1e3) + TIMESTAMP_OFFSET;
+ finalizedChunks = [];
+ nextFragmentNumber = 1;
constructor(output, format) {
super(output);
- this.timestampsMustStartAtZero = true;
- this.auxTarget = new ArrayBufferTarget();
- this.auxWriter = this.auxTarget._createWriter();
- this.auxBoxWriter = new IsobmffBoxWriter(this.auxWriter);
- this.ftypSize = null;
- this.mdat = null;
- this.trackDatas = [];
- this.creationTime = Math.floor(Date.now() / 1e3) + TIMESTAMP_OFFSET;
- this.finalizedChunks = [];
- this.nextFragmentNumber = 1;
this.writer = output._writer;
this.boxWriter = new IsobmffBoxWriter(this.writer);
const fastStartDefault = this.writer instanceof ArrayBufferTargetWriter ? "in-memory" : false;
@@ -2422,16 +2539,19 @@ var IsobmffMuxer = class extends Muxer {
// src/matroska/ebml.ts
var EBMLFloat32 = class {
+ value;
constructor(value) {
this.value = value;
}
};
var EBMLFloat64 = class {
+ value;
constructor(value) {
this.value = value;
}
};
var EBMLSignedInt = class {
+ value;
constructor(value) {
this.value = value;
}
@@ -2505,29 +2625,31 @@ var TRACK_TYPE_MAP = {
subtitle: 17
};
var MatroskaMuxer = class extends Muxer {
+ timestampsMustStartAtZero = false;
+ writer;
+ format;
+ helper = new Uint8Array(8);
+ helperView = new DataView(this.helper.buffer);
+ /**
+ * Stores the position from the start of the file to where EBML elements have been written. This is used to
+ * rewrite/edit elements that were already added before, and to measure sizes of things.
+ */
+ offsets = /* @__PURE__ */ new WeakMap();
+ /** Same as offsets, but stores position where the element's data starts (after ID and size fields). */
+ dataOffsets = /* @__PURE__ */ new WeakMap();
+ trackDatas = [];
+ segment = null;
+ segmentInfo = null;
+ seekHead = null;
+ tracksElement = null;
+ segmentDuration = null;
+ cues = null;
+ currentCluster = null;
+ currentClusterMsTimestamp = null;
+ trackDatasInCurrentCluster = /* @__PURE__ */ new Set();
+ duration = 0;
constructor(output, format) {
super(output);
- this.timestampsMustStartAtZero = false;
- this.helper = new Uint8Array(8);
- this.helperView = new DataView(this.helper.buffer);
- /**
- * Stores the position from the start of the file to where EBML elements have been written. This is used to
- * rewrite/edit elements that were already added before, and to measure sizes of things.
- */
- this.offsets = /* @__PURE__ */ new WeakMap();
- /** Same as offsets, but stores position where the element's data starts (after ID and size fields). */
- this.dataOffsets = /* @__PURE__ */ 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 = /* @__PURE__ */ new Set();
- this.duration = 0;
this.writer = output._writer;
this.format = format;
if (this.format._options.streamable) {
@@ -3202,6 +3324,8 @@ ${cue.notes ?? ""}`;
var OutputFormat = class {
};
var Mp4OutputFormat = class extends OutputFormat {
+ /** @internal */
+ _options;
constructor(options = {}) {
if (!options || typeof options !== "object") {
throw new TypeError("options must be an object.");
@@ -3218,6 +3342,8 @@ var Mp4OutputFormat = class extends OutputFormat {
}
};
var MkvOutputFormat2 = class extends OutputFormat {
+ /** @internal */
+ _options;
constructor(options = {}) {
if (!options || typeof options !== "object") {
throw new TypeError("options must be an object.");
@@ -3236,19 +3362,14 @@ var MkvOutputFormat2 = class extends OutputFormat {
var WebMOutputFormat = class extends MkvOutputFormat2 {
};
-// src/source.ts
-var VIDEO_CODECS = ["avc", "hevc", "vp8", "vp9", "av1"];
-var AUDIO_CODECS = ["aac", "opus"];
-var SUBTITLE_CODECS = ["webvtt"];
+// src/media-source.ts
var MediaSource = class {
- constructor() {
- /** @internal */
- this._connectedTrack = null;
- /** @internal */
- this._closed = false;
- /** @internal */
- this._offsetTimestamps = false;
- }
+ /** @internal */
+ _connectedTrack = null;
+ /** @internal */
+ _closed = false;
+ /** @internal */
+ _offsetTimestamps = false;
/** @internal */
_ensureValidDigest() {
if (!this._connectedTrack) {
@@ -3288,10 +3409,12 @@ var MediaSource = class {
}
};
var VideoSource = class extends MediaSource {
+ /** @internal */
+ _connectedTrack = null;
+ /** @internal */
+ _codec;
constructor(codec) {
super();
- /** @internal */
- this._connectedTrack = null;
if (!VIDEO_CODECS.includes(codec)) {
throw new TypeError(`Invalid video codec '${codec}'. Must be one of: ${VIDEO_CODECS.join(", ")}.`);
}
@@ -3329,13 +3452,13 @@ var VideoEncoderWrapper = class {
constructor(source, codecConfig) {
this.source = source;
this.codecConfig = codecConfig;
- this.encoder = null;
- this.muxer = null;
- this.lastMultipleOfKeyFrameInterval = -1;
- this.lastWidth = null;
- this.lastHeight = null;
validateVideoCodecConfig(codecConfig);
}
+ encoder = null;
+ muxer = null;
+ lastMultipleOfKeyFrameInterval = -1;
+ lastWidth = null;
+ lastHeight = null;
async digest(videoFrame) {
this.source._ensureValidDigest();
if (this.lastWidth !== null && this.lastHeight !== null) {
@@ -3393,6 +3516,8 @@ var VideoEncoderWrapper = class {
}
};
var VideoFrameSource = class extends VideoSource {
+ /** @internal */
+ _encoder;
constructor(codecConfig) {
super(codecConfig.codec);
this._encoder = new VideoEncoderWrapper(this, codecConfig);
@@ -3409,6 +3534,10 @@ var VideoFrameSource = class extends VideoSource {
}
};
var CanvasSource = class extends VideoSource {
+ /** @internal */
+ _encoder;
+ /** @internal */
+ _canvas;
constructor(canvas, codecConfig) {
if (!(canvas instanceof HTMLCanvasElement)) {
throw new TypeError("canvas must be an HTMLCanvasElement.");
@@ -3439,6 +3568,14 @@ var CanvasSource = class extends VideoSource {
}
};
var MediaStreamVideoTrackSource = class extends VideoSource {
+ /** @internal */
+ _encoder;
+ /** @internal */
+ _abortController = null;
+ /** @internal */
+ _track;
+ /** @internal */
+ _offsetTimestamps = true;
constructor(track, codecConfig) {
if (!(track instanceof MediaStreamTrack) || track.kind !== "video") {
throw new TypeError("track must be a video MediaStreamTrack.");
@@ -3448,10 +3585,6 @@ var MediaStreamVideoTrackSource = class extends VideoSource {
latencyMode: "realtime"
};
super(codecConfig.codec);
- /** @internal */
- this._abortController = null;
- /** @internal */
- this._offsetTimestamps = true;
this._encoder = new VideoEncoderWrapper(this, codecConfig);
this._track = track;
}
@@ -3482,10 +3615,12 @@ var MediaStreamVideoTrackSource = class extends VideoSource {
}
};
var AudioSource = class extends MediaSource {
+ /** @internal */
+ _connectedTrack = null;
+ /** @internal */
+ _codec;
constructor(codec) {
super();
- /** @internal */
- this._connectedTrack = null;
if (!AUDIO_CODECS.includes(codec)) {
throw new TypeError(`Invalid audio codec '${codec}'. Must be one of: ${AUDIO_CODECS.join(", ")}.`);
}
@@ -3519,12 +3654,12 @@ var AudioEncoderWrapper = class {
constructor(source, codecConfig) {
this.source = source;
this.codecConfig = codecConfig;
- this.encoder = null;
- this.muxer = null;
- this.lastNumberOfChannels = null;
- this.lastSampleRate = null;
validateAudioCodecConfig(codecConfig);
}
+ encoder = null;
+ muxer = null;
+ lastNumberOfChannels = null;
+ lastSampleRate = null;
async digest(audioData) {
this.source._ensureValidDigest();
if (this.lastNumberOfChannels !== null && this.lastSampleRate !== null) {
@@ -3571,6 +3706,8 @@ var AudioEncoderWrapper = class {
}
};
var AudioDataSource = class extends AudioSource {
+ /** @internal */
+ _encoder;
constructor(codecConfig) {
super(codecConfig.codec);
this._encoder = new AudioEncoderWrapper(this, codecConfig);
@@ -3587,10 +3724,12 @@ var AudioDataSource = class extends AudioSource {
}
};
var AudioBufferSource = class extends AudioSource {
+ /** @internal */
+ _encoder;
+ /** @internal */
+ _accumulatedFrameCount = 0;
constructor(codecConfig) {
super(codecConfig.codec);
- /** @internal */
- this._accumulatedFrameCount = 0;
this._encoder = new AudioEncoderWrapper(this, codecConfig);
}
digest(audioBuffer) {
@@ -3624,15 +3763,19 @@ var AudioBufferSource = class extends AudioSource {
}
};
var MediaStreamAudioTrackSource = class extends AudioSource {
+ /** @internal */
+ _encoder;
+ /** @internal */
+ _abortController = null;
+ /** @internal */
+ _track;
+ /** @internal */
+ _offsetTimestamps = true;
constructor(track, codecConfig) {
if (!(track instanceof MediaStreamTrack) || track.kind !== "audio") {
throw new TypeError("track must be an audio MediaStreamTrack.");
}
super(codecConfig.codec);
- /** @internal */
- this._abortController = null;
- /** @internal */
- this._offsetTimestamps = true;
this._encoder = new AudioEncoderWrapper(this, codecConfig);
this._track = track;
}
@@ -3663,10 +3806,12 @@ var MediaStreamAudioTrackSource = class extends AudioSource {
}
};
var SubtitleSource = class extends MediaSource {
+ /** @internal */
+ _connectedTrack = null;
+ /** @internal */
+ _codec;
constructor(codec) {
super();
- /** @internal */
- this._connectedTrack = null;
if (!SUBTITLE_CODECS.includes(codec)) {
throw new TypeError(`Invalid subtitle codec '${codec}'. Must be one of: ${SUBTITLE_CODECS.join(", ")}.`);
}
@@ -3674,6 +3819,8 @@ var SubtitleSource = class extends MediaSource {
}
};
var TextSubtitleSource = class extends SubtitleSource {
+ /** @internal */
+ _parser;
constructor(codec) {
super(codec);
this._parser = new SubtitleParser({
@@ -3694,15 +3841,19 @@ var TextSubtitleSource = class extends SubtitleSource {
// src/output.ts
var Output = class {
+ /** @internal */
+ _muxer;
+ /** @internal */
+ _writer;
+ /** @internal */
+ _tracks = [];
+ /** @internal */
+ _started = false;
+ /** @internal */
+ _finalizing = false;
+ /** @internal */
+ _mutex = new AsyncMutex();
constructor(options) {
- /** @internal */
- this._tracks = [];
- /** @internal */
- 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.");
}
@@ -3805,15 +3956,1321 @@ var Output = class {
release();
}
};
+
+// src/source.ts
+var Source = class {
+};
+var ArrayBufferSource = class extends Source {
+ constructor(buffer) {
+ super();
+ this.buffer = buffer;
+ }
+ /** @internal */
+ async _read(start, end) {
+ return new Uint8Array(this.buffer, start, end - start);
+ }
+ /** @internal */
+ async _getSize() {
+ return this.buffer.byteLength;
+ }
+};
+var BlobSource = class extends Source {
+ constructor(blob) {
+ super();
+ this.blob = blob;
+ }
+ /** @internal */
+ async _read(start, end) {
+ const slice = this.blob.slice(start, end);
+ const buffer = await slice.arrayBuffer();
+ return new Uint8Array(buffer);
+ }
+ /** @internal */
+ async _getSize() {
+ return this.blob.size;
+ }
+};
+
+// src/demuxer.ts
+var Demuxer = class {
+ input;
+ constructor(input) {
+ this.input = input;
+ }
+};
+
+// src/input-track.ts
+var InputTrack = class {
+ /** @internal */
+ _backing;
+ /** @internal */
+ constructor(backing) {
+ this._backing = backing;
+ }
+ isVideoTrack() {
+ return this instanceof InputVideoTrack;
+ }
+ isAudioTrack() {
+ return this instanceof InputAudioTrack;
+ }
+ getDuration() {
+ return this._backing.getDuration();
+ }
+};
+var InputVideoTrack = class extends InputTrack {
+ /** @internal */
+ _backing;
+ /** @internal */
+ constructor(backing) {
+ super(backing);
+ this._backing = backing;
+ }
+ getCodec() {
+ return this._backing.getCodec();
+ }
+ getWidth() {
+ return this._backing.getWidth();
+ }
+ getHeight() {
+ return this._backing.getHeight();
+ }
+ getRotation() {
+ return this._backing.getRotation();
+ }
+ getDecoderConfig() {
+ return this._backing.getDecoderConfig();
+ }
+ async getCodecMimeType() {
+ const decoderConfig = await this.getDecoderConfig();
+ return decoderConfig.codec;
+ }
+};
+var InputAudioTrack = class extends InputTrack {
+ /** @internal */
+ _backing;
+ /** @internal */
+ constructor(backing) {
+ super(backing);
+ this._backing = backing;
+ }
+ getCodec() {
+ return this._backing.getCodec();
+ }
+ getNumberOfChannels() {
+ return this._backing.getNumberOfChannels();
+ }
+ getSampleRate() {
+ return this._backing.getSampleRate();
+ }
+ getDecoderConfig() {
+ return this._backing.getDecoderConfig();
+ }
+ async getCodecMimeType() {
+ const decoderConfig = await this.getDecoderConfig();
+ return decoderConfig.codec;
+ }
+};
+
+// src/isobmff/isobmff-reader.ts
+var IsobmffReader = class {
+ constructor(reader) {
+ this.reader = reader;
+ }
+ pos = 0;
+ readRange(start, end) {
+ const { view: view2, offset } = this.reader.getViewAndOffset(start, end);
+ return new Uint8Array(view2.buffer, offset, end - start);
+ }
+ readU8() {
+ const { view: view2, offset } = this.reader.getViewAndOffset(this.pos, this.pos + 1);
+ this.pos++;
+ return view2.getUint8(offset);
+ }
+ readU16() {
+ const { view: view2, offset } = this.reader.getViewAndOffset(this.pos, this.pos + 2);
+ this.pos += 2;
+ return view2.getUint16(offset, false);
+ }
+ readU24() {
+ const { view: view2, offset } = this.reader.getViewAndOffset(this.pos, this.pos + 3);
+ this.pos += 3;
+ const high = view2.getUint16(offset, false);
+ const low = view2.getUint8(offset + 2);
+ return high * 256 + low;
+ }
+ readS32() {
+ const { view: view2, offset } = this.reader.getViewAndOffset(this.pos, this.pos + 4);
+ this.pos += 4;
+ return view2.getInt32(offset, false);
+ }
+ readU32() {
+ const { view: view2, offset } = this.reader.getViewAndOffset(this.pos, this.pos + 4);
+ this.pos += 4;
+ return view2.getUint32(offset, false);
+ }
+ readI32() {
+ const { view: view2, offset } = this.reader.getViewAndOffset(this.pos, this.pos + 4);
+ this.pos += 4;
+ return view2.getInt32(offset, false);
+ }
+ readU64() {
+ const high = this.readU32();
+ const low = this.readU32();
+ return high * 4294967296 + low;
+ }
+ readF64() {
+ const { view: view2, offset } = this.reader.getViewAndOffset(this.pos, this.pos + 8);
+ this.pos += 8;
+ return view2.getFloat64(offset, false);
+ }
+ readFixed_16_16() {
+ return this.readS32() / 65536;
+ }
+ readFixed_2_30() {
+ return this.readS32() / 1073741824;
+ }
+ readAscii(length) {
+ const { view: view2, offset } = this.reader.getViewAndOffset(this.pos, this.pos + length);
+ this.pos += length;
+ let str = "";
+ for (let i = 0; i < length; i++) {
+ str += String.fromCharCode(view2.getUint8(offset + i));
+ }
+ return str;
+ }
+ readIsomVariableInteger() {
+ let result = 0;
+ for (let i = 0; i < 4; i++) {
+ result <<= 7;
+ const nextByte = this.readU8();
+ result |= nextByte & 127;
+ if ((nextByte & 128) === 0) {
+ break;
+ }
+ }
+ return result;
+ }
+ readBoxHeader() {
+ let totalSize = this.readU32();
+ const name = this.readAscii(4);
+ let headerSize = 8;
+ const hasLargeSize = totalSize === 1;
+ if (hasLargeSize) {
+ totalSize = this.readU64();
+ headerSize = 16;
+ }
+ return { name, totalSize, headerSize, contentSize: totalSize - headerSize };
+ }
+};
+
+// src/isobmff/isobmff-demuxer.ts
+var knownMatrixes = [rotationMatrix(0), rotationMatrix(90), rotationMatrix(180), rotationMatrix(270)];
+var IsobmffDemuxer = class extends Demuxer {
+ isobmffReader;
+ currentTrack = null;
+ tracks = [];
+ metadataPromise = null;
+ movieTimescale = -1;
+ movieDurationInTimescale = -1;
+ constructor(input) {
+ super(input);
+ this.isobmffReader = new IsobmffReader(input._reader);
+ }
+ async getDuration() {
+ await this.readMetadata();
+ if (this.movieDurationInTimescale === -1) {
+ throw new Error("Could not read movie duration.");
+ }
+ return this.movieDurationInTimescale / this.movieTimescale;
+ }
+ async getTracks() {
+ await this.readMetadata();
+ return this.tracks.map((track) => track.inputTrack);
+ }
+ async getMimeType() {
+ await this.readMetadata();
+ let string = "video/mp4";
+ if (this.tracks.length > 0) {
+ const codecMimeTypes = await Promise.all(this.tracks.map((x) => x.inputTrack.getCodecMimeType()));
+ const uniqueCodecMimeTypes = [...new Set(codecMimeTypes)];
+ string += `; codecs="${uniqueCodecMimeTypes.join(", ")}"`;
+ }
+ return string;
+ }
+ readMetadata() {
+ return this.metadataPromise ??= (async () => {
+ const sourceSize = await this.isobmffReader.reader.getSourceSize();
+ while (this.isobmffReader.pos < sourceSize) {
+ await this.isobmffReader.reader.loadRange(this.isobmffReader.pos, this.isobmffReader.pos + 16);
+ const startPos = this.isobmffReader.pos;
+ const boxInfo = this.isobmffReader.readBoxHeader();
+ if (boxInfo.name === "moov") {
+ await this.isobmffReader.reader.loadRange(
+ this.isobmffReader.pos,
+ this.isobmffReader.pos + boxInfo.contentSize
+ );
+ this.readContiguousBoxes(boxInfo.contentSize);
+ return;
+ }
+ this.isobmffReader.pos = startPos + boxInfo.totalSize;
+ }
+ })();
+ }
+ getSampleTableForTrack(internalTrack) {
+ if (internalTrack.sampleTable) {
+ return internalTrack.sampleTable;
+ }
+ const sampleTable = {
+ sampleTimingEntries: [],
+ sampleCompositionTimeOffsets: [],
+ sampleSizes: [],
+ keySampleIndices: null,
+ chunkOffsets: [],
+ sampleToChunk: [],
+ presentationTimestamps: []
+ };
+ internalTrack.sampleTable = sampleTable;
+ this.isobmffReader.pos = internalTrack.sampleTableOffset;
+ this.currentTrack = internalTrack;
+ this.traverseBox();
+ this.currentTrack = null;
+ for (const entry of sampleTable.sampleTimingEntries) {
+ for (let i = 0; i < entry.count; i++) {
+ sampleTable.presentationTimestamps.push({
+ presentationTimestamp: entry.startDecodeTimestamp + i * entry.delta,
+ sampleIndex: entry.startIndex + i
+ });
+ }
+ }
+ for (const entry of sampleTable.sampleCompositionTimeOffsets) {
+ for (let i = 0; i < entry.count; i++) {
+ const sampleIndex = entry.startIndex + i;
+ const sample = sampleTable.presentationTimestamps[sampleIndex];
+ if (!sample) {
+ continue;
+ }
+ sample.presentationTimestamp += entry.offset;
+ }
+ }
+ sampleTable.presentationTimestamps.sort((a, b) => a.presentationTimestamp - b.presentationTimestamp);
+ return internalTrack.sampleTable;
+ }
+ readContiguousBoxes(totalSize) {
+ const startIndex = this.isobmffReader.pos;
+ while (this.isobmffReader.pos - startIndex < totalSize) {
+ this.traverseBox();
+ }
+ }
+ traverseBox() {
+ const startPos = this.isobmffReader.pos;
+ const boxInfo = this.isobmffReader.readBoxHeader();
+ const boxEndPos = startPos + boxInfo.totalSize;
+ switch (boxInfo.name) {
+ case "mdia":
+ case "minf":
+ case "dinf":
+ {
+ this.readContiguousBoxes(boxInfo.contentSize);
+ }
+ ;
+ break;
+ case "mvhd":
+ {
+ const version = this.isobmffReader.readU8();
+ this.isobmffReader.pos += 3;
+ if (version === 1) {
+ this.isobmffReader.pos += 8 + 8;
+ this.movieTimescale = this.isobmffReader.readU32();
+ this.movieDurationInTimescale = this.isobmffReader.readU64();
+ } else {
+ this.isobmffReader.pos += 4 + 4;
+ this.movieTimescale = this.isobmffReader.readU32();
+ this.movieDurationInTimescale = this.isobmffReader.readU32();
+ }
+ }
+ ;
+ break;
+ case "trak":
+ {
+ const track = {
+ id: -1,
+ demuxer: this,
+ inputTrack: null,
+ info: null,
+ timescale: -1,
+ durationInTimescale: -1,
+ rotation: 0,
+ sampleTableOffset: -1,
+ sampleTable: null
+ };
+ this.currentTrack = track;
+ this.readContiguousBoxes(boxInfo.contentSize);
+ if (track.id !== -1 && track.timescale !== -1 && track.info !== null) {
+ if (track.info.type === "video" && track.info.codec !== null) {
+ const videoTrack = track;
+ track.inputTrack = new InputVideoTrack(new IsobmffVideoTrackBacking(videoTrack));
+ this.tracks.push(track);
+ } else if (track.info.type === "audio" && track.info.codec !== null) {
+ const audioTrack = track;
+ track.inputTrack = new InputAudioTrack(new IsobmffAudioTrackBacking(audioTrack));
+ this.tracks.push(track);
+ }
+ }
+ this.currentTrack = null;
+ }
+ ;
+ break;
+ case "tkhd":
+ {
+ const track = this.currentTrack;
+ assert(track);
+ const version = this.isobmffReader.readU8();
+ const flags = this.isobmffReader.readU24();
+ const trackEnabled = (flags & 1) !== 0;
+ if (!trackEnabled) {
+ break;
+ }
+ if (version === 0) {
+ this.isobmffReader.pos += 8;
+ track.id = this.isobmffReader.readU32();
+ this.isobmffReader.pos += 8;
+ } else if (version === 1) {
+ this.isobmffReader.pos += 16;
+ track.id = this.isobmffReader.readU32();
+ this.isobmffReader.pos += 12;
+ } else {
+ throw new Error(`Incorrect track header version ${version}.`);
+ }
+ this.isobmffReader.pos += 2 * 4 + 2 + 2 + 2 + 2;
+ const rotationMatrix2 = [];
+ rotationMatrix2.push(this.isobmffReader.readFixed_16_16(), this.isobmffReader.readFixed_16_16());
+ this.isobmffReader.pos += 4;
+ rotationMatrix2.push(this.isobmffReader.readFixed_16_16(), this.isobmffReader.readFixed_16_16());
+ const matrixIndex = knownMatrixes.findIndex((x) => x.every((y, i) => y === rotationMatrix2[i]));
+ if (matrixIndex === -1) {
+ track.rotation = 0;
+ } else {
+ track.rotation = 90 * matrixIndex;
+ }
+ }
+ ;
+ break;
+ case "mdhd":
+ {
+ const track = this.currentTrack;
+ assert(track);
+ const version = this.isobmffReader.readU8();
+ this.isobmffReader.pos += 3;
+ if (version === 0) {
+ this.isobmffReader.pos += 8;
+ track.timescale = this.isobmffReader.readU32();
+ track.durationInTimescale = this.isobmffReader.readU32();
+ } else if (version === 1) {
+ this.isobmffReader.pos += 16;
+ track.timescale = this.isobmffReader.readU32();
+ track.durationInTimescale = this.isobmffReader.readU64();
+ }
+ }
+ ;
+ break;
+ case "hdlr":
+ {
+ const track = this.currentTrack;
+ assert(track);
+ this.isobmffReader.pos += 8;
+ const handlerType = this.isobmffReader.readAscii(4);
+ if (handlerType === "vide") {
+ track.info = {
+ type: "video",
+ width: -1,
+ height: -1,
+ codec: null,
+ codecDescription: null,
+ colorSpace: null
+ };
+ } else if (handlerType === "soun") {
+ track.info = {
+ type: "audio",
+ numberOfChannels: -1,
+ sampleRate: -1,
+ codec: null,
+ codecDescription: null
+ };
+ }
+ }
+ ;
+ break;
+ case "stbl":
+ {
+ const track = this.currentTrack;
+ assert(track);
+ track.sampleTableOffset = startPos;
+ this.readContiguousBoxes(boxInfo.contentSize);
+ }
+ ;
+ break;
+ case "stsd":
+ {
+ const track = this.currentTrack;
+ assert(track);
+ if (track.info === null || track.sampleTable) {
+ break;
+ }
+ const stsdVersion = this.isobmffReader.readU8();
+ this.isobmffReader.pos += 3;
+ const entries = this.isobmffReader.readU32();
+ for (let i = 0; i < entries; i++) {
+ const sampleBoxInfo = this.isobmffReader.readBoxHeader();
+ if (track.info.type === "video") {
+ if (sampleBoxInfo.name === "avc1") {
+ track.info.codec = "avc";
+ } else if (sampleBoxInfo.name === "hvc1" || sampleBoxInfo.name === "hev1") {
+ track.info.codec = "hevc";
+ } else {
+ console.warn(`Unsupported video sample entry type ${sampleBoxInfo.name}.`);
+ break;
+ }
+ this.isobmffReader.pos += 6 * 1 + 2 + 2 + 2 + 3 * 4;
+ track.info.width = this.isobmffReader.readU16();
+ track.info.height = this.isobmffReader.readU16();
+ this.isobmffReader.pos += 4 + 4 + 4 + 2 + 32 + 2 + 2;
+ this.readContiguousBoxes(startPos + sampleBoxInfo.totalSize - this.isobmffReader.pos);
+ } else {
+ if (sampleBoxInfo.name === "mp4a") {
+ track.info.codec = "aac";
+ } else if (sampleBoxInfo.name.toLowerCase() === "opus") {
+ track.info.codec = "opus";
+ } else {
+ console.warn(`Unsupported audio sample entry type ${sampleBoxInfo.name}.`);
+ break;
+ }
+ this.isobmffReader.pos += 6 * 1 + 2;
+ const version = this.isobmffReader.readU16();
+ this.isobmffReader.pos += 3 * 2;
+ let channelCount = this.isobmffReader.readU16();
+ this.isobmffReader.pos += 2 + 2 + 2;
+ let sampleRate = this.isobmffReader.readU32() / 65536;
+ if (stsdVersion === 0 && version > 0) {
+ if (version === 1) {
+ this.isobmffReader.pos += 4 * 4;
+ } else if (version === 2) {
+ this.isobmffReader.pos += 4;
+ sampleRate = this.isobmffReader.readF64();
+ channelCount = this.isobmffReader.readU32();
+ this.isobmffReader.pos += 4;
+ const sampleSize = this.isobmffReader.readU32();
+ const flags = this.isobmffReader.readU32();
+ const bytesPerFrame = this.isobmffReader.readU32();
+ const samplesPerFrame = this.isobmffReader.readU32();
+ }
+ }
+ track.info.numberOfChannels = channelCount;
+ track.info.sampleRate = sampleRate;
+ this.readContiguousBoxes(startPos + sampleBoxInfo.totalSize - this.isobmffReader.pos);
+ }
+ }
+ }
+ ;
+ break;
+ case "avcC":
+ {
+ const track = this.currentTrack;
+ assert(track && track.info);
+ track.info.codecDescription = this.isobmffReader.readRange(
+ this.isobmffReader.pos,
+ this.isobmffReader.pos + boxInfo.contentSize
+ );
+ }
+ ;
+ break;
+ case "hvcC":
+ {
+ const track = this.currentTrack;
+ assert(track && track.info);
+ track.info.codecDescription = this.isobmffReader.readRange(
+ this.isobmffReader.pos,
+ this.isobmffReader.pos + boxInfo.contentSize
+ );
+ }
+ ;
+ break;
+ case "colr":
+ {
+ const track = this.currentTrack;
+ assert(track && track.info?.type === "video");
+ const colourType = this.isobmffReader.readAscii(4);
+ if (colourType !== "nclx") {
+ break;
+ }
+ const colourPrimaries = this.isobmffReader.readU16();
+ const transferCharacteristics = this.isobmffReader.readU16();
+ const matrixCoefficients = this.isobmffReader.readU16();
+ const fullRangeFlag = Boolean(this.isobmffReader.readU8() & 128);
+ track.info.colorSpace = {
+ primaries: COLOR_PRIMARIES_MAP_INVERSE[colourPrimaries],
+ transfer: TRANSFER_CHARACTERISTICS_MAP_INVERSE[transferCharacteristics],
+ matrix: MATRIX_COEFFICIENTS_MAP_INVERSE[matrixCoefficients],
+ fullRange: fullRangeFlag
+ };
+ }
+ ;
+ break;
+ case "wave":
+ {
+ if (boxInfo.totalSize > 8) {
+ this.readContiguousBoxes(boxInfo.contentSize);
+ }
+ }
+ ;
+ break;
+ case "esds":
+ {
+ const track = this.currentTrack;
+ assert(track && track.info);
+ this.isobmffReader.pos += 4;
+ const tag = this.isobmffReader.readU8();
+ assert(tag === 3);
+ this.isobmffReader.readIsomVariableInteger();
+ this.isobmffReader.pos += 2;
+ const mixed = this.isobmffReader.readU8();
+ const streamDependenceFlag = (mixed & 128) !== 0;
+ const urlFlag = (mixed & 64) !== 0;
+ const ocrStreamFlag = (mixed & 32) !== 0;
+ if (streamDependenceFlag) {
+ this.isobmffReader.pos += 2;
+ }
+ if (urlFlag) {
+ const urlLength = this.isobmffReader.readU8();
+ this.isobmffReader.pos += urlLength;
+ }
+ if (ocrStreamFlag) {
+ this.isobmffReader.pos += 2;
+ }
+ const decoderConfigTag = this.isobmffReader.readU8();
+ assert(decoderConfigTag === 4);
+ this.isobmffReader.readIsomVariableInteger();
+ const objectTypeIndication = this.isobmffReader.readU8();
+ assert(objectTypeIndication === 64);
+ this.isobmffReader.pos += 1 + 3 + 4 + 4;
+ const decoderSpecificInfoTag = this.isobmffReader.readU8();
+ assert(decoderSpecificInfoTag === 5);
+ const decoderSpecificInfoLength = this.isobmffReader.readIsomVariableInteger();
+ track.info.codecDescription = this.isobmffReader.readRange(
+ this.isobmffReader.pos,
+ this.isobmffReader.pos + decoderSpecificInfoLength
+ );
+ }
+ ;
+ break;
+ case "stts":
+ {
+ const track = this.currentTrack;
+ assert(track);
+ if (!track.sampleTable) {
+ break;
+ }
+ this.isobmffReader.pos += 4;
+ const entryCount = this.isobmffReader.readU32();
+ let currentIndex = 0;
+ let currentTimestamp = 0;
+ for (let i = 0; i < entryCount; i++) {
+ const sampleCount = this.isobmffReader.readU32();
+ const sampleDelta = this.isobmffReader.readU32();
+ track.sampleTable.sampleTimingEntries.push({
+ startIndex: currentIndex,
+ startDecodeTimestamp: currentTimestamp,
+ count: sampleCount,
+ delta: sampleDelta
+ });
+ currentIndex += sampleCount;
+ currentTimestamp += sampleCount * sampleDelta;
+ }
+ }
+ ;
+ break;
+ case "ctts":
+ {
+ const track = this.currentTrack;
+ assert(track);
+ if (!track.sampleTable) {
+ break;
+ }
+ this.isobmffReader.pos += 1 + 3;
+ const entryCount = this.isobmffReader.readU32();
+ let sampleIndex = 0;
+ for (let i = 0; i < entryCount; i++) {
+ const sampleCount = this.isobmffReader.readU32();
+ const sampleOffset = this.isobmffReader.readI32();
+ track.sampleTable.sampleCompositionTimeOffsets.push({
+ startIndex: sampleIndex,
+ count: sampleCount,
+ offset: sampleOffset
+ });
+ sampleIndex += sampleCount;
+ }
+ }
+ ;
+ break;
+ case "stsz":
+ {
+ const track = this.currentTrack;
+ assert(track);
+ if (!track.sampleTable) {
+ break;
+ }
+ this.isobmffReader.pos += 4;
+ const sampleSize = this.isobmffReader.readU32();
+ const sampleCount = this.isobmffReader.readU32();
+ if (sampleSize === 0) {
+ for (let i = 0; i < sampleCount; i++) {
+ const sampleSize2 = this.isobmffReader.readU32();
+ track.sampleTable.sampleSizes.push(sampleSize2);
+ }
+ } else {
+ track.sampleTable.sampleSizes.push(sampleSize);
+ }
+ }
+ ;
+ break;
+ case "stz2":
+ {
+ throw new Error("Unsupported.");
+ }
+ ;
+ case "stss":
+ {
+ const track = this.currentTrack;
+ assert(track);
+ if (!track.sampleTable) {
+ break;
+ }
+ this.isobmffReader.pos += 4;
+ track.sampleTable.keySampleIndices = [];
+ const entryCount = this.isobmffReader.readU32();
+ for (let i = 0; i < entryCount; i++) {
+ const sampleIndex = this.isobmffReader.readU32() - 1;
+ track.sampleTable.keySampleIndices.push(sampleIndex);
+ }
+ }
+ ;
+ break;
+ case "stsc":
+ {
+ const track = this.currentTrack;
+ assert(track);
+ if (!track.sampleTable) {
+ break;
+ }
+ this.isobmffReader.pos += 4;
+ const entryCount = this.isobmffReader.readU32();
+ for (let i = 0; i < entryCount; i++) {
+ const startChunkIndex = this.isobmffReader.readU32() - 1;
+ const samplesPerChunk = this.isobmffReader.readU32();
+ const sampleDescriptionIndex = this.isobmffReader.readU32();
+ track.sampleTable.sampleToChunk.push({
+ startSampleIndex: -1,
+ startChunkIndex,
+ samplesPerChunk,
+ sampleDescriptionIndex
+ });
+ }
+ let startSampleIndex = 0;
+ for (let i = 0; i < track.sampleTable.sampleToChunk.length; i++) {
+ track.sampleTable.sampleToChunk[i].startSampleIndex = startSampleIndex;
+ if (i < track.sampleTable.sampleToChunk.length - 1) {
+ const nextChunk = track.sampleTable.sampleToChunk[i + 1];
+ const chunkCount = nextChunk.startChunkIndex - track.sampleTable.sampleToChunk[i].startChunkIndex;
+ startSampleIndex += chunkCount * track.sampleTable.sampleToChunk[i].samplesPerChunk;
+ }
+ }
+ }
+ ;
+ break;
+ case "stco":
+ {
+ const track = this.currentTrack;
+ assert(track);
+ if (!track.sampleTable) {
+ break;
+ }
+ this.isobmffReader.pos += 4;
+ const entryCount = this.isobmffReader.readU32();
+ for (let i = 0; i < entryCount; i++) {
+ const chunkOffset = this.isobmffReader.readU32();
+ track.sampleTable.chunkOffsets.push(chunkOffset);
+ }
+ }
+ ;
+ break;
+ case "co64":
+ {
+ const track = this.currentTrack;
+ assert(track);
+ if (!track.sampleTable) {
+ break;
+ }
+ this.isobmffReader.pos += 4;
+ const entryCount = this.isobmffReader.readU32();
+ for (let i = 0; i < entryCount; i++) {
+ const chunkOffset = this.isobmffReader.readU64();
+ track.sampleTable.chunkOffsets.push(chunkOffset);
+ }
+ }
+ ;
+ break;
+ }
+ this.isobmffReader.pos = boxEndPos;
+ }
+};
+var IsobmffTrackBacking = class {
+ constructor(internalTrack) {
+ this.internalTrack = internalTrack;
+ }
+ chunkToSampleIndex = /* @__PURE__ */ new WeakMap();
+ sampleIndexToChunk = /* @__PURE__ */ new Map();
+ getCodec() {
+ throw new Error("Not implemented on base class.");
+ }
+ async getDuration() {
+ return this.internalTrack.durationInTimescale / this.internalTrack.timescale;
+ }
+};
+var IsobmffVideoTrackBacking = class extends IsobmffTrackBacking {
+ internalTrack;
+ constructor(internalTrack) {
+ super(internalTrack);
+ this.internalTrack = internalTrack;
+ }
+ async getCodec() {
+ return this.internalTrack.info.codec;
+ }
+ async getWidth() {
+ return this.internalTrack.info.width;
+ }
+ async getHeight() {
+ return this.internalTrack.info.height;
+ }
+ async getRotation() {
+ return this.internalTrack.rotation;
+ }
+ async getDecoderConfig() {
+ return {
+ codec: extractVideoCodecString(this.internalTrack.info.codec, this.internalTrack.info.codecDescription),
+ codedWidth: this.internalTrack.info.width,
+ codedHeight: this.internalTrack.info.height,
+ description: this.internalTrack.info.codecDescription ?? void 0,
+ colorSpace: this.internalTrack.info.colorSpace ?? void 0
+ };
+ }
+ async fetchChunkForSampleIndex(sampleIndex) {
+ if (sampleIndex === -1) {
+ return null;
+ }
+ const existingChunk = this.sampleIndexToChunk.get(sampleIndex)?.deref();
+ if (existingChunk) {
+ return existingChunk;
+ }
+ const sampleTable = this.internalTrack.demuxer.getSampleTableForTrack(this.internalTrack);
+ const sampleInfo = getSampleInfo(sampleTable, sampleIndex);
+ if (!sampleInfo) {
+ return null;
+ }
+ const data = await this.internalTrack.demuxer.isobmffReader.reader.source._read(
+ sampleInfo.byteOffset,
+ sampleInfo.byteOffset + sampleInfo.byteSize
+ );
+ const chunk = new EncodedVideoChunk({
+ data,
+ timestamp: 1e6 * sampleInfo.presentationTimestamp / this.internalTrack.timescale,
+ duration: 1e6 * sampleInfo.duration / this.internalTrack.timescale,
+ type: sampleInfo.isKeyFrame ? "key" : "delta"
+ });
+ this.chunkToSampleIndex.set(chunk, sampleIndex);
+ this.sampleIndexToChunk.set(sampleIndex, new WeakRef(chunk));
+ return chunk;
+ }
+ async getFirstChunk() {
+ return this.fetchChunkForSampleIndex(0);
+ }
+ async getChunk(timestamp) {
+ const sampleTable = this.internalTrack.demuxer.getSampleTableForTrack(this.internalTrack);
+ const sampleIndex = getSampleIndexForTimestamp(sampleTable, timestamp * this.internalTrack.timescale);
+ return this.fetchChunkForSampleIndex(sampleIndex);
+ }
+ async getNextChunk(chunk) {
+ const sampleIndex = this.chunkToSampleIndex.get(chunk);
+ if (sampleIndex === void 0) {
+ throw new Error("Chunk was not created from this track.");
+ }
+ return this.fetchChunkForSampleIndex(sampleIndex + 1);
+ }
+ async getKeyChunk(timestamp) {
+ const sampleTable = this.internalTrack.demuxer.getSampleTableForTrack(this.internalTrack);
+ const sampleIndex = getSampleIndexForTimestamp(sampleTable, timestamp * this.internalTrack.timescale);
+ const keyFrameSampleIndex = sampleIndex === -1 ? -1 : getRelevantKeyframeIndexForSample(sampleTable, sampleIndex);
+ return this.fetchChunkForSampleIndex(keyFrameSampleIndex);
+ }
+ async getNextKeyChunk(chunk) {
+ const sampleIndex = this.chunkToSampleIndex.get(chunk);
+ if (sampleIndex === void 0) {
+ throw new Error("Chunk was not created from this track.");
+ }
+ const sampleTable = this.internalTrack.demuxer.getSampleTableForTrack(this.internalTrack);
+ const nextKeyFrameSampleIndex = getNextKeyframeIndexForSample(sampleTable, sampleIndex);
+ return this.fetchChunkForSampleIndex(nextKeyFrameSampleIndex);
+ }
+};
+var IsobmffAudioTrackBacking = class extends IsobmffTrackBacking {
+ internalTrack;
+ constructor(internalTrack) {
+ super(internalTrack);
+ this.internalTrack = internalTrack;
+ }
+ async getCodec() {
+ return this.internalTrack.info.codec;
+ }
+ async getNumberOfChannels() {
+ return this.internalTrack.info.numberOfChannels;
+ }
+ async getSampleRate() {
+ return this.internalTrack.info.sampleRate;
+ }
+ async getDecoderConfig() {
+ return {
+ codec: extractAudioCodecString(this.internalTrack.info.codec, this.internalTrack.info.codecDescription),
+ numberOfChannels: this.internalTrack.info.numberOfChannels,
+ sampleRate: this.internalTrack.info.sampleRate,
+ description: this.internalTrack.info.codecDescription ?? void 0
+ };
+ }
+};
+var getSampleIndexForTimestamp = (sampleTable, timescaleUnits) => {
+ const index = binarySearchLessOrEqual(
+ sampleTable.presentationTimestamps,
+ timescaleUnits,
+ (x) => x.presentationTimestamp
+ );
+ if (index === -1) {
+ return -1;
+ }
+ return sampleTable.presentationTimestamps[index].sampleIndex;
+};
+var getSampleInfo = (sampleTable, sampleIndex) => {
+ const timingEntryIndex = binarySearchLessOrEqual(sampleTable.sampleTimingEntries, sampleIndex, (x) => x.startIndex);
+ const timingEntry = sampleTable.sampleTimingEntries[timingEntryIndex];
+ if (!timingEntry || timingEntry.startIndex + timingEntry.count <= sampleIndex) {
+ return null;
+ }
+ const decodeTimestamp = timingEntry.startDecodeTimestamp + (sampleIndex - timingEntry.startIndex) * timingEntry.delta;
+ let presentationTimestamp = decodeTimestamp;
+ const offsetEntryIndex = binarySearchLessOrEqual(
+ sampleTable.sampleCompositionTimeOffsets,
+ sampleIndex,
+ (x) => x.startIndex
+ );
+ const offsetEntry = sampleTable.sampleCompositionTimeOffsets[offsetEntryIndex];
+ if (offsetEntry) {
+ presentationTimestamp += offsetEntry.offset;
+ }
+ const sampleSize = sampleTable.sampleSizes[Math.min(sampleIndex, sampleTable.sampleSizes.length - 1)];
+ const chunkEntryIndex = binarySearchLessOrEqual(sampleTable.sampleToChunk, sampleIndex, (x) => x.startSampleIndex);
+ const chunkEntry = sampleTable.sampleToChunk[chunkEntryIndex];
+ assert(chunkEntry);
+ const chunkIndex = chunkEntry.startChunkIndex + Math.floor((sampleIndex - chunkEntry.startSampleIndex) / chunkEntry.samplesPerChunk);
+ const chunkOffset = sampleTable.chunkOffsets[chunkIndex];
+ let sampleOffset = chunkOffset;
+ if (sampleTable.sampleSizes.length === 1) {
+ sampleOffset += sampleSize * (sampleIndex - chunkEntry.startSampleIndex);
+ } else {
+ const startSampleIndex = chunkEntry.startSampleIndex + (chunkIndex - chunkEntry.startChunkIndex) * chunkEntry.samplesPerChunk;
+ for (let i = startSampleIndex; i < sampleIndex; i++) {
+ sampleOffset += sampleTable.sampleSizes[i];
+ }
+ }
+ return {
+ presentationTimestamp,
+ duration: timingEntry.delta,
+ byteOffset: sampleOffset,
+ byteSize: sampleSize,
+ isKeyFrame: sampleTable.keySampleIndices ? binarySearchExact(sampleTable.keySampleIndices, sampleIndex, (x) => x) !== -1 : true
+ };
+};
+var getRelevantKeyframeIndexForSample = (sampleTable, sampleIndex) => {
+ if (!sampleTable.keySampleIndices) {
+ return sampleIndex;
+ }
+ const index = binarySearchLessOrEqual(sampleTable.keySampleIndices, sampleIndex, (x) => x);
+ return sampleTable.keySampleIndices[index] ?? -1;
+};
+var getNextKeyframeIndexForSample = (sampleTable, sampleIndex) => {
+ if (!sampleTable.keySampleIndices) {
+ return sampleIndex + 1;
+ }
+ const index = binarySearchLessOrEqual(sampleTable.keySampleIndices, sampleIndex, (x) => x);
+ return sampleTable.keySampleIndices[index + 1] ?? -1;
+};
+
+// src/matroska/matroska-demuxer.ts
+var MatroskaDemuxer = class extends Demuxer {
+};
+
+// src/input-format.ts
+var InputFormat = class {
+};
+var IsobmffInputFormat = class extends InputFormat {
+ /** @internal */
+ async _canReadInput(input) {
+ const sourceSize = await input._reader.getSourceSize();
+ if (sourceSize < 8) {
+ return false;
+ }
+ await input._reader.loadRange(4, 8);
+ const isobmffReader = new IsobmffReader(input._reader);
+ isobmffReader.pos = 4;
+ const fourCc = isobmffReader.readAscii(4);
+ return fourCc === "ftyp";
+ }
+ _createDemuxer(input) {
+ return new IsobmffDemuxer(input);
+ }
+};
+var MatroskaInputFormat = class extends InputFormat {
+ /** @internal */
+ async _canReadInput() {
+ return false;
+ }
+ _createDemuxer(input) {
+ return new MatroskaDemuxer(input);
+ }
+};
+var ISOBMFF = new IsobmffInputFormat();
+var MP4 = ISOBMFF;
+var MOV = ISOBMFF;
+var MATROSKA = new MatroskaInputFormat();
+var MKV = MATROSKA;
+var WEBM = MATROSKA;
+var ALL_FORMATS = [ISOBMFF, MKV];
+
+// src/reader.ts
+var PAGE_SIZE = 4096;
+var Reader = class {
+ constructor(source) {
+ this.source = source;
+ }
+ loadedSegments = [];
+ sourceSizePromise = null;
+ getSourceSize() {
+ if (this.sourceSizePromise) {
+ return this.sourceSizePromise;
+ } else {
+ return this.sourceSizePromise = this.source._getSize();
+ }
+ }
+ async loadRange(start, end) {
+ let alignedStart = Math.floor(start / PAGE_SIZE) * PAGE_SIZE;
+ let alignedEnd = Math.ceil(end / PAGE_SIZE) * PAGE_SIZE;
+ alignedEnd = Math.min(alignedEnd, await this.getSourceSize());
+ const thing = this.loadedSegments.find((x) => x.start <= alignedStart);
+ if (thing) {
+ alignedStart = Math.max(alignedStart, thing.end);
+ }
+ const thing2 = this.loadedSegments.find((x) => x.end >= alignedEnd);
+ if (thing2) {
+ alignedEnd = Math.min(alignedEnd, thing2.start);
+ }
+ if (alignedStart >= alignedEnd) {
+ return;
+ }
+ const bytes2 = await this.source._read(alignedStart, alignedEnd);
+ this.insertIntoLoadedSegments(alignedStart, bytes2);
+ }
+ insertIntoLoadedSegments(start, bytes2) {
+ const segment = {
+ start,
+ end: start + bytes2.byteLength,
+ bytes: bytes2,
+ view: new DataView(bytes2.buffer)
+ };
+ let index = this.loadedSegments.findLastIndex((x) => x.start <= start);
+ this.loadedSegments.splice(index + 1, 0, segment);
+ if (index === -1 || this.loadedSegments[index].end < segment.start) {
+ index++;
+ }
+ const mergeSectionStartIndex = index;
+ const mergeSectionStart = this.loadedSegments[mergeSectionStartIndex].start;
+ let mergeSectionEndIndex = index;
+ let mergeSectionEnd = this.loadedSegments[mergeSectionEndIndex].end;
+ while (this.loadedSegments.length - 1 > mergeSectionEndIndex && this.loadedSegments[mergeSectionEndIndex + 1].start <= mergeSectionEnd) {
+ mergeSectionEndIndex++;
+ mergeSectionEnd = Math.max(mergeSectionEnd, this.loadedSegments[mergeSectionEndIndex].end);
+ }
+ if (mergeSectionStartIndex === mergeSectionEndIndex) {
+ return;
+ }
+ for (let i = mergeSectionStartIndex; i <= mergeSectionEndIndex; i++) {
+ const segment2 = this.loadedSegments[i];
+ const coversEntireMergeSection = segment2.start === mergeSectionStart && segment2.end === mergeSectionEnd;
+ if (coversEntireMergeSection) {
+ this.loadedSegments.splice(i + 1, mergeSectionEndIndex - i);
+ this.loadedSegments.splice(mergeSectionStartIndex, i - mergeSectionStartIndex);
+ return;
+ }
+ }
+ const unifiedBytes = new Uint8Array(mergeSectionEnd - mergeSectionStart);
+ for (let i = mergeSectionStartIndex; i <= mergeSectionEndIndex; i++) {
+ const segment2 = this.loadedSegments[i];
+ unifiedBytes.set(segment2.bytes, segment2.start - mergeSectionStart);
+ }
+ this.loadedSegments.splice(mergeSectionStartIndex + 1, mergeSectionEndIndex - mergeSectionStartIndex);
+ this.loadedSegments[mergeSectionStartIndex].end = mergeSectionEnd;
+ this.loadedSegments[mergeSectionStartIndex].bytes = unifiedBytes;
+ this.loadedSegments[mergeSectionStartIndex].view = new DataView(unifiedBytes.buffer);
+ }
+ getViewAndOffset(start, end) {
+ const segment = this.loadedSegments.find((x) => x.start <= start && end <= x.end);
+ if (!segment) {
+ throw new Error(`No segment loaded for range [${start}, ${end}).`);
+ }
+ return {
+ view: segment.view,
+ offset: segment.bytes.byteOffset + start - segment.start
+ };
+ }
+};
+
+// src/input.ts
+var Input = class {
+ /** @internal */
+ _formats;
+ /** @internal */
+ _reader;
+ /** @internal */
+ _demuxerPromise = null;
+ /** @internal */
+ _format = null;
+ constructor(options) {
+ this._formats = options.formats;
+ this._reader = new Reader(options.source);
+ }
+ /** @internal */
+ _getDemuxer() {
+ return this._demuxerPromise ??= (async () => {
+ for (const format of this._formats) {
+ const canRead = await format._canReadInput(this);
+ if (canRead) {
+ this._format = format;
+ return format._createDemuxer(this);
+ }
+ }
+ throw new Error("Input has an unrecognizable format.");
+ })();
+ }
+ async getFormat() {
+ await this._getDemuxer();
+ assert(this._format);
+ return this._format;
+ }
+ async getDuration() {
+ const demuxer = await this._getDemuxer();
+ return demuxer.getDuration();
+ }
+ async getTracks() {
+ const demuxer = await this._getDemuxer();
+ return demuxer.getTracks();
+ }
+ async getVideoTracks() {
+ const tracks = await this.getTracks();
+ return tracks.filter((x) => x.isVideoTrack());
+ }
+ async getPrimaryVideoTrack() {
+ const tracks = await this.getTracks();
+ return tracks.find((x) => x.isVideoTrack()) ?? null;
+ }
+ async getAudioTracks() {
+ const tracks = await this.getTracks();
+ return tracks.filter((x) => x.isAudioTrack());
+ }
+ async getPrimaryAudioTrack() {
+ const tracks = await this.getTracks();
+ return tracks.find((x) => x.isAudioTrack()) ?? null;
+ }
+ async getMimeType() {
+ const demuxer = await this._getDemuxer();
+ return demuxer.getMimeType();
+ }
+};
+
+// src/media-drain.ts
+var EncodedVideoChunkDrain = class {
+ constructor(videoTrack) {
+ this.videoTrack = videoTrack;
+ }
+ getFirstChunk() {
+ return this.videoTrack._backing.getFirstChunk();
+ }
+ getChunk(timestamp) {
+ return this.videoTrack._backing.getChunk(timestamp);
+ }
+ getNextChunk(chunk) {
+ return this.videoTrack._backing.getNextChunk(chunk);
+ }
+ getKeyChunk(timestamp) {
+ return this.videoTrack._backing.getKeyChunk(timestamp);
+ }
+ getNextKeyChunk(chunk) {
+ return this.videoTrack._backing.getNextKeyChunk(chunk);
+ }
+ async *chunks(startTimestamp = 0) {
+ let chunk = await this.getChunk(startTimestamp);
+ while (chunk) {
+ yield chunk;
+ chunk = await this.getNextChunk(chunk);
+ }
+ }
+};
+var VideoFrameDrain = class {
+ constructor(videoTrack) {
+ this.videoTrack = videoTrack;
+ }
+ decoderConfig = null;
+ async createDecoder(onFrame) {
+ if (!this.decoderConfig) {
+ this.decoderConfig = await this.videoTrack.getDecoderConfig();
+ }
+ const decoder = new VideoDecoder({
+ output: onFrame,
+ error: (error) => console.error(error)
+ });
+ decoder.configure(this.decoderConfig);
+ return decoder;
+ }
+ async getKeyFrame(timestamp) {
+ let result = null;
+ const decoder = await this.createDecoder((frame) => result = frame);
+ const chunk = await this.videoTrack._backing.getKeyChunk(timestamp);
+ if (!chunk) {
+ return null;
+ }
+ decoder.decode(chunk);
+ await decoder.flush();
+ decoder.close();
+ return result;
+ }
+ async getFrame(timestamp) {
+ let result = null;
+ const decoder = await this.createDecoder((frame) => {
+ if (frame.timestamp / 1e6 <= timestamp) {
+ result?.close();
+ result = frame;
+ } else {
+ frame.close();
+ }
+ });
+ const keyChunk = await this.videoTrack._backing.getKeyChunk(timestamp);
+ if (!keyChunk) {
+ return null;
+ }
+ const targetChunk = await this.videoTrack._backing.getChunk(timestamp);
+ assert(targetChunk);
+ decoder.decode(keyChunk);
+ let currentChunk = keyChunk;
+ while (currentChunk !== targetChunk) {
+ const nextChunk = await this.videoTrack._backing.getNextChunk(currentChunk);
+ assert(nextChunk);
+ currentChunk = nextChunk;
+ decoder.decode(nextChunk);
+ if (decoder.decodeQueueSize >= 10) {
+ await new Promise((resolve) => decoder.addEventListener("dequeue", resolve, { once: true }));
+ }
+ }
+ await decoder.flush();
+ decoder.close();
+ return result;
+ }
+ async *frames(startTimestamp = 0) {
+ const frameQueue = [];
+ let firstFrameQueued = false;
+ let lastFrame = null;
+ let { promise: queueNonEmpty, resolve: onQueueNotEmpty } = promiseWithResolvers();
+ let ended = false;
+ const decoder = await this.createDecoder((frame) => {
+ if (ended) {
+ frame.close();
+ return;
+ }
+ const frameTimestamp = frame.timestamp / 1e6;
+ if (lastFrame) {
+ if (frameTimestamp > startTimestamp) {
+ frameQueue.push(lastFrame);
+ firstFrameQueued = true;
+ } else {
+ lastFrame.close();
+ }
+ }
+ if (frameTimestamp >= startTimestamp) {
+ frameQueue.push(frame);
+ firstFrameQueued = true;
+ }
+ lastFrame = firstFrameQueued ? null : frame;
+ if (frameQueue.length > 0) {
+ onQueueNotEmpty();
+ ({ promise: queueNonEmpty, resolve: onQueueNotEmpty } = promiseWithResolvers());
+ }
+ });
+ const keyChunk = await this.videoTrack._backing.getKeyChunk(startTimestamp);
+ if (!keyChunk) {
+ return;
+ }
+ let decoderIsFlushed = false;
+ void (async () => {
+ let currentChunk = keyChunk;
+ while (currentChunk && !ended) {
+ decoder.decode(currentChunk);
+ if (decoder.decodeQueueSize >= 10) {
+ await new Promise((resolve) => decoder.addEventListener("dequeue", resolve, { once: true }));
+ }
+ const nextChunk = await this.videoTrack._backing.getNextChunk(currentChunk);
+ currentChunk = nextChunk;
+ }
+ await decoder.flush();
+ decoder.close();
+ decoderIsFlushed = true;
+ onQueueNotEmpty();
+ })();
+ try {
+ while (true) {
+ if (frameQueue.length > 0) {
+ yield frameQueue.shift();
+ } else if (!decoderIsFlushed) {
+ await queueNonEmpty;
+ } else {
+ break;
+ }
+ }
+ } finally {
+ ended = true;
+ }
+ }
+};
export {
+ ALL_FORMATS,
AUDIO_CODECS,
+ ArrayBufferSource,
ArrayBufferTarget,
AudioBufferSource,
AudioDataSource,
AudioSource,
+ BlobSource,
CanvasSource,
EncodedAudioChunkSource,
+ EncodedVideoChunkDrain,
EncodedVideoChunkSource,
+ ISOBMFF,
+ Input,
+ MATROSKA,
+ MKV,
+ MOV,
+ MP4,
MediaSource,
MediaStreamAudioTrackSource,
MediaStreamVideoTrackSource,
@@ -3822,12 +5279,15 @@ export {
Output,
OutputFormat,
SUBTITLE_CODECS,
+ Source,
StreamTarget,
SubtitleSource,
Target,
TextSubtitleSource,
VIDEO_CODECS,
+ VideoFrameDrain,
VideoFrameSource,
VideoSource,
+ WEBM,
WebMOutputFormat
};
diff --git a/eslint.config.mjs b/eslint.config.mjs
index 6115d15..7c23432 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -25,6 +25,7 @@ export default tseslint.config(
code: 120,
}],
'@typescript-eslint/no-empty-object-type': 'off',
+ '@typescript-eslint/require-await': 'off',
},
},
{
diff --git a/src/codec.ts b/src/codec.ts
index cc97e09..a39a786 100644
--- a/src/codec.ts
+++ b/src/codec.ts
@@ -2,12 +2,29 @@ import {
COLOR_PRIMARIES_MAP,
MATRIX_COEFFICIENTS_MAP,
TRANSFER_CHARACTERISTICS_MAP,
+ bytesToHexString,
isAllowSharedBufferSource,
last,
+ reverseBitsU32,
} from './misc';
-import { AudioCodec, VideoCodec } from './source';
import { SubtitleMetadata } from './subtitles';
+/** @public */
+export const VIDEO_CODECS = ['avc', 'hevc', 'vp8', 'vp9', 'av1'] as const;
+/** @public */
+export const AUDIO_CODECS = ['aac', 'opus'] as const; // TODO add the rest
+/** @public */
+export const SUBTITLE_CODECS = ['webvtt'] as const; // TODO add the rest
+
+/** @public */
+export type VideoCodec = typeof VIDEO_CODECS[number];
+/** @public */
+export type AudioCodec = typeof AUDIO_CODECS[number];
+/** @public */
+export type SubtitleCodec = typeof SUBTITLE_CODECS[number];
+/** @public */
+export type MediaCodec = VideoCodec | AudioCodec | SubtitleCodec;
+
// https://en.wikipedia.org/wiki/Advanced_Video_Coding
const AVC_LEVEL_TABLE = [
{ maxMacroblocks: 99, maxBitrate: 64000, level: 0x0A }, // Level 1
@@ -166,6 +183,65 @@ export const buildVideoCodecString = (codec: VideoCodec, width: number, height:
throw new TypeError(`Unhandled codec '${codec}'.`);
};
+export const extractVideoCodecString = (codec: VideoCodec, description: Uint8Array | null) => {
+ if (codec === 'avc') {
+ if (!description || description.byteLength < 4) {
+ throw new TypeError('AVC description must be at least 4 bytes long.');
+ }
+
+ // TODO: The "temp hack". Something is amiss with the second byte in the hex string, check the specification
+
+ return `avc1.${bytesToHexString(description.subarray(1, 4))}`;
+ } else if (codec === 'hevc') {
+ if (!description) {
+ throw new TypeError('HEVC description must be provided.');
+ }
+
+ const view = new DataView(description.buffer, description.byteOffset, description.byteLength);
+ let codecString = 'hev1.';
+
+ // general_profile_space and general_profile_idc
+ const generalProfileSpace = (description[1]! >> 6) & 0x03;
+ const generalProfileIdc = description[1]! & 0x1F;
+ codecString += ['', 'A', 'B', 'C'][generalProfileSpace]! + generalProfileIdc;
+
+ codecString += '.';
+
+ // general_profile_compatibility_flags (in reverse bit order)
+ const compatibilityFlags = reverseBitsU32(view.getUint32(2));
+ codecString += compatibilityFlags.toString(16);
+
+ codecString += '.';
+
+ // general_tier_flag and general_level_idc
+ const generalTierFlag = (description[1]! >> 5) & 0x01;
+ const generalLevelIdc = description[12]!;
+ codecString += generalTierFlag === 0 ? 'L' : 'H';
+ codecString += generalLevelIdc;
+
+ codecString += '.';
+
+ // constraint_flags (6 bytes)
+ const constraintFlags: number[] = [];
+ for (let i = 0; i < 6; i++) {
+ const byte = description[i + 13]!;
+ constraintFlags.push(byte);
+ }
+
+ while (constraintFlags[constraintFlags.length - 1] === 0) {
+ constraintFlags.pop();
+ }
+
+ codecString += constraintFlags.map(x => x.toString(16)).join('.');
+
+ return codecString;
+ }
+
+ // TODO
+
+ throw new TypeError(`Unhandled codec '${codec}'.`);
+};
+
export const buildAudioCodecString = (codec: AudioCodec, numberOfChannels: number, sampleRate: number) => {
if (codec === 'aac') {
// If stereo or higher channels and lower sample rate, likely using HE-AAC v2 with PS
@@ -190,6 +266,25 @@ export const buildAudioCodecString = (codec: AudioCodec, numberOfChannels: numbe
throw new TypeError(`Unhandled codec '${codec}'.`);
};
+export const extractAudioCodecString = (codec: AudioCodec, description: Uint8Array | null) => {
+ if (codec === 'aac') {
+ if (!description || description.byteLength < 2) {
+ throw new TypeError('AAC description must be at least 2 bytes long.');
+ }
+
+ // TODO: Is this correct? Give a source/reason
+ const mpeg4AudioObjectType = description[0]! >> 3;
+ return `mp4a.40.${mpeg4AudioObjectType}`;
+ } else if (codec === 'opus') {
+ return 'opus';
+ } else if (codec === 'vorbis') {
+ return 'vorbis';
+ }
+
+ // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+ throw new TypeError(`Unhandled codec '${codec}'.`);
+};
+
export const getVideoEncoderConfigExtension = (codec: VideoCodec) => {
if (codec === 'avc') {
return {
diff --git a/src/demuxer.ts b/src/demuxer.ts
new file mode 100644
index 0000000..7d66b09
--- /dev/null
+++ b/src/demuxer.ts
@@ -0,0 +1,14 @@
+import { Input } from './input';
+import { InputTrack } from './input-track';
+
+export abstract class Demuxer {
+ input: Input;
+
+ constructor(input: Input) {
+ this.input = input;
+ }
+
+ abstract getDuration(): Promise;
+ abstract getTracks(): Promise;
+ abstract getMimeType(): Promise;
+}
diff --git a/src/index.ts b/src/index.ts
index 5f3642f..694b19f 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -9,14 +9,8 @@ export {
WebMOutputFormatOptions,
} from './output-format';
export {
- VIDEO_CODECS,
- VideoCodec,
VideoCodecConfig,
- AUDIO_CODECS,
- AudioCodec,
AudioCodecConfig,
- SUBTITLE_CODECS,
- SubtitleCodec,
MediaSource,
VideoSource,
EncodedVideoChunkSource,
@@ -30,6 +24,18 @@ export {
MediaStreamAudioTrackSource,
SubtitleSource,
TextSubtitleSource,
-} from './source';
+} from './media-source';
+export {
+ VIDEO_CODECS,
+ VideoCodec,
+ AUDIO_CODECS,
+ AudioCodec,
+ SUBTITLE_CODECS,
+ SubtitleCodec,
+} from './codec';
export { Target, ArrayBufferTarget, StreamTarget, StreamTargetChunk, StreamTargetOptions } from './target';
export { TransformationMatrix } from './misc';
+export { Source, ArrayBufferSource, BlobSource } from './source';
+export { ALL_FORMATS, ISOBMFF, MP4, MOV, MATROSKA, MKV, WEBM } from './input-format';
+export { Input, InputOptions } from './input';
+export { EncodedVideoChunkDrain, VideoFrameDrain } from './media-drain';
diff --git a/src/input-format.ts b/src/input-format.ts
new file mode 100644
index 0000000..9bd030f
--- /dev/null
+++ b/src/input-format.ts
@@ -0,0 +1,55 @@
+import { Demuxer } from './demuxer';
+import { Input } from './input';
+import { IsobmffDemuxer } from './isobmff/isobmff-demuxer';
+import { IsobmffReader } from './isobmff/isobmff-reader';
+import { MatroskaDemuxer } from './matroska/matroska-demuxer';
+
+export abstract class InputFormat {
+ /** @internal */
+ abstract _canReadInput(input: Input): Promise;
+
+ /** @internal */
+ abstract _createDemuxer(input: Input): Demuxer;
+}
+
+class IsobmffInputFormat extends InputFormat {
+ /** @internal */
+ override async _canReadInput(input: Input) {
+ const sourceSize = await input._reader.getSourceSize();
+ if (sourceSize < 8) {
+ return false;
+ }
+
+ await input._reader.loadRange(4, 8);
+
+ const isobmffReader = new IsobmffReader(input._reader);
+ isobmffReader.pos = 4;
+ const fourCc = isobmffReader.readAscii(4);
+
+ return fourCc === 'ftyp';
+ }
+
+ override _createDemuxer(input: Input) {
+ return new IsobmffDemuxer(input);
+ }
+}
+
+class MatroskaInputFormat extends InputFormat {
+ /** @internal */
+ override async _canReadInput() {
+ return false; // TODO
+ }
+
+ override _createDemuxer(input: Input) {
+ return new MatroskaDemuxer(input);
+ }
+}
+
+export const ISOBMFF = new IsobmffInputFormat();
+export const MP4 = ISOBMFF;
+export const MOV = ISOBMFF;
+export const MATROSKA = new MatroskaInputFormat();
+export const MKV = MATROSKA;
+export const WEBM = MATROSKA;
+
+export const ALL_FORMATS: InputFormat[] = [ISOBMFF, MKV];
diff --git a/src/input-track.ts b/src/input-track.ts
new file mode 100644
index 0000000..b28aa58
--- /dev/null
+++ b/src/input-track.ts
@@ -0,0 +1,121 @@
+import { AudioCodec, MediaCodec, VideoCodec } from './codec';
+
+export interface InputTrackBacking {
+ getCodec(): Promise;
+ getDuration(): Promise;
+}
+
+export abstract class InputTrack {
+ /** @internal */
+ _backing: InputTrackBacking;
+
+ /** @internal */
+ constructor(backing: InputTrackBacking) {
+ this._backing = backing;
+ }
+
+ abstract getCodec(): Promise;
+ abstract getCodecMimeType(): Promise;
+
+ isVideoTrack(): this is InputVideoTrack {
+ return this instanceof InputVideoTrack;
+ }
+
+ isAudioTrack(): this is InputAudioTrack {
+ return this instanceof InputAudioTrack;
+ }
+
+ getDuration() {
+ return this._backing.getDuration();
+ }
+}
+
+export interface InputVideoTrackBacking extends InputTrackBacking {
+ getCodec(): Promise;
+ getWidth(): Promise;
+ getHeight(): Promise;
+ getRotation(): Promise;
+ getDecoderConfig(): Promise;
+ getFirstChunk(): Promise;
+ getChunk(timestamp: number): Promise;
+ getNextChunk(chunk: EncodedVideoChunk): Promise;
+ getKeyChunk(timestamp: number): Promise;
+ getNextKeyChunk(chunk: EncodedVideoChunk): Promise;
+}
+
+export class InputVideoTrack extends InputTrack {
+ /** @internal */
+ override _backing: InputVideoTrackBacking;
+
+ /** @internal */
+ constructor(backing: InputVideoTrackBacking) {
+ super(backing);
+
+ this._backing = backing;
+ }
+
+ getCodec() {
+ return this._backing.getCodec();
+ }
+
+ getWidth() {
+ return this._backing.getWidth();
+ }
+
+ getHeight() {
+ return this._backing.getHeight();
+ }
+
+ getRotation() {
+ return this._backing.getRotation();
+ }
+
+ getDecoderConfig() {
+ return this._backing.getDecoderConfig();
+ }
+
+ async getCodecMimeType() {
+ const decoderConfig = await this.getDecoderConfig();
+ return decoderConfig.codec;
+ }
+}
+
+export interface InputAudioTrackBacking extends InputTrackBacking {
+ getCodec(): Promise;
+ getNumberOfChannels(): Promise;
+ getSampleRate(): Promise;
+ getDecoderConfig(): Promise;
+}
+
+export class InputAudioTrack extends InputTrack {
+ /** @internal */
+ override _backing: InputAudioTrackBacking;
+
+ /** @internal */
+ constructor(backing: InputAudioTrackBacking) {
+ super(backing);
+
+ this._backing = backing;
+ }
+
+ getCodec() {
+ return this._backing.getCodec();
+ }
+
+ getNumberOfChannels() {
+ return this._backing.getNumberOfChannels();
+ }
+
+ getSampleRate() {
+ return this._backing.getSampleRate();
+ }
+
+ getDecoderConfig() {
+ return this._backing.getDecoderConfig();
+ }
+
+ async getCodecMimeType() {
+ const decoderConfig = await this.getDecoderConfig();
+ return decoderConfig.codec;
+ }
+}
diff --git a/src/input.ts b/src/input.ts
new file mode 100644
index 0000000..ea62844
--- /dev/null
+++ b/src/input.ts
@@ -0,0 +1,82 @@
+import { Demuxer } from './demuxer';
+import { InputFormat } from './input-format';
+import { assert } from './misc';
+import { Reader } from './reader';
+import { Source } from './source';
+
+export type InputOptions = {
+ formats: InputFormat[];
+ source: Source;
+};
+
+export class Input {
+ /** @internal */
+ _formats: InputFormat[];
+ /** @internal */
+ _reader: Reader;
+ /** @internal */
+ _demuxerPromise: Promise | null = null;
+ /** @internal */
+ _format: InputFormat | null = null;
+
+ constructor(options: InputOptions) {
+ this._formats = options.formats;
+ this._reader = new Reader(options.source);
+ }
+
+ /** @internal */
+ _getDemuxer() {
+ return this._demuxerPromise ??= (async () => {
+ for (const format of this._formats) {
+ const canRead = await format._canReadInput(this);
+ if (canRead) {
+ this._format = format;
+ return format._createDemuxer(this);
+ }
+ }
+
+ throw new Error('Input has an unrecognizable format.');
+ })();
+ }
+
+ async getFormat() {
+ await this._getDemuxer();
+ assert(this._format!);
+ return this._format;
+ }
+
+ async getDuration() {
+ const demuxer = await this._getDemuxer();
+ return demuxer.getDuration();
+ }
+
+ async getTracks() {
+ const demuxer = await this._getDemuxer();
+ return demuxer.getTracks();
+ }
+
+ async getVideoTracks() {
+ const tracks = await this.getTracks();
+ return tracks.filter(x => x.isVideoTrack());
+ }
+
+ async getPrimaryVideoTrack() {
+ const tracks = await this.getTracks();
+ return tracks.find(x => x.isVideoTrack()) ?? null;
+ }
+
+ async getAudioTracks() {
+ const tracks = await this.getTracks();
+ return tracks.filter(x => x.isAudioTrack());
+ }
+
+ async getPrimaryAudioTrack() {
+ const tracks = await this.getTracks();
+ return tracks.find(x => x.isAudioTrack()) ?? null;
+ }
+
+ async getMimeType() {
+ const demuxer = await this._getDemuxer();
+ return demuxer.getMimeType();
+ }
+}
diff --git a/src/isobmff/isobmff-boxes.ts b/src/isobmff/isobmff-boxes.ts
index 99b1853..b2523ec 100644
--- a/src/isobmff/isobmff-boxes.ts
+++ b/src/isobmff/isobmff-boxes.ts
@@ -9,8 +9,10 @@ import {
TRANSFER_CHARACTERISTICS_MAP,
MATRIX_COEFFICIENTS_MAP,
colorSpaceIsComplete,
+ IDENTITY_MATRIX,
+ rotationMatrix,
} from '../misc';
-import { AudioCodec, SubtitleCodec, VideoCodec } from '../source';
+import { AudioCodec, SubtitleCodec, VideoCodec } from '../codec';
import { formatSubtitleTimestamp } from '../subtitles';
import { Writer } from '../writer';
import {
@@ -208,21 +210,6 @@ const lastPresentedSample = (samples: Sample[]) => {
return result;
};
-const rotationMatrix = (rotationInDegrees: number): TransformationMatrix => {
- const theta = rotationInDegrees * (Math.PI / 180);
- const cosTheta = Math.cos(theta);
- const sinTheta = Math.sin(theta);
-
- // Matrices are post-multiplied in ISOBMFF, meaning this is the transpose of your typical rotation matrix
- return [
- cosTheta, sinTheta, 0,
- -sinTheta, cosTheta, 0,
- 0, 0, 1,
- ];
-};
-
-const IDENTITY_MATRIX = rotationMatrix(0);
-
const matrixToBytes = (matrix: TransformationMatrix) => {
return [
fixed_16_16(matrix[0]), fixed_16_16(matrix[1]), fixed_2_30(matrix[2]),
diff --git a/src/isobmff/isobmff-demuxer.ts b/src/isobmff/isobmff-demuxer.ts
new file mode 100644
index 0000000..8baa7d2
--- /dev/null
+++ b/src/isobmff/isobmff-demuxer.ts
@@ -0,0 +1,1009 @@
+import { AudioCodec, extractAudioCodecString, extractVideoCodecString, MediaCodec, VideoCodec } from '../codec';
+import { Demuxer } from '../demuxer';
+import { Input } from '../input';
+import {
+ InputAudioTrack,
+ InputAudioTrackBacking,
+ InputTrack,
+ InputTrackBacking,
+ InputVideoTrack,
+ InputVideoTrackBacking,
+} from '../input-track';
+import {
+ assert,
+ COLOR_PRIMARIES_MAP_INVERSE,
+ MATRIX_COEFFICIENTS_MAP_INVERSE,
+ TRANSFER_CHARACTERISTICS_MAP_INVERSE,
+ rotationMatrix,
+ binarySearchLessOrEqual,
+ binarySearchExact,
+} from '../misc';
+import { IsobmffReader } from './isobmff-reader';
+
+type InternalTrack = {
+ id: number;
+ demuxer: IsobmffDemuxer;
+ inputTrack: InputTrack | null;
+ timescale: number;
+ durationInTimescale: number;
+ rotation: number;
+ sampleTableOffset: number;
+ sampleTable: SampleTable | null;
+} & ({
+ info: null;
+} | {
+ info: {
+ type: 'video';
+ width: number;
+ height: number;
+ codec: VideoCodec | null;
+ codecDescription: Uint8Array | null;
+ colorSpace?: VideoColorSpaceInit | null;
+ };
+} | {
+ info: {
+ type: 'audio';
+ numberOfChannels: number;
+ sampleRate: number;
+ codec: AudioCodec | null;
+ codecDescription: Uint8Array | null;
+ };
+});
+
+type InternalVideoTrack = InternalTrack & { info: { type: 'video' } };
+type InternalAudioTrack = InternalTrack & { info: { type: 'audio' } };
+
+type SampleTable = {
+ sampleTimingEntries: SampleTimingEntry[];
+ sampleCompositionTimeOffsets: SampleCompositionTimeOffsetEntry[];
+ sampleSizes: number[];
+ keySampleIndices: number[] | null; // Samples that are keyframes
+ chunkOffsets: number[];
+ sampleToChunk: SampleToChunkEntry[];
+ presentationTimestamps: {
+ presentationTimestamp: number;
+ sampleIndex: number;
+ }[];
+};
+type SampleTimingEntry = {
+ startIndex: number;
+ startDecodeTimestamp: number;
+ count: number;
+ delta: number;
+};
+type SampleCompositionTimeOffsetEntry = {
+ startIndex: number;
+ count: number;
+ offset: number;
+};
+type SampleToChunkEntry = {
+ startSampleIndex: number;
+ startChunkIndex: number;
+ samplesPerChunk: number;
+ sampleDescriptionIndex: number;
+};
+
+const knownMatrixes = [rotationMatrix(0), rotationMatrix(90), rotationMatrix(180), rotationMatrix(270)];
+
+export class IsobmffDemuxer extends Demuxer {
+ isobmffReader: IsobmffReader;
+ private currentTrack: InternalTrack | null = null;
+ private tracks: InternalTrack[] = [];
+ private metadataPromise: Promise | null = null;
+ private movieTimescale = -1;
+ private movieDurationInTimescale = -1;
+
+ constructor(input: Input) {
+ super(input);
+
+ this.isobmffReader = new IsobmffReader(input._reader);
+ }
+
+ override async getDuration() {
+ await this.readMetadata();
+
+ if (this.movieDurationInTimescale === -1) {
+ throw new Error('Could not read movie duration.');
+ }
+
+ return this.movieDurationInTimescale / this.movieTimescale;
+ }
+
+ override async getTracks() {
+ await this.readMetadata();
+ return this.tracks.map(track => track.inputTrack!);
+ }
+
+ override async getMimeType() {
+ await this.readMetadata();
+
+ let string = 'video/mp4';
+
+ if (this.tracks.length > 0) {
+ const codecMimeTypes = await Promise.all(this.tracks.map(x => x.inputTrack!.getCodecMimeType()));
+ const uniqueCodecMimeTypes = [...new Set(codecMimeTypes)];
+
+ string += `; codecs="${uniqueCodecMimeTypes.join(', ')}"`;
+ }
+
+ return string;
+ }
+
+ readMetadata() {
+ return this.metadataPromise ??= (async () => {
+ const sourceSize = await this.isobmffReader.reader.getSourceSize();
+
+ while (this.isobmffReader.pos < sourceSize) {
+ await this.isobmffReader.reader.loadRange(this.isobmffReader.pos, this.isobmffReader.pos + 16);
+ const startPos = this.isobmffReader.pos;
+ const boxInfo = this.isobmffReader.readBoxHeader();
+
+ if (boxInfo.name === 'moov') {
+ // Found moov, load it
+ await this.isobmffReader.reader.loadRange(
+ this.isobmffReader.pos,
+ this.isobmffReader.pos + boxInfo.contentSize,
+ );
+ this.readContiguousBoxes(boxInfo.contentSize);
+
+ return;
+ }
+
+ this.isobmffReader.pos = startPos + boxInfo.totalSize;
+ }
+ })();
+ }
+
+ getSampleTableForTrack(internalTrack: InternalTrack) {
+ if (internalTrack.sampleTable) {
+ return internalTrack.sampleTable;
+ }
+
+ const sampleTable: SampleTable = {
+ sampleTimingEntries: [],
+ sampleCompositionTimeOffsets: [],
+ sampleSizes: [],
+ keySampleIndices: null,
+ chunkOffsets: [],
+ sampleToChunk: [],
+ presentationTimestamps: [],
+ };
+ internalTrack.sampleTable = sampleTable;
+
+ this.isobmffReader.pos = internalTrack.sampleTableOffset;
+ this.currentTrack = internalTrack;
+ this.traverseBox();
+ this.currentTrack = null;
+
+ for (const entry of sampleTable.sampleTimingEntries) {
+ for (let i = 0; i < entry.count; i++) {
+ sampleTable.presentationTimestamps.push({
+ presentationTimestamp: entry.startDecodeTimestamp + i * entry.delta,
+ sampleIndex: entry.startIndex + i,
+ });
+ }
+ }
+
+ for (const entry of sampleTable.sampleCompositionTimeOffsets) {
+ for (let i = 0; i < entry.count; i++) {
+ const sampleIndex = entry.startIndex + i;
+ const sample = sampleTable.presentationTimestamps[sampleIndex];
+ if (!sample) {
+ continue;
+ }
+
+ sample.presentationTimestamp += entry.offset;
+ }
+ }
+
+ sampleTable.presentationTimestamps.sort((a, b) => a.presentationTimestamp - b.presentationTimestamp);
+
+ return internalTrack.sampleTable;
+ }
+
+ readContiguousBoxes(totalSize: number) {
+ const startIndex = this.isobmffReader.pos;
+
+ while (this.isobmffReader.pos - startIndex < totalSize) {
+ this.traverseBox();
+ }
+ }
+
+ traverseBox() {
+ const startPos = this.isobmffReader.pos;
+ const boxInfo = this.isobmffReader.readBoxHeader();
+ const boxEndPos = startPos + boxInfo.totalSize;
+
+ switch (boxInfo.name) {
+ case 'mdia':
+ case 'minf':
+ case 'dinf': {
+ this.readContiguousBoxes(boxInfo.contentSize);
+ }; break;
+
+ case 'mvhd': {
+ const version = this.isobmffReader.readU8();
+ this.isobmffReader.pos += 3; // Flags
+
+ if (version === 1) {
+ this.isobmffReader.pos += 8 + 8;
+ this.movieTimescale = this.isobmffReader.readU32();
+ this.movieDurationInTimescale = this.isobmffReader.readU64();
+ } else {
+ this.isobmffReader.pos += 4 + 4;
+ this.movieTimescale = this.isobmffReader.readU32();
+ this.movieDurationInTimescale = this.isobmffReader.readU32();
+ }
+ }; break;
+
+ case 'trak': {
+ const track = {
+ id: -1,
+ demuxer: this,
+ inputTrack: null,
+ info: null,
+ timescale: -1,
+ durationInTimescale: -1,
+ rotation: 0,
+ sampleTableOffset: -1,
+ sampleTable: null,
+ } satisfies InternalTrack as InternalTrack;
+ this.currentTrack = track;
+
+ this.readContiguousBoxes(boxInfo.contentSize);
+
+ if (track.id !== -1 && track.timescale !== -1 && track.info !== null) {
+ if (track.info.type === 'video' && track.info.codec !== null) {
+ const videoTrack = track as InternalVideoTrack;
+ track.inputTrack = new InputVideoTrack(new IsobmffVideoTrackBacking(videoTrack));
+ this.tracks.push(track);
+ } else if (track.info.type === 'audio' && track.info.codec !== null) {
+ const audioTrack = track as InternalAudioTrack;
+ track.inputTrack = new InputAudioTrack(new IsobmffAudioTrackBacking(audioTrack));
+ this.tracks.push(track);
+ }
+ }
+
+ this.currentTrack = null;
+ }; break;
+
+ case 'tkhd': {
+ const track = this.currentTrack;
+ assert(track);
+
+ const version = this.isobmffReader.readU8();
+ const flags = this.isobmffReader.readU24();
+
+ const trackEnabled = (flags & 0x1) !== 0;
+ if (!trackEnabled) {
+ break;
+ }
+
+ // Skip over creation & modification time to reach the track ID
+ if (version === 0) {
+ this.isobmffReader.pos += 8;
+ track.id = this.isobmffReader.readU32();
+ this.isobmffReader.pos += 8;
+ } else if (version === 1) {
+ this.isobmffReader.pos += 16;
+ track.id = this.isobmffReader.readU32();
+ this.isobmffReader.pos += 12;
+ } else {
+ throw new Error(`Incorrect track header version ${version}.`);
+ }
+
+ this.isobmffReader.pos += 2 * 4 + 2 + 2 + 2 + 2;
+ const rotationMatrix: number[] = [];
+ rotationMatrix.push(this.isobmffReader.readFixed_16_16(), this.isobmffReader.readFixed_16_16());
+ this.isobmffReader.pos += 4;
+ rotationMatrix.push(this.isobmffReader.readFixed_16_16(), this.isobmffReader.readFixed_16_16());
+
+ const matrixIndex = knownMatrixes.findIndex(x => x.every((y, i) => y === rotationMatrix[i]));
+ if (matrixIndex === -1) {
+ // console.warn(`Wacky rotation matrix ${rotationMatrix}; sticking with no rotation.`);
+ track.rotation = 0;
+ } else {
+ track.rotation = 90 * matrixIndex;
+ }
+ }; break;
+
+ case 'mdhd': {
+ const track = this.currentTrack;
+ assert(track);
+
+ const version = this.isobmffReader.readU8();
+ this.isobmffReader.pos += 3; // Flags
+
+ if (version === 0) {
+ this.isobmffReader.pos += 8;
+ track.timescale = this.isobmffReader.readU32();
+ track.durationInTimescale = this.isobmffReader.readU32();
+ } else if (version === 1) {
+ this.isobmffReader.pos += 16;
+ track.timescale = this.isobmffReader.readU32();
+ track.durationInTimescale = this.isobmffReader.readU64();
+ }
+ }; break;
+
+ case 'hdlr': {
+ const track = this.currentTrack;
+ assert(track);
+
+ this.isobmffReader.pos += 8; // Version + flags + pre-defined
+ const handlerType = this.isobmffReader.readAscii(4);
+
+ if (handlerType === 'vide') {
+ track.info = {
+ type: 'video',
+ width: -1,
+ height: -1,
+ codec: null,
+ codecDescription: null,
+ colorSpace: null,
+ };
+ } else if (handlerType === 'soun') {
+ track.info = {
+ type: 'audio',
+ numberOfChannels: -1,
+ sampleRate: -1,
+ codec: null,
+ codecDescription: null,
+ };
+ }
+ }; break;
+
+ case 'stbl': {
+ const track = this.currentTrack;
+ assert(track);
+
+ track.sampleTableOffset = startPos;
+
+ this.readContiguousBoxes(boxInfo.contentSize);
+ }; break;
+
+ case 'stsd': {
+ const track = this.currentTrack;
+ assert(track);
+
+ if (track.info === null || track.sampleTable) {
+ break;
+ }
+
+ const stsdVersion = this.isobmffReader.readU8();
+ this.isobmffReader.pos += 3; // Flags
+
+ const entries = this.isobmffReader.readU32();
+
+ for (let i = 0; i < entries; i++) {
+ const sampleBoxInfo = this.isobmffReader.readBoxHeader();
+
+ if (track.info.type === 'video') {
+ if (sampleBoxInfo.name === 'avc1') {
+ track.info.codec = 'avc';
+ } else if (sampleBoxInfo.name === 'hvc1' || sampleBoxInfo.name === 'hev1') {
+ track.info.codec = 'hevc';
+ } else {
+ // TODO a more user-friendly message
+ console.warn(`Unsupported video sample entry type ${sampleBoxInfo.name}.`);
+ break;
+ }
+
+ this.isobmffReader.pos += 6 * 1 + 2 + 2 + 2 + 3 * 4;
+
+ track.info.width = this.isobmffReader.readU16();
+ track.info.height = this.isobmffReader.readU16();
+
+ this.isobmffReader.pos += 4 + 4 + 4 + 2 + 32 + 2 + 2;
+
+ this.readContiguousBoxes(startPos + sampleBoxInfo.totalSize - this.isobmffReader.pos);
+ } else {
+ if (sampleBoxInfo.name === 'mp4a') {
+ track.info.codec = 'aac';
+ } else if (sampleBoxInfo.name.toLowerCase() === 'opus') {
+ track.info.codec = 'opus';
+ } else {
+ console.warn(`Unsupported audio sample entry type ${sampleBoxInfo.name}.`);
+ break;
+ }
+
+ this.isobmffReader.pos += 6 * 1 + 2;
+
+ const version = this.isobmffReader.readU16();
+ this.isobmffReader.pos += 3 * 2;
+
+ let channelCount = this.isobmffReader.readU16();
+
+ this.isobmffReader.pos += 2 + 2 + 2;
+
+ // Can't use fixed16_16 as that's signed
+ let sampleRate = this.isobmffReader.readU32() / 0x10000;
+
+ if (stsdVersion === 0 && version > 0) {
+ // Additional QuickTime fields
+ if (version === 1) {
+ this.isobmffReader.pos += 4 * 4;
+ } else if (version === 2) {
+ this.isobmffReader.pos += 4;
+ sampleRate = this.isobmffReader.readF64();
+ channelCount = this.isobmffReader.readU32();
+ this.isobmffReader.pos += 4; // Always 0x7F000000
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ const sampleSize = this.isobmffReader.readU32();
+
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ const flags = this.isobmffReader.readU32();
+
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ const bytesPerFrame = this.isobmffReader.readU32();
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ const samplesPerFrame = this.isobmffReader.readU32();
+
+ /*
+ if (sampleBoxInfo.name === 'lpcm') {
+ const bytesPerSample = (sampleSize + 7) >> 3;
+ const isFloat = Boolean(flags & 1);
+ const isBigEndian = Boolean(flags & 2);
+ const sFlags = flags & 4 ? -1 : 0; // I guess it means "signed flags" or something?
+
+ if (sampleSize > 0 && sampleSize <= 64) {
+ if (isFloat) {
+ if (sampleSize === 32 && !isBigEndian) {
+ track.pcmType = 'pcm-f32';
+ }
+ } else {
+ if (sFlags & (1 << (bytesPerSample - 1))) {
+ if (bytesPerSample === 2 && !isBigEndian) {
+ track.pcmType = 'pcm-s16';
+ } else if (bytesPerSample === 3 && !isBigEndian) {
+ track.pcmType = 'pcm-s24';
+ } else if (bytesPerSample === 4 && !isBigEndian) {
+ track.pcmType = 'pcm-s32';
+ }
+ } else {
+ if (bytesPerSample === 1) {
+ track.pcmType = 'pcm-u8';
+ }
+ }
+ }
+ }
+
+ if (track.pcmType === null) {
+ throw new Error(`Unsupported linear PCM type.`);
+ }
+ }
+ */
+ }
+ }
+
+ track.info.numberOfChannels = channelCount;
+ track.info.sampleRate = sampleRate;
+
+ this.readContiguousBoxes(startPos + sampleBoxInfo.totalSize - this.isobmffReader.pos);
+ }
+ }
+ }; break;
+
+ case 'avcC': {
+ const track = this.currentTrack;
+ assert(track && track.info);
+
+ track.info.codecDescription = this.isobmffReader.readRange(
+ this.isobmffReader.pos,
+ this.isobmffReader.pos + boxInfo.contentSize,
+ );
+ }; break;
+
+ case 'hvcC': {
+ const track = this.currentTrack;
+ assert(track && track.info);
+
+ track.info.codecDescription = this.isobmffReader.readRange(
+ this.isobmffReader.pos,
+ this.isobmffReader.pos + boxInfo.contentSize,
+ );
+ }; break;
+
+ case 'colr': {
+ const track = this.currentTrack;
+ assert(track && track.info?.type === 'video');
+
+ const colourType = this.isobmffReader.readAscii(4);
+ if (colourType !== 'nclx') {
+ break;
+ }
+
+ const colourPrimaries = this.isobmffReader.readU16();
+ const transferCharacteristics = this.isobmffReader.readU16();
+ const matrixCoefficients = this.isobmffReader.readU16();
+ const fullRangeFlag = Boolean(this.isobmffReader.readU8() & 0x80);
+
+ track.info.colorSpace = {
+ primaries: COLOR_PRIMARIES_MAP_INVERSE[colourPrimaries],
+ transfer: TRANSFER_CHARACTERISTICS_MAP_INVERSE[transferCharacteristics],
+ matrix: MATRIX_COEFFICIENTS_MAP_INVERSE[matrixCoefficients],
+ fullRange: fullRangeFlag,
+ };
+ }; break;
+
+ case 'wave': {
+ if (boxInfo.totalSize > 8) {
+ this.readContiguousBoxes(boxInfo.contentSize);
+ }
+ }; break;
+
+ case 'esds': {
+ const track = this.currentTrack;
+ assert(track && track.info);
+
+ this.isobmffReader.pos += 4; // Version + flags
+
+ const tag = this.isobmffReader.readU8();
+ assert(tag === 0x03);
+
+ this.isobmffReader.readIsomVariableInteger(); // Length
+
+ this.isobmffReader.pos += 2; // ES ID
+ const mixed = this.isobmffReader.readU8();
+
+ const streamDependenceFlag = (mixed & 0x80) !== 0;
+ const urlFlag = (mixed & 0x40) !== 0;
+ const ocrStreamFlag = (mixed & 0x20) !== 0;
+
+ if (streamDependenceFlag) {
+ this.isobmffReader.pos += 2;
+ }
+ if (urlFlag) {
+ const urlLength = this.isobmffReader.readU8();
+ this.isobmffReader.pos += urlLength;
+ }
+ if (ocrStreamFlag) {
+ this.isobmffReader.pos += 2;
+ }
+
+ const decoderConfigTag = this.isobmffReader.readU8();
+ assert(decoderConfigTag === 0x04);
+
+ this.isobmffReader.readIsomVariableInteger(); // Length
+
+ const objectTypeIndication = this.isobmffReader.readU8();
+ assert(objectTypeIndication === 0x40); // Assert it's MPEG-4 audio
+
+ this.isobmffReader.pos += 1 + 3 + 4 + 4;
+
+ const decoderSpecificInfoTag = this.isobmffReader.readU8();
+ assert(decoderSpecificInfoTag === 0x05);
+
+ const decoderSpecificInfoLength = this.isobmffReader.readIsomVariableInteger();
+
+ track.info.codecDescription = this.isobmffReader.readRange(
+ this.isobmffReader.pos,
+ this.isobmffReader.pos + decoderSpecificInfoLength,
+ );
+ }; break;
+
+ case 'stts': {
+ const track = this.currentTrack;
+ assert(track);
+
+ if (!track.sampleTable) {
+ break;
+ }
+
+ this.isobmffReader.pos += 4; // Version + flags
+
+ const entryCount = this.isobmffReader.readU32();
+
+ let currentIndex = 0;
+ let currentTimestamp = 0;
+
+ for (let i = 0; i < entryCount; i++) {
+ const sampleCount = this.isobmffReader.readU32();
+ const sampleDelta = this.isobmffReader.readU32();
+
+ track.sampleTable.sampleTimingEntries.push({
+ startIndex: currentIndex,
+ startDecodeTimestamp: currentTimestamp,
+ count: sampleCount,
+ delta: sampleDelta,
+ });
+
+ currentIndex += sampleCount;
+ currentTimestamp += sampleCount * sampleDelta;
+ }
+ }; break;
+
+ case 'ctts': {
+ const track = this.currentTrack;
+ assert(track);
+
+ if (!track.sampleTable) {
+ break;
+ }
+
+ this.isobmffReader.pos += 1 + 3; // Version + flags
+
+ const entryCount = this.isobmffReader.readU32();
+
+ let sampleIndex = 0;
+ for (let i = 0; i < entryCount; i++) {
+ const sampleCount = this.isobmffReader.readU32();
+ // version === 0 ? this.isobmffReader.readU32() : this.isobmffReader.readI32();
+ const sampleOffset = this.isobmffReader.readI32();
+
+ track.sampleTable.sampleCompositionTimeOffsets.push({
+ startIndex: sampleIndex,
+ count: sampleCount,
+ offset: sampleOffset,
+ });
+
+ sampleIndex += sampleCount;
+ }
+ }; break;
+
+ case 'stsz': {
+ const track = this.currentTrack;
+ assert(track);
+
+ if (!track.sampleTable) {
+ break;
+ }
+
+ this.isobmffReader.pos += 4; // Version + flags
+
+ const sampleSize = this.isobmffReader.readU32();
+ const sampleCount = this.isobmffReader.readU32();
+
+ if (sampleSize === 0) {
+ for (let i = 0; i < sampleCount; i++) {
+ const sampleSize = this.isobmffReader.readU32();
+ track.sampleTable.sampleSizes.push(sampleSize);
+ }
+ } else {
+ track.sampleTable.sampleSizes.push(sampleSize);
+ }
+ }; break;
+
+ case 'stz2': {
+ throw new Error('Unsupported.');
+ };
+
+ case 'stss': {
+ const track = this.currentTrack;
+ assert(track);
+
+ if (!track.sampleTable) {
+ break;
+ }
+
+ this.isobmffReader.pos += 4; // Version + flags
+
+ track.sampleTable.keySampleIndices = [];
+
+ const entryCount = this.isobmffReader.readU32();
+ for (let i = 0; i < entryCount; i++) {
+ const sampleIndex = this.isobmffReader.readU32() - 1; // Convert to 0-indexed
+ track.sampleTable.keySampleIndices.push(sampleIndex);
+ }
+ }; break;
+
+ case 'stsc': {
+ const track = this.currentTrack;
+ assert(track);
+
+ if (!track.sampleTable) {
+ break;
+ }
+
+ this.isobmffReader.pos += 4;
+
+ const entryCount = this.isobmffReader.readU32();
+
+ for (let i = 0; i < entryCount; i++) {
+ const startChunkIndex = this.isobmffReader.readU32() - 1; // Convert to 0-indexed
+ const samplesPerChunk = this.isobmffReader.readU32();
+ const sampleDescriptionIndex = this.isobmffReader.readU32();
+
+ track.sampleTable.sampleToChunk.push({
+ startSampleIndex: -1,
+ startChunkIndex,
+ samplesPerChunk,
+ sampleDescriptionIndex,
+ });
+ }
+
+ let startSampleIndex = 0;
+ for (let i = 0; i < track.sampleTable.sampleToChunk.length; i++) {
+ track.sampleTable.sampleToChunk[i]!.startSampleIndex = startSampleIndex;
+
+ if (i < track.sampleTable.sampleToChunk.length - 1) {
+ const nextChunk = track.sampleTable.sampleToChunk[i + 1]!;
+ const chunkCount = nextChunk.startChunkIndex
+ - track.sampleTable.sampleToChunk[i]!.startChunkIndex;
+ startSampleIndex += chunkCount * track.sampleTable.sampleToChunk[i]!.samplesPerChunk;
+ }
+ }
+ }; break;
+
+ case 'stco': {
+ const track = this.currentTrack;
+ assert(track);
+
+ if (!track.sampleTable) {
+ break;
+ }
+
+ this.isobmffReader.pos += 4; // Version + flags
+
+ const entryCount = this.isobmffReader.readU32();
+
+ for (let i = 0; i < entryCount; i++) {
+ const chunkOffset = this.isobmffReader.readU32();
+ track.sampleTable.chunkOffsets.push(chunkOffset);
+ }
+ }; break;
+
+ case 'co64': {
+ const track = this.currentTrack;
+ assert(track);
+
+ if (!track.sampleTable) {
+ break;
+ }
+
+ this.isobmffReader.pos += 4; // Version + flags
+
+ const entryCount = this.isobmffReader.readU32();
+
+ for (let i = 0; i < entryCount; i++) {
+ const chunkOffset = this.isobmffReader.readU64();
+ track.sampleTable.chunkOffsets.push(chunkOffset);
+ }
+ }; break;
+ }
+
+ this.isobmffReader.pos = boxEndPos;
+ }
+}
+
+abstract class IsobmffTrackBacking implements InputTrackBacking {
+ chunkToSampleIndex = new WeakMap();
+ sampleIndexToChunk = new Map>();
+
+ constructor(public internalTrack: InternalTrack) {
+
+ }
+
+ getCodec(): Promise {
+ throw new Error('Not implemented on base class.');
+ }
+
+ async getDuration() {
+ return this.internalTrack.durationInTimescale / this.internalTrack.timescale;
+ }
+}
+
+class IsobmffVideoTrackBacking extends IsobmffTrackBacking implements InputVideoTrackBacking {
+ override internalTrack: InternalVideoTrack;
+
+ constructor(internalTrack: InternalVideoTrack) {
+ super(internalTrack);
+ this.internalTrack = internalTrack;
+ }
+
+ override async getCodec(): Promise {
+ return this.internalTrack.info.codec!;
+ }
+
+ async getWidth() {
+ return this.internalTrack.info.width;
+ }
+
+ async getHeight() {
+ return this.internalTrack.info.height;
+ }
+
+ async getRotation() {
+ return this.internalTrack.rotation;
+ }
+
+ async getDecoderConfig(): Promise {
+ return {
+ codec: extractVideoCodecString(this.internalTrack.info.codec!, this.internalTrack.info.codecDescription),
+ codedWidth: this.internalTrack.info.width,
+ codedHeight: this.internalTrack.info.height,
+ description: this.internalTrack.info.codecDescription ?? undefined,
+ colorSpace: this.internalTrack.info.colorSpace ?? undefined,
+ };
+ }
+
+ private async fetchChunkForSampleIndex(sampleIndex: number) {
+ if (sampleIndex === -1) {
+ return null;
+ }
+
+ const existingChunk = this.sampleIndexToChunk.get(sampleIndex)?.deref();
+ if (existingChunk) {
+ return existingChunk;
+ }
+
+ const sampleTable = this.internalTrack.demuxer.getSampleTableForTrack(this.internalTrack);
+ const sampleInfo = getSampleInfo(sampleTable, sampleIndex);
+ if (!sampleInfo) {
+ return null;
+ }
+
+ const data = await this.internalTrack.demuxer.isobmffReader.reader.source._read(
+ sampleInfo.byteOffset,
+ sampleInfo.byteOffset + sampleInfo.byteSize,
+ );
+
+ const chunk = new EncodedVideoChunk({
+ data,
+ timestamp: (1e6 * sampleInfo.presentationTimestamp) / this.internalTrack.timescale,
+ duration: (1e6 * sampleInfo.duration) / this.internalTrack.timescale,
+ type: sampleInfo.isKeyFrame ? 'key' : 'delta',
+ });
+
+ this.chunkToSampleIndex.set(chunk, sampleIndex);
+ this.sampleIndexToChunk.set(sampleIndex, new WeakRef(chunk));
+
+ return chunk;
+ }
+
+ async getFirstChunk() {
+ return this.fetchChunkForSampleIndex(0);
+ }
+
+ async getChunk(timestamp: number) {
+ const sampleTable = this.internalTrack.demuxer.getSampleTableForTrack(this.internalTrack);
+ const sampleIndex = getSampleIndexForTimestamp(sampleTable, timestamp * this.internalTrack.timescale);
+ return this.fetchChunkForSampleIndex(sampleIndex);
+ }
+
+ async getNextChunk(chunk: EncodedVideoChunk) {
+ const sampleIndex = this.chunkToSampleIndex.get(chunk);
+ if (sampleIndex === undefined) {
+ throw new Error('Chunk was not created from this track.');
+ }
+ return this.fetchChunkForSampleIndex(sampleIndex + 1);
+ }
+
+ async getKeyChunk(timestamp: number) {
+ const sampleTable = this.internalTrack.demuxer.getSampleTableForTrack(this.internalTrack);
+ const sampleIndex = getSampleIndexForTimestamp(sampleTable, timestamp * this.internalTrack.timescale);
+ const keyFrameSampleIndex = sampleIndex === -1
+ ? -1
+ : getRelevantKeyframeIndexForSample(sampleTable, sampleIndex);
+ return this.fetchChunkForSampleIndex(keyFrameSampleIndex);
+ }
+
+ async getNextKeyChunk(chunk: EncodedVideoChunk) {
+ const sampleIndex = this.chunkToSampleIndex.get(chunk);
+ if (sampleIndex === undefined) {
+ throw new Error('Chunk was not created from this track.');
+ }
+ const sampleTable = this.internalTrack.demuxer.getSampleTableForTrack(this.internalTrack);
+ const nextKeyFrameSampleIndex = getNextKeyframeIndexForSample(sampleTable, sampleIndex);
+ return this.fetchChunkForSampleIndex(nextKeyFrameSampleIndex);
+ }
+}
+
+class IsobmffAudioTrackBacking extends IsobmffTrackBacking implements InputAudioTrackBacking {
+ override internalTrack: InternalAudioTrack;
+
+ constructor(internalTrack: InternalAudioTrack) {
+ super(internalTrack);
+ this.internalTrack = internalTrack;
+ }
+
+ override async getCodec(): Promise {
+ return this.internalTrack.info.codec!;
+ }
+
+ async getNumberOfChannels() {
+ return this.internalTrack.info.numberOfChannels;
+ }
+
+ async getSampleRate() {
+ return this.internalTrack.info.sampleRate;
+ }
+
+ async getDecoderConfig(): Promise {
+ return {
+ codec: extractAudioCodecString(this.internalTrack.info.codec!, this.internalTrack.info.codecDescription),
+ numberOfChannels: this.internalTrack.info.numberOfChannels,
+ sampleRate: this.internalTrack.info.sampleRate,
+ description: this.internalTrack.info.codecDescription ?? undefined,
+ };
+ }
+}
+
+const getSampleIndexForTimestamp = (sampleTable: SampleTable, timescaleUnits: number) => {
+ const index = binarySearchLessOrEqual(
+ sampleTable.presentationTimestamps,
+ timescaleUnits,
+ x => x.presentationTimestamp,
+ );
+ if (index === -1) {
+ return -1;
+ }
+
+ return sampleTable.presentationTimestamps[index]!.sampleIndex;
+};
+
+type SampleInfo = {
+ presentationTimestamp: number;
+ duration: number;
+ byteOffset: number;
+ byteSize: number;
+ isKeyFrame: boolean;
+};
+
+const getSampleInfo = (sampleTable: SampleTable, sampleIndex: number): SampleInfo | null => {
+ const timingEntryIndex = binarySearchLessOrEqual(sampleTable.sampleTimingEntries, sampleIndex, x => x.startIndex);
+ const timingEntry = sampleTable.sampleTimingEntries[timingEntryIndex];
+ if (!timingEntry || timingEntry.startIndex + timingEntry.count <= sampleIndex) {
+ return null;
+ }
+
+ const decodeTimestamp = timingEntry.startDecodeTimestamp
+ + (sampleIndex - timingEntry.startIndex) * timingEntry.delta;
+ let presentationTimestamp = decodeTimestamp;
+ const offsetEntryIndex = binarySearchLessOrEqual(
+ sampleTable.sampleCompositionTimeOffsets,
+ sampleIndex,
+ x => x.startIndex,
+ );
+ const offsetEntry = sampleTable.sampleCompositionTimeOffsets[offsetEntryIndex];
+ if (offsetEntry) {
+ presentationTimestamp += offsetEntry.offset;
+ }
+
+ const sampleSize = sampleTable.sampleSizes[Math.min(sampleIndex, sampleTable.sampleSizes.length - 1)]!;
+ const chunkEntryIndex = binarySearchLessOrEqual(sampleTable.sampleToChunk, sampleIndex, x => x.startSampleIndex);
+ const chunkEntry = sampleTable.sampleToChunk[chunkEntryIndex];
+ assert(chunkEntry);
+
+ const chunkIndex = chunkEntry.startChunkIndex
+ + Math.floor((sampleIndex - chunkEntry.startSampleIndex) / chunkEntry.samplesPerChunk);
+ const chunkOffset = sampleTable.chunkOffsets[chunkIndex]!;
+
+ let sampleOffset = chunkOffset;
+ if (sampleTable.sampleSizes.length === 1) {
+ sampleOffset += sampleSize * (sampleIndex - chunkEntry.startSampleIndex);
+ } else {
+ const startSampleIndex = chunkEntry.startSampleIndex
+ + (chunkIndex - chunkEntry.startChunkIndex) * chunkEntry.samplesPerChunk;
+ for (let i = startSampleIndex; i < sampleIndex; i++) {
+ sampleOffset += sampleTable.sampleSizes[i]!;
+ }
+ }
+
+ return {
+ presentationTimestamp,
+ duration: timingEntry.delta,
+ byteOffset: sampleOffset,
+ byteSize: sampleSize,
+ isKeyFrame: sampleTable.keySampleIndices
+ ? binarySearchExact(sampleTable.keySampleIndices, sampleIndex, x => x) !== -1
+ : true,
+ };
+};
+
+const getRelevantKeyframeIndexForSample = (sampleTable: SampleTable, sampleIndex: number) => {
+ if (!sampleTable.keySampleIndices) {
+ return sampleIndex;
+ }
+
+ const index = binarySearchLessOrEqual(sampleTable.keySampleIndices, sampleIndex, x => x);
+ return sampleTable.keySampleIndices[index] ?? -1;
+};
+
+const getNextKeyframeIndexForSample = (sampleTable: SampleTable, sampleIndex: number) => {
+ if (!sampleTable.keySampleIndices) {
+ return sampleIndex + 1;
+ }
+
+ const index = binarySearchLessOrEqual(sampleTable.keySampleIndices, sampleIndex, x => x);
+ return sampleTable.keySampleIndices[index + 1] ?? -1;
+};
diff --git a/src/isobmff/isobmff-reader.ts b/src/isobmff/isobmff-reader.ts
new file mode 100644
index 0000000..8424d52
--- /dev/null
+++ b/src/isobmff/isobmff-reader.ts
@@ -0,0 +1,118 @@
+import { Reader } from '../reader';
+
+export class IsobmffReader {
+ pos = 0;
+
+ constructor(public reader: Reader) {}
+
+ readRange(start: number, end: number) {
+ const { view, offset } = this.reader.getViewAndOffset(start, end);
+ return new Uint8Array(view.buffer, offset, end - start);
+ }
+
+ readU8() {
+ const { view, offset } = this.reader.getViewAndOffset(this.pos, this.pos + 1);
+ this.pos++;
+
+ return view.getUint8(offset);
+ }
+
+ readU16() {
+ const { view, offset } = this.reader.getViewAndOffset(this.pos, this.pos + 2);
+ this.pos += 2;
+
+ return view.getUint16(offset, false);
+ }
+
+ readU24() {
+ const { view, offset } = this.reader.getViewAndOffset(this.pos, this.pos + 3);
+ this.pos += 3;
+
+ const high = view.getUint16(offset, false);
+ const low = view.getUint8(offset + 2);
+ return high * 0x100 + low;
+ }
+
+ readS32() {
+ const { view, offset } = this.reader.getViewAndOffset(this.pos, this.pos + 4);
+ this.pos += 4;
+
+ return view.getInt32(offset, false);
+ }
+
+ readU32() {
+ const { view, offset } = this.reader.getViewAndOffset(this.pos, this.pos + 4);
+ this.pos += 4;
+
+ return view.getUint32(offset, false);
+ }
+
+ readI32() {
+ const { view, offset } = this.reader.getViewAndOffset(this.pos, this.pos + 4);
+ this.pos += 4;
+
+ return view.getInt32(offset, false);
+ }
+
+ readU64() {
+ const high = this.readU32();
+ const low = this.readU32();
+ return high * 0x100000000 + low;
+ }
+
+ readF64() {
+ const { view, offset } = this.reader.getViewAndOffset(this.pos, this.pos + 8);
+ this.pos += 8;
+
+ return view.getFloat64(offset, false);
+ }
+
+ readFixed_16_16() {
+ return this.readS32() / 0x10000;
+ }
+
+ readFixed_2_30() {
+ return this.readS32() / 0x40000000;
+ }
+
+ readAscii(length: number) {
+ const { view, offset } = this.reader.getViewAndOffset(this.pos, this.pos + length);
+ this.pos += length;
+
+ let str = '';
+ for (let i = 0; i < length; i++) {
+ str += String.fromCharCode(view.getUint8(offset + i));
+ }
+ return str;
+ }
+
+ readIsomVariableInteger() {
+ let result = 0;
+
+ for (let i = 0; i < 4; i++) {
+ result <<= 7;
+ const nextByte = this.readU8();
+ result |= nextByte & 0x7f;
+
+ if ((nextByte & 0x80) === 0) {
+ break;
+ }
+ }
+
+ return result;
+ }
+
+ readBoxHeader() {
+ let totalSize = this.readU32();
+ const name = this.readAscii(4);
+ let headerSize = 8;
+
+ const hasLargeSize = totalSize === 1;
+ if (hasLargeSize) {
+ totalSize = this.readU64();
+ headerSize = 16;
+ }
+
+ return { name, totalSize, headerSize, contentSize: totalSize - headerSize };
+ }
+}
diff --git a/src/matroska/matroska-demuxer.ts b/src/matroska/matroska-demuxer.ts
new file mode 100644
index 0000000..7dab093
--- /dev/null
+++ b/src/matroska/matroska-demuxer.ts
@@ -0,0 +1,6 @@
+import { Demuxer } from '../demuxer';
+import { Input } from '../input';
+
+export class MatroskaDemuxer extends Demuxer {
+
+}
diff --git a/src/matroska/matroska-muxer.ts b/src/matroska/matroska-muxer.ts
index 15453b8..46c9b89 100644
--- a/src/matroska/matroska-muxer.ts
+++ b/src/matroska/matroska-muxer.ts
@@ -1,4 +1,3 @@
-import { AudioCodec, SubtitleCodec, VideoCodec } from '../source';
import {
COLOR_PRIMARIES_MAP,
MATRIX_COEFFICIENTS_MAP,
@@ -31,7 +30,14 @@ import {
inlineTimestampRegex,
parseSubtitleTimestamp,
} from '../subtitles';
-import { validateAudioChunkMetadata, validateSubtitleMetadata, validateVideoChunkMetadata } from '../codec';
+import {
+ AudioCodec,
+ SubtitleCodec,
+ VideoCodec,
+ validateAudioChunkMetadata,
+ validateSubtitleMetadata,
+ validateVideoChunkMetadata,
+} from '../codec';
import { Muxer } from '../muxer';
import { Writer } from '../writer';
diff --git a/src/media-drain.ts b/src/media-drain.ts
new file mode 100644
index 0000000..fb9cc4d
--- /dev/null
+++ b/src/media-drain.ts
@@ -0,0 +1,192 @@
+import { InputVideoTrack } from './input-track';
+import { assert, promiseWithResolvers } from './misc';
+
+export class EncodedVideoChunkDrain {
+ constructor(public videoTrack: InputVideoTrack) {}
+
+ getFirstChunk() {
+ return this.videoTrack._backing.getFirstChunk();
+ }
+
+ getChunk(timestamp: number) {
+ return this.videoTrack._backing.getChunk(timestamp);
+ }
+
+ getNextChunk(chunk: EncodedVideoChunk) {
+ return this.videoTrack._backing.getNextChunk(chunk);
+ }
+
+ getKeyChunk(timestamp: number) {
+ return this.videoTrack._backing.getKeyChunk(timestamp);
+ }
+
+ getNextKeyChunk(chunk: EncodedVideoChunk) {
+ return this.videoTrack._backing.getNextKeyChunk(chunk);
+ }
+
+ async* chunks(startTimestamp = 0) {
+ let chunk = await this.getChunk(startTimestamp); // Not necessarily correct if there is no chunk at timestamp 0
+ while (chunk) {
+ yield chunk;
+ chunk = await this.getNextChunk(chunk);
+ }
+ }
+}
+
+export class VideoFrameDrain {
+ decoderConfig: VideoDecoderConfig | null = null;
+
+ constructor(public videoTrack: InputVideoTrack) {}
+
+ async createDecoder(onFrame: (frame: VideoFrame) => unknown) {
+ if (!this.decoderConfig) {
+ this.decoderConfig = await this.videoTrack.getDecoderConfig();
+ }
+
+ const decoder = new VideoDecoder({
+ output: onFrame,
+ error: error => console.error(error),
+ });
+ decoder.configure(this.decoderConfig);
+
+ return decoder;
+ }
+
+ async getKeyFrame(timestamp: number) {
+ let result: VideoFrame | null = null;
+
+ const decoder = await this.createDecoder(frame => result = frame);
+ const chunk = await this.videoTrack._backing.getKeyChunk(timestamp);
+ if (!chunk) {
+ return null;
+ }
+
+ decoder.decode(chunk);
+
+ await decoder.flush();
+ decoder.close();
+
+ return result;
+ }
+
+ async getFrame(timestamp: number) {
+ let result: VideoFrame | null = null;
+
+ const decoder = await this.createDecoder((frame) => {
+ if (frame.timestamp / 1e6 <= timestamp) {
+ result?.close();
+ result = frame;
+ } else {
+ frame.close();
+ }
+ });
+ const keyChunk = await this.videoTrack._backing.getKeyChunk(timestamp);
+ if (!keyChunk) {
+ return null;
+ }
+
+ const targetChunk = await this.videoTrack._backing.getChunk(timestamp);
+ assert(targetChunk);
+
+ decoder.decode(keyChunk);
+
+ let currentChunk = keyChunk;
+ while (currentChunk !== targetChunk) {
+ const nextChunk = await this.videoTrack._backing.getNextChunk(currentChunk);
+ assert(nextChunk);
+
+ currentChunk = nextChunk;
+ decoder.decode(nextChunk);
+
+ if (decoder.decodeQueueSize >= 10) {
+ await new Promise(resolve => decoder.addEventListener('dequeue', resolve, { once: true }));
+ }
+ }
+
+ await decoder.flush();
+ decoder.close();
+
+ return result;
+ }
+
+ async* frames(startTimestamp = 0) {
+ const frameQueue: VideoFrame[] = [];
+ let firstFrameQueued = false;
+ let lastFrame: VideoFrame | null = null;
+ let { promise: queueNonEmpty, resolve: onQueueNotEmpty } = promiseWithResolvers();
+ let ended = false;
+
+ const decoder = await this.createDecoder((frame) => {
+ if (ended) {
+ frame.close();
+ return;
+ }
+
+ const frameTimestamp = frame.timestamp / 1e6;
+
+ if (lastFrame) {
+ if (frameTimestamp > startTimestamp) {
+ frameQueue.push(lastFrame);
+ firstFrameQueued = true;
+ } else {
+ lastFrame.close();
+ }
+ }
+
+ if (frameTimestamp >= startTimestamp) {
+ frameQueue.push(frame);
+ firstFrameQueued = true;
+ }
+
+ lastFrame = firstFrameQueued ? null : frame;
+
+ if (frameQueue.length > 0) {
+ onQueueNotEmpty();
+ ({ promise: queueNonEmpty, resolve: onQueueNotEmpty } = promiseWithResolvers());
+ }
+ });
+
+ const keyChunk = await this.videoTrack._backing.getKeyChunk(startTimestamp);
+ if (!keyChunk) {
+ return;
+ }
+
+ let decoderIsFlushed = false;
+
+ // The following is the "pump" process that keeps pumping chunks into the decoder
+ void (async () => {
+ let currentChunk: EncodedVideoChunk | null = keyChunk;
+
+ while (currentChunk && !ended) {
+ decoder.decode(currentChunk);
+
+ if (decoder.decodeQueueSize >= 10) {
+ await new Promise(resolve => decoder.addEventListener('dequeue', resolve, { once: true }));
+ }
+
+ const nextChunk = await this.videoTrack._backing.getNextChunk(currentChunk);
+ currentChunk = nextChunk;
+ }
+
+ await decoder.flush();
+ decoder.close();
+
+ decoderIsFlushed = true;
+ onQueueNotEmpty(); // To unstuck the generator
+ })();
+
+ try {
+ while (true) {
+ if (frameQueue.length > 0) {
+ yield frameQueue.shift()!;
+ } else if (!decoderIsFlushed) {
+ await queueNonEmpty;
+ } else {
+ break;
+ }
+ }
+ } finally {
+ ended = true;
+ }
+ }
+}
diff --git a/src/media-source.ts b/src/media-source.ts
new file mode 100644
index 0000000..59d77f5
--- /dev/null
+++ b/src/media-source.ts
@@ -0,0 +1,646 @@
+import {
+ AUDIO_CODECS,
+ AudioCodec,
+ buildAudioCodecString,
+ buildVideoCodecString,
+ getAudioEncoderConfigExtension,
+ getVideoEncoderConfigExtension,
+ SUBTITLE_CODECS,
+ SubtitleCodec,
+ VIDEO_CODECS,
+ VideoCodec,
+} from './codec';
+import { OutputAudioTrack, OutputSubtitleTrack, OutputTrack, OutputVideoTrack } from './output';
+import { assert } from './misc';
+import { Muxer } from './muxer';
+import { SubtitleParser } from './subtitles';
+
+/** @public */
+export abstract class MediaSource {
+ /** @internal */
+ _connectedTrack: OutputTrack | null = null;
+ /** @internal */
+ _closed = false;
+ /** @internal */
+ _offsetTimestamps = false;
+
+ /** @internal */
+ _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.');
+ }
+ }
+
+ /** @internal */
+ _start() {}
+ /** @internal */
+ 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 = true;
+
+ if (this._connectedTrack.output._finalizing) {
+ return;
+ }
+
+ this._connectedTrack.output._muxer.onTrackClose(this._connectedTrack);
+ }
+}
+
+/** @public */
+export abstract class VideoSource extends MediaSource {
+ /** @internal */
+ override _connectedTrack: OutputVideoTrack | null = null;
+ /** @internal */
+ _codec: VideoCodec;
+
+ constructor(codec: VideoCodec) {
+ super();
+
+ if (!VIDEO_CODECS.includes(codec)) {
+ throw new TypeError(`Invalid video codec '${codec}'. Must be one of: ${VIDEO_CODECS.join(', ')}.`);
+ }
+
+ this._codec = codec;
+ }
+}
+
+/** @public */
+export class EncodedVideoChunkSource extends VideoSource {
+ constructor(codec: VideoCodec) {
+ super(codec);
+ }
+
+ digest(chunk: EncodedVideoChunk, meta?: EncodedVideoChunkMetadata) {
+ if (!(chunk instanceof EncodedVideoChunk)) {
+ // TODO add polyfill for browsers that don't have this
+ throw new TypeError('chunk must be an EncodedVideoChunk.');
+ }
+
+ this._ensureValidDigest();
+ return this._connectedTrack!.output._muxer.addEncodedVideoChunk(this._connectedTrack!, chunk, meta);
+ }
+}
+
+const KEY_FRAME_INTERVAL = 5;
+
+/** @public */
+export type VideoCodecConfig = {
+ codec: VideoCodec;
+ bitrate: number;
+ latencyMode?: VideoEncoderConfig['latencyMode'];
+};
+
+const validateVideoCodecConfig = (config: VideoCodecConfig) => {
+ if (!config || typeof config !== 'object') {
+ throw new TypeError('Codec config must be an object.');
+ }
+ if (!VIDEO_CODECS.includes(config.codec)) {
+ throw new TypeError(`Invalid video codec '${config.codec}'. Must be one of: ${VIDEO_CODECS.join(', ')}.`);
+ }
+ if (!Number.isInteger(config.bitrate) || config.bitrate <= 0) {
+ throw new TypeError('config.bitrate must be a positive integer.');
+ }
+ if (config.latencyMode !== undefined && !['quality', 'realtime'].includes(config.latencyMode)) {
+ throw new TypeError('config.latencyMode, when provided, must be \'quality\' or \'realtime\'.');
+ }
+};
+
+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;
+
+ constructor(private source: VideoSource, private codecConfig: VideoCodecConfig) {
+ validateVideoCodecConfig(codecConfig);
+ }
+
+ async digest(videoFrame: VideoFrame) {
+ this.source._ensureValidDigest();
+
+ // Ensure video frame size remains constant
+ if (this.lastWidth !== null && this.lastHeight !== null) {
+ if (videoFrame.codedWidth !== this.lastWidth || videoFrame.codedHeight !== this.lastHeight) {
+ throw new Error(
+ `Video frame size must remain constant. Expected ${this.lastWidth}x${this.lastHeight},`
+ + ` got ${videoFrame.codedWidth}x${videoFrame.codedHeight}.`,
+ );
+ }
+ } else {
+ this.lastWidth = videoFrame.codedWidth;
+ this.lastHeight = videoFrame.codedHeight;
+ }
+
+ this.ensureEncoder(videoFrame);
+ assert(this.encoder);
+
+ const multipleOfKeyFrameInterval = Math.floor((videoFrame.timestamp / 1e6) / KEY_FRAME_INTERVAL);
+
+ // Ensure a key frame every KEY_FRAME_INTERVAL seconds. It is important that all video tracks follow the same
+ // "key frame" rhythm, because aligned key frames are required to start new fragments in ISOBMFF or clusters
+ // in Matroska.
+ 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) {
+ if (this.encoder) {
+ return;
+ }
+
+ this.encoder = new VideoEncoder({
+ output: (chunk, meta) => void this.muxer!.addEncodedVideoChunk(this.source._connectedTrack!, chunk, meta),
+ error: error => console.error('Video encode error:', error),
+ });
+
+ this.encoder.configure({
+ codec: buildVideoCodecString(
+ this.codecConfig.codec,
+ videoFrame.codedWidth,
+ videoFrame.codedHeight,
+ this.codecConfig.bitrate,
+ ),
+ width: videoFrame.codedWidth,
+ height: videoFrame.codedHeight,
+ bitrate: this.codecConfig.bitrate,
+ framerate: this.source._connectedTrack?.metadata.frameRate,
+ latencyMode: this.codecConfig.latencyMode,
+ ...getVideoEncoderConfigExtension(this.codecConfig.codec),
+ });
+
+ assert(this.source._connectedTrack);
+ this.muxer = this.source._connectedTrack.output._muxer;
+ }
+
+ async flush() {
+ if (this.encoder) {
+ await this.encoder.flush();
+ this.encoder.close();
+ }
+ }
+}
+
+/** @public */
+export class VideoFrameSource extends VideoSource {
+ /** @internal */
+ private _encoder: VideoEncoderWrapper;
+
+ constructor(codecConfig: VideoCodecConfig) {
+ super(codecConfig.codec);
+ this._encoder = new VideoEncoderWrapper(this, codecConfig);
+ }
+
+ digest(videoFrame: VideoFrame) {
+ if (!(videoFrame instanceof VideoFrame)) {
+ throw new TypeError('videoFrame must be a VideoFrame.');
+ }
+
+ return this._encoder.digest(videoFrame);
+ }
+
+ /** @internal */
+ override _flush() {
+ return this._encoder.flush();
+ }
+}
+
+/** @public */
+export class CanvasSource extends VideoSource {
+ /** @internal */
+ private _encoder: VideoEncoderWrapper;
+ /** @internal */
+ private _canvas: HTMLCanvasElement | OffscreenCanvas;
+
+ constructor(canvas: HTMLCanvasElement | OffscreenCanvas, codecConfig: VideoCodecConfig) {
+ if (!(canvas instanceof HTMLCanvasElement)) {
+ throw new TypeError('canvas must be an HTMLCanvasElement.');
+ }
+
+ super(codecConfig.codec);
+ this._encoder = new VideoEncoderWrapper(this, codecConfig);
+ this._canvas = canvas;
+ }
+
+ digest(timestamp: number, duration = 0) {
+ if (!Number.isFinite(timestamp) || timestamp < 0) {
+ throw new TypeError('timestamp must be a non-negative number.');
+ }
+ if (!Number.isFinite(duration) || duration < 0) {
+ throw new TypeError('duration must be a non-negative number.');
+ }
+
+ const frame = new VideoFrame(this._canvas, {
+ timestamp: Math.round(1e6 * timestamp),
+ duration: Math.round(1e6 * duration),
+ alpha: 'discard',
+ });
+
+ const promise = this._encoder.digest(frame);
+ frame.close();
+
+ return promise;
+ }
+
+ /** @internal */
+ override _flush() {
+ return this._encoder.flush();
+ }
+}
+
+/** @public */
+export class MediaStreamVideoTrackSource extends VideoSource {
+ /** @internal */
+ private _encoder: VideoEncoderWrapper;
+ /** @internal */
+ private _abortController: AbortController | null = null;
+ /** @internal */
+ private _track: MediaStreamVideoTrack;
+
+ /** @internal */
+ override _offsetTimestamps = true;
+
+ constructor(track: MediaStreamVideoTrack, codecConfig: VideoCodecConfig) {
+ if (!(track instanceof MediaStreamTrack) || track.kind !== 'video') {
+ throw new TypeError('track must be a video MediaStreamTrack.');
+ }
+
+ codecConfig = {
+ ...codecConfig,
+ latencyMode: 'realtime',
+ };
+
+ super(codecConfig.codec);
+ this._encoder = new VideoEncoderWrapper(this, codecConfig);
+ this._track = track;
+ }
+
+ /** @internal */
+ override _start() {
+ this._abortController = new AbortController();
+
+ const processor = new MediaStreamTrackProcessor({ track: this._track });
+ const consumer = new WritableStream({
+ write: (videoFrame) => {
+ // TODO: Drop frames if encoder overloaded
+ void this._encoder.digest(videoFrame);
+ videoFrame.close();
+ },
+ });
+
+ processor.readable.pipeTo(consumer, {
+ signal: this._abortController.signal,
+ }).catch((err) => {
+ // Handle abort error silently
+ if (err instanceof DOMException && err.name === 'AbortError') return;
+ // Handle other errors
+ console.error('Pipe error:', err);
+ });
+ }
+
+ /** @internal */
+ override async _flush() {
+ if (this._abortController) {
+ this._abortController.abort();
+ this._abortController = null;
+ }
+
+ await this._encoder.flush();
+ }
+}
+
+/** @public */
+export abstract class AudioSource extends MediaSource {
+ /** @internal */
+ override _connectedTrack: OutputAudioTrack | null = null;
+ /** @internal */
+ _codec: AudioCodec;
+
+ constructor(codec: AudioCodec) {
+ super();
+
+ if (!AUDIO_CODECS.includes(codec)) {
+ throw new TypeError(`Invalid audio codec '${codec}'. Must be one of: ${AUDIO_CODECS.join(', ')}.`);
+ }
+
+ this._codec = codec;
+ }
+}
+
+/** @public */
+export class EncodedAudioChunkSource extends AudioSource {
+ constructor(codec: AudioCodec) {
+ super(codec);
+ }
+
+ digest(chunk: EncodedAudioChunk, meta?: EncodedAudioChunkMetadata) {
+ if (!(chunk instanceof EncodedAudioChunk)) {
+ // TODO add polyfill for browsers that don't have this
+ throw new TypeError('chunk must be an EncodedAudioChunk.');
+ }
+
+ this._ensureValidDigest();
+ return this._connectedTrack!.output._muxer.addEncodedAudioChunk(this._connectedTrack!, chunk, meta);
+ }
+}
+/** @public */
+export type AudioCodecConfig = {
+ codec: AudioCodec;
+ bitrate: number;
+};
+
+const validateAudioCodecConfig = (config: AudioCodecConfig) => {
+ if (!config || typeof config !== 'object') {
+ throw new TypeError('Codec config must be an object.');
+ }
+ if (!AUDIO_CODECS.includes(config.codec)) {
+ throw new TypeError(`Invalid audio codec '${config.codec}'. Must be one of: ${AUDIO_CODECS.join(', ')}.`);
+ }
+ if (!Number.isInteger(config.bitrate) || config.bitrate <= 0) {
+ throw new TypeError('config.bitrate must be a positive integer.');
+ }
+};
+
+class AudioEncoderWrapper {
+ private encoder: AudioEncoder | null = null;
+ private muxer: Muxer | null = null;
+ private lastNumberOfChannels: number | null = null;
+ private lastSampleRate: number | null = null;
+
+ constructor(private source: AudioSource, private codecConfig: AudioCodecConfig) {
+ validateAudioCodecConfig(codecConfig);
+ }
+
+ async digest(audioData: AudioData) {
+ this.source._ensureValidDigest();
+
+ // Ensure audio parameters remain constant
+ if (this.lastNumberOfChannels !== null && this.lastSampleRate !== null) {
+ if (
+ audioData.numberOfChannels !== this.lastNumberOfChannels
+ || audioData.sampleRate !== this.lastSampleRate
+ ) {
+ throw new Error(
+ `Audio parameters must remain constant. Expected ${this.lastNumberOfChannels} channels at`
+ + ` ${this.lastSampleRate} Hz, got ${audioData.numberOfChannels} channels at`
+ + ` ${audioData.sampleRate} Hz.`,
+ );
+ }
+ } else {
+ this.lastNumberOfChannels = audioData.numberOfChannels;
+ this.lastSampleRate = audioData.sampleRate;
+ }
+
+ 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; // Allow the writer to apply backpressure
+ }
+
+ private ensureEncoder(audioData: AudioData) {
+ if (this.encoder) {
+ return;
+ }
+
+ this.encoder = new AudioEncoder({
+ output: (chunk, meta) => void this.muxer!.addEncodedAudioChunk(this.source._connectedTrack!, chunk, meta),
+ error: error => console.error('Audio encode error:', error),
+ });
+
+ this.encoder.configure({
+ codec: buildAudioCodecString(this.codecConfig.codec, audioData.numberOfChannels, audioData.sampleRate),
+ numberOfChannels: audioData.numberOfChannels,
+ sampleRate: audioData.sampleRate,
+ bitrate: this.codecConfig.bitrate,
+ ...getAudioEncoderConfigExtension(this.codecConfig.codec),
+ });
+
+ assert(this.source._connectedTrack);
+ this.muxer = this.source._connectedTrack.output._muxer;
+ }
+
+ async flush() {
+ if (this.encoder) {
+ await this.encoder.flush();
+ this.encoder.close();
+ }
+ }
+}
+
+/** @public */
+export class AudioDataSource extends AudioSource {
+ /** @internal */
+ private _encoder: AudioEncoderWrapper;
+
+ constructor(codecConfig: AudioCodecConfig) {
+ super(codecConfig.codec);
+ this._encoder = new AudioEncoderWrapper(this, codecConfig);
+ }
+
+ digest(audioData: AudioData) {
+ if (!(audioData instanceof AudioData)) {
+ throw new TypeError('audioData must be an AudioData.');
+ }
+
+ return this._encoder.digest(audioData);
+ }
+
+ /** @internal */
+ override _flush() {
+ return this._encoder.flush();
+ }
+}
+
+/** @public */
+export class AudioBufferSource extends AudioSource {
+ /** @internal */
+ private _encoder: AudioEncoderWrapper;
+ /** @internal */
+ private _accumulatedFrameCount = 0;
+
+ constructor(codecConfig: AudioCodecConfig) {
+ super(codecConfig.codec);
+ this._encoder = new AudioEncoderWrapper(this, codecConfig);
+ }
+
+ digest(audioBuffer: AudioBuffer) {
+ if (!(audioBuffer instanceof AudioBuffer)) {
+ throw new TypeError('audioBuffer must be an AudioBuffer.');
+ }
+
+ const numberOfChannels = audioBuffer.numberOfChannels;
+ const sampleRate = audioBuffer.sampleRate;
+ const numberOfFrames = audioBuffer.length;
+
+ // Create a planar F32 array containing all channels
+ const data = new Float32Array(numberOfChannels * numberOfFrames);
+ for (let channel = 0; channel < numberOfChannels; channel++) {
+ const channelData = audioBuffer.getChannelData(channel);
+ data.set(channelData, channel * numberOfFrames);
+ }
+
+ const audioData = new AudioData({
+ format: 'f32-planar',
+ sampleRate,
+ numberOfFrames,
+ numberOfChannels,
+ timestamp: Math.round(1e6 * this._accumulatedFrameCount / sampleRate),
+ data: data,
+ });
+
+ const promise = this._encoder.digest(audioData);
+ audioData.close();
+
+ this._accumulatedFrameCount += numberOfFrames;
+
+ return promise;
+ }
+
+ /** @internal */
+ override _flush() {
+ return this._encoder.flush();
+ }
+}
+
+/** @public */
+export class MediaStreamAudioTrackSource extends AudioSource {
+ /** @internal */
+ private _encoder: AudioEncoderWrapper;
+ /** @internal */
+ private _abortController: AbortController | null = null;
+ /** @internal */
+ private _track: MediaStreamAudioTrack;
+
+ /** @internal */
+ override _offsetTimestamps = true;
+
+ constructor(track: MediaStreamAudioTrack, codecConfig: AudioCodecConfig) {
+ if (!(track instanceof MediaStreamTrack) || track.kind !== 'audio') {
+ throw new TypeError('track must be an audio MediaStreamTrack.');
+ }
+
+ super(codecConfig.codec);
+ this._encoder = new AudioEncoderWrapper(this, codecConfig);
+ this._track = track;
+ }
+
+ /** @internal */
+ override _start() {
+ this._abortController = new AbortController();
+
+ const processor = new MediaStreamTrackProcessor({ track: this._track });
+ const consumer = new WritableStream({
+ write: (audioData) => {
+ // TODO: Drop frames if encoder overloaded
+ void this._encoder.digest(audioData);
+ audioData.close();
+ },
+ });
+
+ processor.readable.pipeTo(consumer, {
+ signal: this._abortController.signal,
+ }).catch((err) => {
+ // Handle abort error silently
+ if (err instanceof DOMException && err.name === 'AbortError') return;
+ // Handle other errors
+ console.error('Pipe error:', err);
+ });
+ }
+
+ /** @internal */
+ override async _flush() {
+ if (this._abortController) {
+ this._abortController.abort();
+ this._abortController = null;
+ }
+
+ await this._encoder.flush();
+ }
+}
+
+/** @public */
+export abstract class SubtitleSource extends MediaSource {
+ /** @internal */
+ override _connectedTrack: OutputSubtitleTrack | null = null;
+ /** @internal */
+ _codec: SubtitleCodec;
+
+ constructor(codec: SubtitleCodec) {
+ super();
+
+ if (!SUBTITLE_CODECS.includes(codec)) {
+ throw new TypeError(`Invalid subtitle codec '${codec}'. Must be one of: ${SUBTITLE_CODECS.join(', ')}.`);
+ }
+
+ this._codec = codec;
+ }
+}
+
+/** @public */
+export class TextSubtitleSource extends SubtitleSource {
+ /** @internal */
+ private _parser: SubtitleParser;
+
+ constructor(codec: SubtitleCodec) {
+ super(codec);
+
+ this._parser = new SubtitleParser({
+ codec,
+ output: (cue, metadata) =>
+ this._connectedTrack?.output._muxer.addSubtitleCue(this._connectedTrack, cue, metadata),
+ error: error => console.error('Subtitle parse error:', error),
+ });
+ }
+
+ digest(text: string) {
+ if (typeof text !== 'string') {
+ throw new TypeError('text must be a string.');
+ }
+
+ this._ensureValidDigest();
+ this._parser.parse(text);
+
+ return this._connectedTrack!.output._muxer.mutex.currentPromise;
+ }
+}
diff --git a/src/misc.ts b/src/misc.ts
index 49abcb5..21a498a 100644
--- a/src/misc.ts
+++ b/src/misc.ts
@@ -53,6 +53,10 @@ export const toUint8Array = (source: AllowSharedBufferSource): Uint8Array => {
export const textEncoder = new TextEncoder();
+const invertObject = (object: Record) => {
+ return Object.fromEntries(Object.entries(object).map(([key, value]) => [value, key])) as Record;
+};
+
// These maps are taken from https://www.matroska.org/technical/elements.html,
// which references the tables in ITU-T H.273 - they should be valid for Matroska and ISOBMFF.
export const COLOR_PRIMARIES_MAP: Record = {
@@ -60,17 +64,22 @@ export const COLOR_PRIMARIES_MAP: Record = {
bt470bg: 5, // ITU-R BT.470BG
smpte170m: 6, // ITU-R BT.601 525 - SMPTE 170M
};
+export const COLOR_PRIMARIES_MAP_INVERSE = invertObject(COLOR_PRIMARIES_MAP);
+
export const TRANSFER_CHARACTERISTICS_MAP: Record = {
'bt709': 1, // ITU-R BT.709
'smpte170m': 6, // SMPTE 170M
'iec61966-2-1': 13, // IEC 61966-2-1
};
+export const TRANSFER_CHARACTERISTICS_MAP_INVERSE = invertObject(TRANSFER_CHARACTERISTICS_MAP);
+
export const MATRIX_COEFFICIENTS_MAP: Record = {
rgb: 0, // Identity
bt709: 1, // ITU-R BT.709
bt470bg: 5, // ITU-R BT.470BG
smpte170m: 6, // SMPTE 170M
};
+export const MATRIX_COEFFICIENTS_MAP_INVERSE = invertObject(MATRIX_COEFFICIENTS_MAP);
export type RequiredNonNull = {
[K in keyof T]-?: NonNullable;
@@ -114,3 +123,84 @@ export class AsyncMutex {
return resolver!;
}
}
+
+export const rotationMatrix = (rotationInDegrees: number): TransformationMatrix => {
+ const theta = rotationInDegrees * (Math.PI / 180);
+ const cosTheta = Math.cos(theta);
+ const sinTheta = Math.sin(theta);
+
+ // Matrices are post-multiplied in ISOBMFF, meaning this is the transpose of your typical rotation matrix
+ return [
+ cosTheta, sinTheta, 0,
+ -sinTheta, cosTheta, 0,
+ 0, 0, 1,
+ ];
+};
+
+export const IDENTITY_MATRIX = rotationMatrix(0);
+
+export const bytesToHexString = (bytes: Uint8Array) => {
+ return [...bytes].map(x => x.toString(16).padStart(2, '0')).join('');
+};
+
+export const reverseBitsU32 = (x: number): number => {
+ x = ((x >> 1) & 0x55555555) | ((x & 0x55555555) << 1);
+ x = ((x >> 2) & 0x33333333) | ((x & 0x33333333) << 2);
+ x = ((x >> 4) & 0x0f0f0f0f) | ((x & 0x0f0f0f0f) << 4);
+ x = ((x >> 8) & 0x00ff00ff) | ((x & 0x00ff00ff) << 8);
+ x = ((x >> 16) & 0x0000ffff) | ((x & 0x0000ffff) << 16);
+ return x >>> 0; // Ensure it's treated as an unsigned 32-bit integer
+};
+
+export const binarySearchExact = (arr: T[], key: number, valueGetter: (x: T) => number): number => {
+ let low = 0;
+ let high = arr.length - 1;
+ let res = -1;
+
+ while (low <= high) {
+ const mid = (low + high) >> 1;
+ const midVal = valueGetter(arr[mid]!);
+
+ if (midVal === key) {
+ res = mid;
+ high = mid - 1; // continue searching left to find the lowest index
+ } else if (midVal < key) {
+ low = mid + 1;
+ } else {
+ high = mid - 1;
+ }
+ }
+
+ return res;
+};
+
+export const binarySearchLessOrEqual = (arr: T[], key: number, valueGetter: (x: T) => number) => {
+ let ans = -1;
+ let low = 0;
+ let high = arr.length - 1;
+
+ while (low <= high) {
+ const mid = (low + (high - low + 1) / 2) | 0;
+ const midVal = valueGetter(arr[mid]!);
+
+ if (midVal <= key) {
+ ans = mid;
+ low = mid + 1;
+ } else {
+ high = mid - 1;
+ }
+ }
+
+ return ans;
+};
+
+export const promiseWithResolvers = () => {
+ let resolve: (value: T) => void;
+ let reject: (reason: unknown) => void;
+ const promise = new Promise((res, rej) => {
+ resolve = res;
+ reject = rej;
+ });
+
+ return { promise, resolve: resolve!, reject: reject! };
+};
diff --git a/src/output.ts b/src/output.ts
index be22d59..77a76d7 100644
--- a/src/output.ts
+++ b/src/output.ts
@@ -1,7 +1,7 @@
import { AsyncMutex, TransformationMatrix } from './misc';
import { Muxer } from './muxer';
import { OutputFormat } from './output-format';
-import { AudioSource, MediaSource, SubtitleSource, VideoSource } from './source';
+import { AudioSource, MediaSource, SubtitleSource, VideoSource } from './media-source';
import { Target } from './target';
import { Writer } from './writer';
diff --git a/src/reader.ts b/src/reader.ts
new file mode 100644
index 0000000..5d3265c
--- /dev/null
+++ b/src/reader.ts
@@ -0,0 +1,135 @@
+import { Source } from './source';
+
+const PAGE_SIZE = 4096;
+
+type ReadSegment = {
+ start: number;
+ end: number;
+ bytes: Uint8Array;
+ view: DataView;
+};
+
+export class Reader {
+ loadedSegments: ReadSegment[] = [];
+ sourceSizePromise: Promise | null = null;
+
+ constructor(public source: Source) {}
+
+ getSourceSize() {
+ if (this.sourceSizePromise) {
+ return this.sourceSizePromise;
+ } else {
+ return this.sourceSizePromise = this.source._getSize();
+ }
+ }
+
+ async loadRange(start: number, end: number) {
+ // Read rounded to the nearest page
+ let alignedStart = Math.floor(start / PAGE_SIZE) * PAGE_SIZE;
+ let alignedEnd = Math.ceil(end / PAGE_SIZE) * PAGE_SIZE;
+ alignedEnd = Math.min(alignedEnd, await this.getSourceSize());
+
+ const thing = this.loadedSegments.find(x => x.start <= alignedStart);
+ if (thing) {
+ alignedStart = Math.max(alignedStart, thing.end);
+ }
+
+ const thing2 = this.loadedSegments.find(x => x.end >= alignedEnd);
+ if (thing2) {
+ alignedEnd = Math.min(alignedEnd, thing2.start);
+ }
+
+ if (alignedStart >= alignedEnd) {
+ // Nothing to load
+ return;
+ }
+
+ const bytes = await this.source._read(alignedStart, alignedEnd);
+ this.insertIntoLoadedSegments(alignedStart, bytes);
+ }
+
+ private insertIntoLoadedSegments(start: number, bytes: Uint8Array) {
+ /*
+ let index = -1;
+ let low = 0;
+ let high = this.loadedSegments.length - 1;
+
+ while (low <= high) {
+ let mid = Math.floor(low + (high - low + 1) / 2);
+ let midVal = this.loadedSegments[mid].start;
+
+ if (midVal >= start) {
+ index = mid;
+ high = mid - 1;
+ } else {
+ low = mid + 1;
+ }
+ }
+ */
+
+ const segment: ReadSegment = {
+ start,
+ end: start + bytes.byteLength,
+ bytes,
+ view: new DataView(bytes.buffer),
+ };
+ let index = this.loadedSegments.findLastIndex(x => x.start <= start);
+
+ this.loadedSegments.splice(index + 1, 0, segment);
+ if (index === -1 || this.loadedSegments[index]!.end < segment.start) {
+ index++;
+ }
+
+ const mergeSectionStartIndex = index;
+ const mergeSectionStart = this.loadedSegments[mergeSectionStartIndex]!.start;
+ let mergeSectionEndIndex = index;
+ let mergeSectionEnd = this.loadedSegments[mergeSectionEndIndex]!.end;
+
+ while (
+ this.loadedSegments.length - 1 > mergeSectionEndIndex
+ && this.loadedSegments[mergeSectionEndIndex + 1]!.start <= mergeSectionEnd
+ ) {
+ mergeSectionEndIndex++;
+ mergeSectionEnd = Math.max(mergeSectionEnd, this.loadedSegments[mergeSectionEndIndex]!.end);
+ }
+
+ if (mergeSectionStartIndex === mergeSectionEndIndex) {
+ return;
+ }
+
+ for (let i = mergeSectionStartIndex; i <= mergeSectionEndIndex; i++) {
+ const segment = this.loadedSegments[i]!;
+ const coversEntireMergeSection = segment.start === mergeSectionStart && segment.end === mergeSectionEnd;
+
+ if (coversEntireMergeSection) {
+ this.loadedSegments.splice(i + 1, mergeSectionEndIndex - i);
+ this.loadedSegments.splice(mergeSectionStartIndex, i - mergeSectionStartIndex);
+
+ return;
+ }
+ }
+
+ const unifiedBytes = new Uint8Array(mergeSectionEnd - mergeSectionStart);
+ for (let i = mergeSectionStartIndex; i <= mergeSectionEndIndex; i++) {
+ const segment = this.loadedSegments[i]!;
+ unifiedBytes.set(segment.bytes, segment.start - mergeSectionStart);
+ }
+
+ this.loadedSegments.splice(mergeSectionStartIndex + 1, mergeSectionEndIndex - mergeSectionStartIndex);
+ this.loadedSegments[mergeSectionStartIndex]!.end = mergeSectionEnd;
+ this.loadedSegments[mergeSectionStartIndex]!.bytes = unifiedBytes;
+ this.loadedSegments[mergeSectionStartIndex]!.view = new DataView(unifiedBytes.buffer);
+ }
+
+ getViewAndOffset(start: number, end: number) {
+ const segment = this.loadedSegments.find(x => x.start <= start && end <= x.end);
+ if (!segment) {
+ throw new Error(`No segment loaded for range [${start}, ${end}).`);
+ }
+
+ return {
+ view: segment.view,
+ offset: segment.bytes.byteOffset + start - segment.start,
+ };
+ }
+}
diff --git a/src/source.ts b/src/source.ts
index 8a76705..9cc1010 100644
--- a/src/source.ts
+++ b/src/source.ts
@@ -1,654 +1,40 @@
-import {
- buildAudioCodecString,
- buildVideoCodecString,
- getAudioEncoderConfigExtension,
- getVideoEncoderConfigExtension,
-} from './codec';
-import { OutputAudioTrack, OutputSubtitleTrack, OutputTrack, OutputVideoTrack } from './output';
-import { assert } from './misc';
-import { Muxer } from './muxer';
-import { SubtitleParser } from './subtitles';
-
-/** @public */
-export const VIDEO_CODECS = ['avc', 'hevc', 'vp8', 'vp9', 'av1'] as const;
-/** @public */
-export const AUDIO_CODECS = ['aac', 'opus'] as const; // TODO add the rest
-/** @public */
-export const SUBTITLE_CODECS = ['webvtt'] as const; // TODO add the rest
-
-/** @public */
-export type VideoCodec = typeof VIDEO_CODECS[number];
-/** @public */
-export type AudioCodec = typeof AUDIO_CODECS[number];
-/** @public */
-export type SubtitleCodec = typeof SUBTITLE_CODECS[number];
-
-/** @public */
-export abstract class MediaSource {
+export abstract class Source {
/** @internal */
- _connectedTrack: OutputTrack | null = null;
+ abstract _read(start: number, end: number): Promise;
/** @internal */
- _closed = false;
- /** @internal */
- _offsetTimestamps = false;
-
- /** @internal */
- _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.');
- }
- }
-
- /** @internal */
- _start() {}
- /** @internal */
- 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 = true;
-
- if (this._connectedTrack.output._finalizing) {
- return;
- }
-
- this._connectedTrack.output._muxer.onTrackClose(this._connectedTrack);
- }
+ abstract _getSize(): Promise;
}
-/** @public */
-export abstract class VideoSource extends MediaSource {
- /** @internal */
- override _connectedTrack: OutputVideoTrack | null = null;
- /** @internal */
- _codec: VideoCodec;
-
- constructor(codec: VideoCodec) {
+export class ArrayBufferSource extends Source {
+ constructor(private buffer: ArrayBuffer) {
super();
+ }
- if (!VIDEO_CODECS.includes(codec)) {
- throw new TypeError(`Invalid video codec '${codec}'. Must be one of: ${VIDEO_CODECS.join(', ')}.`);
- }
+ /** @internal */
+ override async _read(start: number, end: number) {
+ return new Uint8Array(this.buffer, start, end - start);
+ }
- this._codec = codec;
+ /** @internal */
+ override async _getSize() {
+ return this.buffer.byteLength;
}
}
-/** @public */
-export class EncodedVideoChunkSource extends VideoSource {
- constructor(codec: VideoCodec) {
- super(codec);
- }
-
- digest(chunk: EncodedVideoChunk, meta?: EncodedVideoChunkMetadata) {
- if (!(chunk instanceof EncodedVideoChunk)) {
- // TODO add polyfill for browsers that don't have this
- throw new TypeError('chunk must be an EncodedVideoChunk.');
- }
-
- this._ensureValidDigest();
- return this._connectedTrack!.output._muxer.addEncodedVideoChunk(this._connectedTrack!, chunk, meta);
- }
-}
-
-const KEY_FRAME_INTERVAL = 5;
-
-/** @public */
-export type VideoCodecConfig = {
- codec: VideoCodec;
- bitrate: number;
- latencyMode?: VideoEncoderConfig['latencyMode'];
-};
-
-const validateVideoCodecConfig = (config: VideoCodecConfig) => {
- if (!config || typeof config !== 'object') {
- throw new TypeError('Codec config must be an object.');
- }
- if (!VIDEO_CODECS.includes(config.codec)) {
- throw new TypeError(`Invalid video codec '${config.codec}'. Must be one of: ${VIDEO_CODECS.join(', ')}.`);
- }
- if (!Number.isInteger(config.bitrate) || config.bitrate <= 0) {
- throw new TypeError('config.bitrate must be a positive integer.');
- }
- if (config.latencyMode !== undefined && !['quality', 'realtime'].includes(config.latencyMode)) {
- throw new TypeError('config.latencyMode, when provided, must be \'quality\' or \'realtime\'.');
- }
-};
-
-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;
-
- constructor(private source: VideoSource, private codecConfig: VideoCodecConfig) {
- validateVideoCodecConfig(codecConfig);
- }
-
- async digest(videoFrame: VideoFrame) {
- this.source._ensureValidDigest();
-
- // Ensure video frame size remains constant
- if (this.lastWidth !== null && this.lastHeight !== null) {
- if (videoFrame.codedWidth !== this.lastWidth || videoFrame.codedHeight !== this.lastHeight) {
- throw new Error(
- `Video frame size must remain constant. Expected ${this.lastWidth}x${this.lastHeight},`
- + ` got ${videoFrame.codedWidth}x${videoFrame.codedHeight}.`,
- );
- }
- } else {
- this.lastWidth = videoFrame.codedWidth;
- this.lastHeight = videoFrame.codedHeight;
- }
-
- this.ensureEncoder(videoFrame);
- assert(this.encoder);
-
- const multipleOfKeyFrameInterval = Math.floor((videoFrame.timestamp / 1e6) / KEY_FRAME_INTERVAL);
-
- // Ensure a key frame every KEY_FRAME_INTERVAL seconds. It is important that all video tracks follow the same
- // "key frame" rhythm, because aligned key frames are required to start new fragments in ISOBMFF or clusters
- // in Matroska.
- 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) {
- if (this.encoder) {
- return;
- }
-
- this.encoder = new VideoEncoder({
- output: (chunk, meta) => void this.muxer!.addEncodedVideoChunk(this.source._connectedTrack!, chunk, meta),
- error: error => console.error('Video encode error:', error),
- });
-
- this.encoder.configure({
- codec: buildVideoCodecString(
- this.codecConfig.codec,
- videoFrame.codedWidth,
- videoFrame.codedHeight,
- this.codecConfig.bitrate,
- ),
- width: videoFrame.codedWidth,
- height: videoFrame.codedHeight,
- bitrate: this.codecConfig.bitrate,
- framerate: this.source._connectedTrack?.metadata.frameRate,
- latencyMode: this.codecConfig.latencyMode,
- ...getVideoEncoderConfigExtension(this.codecConfig.codec),
- });
-
- assert(this.source._connectedTrack);
- this.muxer = this.source._connectedTrack.output._muxer;
- }
-
- async flush() {
- if (this.encoder) {
- await this.encoder.flush();
- this.encoder.close();
- }
- }
-}
-
-/** @public */
-export class VideoFrameSource extends VideoSource {
- /** @internal */
- private _encoder: VideoEncoderWrapper;
-
- constructor(codecConfig: VideoCodecConfig) {
- super(codecConfig.codec);
- this._encoder = new VideoEncoderWrapper(this, codecConfig);
- }
-
- digest(videoFrame: VideoFrame) {
- if (!(videoFrame instanceof VideoFrame)) {
- throw new TypeError('videoFrame must be a VideoFrame.');
- }
-
- return this._encoder.digest(videoFrame);
- }
-
- /** @internal */
- override _flush() {
- return this._encoder.flush();
- }
-}
-
-/** @public */
-export class CanvasSource extends VideoSource {
- /** @internal */
- private _encoder: VideoEncoderWrapper;
- /** @internal */
- private _canvas: HTMLCanvasElement | OffscreenCanvas;
-
- constructor(canvas: HTMLCanvasElement | OffscreenCanvas, codecConfig: VideoCodecConfig) {
- if (!(canvas instanceof HTMLCanvasElement)) {
- throw new TypeError('canvas must be an HTMLCanvasElement.');
- }
-
- super(codecConfig.codec);
- this._encoder = new VideoEncoderWrapper(this, codecConfig);
- this._canvas = canvas;
- }
-
- digest(timestamp: number, duration = 0) {
- if (!Number.isFinite(timestamp) || timestamp < 0) {
- throw new TypeError('timestamp must be a non-negative number.');
- }
- if (!Number.isFinite(duration) || duration < 0) {
- throw new TypeError('duration must be a non-negative number.');
- }
-
- const frame = new VideoFrame(this._canvas, {
- timestamp: Math.round(1e6 * timestamp),
- duration: Math.round(1e6 * duration),
- alpha: 'discard',
- });
-
- const promise = this._encoder.digest(frame);
- frame.close();
-
- return promise;
- }
-
- /** @internal */
- override _flush() {
- return this._encoder.flush();
- }
-}
-
-/** @public */
-export class MediaStreamVideoTrackSource extends VideoSource {
- /** @internal */
- private _encoder: VideoEncoderWrapper;
- /** @internal */
- private _abortController: AbortController | null = null;
- /** @internal */
- private _track: MediaStreamVideoTrack;
-
- /** @internal */
- override _offsetTimestamps = true;
-
- constructor(track: MediaStreamVideoTrack, codecConfig: VideoCodecConfig) {
- if (!(track instanceof MediaStreamTrack) || track.kind !== 'video') {
- throw new TypeError('track must be a video MediaStreamTrack.');
- }
-
- codecConfig = {
- ...codecConfig,
- latencyMode: 'realtime',
- };
-
- super(codecConfig.codec);
- this._encoder = new VideoEncoderWrapper(this, codecConfig);
- this._track = track;
- }
-
- /** @internal */
- override _start() {
- this._abortController = new AbortController();
-
- const processor = new MediaStreamTrackProcessor({ track: this._track });
- const consumer = new WritableStream({
- write: (videoFrame) => {
- // TODO: Drop frames if encoder overloaded
- void this._encoder.digest(videoFrame);
- videoFrame.close();
- },
- });
-
- processor.readable.pipeTo(consumer, {
- signal: this._abortController.signal,
- }).catch((err) => {
- // Handle abort error silently
- if (err instanceof DOMException && err.name === 'AbortError') return;
- // Handle other errors
- console.error('Pipe error:', err);
- });
- }
-
- /** @internal */
- override async _flush() {
- if (this._abortController) {
- this._abortController.abort();
- this._abortController = null;
- }
-
- await this._encoder.flush();
- }
-}
-
-/** @public */
-export abstract class AudioSource extends MediaSource {
- /** @internal */
- override _connectedTrack: OutputAudioTrack | null = null;
- /** @internal */
- _codec: AudioCodec;
-
- constructor(codec: AudioCodec) {
+export class BlobSource extends Source {
+ constructor(private blob: Blob) {
super();
+ }
- if (!AUDIO_CODECS.includes(codec)) {
- throw new TypeError(`Invalid audio codec '${codec}'. Must be one of: ${AUDIO_CODECS.join(', ')}.`);
- }
+ /** @internal */
+ override async _read(start: number, end: number) {
+ const slice = this.blob.slice(start, end);
+ const buffer = await slice.arrayBuffer();
+ return new Uint8Array(buffer);
+ }
- this._codec = codec;
- }
-}
-
-/** @public */
-export class EncodedAudioChunkSource extends AudioSource {
- constructor(codec: AudioCodec) {
- super(codec);
- }
-
- digest(chunk: EncodedAudioChunk, meta?: EncodedAudioChunkMetadata) {
- if (!(chunk instanceof EncodedAudioChunk)) {
- // TODO add polyfill for browsers that don't have this
- throw new TypeError('chunk must be an EncodedAudioChunk.');
- }
-
- this._ensureValidDigest();
- return this._connectedTrack!.output._muxer.addEncodedAudioChunk(this._connectedTrack!, chunk, meta);
- }
-}
-/** @public */
-export type AudioCodecConfig = {
- codec: AudioCodec;
- bitrate: number;
-};
-
-const validateAudioCodecConfig = (config: AudioCodecConfig) => {
- if (!config || typeof config !== 'object') {
- throw new TypeError('Codec config must be an object.');
- }
- if (!AUDIO_CODECS.includes(config.codec)) {
- throw new TypeError(`Invalid audio codec '${config.codec}'. Must be one of: ${AUDIO_CODECS.join(', ')}.`);
- }
- if (!Number.isInteger(config.bitrate) || config.bitrate <= 0) {
- throw new TypeError('config.bitrate must be a positive integer.');
- }
-};
-
-class AudioEncoderWrapper {
- private encoder: AudioEncoder | null = null;
- private muxer: Muxer | null = null;
- private lastNumberOfChannels: number | null = null;
- private lastSampleRate: number | null = null;
-
- constructor(private source: AudioSource, private codecConfig: AudioCodecConfig) {
- validateAudioCodecConfig(codecConfig);
- }
-
- async digest(audioData: AudioData) {
- this.source._ensureValidDigest();
-
- // Ensure audio parameters remain constant
- if (this.lastNumberOfChannels !== null && this.lastSampleRate !== null) {
- if (
- audioData.numberOfChannels !== this.lastNumberOfChannels
- || audioData.sampleRate !== this.lastSampleRate
- ) {
- throw new Error(
- `Audio parameters must remain constant. Expected ${this.lastNumberOfChannels} channels at`
- + ` ${this.lastSampleRate} Hz, got ${audioData.numberOfChannels} channels at`
- + ` ${audioData.sampleRate} Hz.`,
- );
- }
- } else {
- this.lastNumberOfChannels = audioData.numberOfChannels;
- this.lastSampleRate = audioData.sampleRate;
- }
-
- 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; // Allow the writer to apply backpressure
- }
-
- private ensureEncoder(audioData: AudioData) {
- if (this.encoder) {
- return;
- }
-
- this.encoder = new AudioEncoder({
- output: (chunk, meta) => void this.muxer!.addEncodedAudioChunk(this.source._connectedTrack!, chunk, meta),
- error: error => console.error('Audio encode error:', error),
- });
-
- this.encoder.configure({
- codec: buildAudioCodecString(this.codecConfig.codec, audioData.numberOfChannels, audioData.sampleRate),
- numberOfChannels: audioData.numberOfChannels,
- sampleRate: audioData.sampleRate,
- bitrate: this.codecConfig.bitrate,
- ...getAudioEncoderConfigExtension(this.codecConfig.codec),
- });
-
- assert(this.source._connectedTrack);
- this.muxer = this.source._connectedTrack.output._muxer;
- }
-
- async flush() {
- if (this.encoder) {
- await this.encoder.flush();
- this.encoder.close();
- }
- }
-}
-
-/** @public */
-export class AudioDataSource extends AudioSource {
/** @internal */
- private _encoder: AudioEncoderWrapper;
-
- constructor(codecConfig: AudioCodecConfig) {
- super(codecConfig.codec);
- this._encoder = new AudioEncoderWrapper(this, codecConfig);
- }
-
- digest(audioData: AudioData) {
- if (!(audioData instanceof AudioData)) {
- throw new TypeError('audioData must be an AudioData.');
- }
-
- return this._encoder.digest(audioData);
- }
-
- /** @internal */
- override _flush() {
- return this._encoder.flush();
- }
-}
-
-/** @public */
-export class AudioBufferSource extends AudioSource {
- /** @internal */
- private _encoder: AudioEncoderWrapper;
- /** @internal */
- private _accumulatedFrameCount = 0;
-
- constructor(codecConfig: AudioCodecConfig) {
- super(codecConfig.codec);
- this._encoder = new AudioEncoderWrapper(this, codecConfig);
- }
-
- digest(audioBuffer: AudioBuffer) {
- if (!(audioBuffer instanceof AudioBuffer)) {
- throw new TypeError('audioBuffer must be an AudioBuffer.');
- }
-
- const numberOfChannels = audioBuffer.numberOfChannels;
- const sampleRate = audioBuffer.sampleRate;
- const numberOfFrames = audioBuffer.length;
-
- // Create a planar F32 array containing all channels
- const data = new Float32Array(numberOfChannels * numberOfFrames);
- for (let channel = 0; channel < numberOfChannels; channel++) {
- const channelData = audioBuffer.getChannelData(channel);
- data.set(channelData, channel * numberOfFrames);
- }
-
- const audioData = new AudioData({
- format: 'f32-planar',
- sampleRate,
- numberOfFrames,
- numberOfChannels,
- timestamp: Math.round(1e6 * this._accumulatedFrameCount / sampleRate),
- data: data,
- });
-
- const promise = this._encoder.digest(audioData);
- audioData.close();
-
- this._accumulatedFrameCount += numberOfFrames;
-
- return promise;
- }
-
- /** @internal */
- override _flush() {
- return this._encoder.flush();
- }
-}
-
-/** @public */
-export class MediaStreamAudioTrackSource extends AudioSource {
- /** @internal */
- private _encoder: AudioEncoderWrapper;
- /** @internal */
- private _abortController: AbortController | null = null;
- /** @internal */
- private _track: MediaStreamAudioTrack;
-
- /** @internal */
- override _offsetTimestamps = true;
-
- constructor(track: MediaStreamAudioTrack, codecConfig: AudioCodecConfig) {
- if (!(track instanceof MediaStreamTrack) || track.kind !== 'audio') {
- throw new TypeError('track must be an audio MediaStreamTrack.');
- }
-
- super(codecConfig.codec);
- this._encoder = new AudioEncoderWrapper(this, codecConfig);
- this._track = track;
- }
-
- /** @internal */
- override _start() {
- this._abortController = new AbortController();
-
- const processor = new MediaStreamTrackProcessor({ track: this._track });
- const consumer = new WritableStream({
- write: (audioData) => {
- // TODO: Drop frames if encoder overloaded
- void this._encoder.digest(audioData);
- audioData.close();
- },
- });
-
- processor.readable.pipeTo(consumer, {
- signal: this._abortController.signal,
- }).catch((err) => {
- // Handle abort error silently
- if (err instanceof DOMException && err.name === 'AbortError') return;
- // Handle other errors
- console.error('Pipe error:', err);
- });
- }
-
- /** @internal */
- override async _flush() {
- if (this._abortController) {
- this._abortController.abort();
- this._abortController = null;
- }
-
- await this._encoder.flush();
- }
-}
-
-/** @public */
-export abstract class SubtitleSource extends MediaSource {
- /** @internal */
- override _connectedTrack: OutputSubtitleTrack | null = null;
- /** @internal */
- _codec: SubtitleCodec;
-
- constructor(codec: SubtitleCodec) {
- super();
-
- if (!SUBTITLE_CODECS.includes(codec)) {
- throw new TypeError(`Invalid subtitle codec '${codec}'. Must be one of: ${SUBTITLE_CODECS.join(', ')}.`);
- }
-
- this._codec = codec;
- }
-}
-
-/** @public */
-export class TextSubtitleSource extends SubtitleSource {
- /** @internal */
- private _parser: SubtitleParser;
-
- constructor(codec: SubtitleCodec) {
- super(codec);
-
- this._parser = new SubtitleParser({
- codec,
- output: (cue, metadata) =>
- this._connectedTrack?.output._muxer.addSubtitleCue(this._connectedTrack, cue, metadata),
- error: error => console.error('Subtitle parse error:', error),
- });
- }
-
- digest(text: string) {
- if (typeof text !== 'string') {
- throw new TypeError('text must be a string.');
- }
-
- this._ensureValidDigest();
- this._parser.parse(text);
-
- return this._connectedTrack!.output._muxer.mutex.currentPromise;
+ override async _getSize() {
+ return this.blob.size;
}
}
diff --git a/src/writer.ts b/src/writer.ts
index def1f6c..b1056ee 100644
--- a/src/writer.ts
+++ b/src/writer.ts
@@ -69,7 +69,6 @@ export class ArrayBufferTargetWriter extends Writer {
async flush() {}
- // eslint-disable-next-line @typescript-eslint/require-await
async finalize() {
this.ensureSize(this.pos);
this.target.buffer = this.buffer.slice(0, Math.max(this.maxPos, this.pos));
diff --git a/tsconfig.json b/tsconfig.json
index 40dbf0f..4157a38 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,6 +1,6 @@
{
"compilerOptions": {
- "target": "ES2021",
+ "target": "ES2023",
"strict": true,
"noImplicitAny": true,
"noImplicitOverride": true,