diff --git a/.gitignore b/.gitignore index 563ba71..629ca84 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /node_modules -/build \ No newline at end of file +/build +todo.txt \ No newline at end of file diff --git a/build.mjs b/build.mjs index 26498b1..d03d615 100644 --- a/build.mjs +++ b/build.mjs @@ -4,7 +4,7 @@ import process from 'node:process'; const baseConfig = { entryPoints: ['src/index.ts'], bundle: true, - logLevel: 'info' + logLevel: 'info', }; const umdConfig = { @@ -16,32 +16,32 @@ const umdConfig = { footer: { js: -`if (typeof module === "object" && typeof module.exports === "object") Object.assign(module.exports, Metamuxer)` - } +`if (typeof module === "object" && typeof module.exports === "object") Object.assign(module.exports, Metamuxer)`, + }, }; const esmConfig = { ...baseConfig, - format: 'esm' + format: 'esm', }; let ctxUmd = await esbuild.context({ ...umdConfig, - outfile: 'dist/metamuxer.js' + outfile: 'dist/metamuxer.js', }); let ctxEsm = await esbuild.context({ ...esmConfig, - outfile: 'dist/metamuxer.mjs' + outfile: 'dist/metamuxer.mjs', }); let ctxUmdMinified = await esbuild.context({ ...umdConfig, outfile: 'dist/metamuxer.min.js', - minify: true + minify: true, }); let ctxEsmMinified = await esbuild.context({ ...esmConfig, outfile: 'dist/metamuxer.min.mjs', - minify: true + minify: true, }); if (process.argv[2] === '--watch') { diff --git a/dist/metamuxer.d.ts b/dist/metamuxer.d.ts index 58f7f94..e099e9d 100644 --- a/dist/metamuxer.d.ts +++ b/dist/metamuxer.d.ts @@ -37,7 +37,7 @@ export declare type AudioTrackMetadata = {}; /** @public */ export declare class CanvasSource extends VideoSource { - constructor(canvas: HTMLCanvasElement, codecConfig: VideoCodecConfig); + constructor(canvas: HTMLCanvasElement | OffscreenCanvas, codecConfig: VideoCodecConfig); digest(timestamp: number, duration?: number): Promise; } @@ -189,7 +189,7 @@ export declare class WebMOutputFormat extends MkvOutputFormat { } /** @public */ -export declare type WebmOutputFormatOptions = MkvOutputFormatOptions; +export declare type WebMOutputFormatOptions = MkvOutputFormatOptions; export { } diff --git a/dist/metamuxer.js b/dist/metamuxer.js index 5837f89..4ae4cce 100644 --- a/dist/metamuxer.js +++ b/dist/metamuxer.js @@ -62,10 +62,10 @@ var Metamuxer = (() => { var readBits = (bytes2, start, end) => { let result = 0; for (let i = start; i < end; i++) { - let byteIndex = Math.floor(i / 8); - let byte = bytes2[byteIndex]; - let bitIndex = 7 - (i & 7); - let bit = (byte & 1 << bitIndex) >> bitIndex; + const byteIndex = Math.floor(i / 8); + const byte = bytes2[byteIndex]; + const bitIndex = 7 - (i & 7); + const bit = (byte & 1 << bitIndex) >> bitIndex; result <<= 1; result |= bit; } @@ -73,9 +73,9 @@ var Metamuxer = (() => { }; var writeBits = (bytes2, start, end, value) => { for (let i = start; i < end; i++) { - let byteIndex = Math.floor(i / 8); + const byteIndex = Math.floor(i / 8); let byte = bytes2[byteIndex]; - let bitIndex = 7 - (i & 7); + const bitIndex = 7 - (i & 7); byte &= ~(1 << bitIndex); byte |= (value & 1 << end - i - 1) >> end - i - 1 << bitIndex; bytes2[byteIndex] = byte; @@ -90,11 +90,11 @@ var Metamuxer = (() => { }; var textEncoder = new TextEncoder(); var COLOR_PRIMARIES_MAP = { - "bt709": 1, + bt709: 1, // ITU-R BT.709 - "bt470bg": 5, + bt470bg: 5, // ITU-R BT.470BG - "smpte170m": 6 + smpte170m: 6 // ITU-R BT.601 525 - SMPTE 170M }; var TRANSFER_CHARACTERISTICS_MAP = { @@ -106,13 +106,13 @@ var Metamuxer = (() => { // IEC 61966-2-1 }; var MATRIX_COEFFICIENTS_MAP = { - "rgb": 0, + rgb: 0, // Identity - "bt709": 1, + bt709: 1, // ITU-R BT.709 - "bt470bg": 5, + bt470bg: 5, // ITU-R BT.470BG - "smpte170m": 6 + smpte170m: 6 // SMPTE 170M }; var colorSpaceIsComplete = (colorSpace) => { @@ -127,10 +127,10 @@ var Metamuxer = (() => { } async acquire() { let resolver; - let nextPromise = new Promise((resolve) => { + const nextPromise = new Promise((resolve) => { resolver = resolve; }); - let currentPromiseAlias = this.currentPromise; + const currentPromiseAlias = this.currentPromise; this.currentPromise = nextPromise; await currentPromiseAlias; return resolver; @@ -153,14 +153,14 @@ var Metamuxer = (() => { let match; if (!this.preambleText) { if (!preambleStartRegex.test(text)) { - let error = new Error("WebVTT preamble incorrect."); + const error = new Error("WebVTT preamble incorrect."); this.options.error(error); throw error; } match = cueBlockHeaderRegex.exec(text); - let preamble = text.slice(0, match?.index ?? text.length).trimEnd(); + const preamble = text.slice(0, match?.index ?? text.length).trimEnd(); if (!preamble) { - let error = new Error("No WebVTT preamble provided."); + const error = new Error("No WebVTT preamble provided."); this.options.error(error); throw error; } @@ -171,20 +171,20 @@ var Metamuxer = (() => { } } while (match = cueBlockHeaderRegex.exec(text)) { - let notes = text.slice(0, match.index); - let cueIdentifier = match[1]; - let matchEnd = match.index + match[0].length; - let bodyStart = text.indexOf("\n", matchEnd) + 1; - let cueSettings = text.slice(matchEnd, bodyStart).trim(); + const notes = text.slice(0, match.index); + const cueIdentifier = match[1]; + const matchEnd = match.index + match[0].length; + const bodyStart = text.indexOf("\n", matchEnd) + 1; + const cueSettings = text.slice(matchEnd, bodyStart).trim(); let bodyEnd = text.indexOf("\n\n", matchEnd); if (bodyEnd === -1) bodyEnd = text.length; - let startTime = parseSubtitleTimestamp(match[2]); - let endTime = parseSubtitleTimestamp(match[3]); - let duration = endTime - startTime; - let body = text.slice(bodyStart, bodyEnd).trim(); + const startTime = parseSubtitleTimestamp(match[2]); + const endTime = parseSubtitleTimestamp(match[3]); + const duration = endTime - startTime; + const body = text.slice(bodyStart, bodyEnd).trim(); text = text.slice(bodyEnd).trimStart(); cueBlockHeaderRegex.lastIndex = 0; - let cue = { + const cue = { timestamp: startTime / 1e3, duration: duration / 1e3, text: body, @@ -192,7 +192,7 @@ var Metamuxer = (() => { settings: cueSettings, notes }; - let meta = {}; + const meta = {}; if (!this.preambleEmitted) { meta.config = { description: this.preambleText @@ -205,15 +205,15 @@ var Metamuxer = (() => { }; var timestampRegex = /(?:(\d{2}):)?(\d{2}):(\d{2}).(\d{3})/; var parseSubtitleTimestamp = (string) => { - let match = timestampRegex.exec(string); + const match = timestampRegex.exec(string); if (!match) throw new Error("Expected match."); return 60 * 60 * 1e3 * Number(match[1] || "0") + 60 * 1e3 * Number(match[2]) + 1e3 * Number(match[3]) + Number(match[4]); }; var formatSubtitleTimestamp = (timestamp) => { - let hours = Math.floor(timestamp / (60 * 60 * 1e3)); - let minutes = Math.floor(timestamp % (60 * 60 * 1e3) / (60 * 1e3)); - let seconds = Math.floor(timestamp % (60 * 1e3) / 1e3); - let milliseconds = timestamp % 1e3; + const hours = Math.floor(timestamp / (60 * 60 * 1e3)); + const minutes = Math.floor(timestamp % (60 * 60 * 1e3) / (60 * 1e3)); + const seconds = Math.floor(timestamp % (60 * 1e3) / 1e3); + const milliseconds = timestamp % 1e3; return hours.toString().padStart(2, "0") + ":" + minutes.toString().padStart(2, "0") + ":" + seconds.toString().padStart(2, "0") + "." + milliseconds.toString().padStart(3, "0"); }; @@ -253,14 +253,14 @@ var Metamuxer = (() => { this.writeBoxHeader(box2, box2.size ?? box2.contents.byteLength + 8); this.writer.write(box2.contents); } else { - let startPos = this.writer.getPos(); + const startPos = this.writer.getPos(); this.writeBoxHeader(box2, 0); if (box2.contents) this.writer.write(box2.contents); if (box2.children) { - for (let child of box2.children) if (child) this.writeBox(child); + for (const child of box2.children) if (child) this.writeBox(child); } - let endPos = this.writer.getPos(); - let size = box2.size ?? endPos - startPos; + const endPos = this.writer.getPos(); + const size = box2.size ?? endPos - startPos; this.writer.seek(startPos); this.writeBoxHeader(box2, size); this.writer.seek(endPos); @@ -277,20 +277,20 @@ var Metamuxer = (() => { patchBox(box2) { const boxOffset = this.offsets.get(box2); assert(boxOffset !== void 0); - let endPos = this.writer.getPos(); + const endPos = this.writer.getPos(); this.writer.seek(boxOffset); this.writeBox(box2); this.writer.seek(endPos); } measureBox(box2) { if (box2.contents && !box2.children) { - let headerSize = this.measureBoxHeader(box2); + const headerSize = this.measureBoxHeader(box2); return headerSize + box2.contents.byteLength; } else { let result = this.measureBoxHeader(box2); if (box2.contents) result += box2.contents.byteLength; if (box2.children) { - for (let child of box2.children) if (child) result += this.measureBox(child); + for (const child of box2.children) if (child) result += this.measureBox(child); } return result; } @@ -339,7 +339,7 @@ var Metamuxer = (() => { return [bytes[0], bytes[1], bytes[2], bytes[3]]; }; var variableUnsignedInt = (value, byteLength) => { - let bytes2 = []; + const bytes2 = []; let remaining = value; do { let byte = remaining & 127; @@ -355,13 +355,13 @@ var Metamuxer = (() => { return bytes2.reverse(); }; var ascii = (text, nullTerminated = false) => { - let bytes2 = Array(text.length).fill(null).map((_, i) => text.charCodeAt(i)); + const bytes2 = Array(text.length).fill(null).map((_, i) => text.charCodeAt(i)); if (nullTerminated) bytes2.push(0); return bytes2; }; var lastPresentedSample = (samples) => { let result = null; - for (let sample of samples) { + for (const sample of samples) { if (!result || sample.timestamp > result.timestamp) { result = sample; } @@ -369,9 +369,9 @@ var Metamuxer = (() => { return result; }; var rotationMatrix = (rotationInDegrees) => { - let theta = rotationInDegrees * (Math.PI / 180); - let cosTheta = Math.cos(theta); - let sinTheta = Math.sin(theta); + const theta = rotationInDegrees * (Math.PI / 180); + const cosTheta = Math.cos(theta); + const sinTheta = Math.sin(theta); return [ cosTheta, sinTheta, @@ -409,7 +409,7 @@ var Metamuxer = (() => { children ); var ftyp = (details) => { - let minorVersion = 512; + const minorVersion = 512; if (details.fragmented) return box("ftyp", [ ascii("iso5"), // Major brand @@ -439,16 +439,16 @@ var Metamuxer = (() => { fragmented ? mvex(trackDatas) : null ]); var mvhd = (creationTime, trackDatas) => { - let duration = intoTimescale(Math.max( + const duration = intoTimescale(Math.max( 0, ...trackDatas.filter((x) => x.samples.length > 0).map((x) => { const lastSample = lastPresentedSample(x.samples); return lastSample.timestamp + lastSample.duration; }) ), GLOBAL_TIMESCALE); - let nextTrackId = Math.max(0, ...trackDatas.map((x) => x.track.id)) + 1; - let needsU64 = !isU32(creationTime) || !isU32(duration); - let u32OrU64 = needsU64 ? u64 : u32; + const nextTrackId = Math.max(0, ...trackDatas.map((x) => x.track.id)) + 1; + const needsU64 = !isU32(creationTime) || !isU32(duration); + const u32OrU64 = needsU64 ? u64 : u32; return fullBox("mvhd", +needsU64, 0, [ u32OrU64(creationTime), // Creation time @@ -477,13 +477,13 @@ var Metamuxer = (() => { mdia(trackData, creationTime) ]); var tkhd = (trackData, creationTime) => { - let lastSample = lastPresentedSample(trackData.samples); - let durationInGlobalTimescale = intoTimescale( + const lastSample = lastPresentedSample(trackData.samples); + const durationInGlobalTimescale = intoTimescale( lastSample ? lastSample.timestamp + lastSample.duration : 0, GLOBAL_TIMESCALE ); - let needsU64 = !isU32(creationTime) || !isU32(durationInGlobalTimescale); - let u32OrU64 = needsU64 ? u64 : u32; + const needsU64 = !isU32(creationTime) || !isU32(durationInGlobalTimescale); + const u32OrU64 = needsU64 ? u64 : u32; let matrix; if (trackData.type === "video") { const rotation = trackData.track.metadata.rotation; @@ -526,13 +526,13 @@ var Metamuxer = (() => { minf(trackData) ]); var mdhd = (trackData, creationTime) => { - let lastSample = lastPresentedSample(trackData.samples); - let localDuration = intoTimescale( + const lastSample = lastPresentedSample(trackData.samples); + const localDuration = intoTimescale( lastSample ? lastSample.timestamp + lastSample.duration : 0, trackData.timescale ); - let needsU64 = !isU32(creationTime) || !isU32(localDuration); - let u32OrU64 = needsU64 ? u64 : u32; + const needsU64 = !isU32(creationTime) || !isU32(localDuration); + const u32OrU64 = needsU64 ? u64 : u32; return fullBox("mdhd", +needsU64, 0, [ u32OrU64(creationTime), // Creation time @@ -704,17 +704,17 @@ var Metamuxer = (() => { if (!trackData.info.decoderConfig) { return null; } - let decoderConfig = trackData.info.decoderConfig; + const decoderConfig = trackData.info.decoderConfig; assert(decoderConfig.colorSpace); - let parts = decoderConfig.codec.split("."); - let profile = Number(parts[1]); - let level = Number(parts[2]); - let bitDepth = Number(parts[3]); - let chromaSubsampling = 0; - let thirdByte = (bitDepth << 4) + (chromaSubsampling << 1) + Number(decoderConfig.colorSpace.fullRange); - let colourPrimaries = 2; - let transferCharacteristics = 2; - let matrixCoefficients = 2; + const parts = decoderConfig.codec.split("."); + const profile = Number(parts[1]); + const level = Number(parts[2]); + const bitDepth = Number(parts[3]); + const chromaSubsampling = 0; + const thirdByte = (bitDepth << 4) + (chromaSubsampling << 1) + Number(decoderConfig.colorSpace.fullRange); + const colourPrimaries = 2; + const transferCharacteristics = 2; + const matrixCoefficients = 2; return fullBox("vpcC", 1, 0, [ u8(profile), // Profile @@ -733,9 +733,9 @@ var Metamuxer = (() => { ]); }; var av1C = () => { - let marker = 1; - let version = 1; - let firstByte = (marker << 7) + version; + const marker = 1; + const version = 1; + const firstByte = (marker << 7) + version; return box("av1C", [ firstByte, 0, @@ -768,7 +768,7 @@ var Metamuxer = (() => { AUDIO_CODEC_TO_CONFIGURATION_BOX[trackData.track.source._codec](trackData) ]); var esds = (trackData) => { - let description = toUint8Array(trackData.info.decoderConfig.description ?? new ArrayBuffer(0)); + const description = toUint8Array(trackData.info.decoderConfig.description ?? new ArrayBuffer(0)); let bytes2 = [ ...description ]; @@ -862,7 +862,7 @@ var Metamuxer = (() => { }; var stss = (trackData) => { if (trackData.samples.every((x) => x.type === "key")) return null; - let keySamples = [...trackData.samples.entries()].filter(([, sample]) => sample.type === "key"); + const keySamples = [...trackData.samples.entries()].filter(([, sample]) => sample.type === "key"); return fullBox("stss", 0, 0, [ u32(keySamples.length), // Number of entries @@ -954,9 +954,9 @@ var Metamuxer = (() => { var fragmentSampleFlags = (sample) => { let byte1 = 0; let byte2 = 0; - let byte3 = 0; - let byte4 = 0; - let sampleIsDifferenceSample = sample.type === "delta"; + const byte3 = 0; + const byte4 = 0; + const sampleIsDifferenceSample = sample.type === "delta"; byte2 |= +sampleIsDifferenceSample; if (sampleIsDifferenceSample) { byte1 |= 1; @@ -979,8 +979,8 @@ var Metamuxer = (() => { tfFlags |= 16; tfFlags |= 32; tfFlags |= 131072; - let referenceSample = trackData.currentChunk.samples[1] ?? trackData.currentChunk.samples[0]; - let referenceSampleInfo = { + const referenceSample = trackData.currentChunk.samples[1] ?? trackData.currentChunk.samples[0]; + const referenceSampleInfo = { duration: referenceSample.timescaleUnitsToNextSample, size: referenceSample.size, flags: fragmentSampleFlags(referenceSample) @@ -1005,19 +1005,19 @@ var Metamuxer = (() => { }; var trun = (trackData) => { assert(trackData.currentChunk); - let allSampleDurations = trackData.currentChunk.samples.map((x) => x.timescaleUnitsToNextSample); - let allSampleSizes = trackData.currentChunk.samples.map((x) => x.size); - let allSampleFlags = trackData.currentChunk.samples.map(fragmentSampleFlags); - let allSampleCompositionTimeOffsets = trackData.currentChunk.samples.map((x) => intoTimescale(x.timestamp - x.decodeTimestamp, trackData.timescale)); - let uniqueSampleDurations = new Set(allSampleDurations); - let uniqueSampleSizes = new Set(allSampleSizes); - let uniqueSampleFlags = new Set(allSampleFlags); - let uniqueSampleCompositionTimeOffsets = new Set(allSampleCompositionTimeOffsets); - let firstSampleFlagsPresent = uniqueSampleFlags.size === 2 && allSampleFlags[0] !== allSampleFlags[1]; - let sampleDurationPresent = uniqueSampleDurations.size > 1; - let sampleSizePresent = uniqueSampleSizes.size > 1; - let sampleFlagsPresent = !firstSampleFlagsPresent && uniqueSampleFlags.size > 1; - let sampleCompositionTimeOffsetsPresent = uniqueSampleCompositionTimeOffsets.size > 1 || [...uniqueSampleCompositionTimeOffsets].some((x) => x !== 0); + const allSampleDurations = trackData.currentChunk.samples.map((x) => x.timescaleUnitsToNextSample); + const allSampleSizes = trackData.currentChunk.samples.map((x) => x.size); + const allSampleFlags = trackData.currentChunk.samples.map(fragmentSampleFlags); + const allSampleCompositionTimeOffsets = trackData.currentChunk.samples.map((x) => intoTimescale(x.timestamp - x.decodeTimestamp, trackData.timescale)); + const uniqueSampleDurations = new Set(allSampleDurations); + const uniqueSampleSizes = new Set(allSampleSizes); + const uniqueSampleFlags = new Set(allSampleFlags); + const uniqueSampleCompositionTimeOffsets = new Set(allSampleCompositionTimeOffsets); + const firstSampleFlagsPresent = uniqueSampleFlags.size === 2 && allSampleFlags[0] !== allSampleFlags[1]; + const sampleDurationPresent = uniqueSampleDurations.size > 1; + const sampleSizePresent = uniqueSampleSizes.size > 1; + const sampleFlagsPresent = !firstSampleFlagsPresent && uniqueSampleFlags.size > 1; + const sampleCompositionTimeOffsetsPresent = uniqueSampleCompositionTimeOffsets.size > 1 || [...uniqueSampleCompositionTimeOffsets].some((x) => x !== 0); let flags = 0; flags |= 1; flags |= 4 * +firstSampleFlagsPresent; @@ -1050,7 +1050,7 @@ var Metamuxer = (() => { ]); }; var tfra = (trackData, trackIndex) => { - let version = 1; + const version = 1; return fullBox("tfra", version, 0, [ u32(trackData.track.id), // Track ID @@ -1090,32 +1090,32 @@ var Metamuxer = (() => { ]); var vtta = (notes) => box("vtta", [...textEncoder.encode(notes)]); var VIDEO_CODEC_TO_BOX_NAME = { - "avc": "avc1", - "hevc": "hvc1", - "vp8": "vp08", - "vp9": "vp09", - "av1": "av01" + avc: "avc1", + hevc: "hvc1", + vp8: "vp08", + vp9: "vp09", + av1: "av01" }; var VIDEO_CODEC_TO_CONFIGURATION_BOX = { - "avc": avcC, - "hevc": hvcC, - "vp8": vpcC, - "vp9": vpcC, - "av1": av1C + avc: avcC, + hevc: hvcC, + vp8: vpcC, + vp9: vpcC, + av1: av1C }; var AUDIO_CODEC_TO_BOX_NAME = { - "aac": "mp4a", - "opus": "Opus" + aac: "mp4a", + opus: "Opus" }; var AUDIO_CODEC_TO_CONFIGURATION_BOX = { - "aac": esds, - "opus": dOps + aac: esds, + opus: dOps }; var SUBTITLE_CODEC_TO_BOX_NAME = { - "webvtt": "wvtt" + webvtt: "wvtt" }; var SUBTITLE_CODEC_TO_CONFIGURATION_BOX = { - "webvtt": vttC + webvtt: vttC }; // src/muxer.ts @@ -1125,8 +1125,10 @@ var Metamuxer = (() => { this.trackTimestampInfo = /* @__PURE__ */ new WeakMap(); this.output = output; } + // eslint-disable-next-line @typescript-eslint/no-unused-vars beforeTrackAdd(track) { } + // eslint-disable-next-line @typescript-eslint/no-unused-vars onTrackClose(track) { } validateAndNormalizeTimestamp(track, rawTimestampInUs, isKeyFrame) { @@ -1153,11 +1155,15 @@ var Metamuxer = (() => { throw new Error(`Timestamps must be non-negative (got ${timestampInSeconds}s).`); } if (timestampInSeconds < timestampInfo.lastKeyFrameTimestamp) { - throw new Error(`Timestamp cannot be smaller than last key frame's timestamp (got ${timestampInSeconds}s, last key frame at ${timestampInfo.lastKeyFrameTimestamp}s).`); + throw new Error( + `Timestamp cannot be smaller than last key frame's timestamp (got ${timestampInSeconds}s, last key frame at ${timestampInfo.lastKeyFrameTimestamp}s).` + ); } if (isKeyFrame) { if (timestampInSeconds < timestampInfo.maxTimestamp) { - throw new Error(`Key frame timestamps cannot be smaller than any timestamp that came before (got ${timestampInSeconds}s, max timestamp was ${timestampInfo.maxTimestamp}s).`); + throw new Error( + `Key frame timestamps cannot be smaller than any timestamp that came before (got ${timestampInSeconds}s, max timestamp was ${timestampInfo.maxTimestamp}s).` + ); } timestampInfo.lastKeyFrameTimestamp = timestampInSeconds; } @@ -1188,8 +1194,8 @@ var Metamuxer = (() => { let newLength = this.buffer.byteLength; while (newLength < size) newLength *= 2; if (newLength === this.buffer.byteLength) return; - let newBuffer = new ArrayBuffer(newLength); - let newBytes = new Uint8Array(newBuffer); + const newBuffer = new ArrayBuffer(newLength); + const newBytes = new Uint8Array(newBuffer); newBytes.set(this.bytes, 0); this.buffer = newBuffer; this.bytes = newBytes; @@ -1208,6 +1214,7 @@ 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)); @@ -1244,15 +1251,15 @@ var Metamuxer = (() => { async flush() { assert(this.writer); if (this.sections.length === 0) return; - let chunks = []; - let sorted = [...this.sections].sort((a, b) => a.start - b.start); + const chunks = []; + const sorted = [...this.sections].sort((a, b) => a.start - b.start); chunks.push({ start: sorted[0].start, size: sorted[0].data.byteLength }); for (let i = 1; i < sorted.length; i++) { - let lastChunk = chunks[chunks.length - 1]; - let section = sorted[i]; + const lastChunk = chunks[chunks.length - 1]; + const section = sorted[i]; if (section.start <= lastChunk.start + lastChunk.size) { lastChunk.size = Math.max(lastChunk.size, section.start + section.data.byteLength - lastChunk.start); } else { @@ -1262,9 +1269,9 @@ var Metamuxer = (() => { }); } } - for (let chunk of chunks) { + for (const chunk of chunks) { chunk.data = new Uint8Array(chunk.size); - for (let section of this.sections) { + for (const section of this.sections) { if (chunk.start <= section.start && section.start < chunk.start + chunk.size) { chunk.data.set(section.data, section.start - chunk.start); } @@ -1275,7 +1282,7 @@ var Metamuxer = (() => { if (this.writer.desiredSize !== null && this.writer.desiredSize <= 0) { await this.writer.ready; } - this.writer.write({ + void this.writer.write({ type: "write", data: chunk.data, position: chunk.start @@ -1326,11 +1333,11 @@ var Metamuxer = (() => { writeDataIntoChunks(data, position) { let chunkIndex = this.chunks.findIndex((x) => x.start <= position && position < x.start + this.chunkSize); if (chunkIndex === -1) chunkIndex = this.createChunk(position); - let chunk = this.chunks[chunkIndex]; - let relativePosition = position - chunk.start; - let toWrite = data.subarray(0, Math.min(this.chunkSize - relativePosition, data.byteLength)); + const chunk = this.chunks[chunkIndex]; + const relativePosition = position - chunk.start; + const toWrite = data.subarray(0, Math.min(this.chunkSize - relativePosition, data.byteLength)); chunk.data.set(toWrite, relativePosition); - let section = { + const section = { start: relativePosition, end: relativePosition + toWrite.byteLength }; @@ -1353,7 +1360,7 @@ var Metamuxer = (() => { let high = chunk.written.length - 1; let index = -1; while (low <= high) { - let mid = Math.floor(low + (high - low + 1) / 2); + const mid = Math.floor(low + (high - low + 1) / 2); if (chunk.written[mid].start <= section.start) { low = mid + 1; index = mid; @@ -1369,8 +1376,8 @@ var Metamuxer = (() => { } } createChunk(includesPosition) { - let start = Math.floor(includesPosition / this.chunkSize) * this.chunkSize; - let chunk = { + const start = Math.floor(includesPosition / this.chunkSize) * this.chunkSize; + const chunk = { start, data: new Uint8Array(this.chunkSize), written: [], @@ -1383,9 +1390,9 @@ var Metamuxer = (() => { queueChunksForFlush(force = false) { assert(this.writer); for (let i = 0; i < this.chunks.length; i++) { - let chunk = this.chunks[i]; + const chunk = this.chunks[i]; if (!chunk.shouldFlush && !force) continue; - for (let section of chunk.written) { + for (const section of chunk.written) { if (this.ensureMonotonicity && chunk.start + section.start !== this.lastFlushEnd) { throw new Error("Internal error: Monotonicity violation."); } @@ -1402,11 +1409,11 @@ var Metamuxer = (() => { async flush() { assert(this.writer); if (this.flushedChunkQueue.length === 0) return; - for (let chunk of this.flushedChunkQueue) { + for (const chunk of this.flushedChunkQueue) { if (this.writer.desiredSize !== null && this.writer.desiredSize <= 0) { await this.writer.ready; } - this.writer.write(chunk); + void this.writer.write(chunk); } this.flushedChunkQueue.length = 0; } @@ -1637,15 +1644,14 @@ var Metamuxer = (() => { const hexLevelIndication = levelIndication.toString(16).padStart(2, "0"); return `avc1.${hexProfileIndication}${hexProfileCompatibility}${hexLevelIndication}`; } else if (codec === "hevc") { - let profileSpace = 0; - let profileIdc = 1; + const profilePrefix = ""; + const profileIdc = 1; const compatibilityFlags = "6"; const pictureSize = width * height; const levelInfo = HEVC_LEVEL_TABLE.find( (level) => pictureSize <= level.maxPictureSize && bitrate <= level.maxBitrate ) ?? last(HEVC_LEVEL_TABLE); const constraintFlags = "B0"; - const profilePrefix = profileSpace === 0 ? "" : String.fromCharCode(65 + profileSpace - 1); return `hev1.${profilePrefix}${profileIdc}.${compatibilityFlags}.${levelInfo.tier}${levelInfo.level}.${constraintFlags}`; } else if (codec === "vp8") { return "vp8"; @@ -1736,42 +1742,62 @@ var Metamuxer = (() => { throw new TypeError("Video chunk metadata decoder configuration must specify a codec string."); } if (!Number.isInteger(metadata.decoderConfig.codedWidth) || metadata.decoderConfig.codedWidth <= 0) { - throw new TypeError("Video chunk metadata decoder configuration must specify a valid codedWidth (positive integer)."); + throw new TypeError( + "Video chunk metadata decoder configuration must specify a valid codedWidth (positive integer)." + ); } if (!Number.isInteger(metadata.decoderConfig.codedHeight) || metadata.decoderConfig.codedHeight <= 0) { - throw new TypeError("Video chunk metadata decoder configuration must specify a valid codedHeight (positive integer)."); + throw new TypeError( + "Video chunk metadata decoder configuration must specify a valid codedHeight (positive integer)." + ); } if (metadata.decoderConfig.description !== void 0) { if (!isAllowSharedBufferSource(metadata.decoderConfig.description)) { - throw new TypeError("Video chunk metadata decoder configuration description, when defined, must be an ArrayBuffer or an ArrayBuffer view."); + throw new TypeError( + "Video chunk metadata decoder configuration description, when defined, must be an ArrayBuffer or an ArrayBuffer view." + ); } } if (metadata.decoderConfig.colorSpace !== void 0) { - let { colorSpace } = metadata.decoderConfig; + const { colorSpace } = metadata.decoderConfig; if (typeof colorSpace !== "object") { - throw new TypeError("Video chunk metadata decoder configuration colorSpace, when provided, must be an object."); + throw new TypeError( + "Video chunk metadata decoder configuration colorSpace, when provided, must be an object." + ); } - let primariesValues = Object.keys(COLOR_PRIMARIES_MAP); + const primariesValues = Object.keys(COLOR_PRIMARIES_MAP); if (colorSpace.primaries != null && !primariesValues.includes(colorSpace.primaries)) { - throw new TypeError(`Video chunk metadata decoder configuration colorSpace primaries, when defined, must be one of ${primariesValues.join(", ")}.`); + throw new TypeError( + `Video chunk metadata decoder configuration colorSpace primaries, when defined, must be one of ${primariesValues.join(", ")}.` + ); } - let transferValues = Object.keys(TRANSFER_CHARACTERISTICS_MAP); + const transferValues = Object.keys(TRANSFER_CHARACTERISTICS_MAP); if (colorSpace.transfer != null && !transferValues.includes(colorSpace.transfer)) { - throw new TypeError(`Video chunk metadata decoder configuration colorSpace transfer, when defined, must be one of ${transferValues.join(", ")}.`); + throw new TypeError( + `Video chunk metadata decoder configuration colorSpace transfer, when defined, must be one of ${transferValues.join(", ")}.` + ); } - let matrixValues = Object.keys(MATRIX_COEFFICIENTS_MAP); + const matrixValues = Object.keys(MATRIX_COEFFICIENTS_MAP); if (colorSpace.matrix != null && !matrixValues.includes(colorSpace.matrix)) { - throw new TypeError(`Video chunk metadata decoder configuration colorSpace matrix, when defined, must be one of ${matrixValues.join(", ")}.`); + throw new TypeError( + `Video chunk metadata decoder configuration colorSpace matrix, when defined, must be one of ${matrixValues.join(", ")}.` + ); } if (colorSpace.fullRange != null && typeof colorSpace.fullRange !== "boolean") { - throw new TypeError("Video chunk metadata decoder configuration colorSpace fullRange, when defined, must be a boolean."); + throw new TypeError( + "Video chunk metadata decoder configuration colorSpace fullRange, when defined, must be a boolean." + ); } } if ((metadata.decoderConfig.codec.startsWith("avc1") || metadata.decoderConfig.codec.startsWith("avc3")) && !metadata.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."); + 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 ((metadata.decoderConfig.codec.startsWith("hev1") || metadata.decoderConfig.codec.startsWith("hvc1")) && !metadata.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."); + 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 ((metadata.decoderConfig.codec === "vp8" || metadata.decoderConfig.codec.startsWith("vp09")) && metadata.decoderConfig.colorSpace === void 0) { throw new TypeError("Video chunk metadata decoder configuration for VP8/VP9 must include a colorSpace."); @@ -1794,18 +1820,26 @@ var Metamuxer = (() => { throw new TypeError("Audio chunk metadata decoder configuration must specify a codec string."); } if (!Number.isInteger(metadata.decoderConfig.sampleRate) || metadata.decoderConfig.sampleRate <= 0) { - throw new TypeError("Audio chunk metadata decoder configuration must specify a valid sampleRate (positive integer)."); + throw new TypeError( + "Audio chunk metadata decoder configuration must specify a valid sampleRate (positive integer)." + ); } if (!Number.isInteger(metadata.decoderConfig.numberOfChannels) || metadata.decoderConfig.numberOfChannels <= 0) { - throw new TypeError("Audio chunk metadata decoder configuration must specify a valid numberOfChannels (positive integer)."); + throw new TypeError( + "Audio chunk metadata decoder configuration must specify a valid numberOfChannels (positive integer)." + ); } if (metadata.decoderConfig.description !== void 0) { if (!isAllowSharedBufferSource(metadata.decoderConfig.description)) { - throw new TypeError("Audio chunk metadata decoder configuration description, when defined, must be an ArrayBuffer or an ArrayBuffer view."); + throw new TypeError( + "Audio chunk metadata decoder configuration description, when defined, must be an ArrayBuffer or an ArrayBuffer view." + ); } } if (metadata.decoderConfig.codec.startsWith("mp4a") && !metadata.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."); + 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 (metadata.decoderConfig.codec === "opus" && metadata.decoderConfig.description && metadata.decoderConfig.description.byteLength < 18) { throw new TypeError("Invalid decoder description provided for Opus; must be at least 18 bytes long."); @@ -1833,7 +1867,7 @@ var Metamuxer = (() => { var GLOBAL_TIMESCALE = 1e3; var TIMESTAMP_OFFSET = 2082844800; var intoTimescale = (timeInSeconds, timescale, round = true) => { - let value = timeInSeconds * timescale; + const value = timeInSeconds * timescale; return round ? Math.round(value) : value; }; var IsobmffMuxer = class extends Muxer { @@ -1851,7 +1885,8 @@ var Metamuxer = (() => { this.nextFragmentNumber = 1; this.writer = output._writer; this.boxWriter = new IsobmffBoxWriter(this.writer); - this.fastStart = format._options.fastStart ?? (this.writer instanceof ArrayBufferTargetWriter ? "in-memory" : false); + const fastStartDefault = this.writer instanceof ArrayBufferTargetWriter ? "in-memory" : false; + this.fastStart = format._options.fastStart ?? fastStartDefault; if (this.fastStart === "in-memory" || this.fastStart === "fragmented") { this.writer.ensureMonotonicity = true; } @@ -1980,10 +2015,20 @@ var Metamuxer = (() => { const release = await this.mutex.acquire(); try { const trackData = this.getVideoTrackData(track, meta); - let data = new Uint8Array(chunk.byteLength); + const data = new Uint8Array(chunk.byteLength); chunk.copyTo(data); - let timestamp = this.validateAndNormalizeTimestamp(trackData.track, chunk.timestamp, chunk.type === "key"); - let sample = this.createSampleForTrack(trackData, data, timestamp, (chunk.duration ?? 0) / 1e6, chunk.type); + const timestamp = this.validateAndNormalizeTimestamp( + trackData.track, + chunk.timestamp, + chunk.type === "key" + ); + const sample = this.createSampleForTrack( + trackData, + data, + timestamp, + (chunk.duration ?? 0) / 1e6, + chunk.type + ); await this.registerSample(trackData, sample); } finally { release(); @@ -1993,10 +2038,21 @@ var Metamuxer = (() => { const release = await this.mutex.acquire(); try { const trackData = this.getAudioTrackData(track, meta); - let data = new Uint8Array(chunk.byteLength); + const data = new Uint8Array(chunk.byteLength); chunk.copyTo(data); - let timestamp = this.validateAndNormalizeTimestamp(trackData.track, chunk.timestamp, chunk.type === "key"); - let sample = this.createSampleForTrack(trackData, data, timestamp, (chunk.duration ?? 0) / 1e6, chunk.type); + const chunkType = chunk.type; + const timestamp = this.validateAndNormalizeTimestamp( + trackData.track, + chunk.timestamp, + chunkType === "key" + ); + const sample = this.createSampleForTrack( + trackData, + data, + timestamp, + (chunk.duration ?? 0) / 1e6, + chunkType + ); await this.registerSample(trackData, sample); } finally { release(); @@ -2018,60 +2074,72 @@ var Metamuxer = (() => { } async processWebVTTCues(trackData, until) { while (trackData.cueQueue.length > 0) { - let timestamps = /* @__PURE__ */ new Set([]); - for (let cue of trackData.cueQueue) { + const timestamps = /* @__PURE__ */ new Set([]); + for (const cue of trackData.cueQueue) { assert(cue.timestamp <= until); assert(trackData.lastCueEndTimestamp <= cue.timestamp + cue.duration); timestamps.add(Math.max(cue.timestamp, trackData.lastCueEndTimestamp)); timestamps.add(cue.timestamp + cue.duration); } - let sortedTimestamps = [...timestamps].sort((a, b) => a - b); - let sampleStart = sortedTimestamps[0]; - let sampleEnd = sortedTimestamps[1] ?? sampleStart; + const sortedTimestamps = [...timestamps].sort((a, b) => a - b); + const sampleStart = sortedTimestamps[0]; + const sampleEnd = sortedTimestamps[1] ?? sampleStart; if (until < sampleEnd) { break; } if (trackData.lastCueEndTimestamp < sampleStart) { this.auxWriter.seek(0); - let box2 = vtte(); + const box2 = vtte(); this.auxBoxWriter.writeBox(box2); - let body2 = this.auxWriter.getSlice(0, this.auxWriter.getPos()); - let sample2 = this.createSampleForTrack(trackData, body2, trackData.lastCueEndTimestamp, sampleStart - trackData.lastCueEndTimestamp, "key"); + const body2 = this.auxWriter.getSlice(0, this.auxWriter.getPos()); + const sample2 = this.createSampleForTrack( + trackData, + body2, + trackData.lastCueEndTimestamp, + sampleStart - trackData.lastCueEndTimestamp, + "key" + ); await this.registerSample(trackData, sample2); trackData.lastCueEndTimestamp = sampleStart; } this.auxWriter.seek(0); for (let i = 0; i < trackData.cueQueue.length; i++) { - let cue = trackData.cueQueue[i]; + const cue = trackData.cueQueue[i]; if (cue.timestamp >= sampleEnd) { break; } inlineTimestampRegex.lastIndex = 0; - let containsTimestamp = inlineTimestampRegex.test(cue.text); - let endTimestamp = cue.timestamp + cue.duration; + const containsTimestamp = inlineTimestampRegex.test(cue.text); + const endTimestamp = cue.timestamp + cue.duration; let sourceId = trackData.cueToSourceId.get(cue); if (sourceId === void 0 && sampleEnd < endTimestamp) { sourceId = trackData.nextSourceId++; trackData.cueToSourceId.set(cue, sourceId); } if (cue.notes) { - let box3 = vtta(cue.notes); + const box3 = vtta(cue.notes); this.auxBoxWriter.writeBox(box3); } - let box2 = vttc(cue.text, containsTimestamp ? sampleStart : null, cue.identifier ?? null, cue.settings ?? null, sourceId ?? null); + const box2 = vttc( + cue.text, + containsTimestamp ? sampleStart : null, + cue.identifier ?? null, + cue.settings ?? null, + sourceId ?? null + ); this.auxBoxWriter.writeBox(box2); if (endTimestamp === sampleEnd) { trackData.cueQueue.splice(i--, 1); } } - let body = this.auxWriter.getSlice(0, this.auxWriter.getPos()); - let sample = this.createSampleForTrack(trackData, body, sampleStart, sampleEnd - sampleStart, "key"); + const body = this.auxWriter.getSlice(0, this.auxWriter.getPos()); + const sample = this.createSampleForTrack(trackData, body, sampleStart, sampleEnd - sampleStart, "key"); await this.registerSample(trackData, sample); trackData.lastCueEndTimestamp = sampleEnd; } } createSampleForTrack(trackData, data, timestamp, duration, type) { - let sample = { + const sample = { timestamp, decodeTimestamp: timestamp, // This may be refined later @@ -2096,8 +2164,8 @@ var Metamuxer = (() => { const durationInTimescale = intoTimescale(sample.duration, trackData.timescale); if (trackData.lastTimescaleUnits !== null) { assert(trackData.lastSample); - let timescaleUnits = intoTimescale(sample.decodeTimestamp, trackData.timescale, false); - let delta = Math.round(timescaleUnits - trackData.lastTimescaleUnits); + const timescaleUnits = intoTimescale(sample.decodeTimestamp, trackData.timescale, false); + const delta = Math.round(timescaleUnits - trackData.lastTimescaleUnits); trackData.lastTimescaleUnits += delta; trackData.lastSample.timescaleUnitsToNextSample = delta; if (this.fastStart !== "fragmented") { @@ -2105,7 +2173,7 @@ var Metamuxer = (() => { assert(lastTableEntry); if (lastTableEntry.sampleCount === 1) { lastTableEntry.sampleDelta = delta; - let entryBefore = trackData.timeToSampleTable[trackData.timeToSampleTable.length - 2]; + const entryBefore = trackData.timeToSampleTable[trackData.timeToSampleTable.length - 2]; if (entryBefore && entryBefore.sampleDelta === delta) { entryBefore.sampleCount++; trackData.timeToSampleTable.pop(); @@ -2173,7 +2241,7 @@ var Metamuxer = (() => { if (!trackData.currentChunk) { beginNewChunk = true; } else { - let currentChunkDuration = sample.timestamp - trackData.currentChunk.startTimestamp; + const currentChunkDuration = sample.timestamp - trackData.currentChunk.startTimestamp; if (this.fastStart === "fragmented") { const keyFrameQueuedEverywhere = this.trackDatas.every((otherTrackData) => { if (trackData === otherTrackData) { @@ -2222,7 +2290,7 @@ var Metamuxer = (() => { return; } trackData.currentChunk.offset = this.writer.getPos(); - for (let sample of trackData.currentChunk.samples) { + for (const sample of trackData.currentChunk.samples) { assert(sample.data); this.writer.write(sample.data); sample.data = null; @@ -2240,7 +2308,7 @@ var Metamuxer = (() => { while (true) { let trackWithMinTimestamp = null; let minTimestamp = Infinity; - for (let trackData of this.trackDatas) { + for (const trackData of this.trackDatas) { if (trackData.sampleQueue.length === 0 && !trackData.track.source._closed) { break outer; } @@ -2252,26 +2320,26 @@ var Metamuxer = (() => { if (!trackWithMinTimestamp) { break; } - let sample = trackWithMinTimestamp.sampleQueue.shift(); + const sample = trackWithMinTimestamp.sampleQueue.shift(); await this.addSampleToTrack(trackWithMinTimestamp, sample); } } async finalizeFragment(flushWriter = true) { assert(this.fastStart === "fragmented"); - let fragmentNumber = this.nextFragmentNumber++; + const fragmentNumber = this.nextFragmentNumber++; if (fragmentNumber === 1) { - let movieBox = moov(this.trackDatas, this.creationTime, true); + const movieBox = moov(this.trackDatas, this.creationTime, true); this.boxWriter.writeBox(movieBox); } - let moofOffset = this.writer.getPos(); - let moofBox = moof(fragmentNumber, this.trackDatas); + const moofOffset = this.writer.getPos(); + const moofBox = moof(fragmentNumber, this.trackDatas); this.boxWriter.writeBox(moofBox); { - let mdatBox = mdat(false); + const mdatBox = mdat(false); let totalTrackSampleSize = 0; - for (let trackData of this.trackDatas) { + for (const trackData of this.trackDatas) { assert(trackData.currentChunk); - for (let sample of trackData.currentChunk.samples) { + for (const sample of trackData.currentChunk.samples) { totalTrackSampleSize += sample.size; } } @@ -2283,20 +2351,20 @@ var Metamuxer = (() => { mdatBox.size = mdatSize; this.boxWriter.writeBox(mdatBox); } - for (let trackData of this.trackDatas) { + for (const trackData of this.trackDatas) { trackData.currentChunk.offset = this.writer.getPos(); trackData.currentChunk.moofOffset = moofOffset; - for (let sample of trackData.currentChunk.samples) { + for (const sample of trackData.currentChunk.samples) { this.writer.write(sample.data); sample.data = null; } } - let endPos = this.writer.getPos(); + const endPos = this.writer.getPos(); this.writer.seek(this.boxWriter.offsets.get(moofBox)); - let newMoofBox = moof(fragmentNumber, this.trackDatas); + const newMoofBox = moof(fragmentNumber, this.trackDatas); this.boxWriter.writeBox(newMoofBox); this.writer.seek(endPos); - for (let trackData of this.trackDatas) { + for (const trackData of this.trackDatas) { trackData.finalizedChunks.push(trackData.currentChunk); this.finalizedChunks.push(trackData.currentChunk); trackData.currentChunk = null; @@ -2305,10 +2373,11 @@ var Metamuxer = (() => { await this.writer.flush(); } } + // eslint-disable-next-line @typescript-eslint/no-misused-promises async onTrackClose(track) { const release = await this.mutex.acquire(); if (track.type === "subtitle" && track.source._codec === "webvtt") { - let trackData = this.trackDatas.find((x) => x.track === track); + const trackData = this.trackDatas.find((x) => x.track === track); if (trackData) { await this.processWebVTTCues(trackData, Infinity); } @@ -2321,21 +2390,21 @@ var Metamuxer = (() => { /** Finalizes the file, making it ready for use. Must be called after all video and audio chunks have been added. */ async finalize() { const release = await this.mutex.acquire(); - for (let trackData of this.trackDatas) { + for (const trackData of this.trackDatas) { if (trackData.type === "subtitle" && trackData.track.source._codec === "webvtt") { await this.processWebVTTCues(trackData, Infinity); } } if (this.fastStart === "fragmented") { - for (let trackData of this.trackDatas) { - for (let sample of trackData.sampleQueue) { + for (const trackData of this.trackDatas) { + for (const sample of trackData.sampleQueue) { await this.addSampleToTrack(trackData, sample); } this.processTimestamps(trackData); } await this.finalizeFragment(false); } else { - for (let trackData of this.trackDatas) { + for (const trackData of this.trackDatas) { this.processTimestamps(trackData); await this.finalizeCurrentChunk(trackData); } @@ -2344,13 +2413,13 @@ var Metamuxer = (() => { assert(this.mdat); let mdatSize; for (let i = 0; i < 2; i++) { - let movieBox2 = moov(this.trackDatas, this.creationTime); - let movieBoxSize = this.boxWriter.measureBox(movieBox2); + const movieBox2 = moov(this.trackDatas, this.creationTime); + const movieBoxSize = this.boxWriter.measureBox(movieBox2); mdatSize = this.boxWriter.measureBox(this.mdat); let currentChunkPos = this.writer.getPos() + movieBoxSize + mdatSize; - for (let chunk of this.finalizedChunks) { + for (const chunk of this.finalizedChunks) { chunk.offset = currentChunkPos; - for (let { data } of chunk.samples) { + for (const { data } of chunk.samples) { assert(data); currentChunkPos += data.byteLength; mdatSize += data.byteLength; @@ -2359,38 +2428,38 @@ var Metamuxer = (() => { if (currentChunkPos < 2 ** 32) break; if (mdatSize >= 2 ** 32) this.mdat.largeSize = true; } - let movieBox = moov(this.trackDatas, this.creationTime); + const movieBox = moov(this.trackDatas, this.creationTime); this.boxWriter.writeBox(movieBox); this.mdat.size = mdatSize; this.boxWriter.writeBox(this.mdat); - for (let chunk of this.finalizedChunks) { - for (let sample of chunk.samples) { + for (const chunk of this.finalizedChunks) { + for (const sample of chunk.samples) { assert(sample.data); this.writer.write(sample.data); sample.data = null; } } } else if (this.fastStart === "fragmented") { - let startPos = this.writer.getPos(); - let mfraBox = mfra(this.trackDatas); + const startPos = this.writer.getPos(); + const mfraBox = mfra(this.trackDatas); this.boxWriter.writeBox(mfraBox); - let mfraBoxSize = this.writer.getPos() - startPos; + const mfraBoxSize = this.writer.getPos() - startPos; this.writer.seek(this.writer.getPos() - 4); this.boxWriter.writeU32(mfraBoxSize); } else { assert(this.mdat); assert(this.ftypSize !== null); - let mdatPos = this.boxWriter.offsets.get(this.mdat); + const mdatPos = this.boxWriter.offsets.get(this.mdat); assert(mdatPos !== void 0); - let mdatSize = this.writer.getPos() - mdatPos; + const mdatSize = this.writer.getPos() - mdatPos; this.mdat.size = mdatSize; this.mdat.largeSize = mdatSize >= 2 ** 32; this.boxWriter.patchBox(this.mdat); - let movieBox = moov(this.trackDatas, this.creationTime); + const movieBox = moov(this.trackDatas, this.creationTime); if (typeof this.fastStart === "object") { this.writer.seek(this.ftypSize); this.boxWriter.writeBox(movieBox); - let remainingBytes = mdatPos - this.writer.getPos(); + const remainingBytes = mdatPos - this.writer.getPos(); this.boxWriter.writeBox(free(remainingBytes)); } else { this.boxWriter.writeBox(movieBox); @@ -2531,14 +2600,19 @@ var Metamuxer = (() => { switch (width) { case 6: this.helperView.setUint8(pos++, value / 2 ** 40 | 0); + // eslint-disable-next-line no-fallthrough case 5: this.helperView.setUint8(pos++, value / 2 ** 32 | 0); + // eslint-disable-next-line no-fallthrough case 4: this.helperView.setUint8(pos++, value >> 24); + // eslint-disable-next-line no-fallthrough case 3: this.helperView.setUint8(pos++, value >> 16); + // eslint-disable-next-line no-fallthrough case 2: this.helperView.setUint8(pos++, value >> 8); + // eslint-disable-next-line no-fallthrough case 1: this.helperView.setUint8(pos++, value); break; @@ -2603,32 +2677,32 @@ var Metamuxer = (() => { if (data instanceof Uint8Array) { this.writer.write(data); } else if (Array.isArray(data)) { - for (let elem of data) { + for (const elem of data) { this.writeEBML(elem); } } else { this.offsets.set(data, this.writer.getPos()); this.writeUnsignedInt(data.id); if (Array.isArray(data.data)) { - let sizePos = this.writer.getPos(); - let sizeSize = data.size === -1 ? 1 : data.size ?? 4; + const sizePos = this.writer.getPos(); + const sizeSize = data.size === -1 ? 1 : data.size ?? 4; if (data.size === -1) { this.writeByte(255); } else { this.writer.seek(this.writer.getPos() + sizeSize); } - let startPos = this.writer.getPos(); + const startPos = this.writer.getPos(); this.dataOffsets.set(data, startPos); this.writeEBML(data.data); if (data.size !== -1) { - let size = this.writer.getPos() - startPos; - let endPos = this.writer.getPos(); + const size = this.writer.getPos() - startPos; + const endPos = this.writer.getPos(); this.writer.seek(sizePos); this.writeEBMLVarInt(size, sizeSize); this.writer.seek(endPos); } } else if (typeof data.data === "number") { - let size = data.size ?? measureUnsignedInt(data.data); + const size = data.size ?? measureUnsignedInt(data.data); this.writeEBMLVarInt(size); this.writeUnsignedInt(data.data, size); } else if (typeof data.data === "string") { @@ -2644,7 +2718,7 @@ var Metamuxer = (() => { this.writeEBMLVarInt(8); this.writeFloat64(data.data.value); } else if (data.data instanceof EBMLSignedInt) { - let size = data.size ?? measureSignedInt(data.data.value); + const size = data.size ?? measureSignedInt(data.data.value); this.writeEBMLVarInt(size); this.writeSignedInt(data.data.value, size); } @@ -2656,18 +2730,26 @@ var Metamuxer = (() => { } if (track.type === "video") { if (!["vp8", "vp9", "av1"].includes(track.source._codec)) { - throw new Error(`WebM only supports VP8, VP9 and AV1 as video codecs. Switching to MKV removes this restriction.`); + throw new Error( + `WebM only supports VP8, VP9 and AV1 as video codecs. Switching to MKV removes this restriction.` + ); } } else if (track.type === "audio") { if (!["opus", "vorbis"].includes(track.source._codec)) { - throw new Error(`WebM only supports Opus and Vorbis as audio codecs. Switching to MKV removes this restriction.`); + throw new Error( + `WebM only supports Opus and Vorbis as audio codecs. Switching to MKV removes this restriction.` + ); } } else if (track.type === "subtitle") { if (track.source._codec !== "webvtt") { - throw new Error(`WebM only supports WebVTT as subtitle codec. Switching to MKV removes this restriction.`); + 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."); + throw new Error( + "WebM only supports video, audio and subtitle tracks. Switching to MKV removes this restriction." + ); } } async start() { @@ -2682,7 +2764,7 @@ var Metamuxer = (() => { release(); } writeEBMLHeader() { - let ebmlHeader = { id: 440786851 /* EBML */, data: [ + const ebmlHeader = { id: 440786851 /* EBML */, data: [ { id: 17030 /* EBMLVersion */, data: 1 }, { id: 17143 /* EBMLReadVersion */, data: 1 }, { id: 17138 /* EBMLMaxIDLength */, data: 4 }, @@ -2701,7 +2783,7 @@ var Metamuxer = (() => { const kaxCues = new Uint8Array([28, 83, 187, 107]); const kaxInfo = new Uint8Array([21, 73, 169, 102]); const kaxTracks = new Uint8Array([22, 84, 174, 107]); - let seekHead = { id: 290298740 /* SeekHead */, data: [ + const seekHead = { id: 290298740 /* SeekHead */, data: [ { id: 19899 /* Seek */, data: [ { id: 21419 /* SeekID */, data: kaxCues }, { id: 21420 /* SeekPosition */, size: 5, data: 0 } @@ -2718,9 +2800,9 @@ var Metamuxer = (() => { this.seekHead = seekHead; } createSegmentInfo() { - let segmentDuration = { id: 17545 /* Duration */, data: new EBMLFloat64(0) }; + const segmentDuration = { id: 17545 /* Duration */, data: new EBMLFloat64(0) }; this.segmentDuration = segmentDuration; - let segmentInfo = { id: 357149030 /* Info */, data: [ + const segmentInfo = { id: 357149030 /* Info */, data: [ { id: 2807729 /* TimestampScale */, data: 1e6 }, { id: 19840 /* MuxingApp */, data: APP_NAME }, { id: 22337 /* WritingApp */, data: APP_NAME }, @@ -2729,53 +2811,80 @@ var Metamuxer = (() => { this.segmentInfo = segmentInfo; } createTracks() { - let tracksElement = { id: 374648427 /* Tracks */, data: [] }; + const tracksElement = { id: 374648427 /* Tracks */, data: [] }; this.tracksElement = tracksElement; - for (let trackData of this.trackDatas) { + for (const trackData of this.trackDatas) { tracksElement.data.push({ id: 174 /* TrackEntry */, data: [ { id: 215 /* TrackNumber */, data: trackData.track.id }, { id: 29637 /* TrackUID */, data: trackData.track.id }, { id: 131 /* TrackType */, data: TRACK_TYPE_MAP[trackData.type] }, { id: 134 /* CodecID */, data: CODEC_STRING_MAP[trackData.track.source._codec] }, - ...trackData.type === "video" ? [ - trackData.info.decoderConfig.description ? { id: 25506 /* CodecPrivate */, data: toUint8Array(trackData.info.decoderConfig.description) } : null, - trackData.track.metadata.frameRate ? { id: 2352003 /* DefaultDuration */, data: 1e9 / trackData.track.metadata.frameRate } : null, - { id: 224 /* Video */, data: [ - { id: 176 /* PixelWidth */, data: trackData.info.width }, - { id: 186 /* PixelHeight */, data: trackData.info.height }, - (() => { - if (trackData.info.decoderConfig.colorSpace) { - let colorSpace = trackData.info.decoderConfig.colorSpace; - if (!colorSpaceIsComplete(colorSpace)) { - return null; - } - return { id: 21936 /* Colour */, data: [ - { id: 21937 /* MatrixCoefficients */, data: MATRIX_COEFFICIENTS_MAP[colorSpace.matrix] }, - { id: 21946 /* TransferCharacteristics */, data: TRANSFER_CHARACTERISTICS_MAP[colorSpace.transfer] }, - { id: 21947 /* Primaries */, data: COLOR_PRIMARIES_MAP[colorSpace.primaries] }, - { id: 21945 /* Range */, data: [1, 2][Number(colorSpace.fullRange)] } - ] }; - } - return null; - })() - ] } - ] : [], - ...trackData.type === "audio" ? [ - trackData.info.decoderConfig.description ? { id: 25506 /* CodecPrivate */, data: toUint8Array(trackData.info.decoderConfig.description) } : null, - { id: 225 /* Audio */, data: [ - { id: 181 /* SamplingFrequency */, data: new EBMLFloat32(trackData.info.sampleRate) }, - { id: 159 /* Channels */, data: trackData.info.numberOfChannels } - // Bit depth for when PCM is a thing - ] } - ] : [], - ...trackData.type === "subtitle" ? [ - { id: 25506 /* CodecPrivate */, data: textEncoder.encode(trackData.info.config.description) } - ] : [] + trackData.type === "video" ? this.videoSpecificTrackInfo(trackData) : null, + trackData.type === "audio" ? this.audioSpecificTrackInfo(trackData) : null, + trackData.type === "subtitle" ? this.subtitleSpecificTrackInfo(trackData) : null ] }); } } + videoSpecificTrackInfo(trackData) { + const elements = [ + trackData.info.decoderConfig.description ? { + id: 25506 /* CodecPrivate */, + data: toUint8Array(trackData.info.decoderConfig.description) + } : null, + trackData.track.metadata.frameRate ? { + id: 2352003 /* DefaultDuration */, + data: 1e9 / trackData.track.metadata.frameRate + } : null + ]; + const colorSpace = trackData.info.decoderConfig.colorSpace; + const videoElement = { id: 224 /* Video */, data: [ + { id: 176 /* PixelWidth */, data: trackData.info.width }, + { id: 186 /* PixelHeight */, data: trackData.info.height }, + colorSpaceIsComplete(colorSpace) ? { + id: 21936 /* Colour */, + data: [ + { + id: 21937 /* MatrixCoefficients */, + data: MATRIX_COEFFICIENTS_MAP[colorSpace.matrix] + }, + { + id: 21946 /* TransferCharacteristics */, + data: TRANSFER_CHARACTERISTICS_MAP[colorSpace.transfer] + }, + { + id: 21947 /* Primaries */, + data: COLOR_PRIMARIES_MAP[colorSpace.primaries] + }, + { + id: 21945 /* Range */, + data: colorSpace.fullRange ? 2 : 1 + } + ] + } : null + ] }; + elements.push(videoElement); + return elements; + } + audioSpecificTrackInfo(trackData) { + return [ + trackData.info.decoderConfig.description ? { + id: 25506 /* CodecPrivate */, + data: toUint8Array(trackData.info.decoderConfig.description) + } : null, + { id: 225 /* Audio */, data: [ + { id: 181 /* SamplingFrequency */, data: new EBMLFloat32(trackData.info.sampleRate) }, + { id: 159 /* Channels */, data: trackData.info.numberOfChannels } + // TODO Bit depth for when PCM is a thing + ] } + ]; + } + subtitleSpecificTrackInfo(trackData) { + return [ + { id: 25506 /* CodecPrivate */, data: textEncoder.encode(trackData.info.config.description) } + ]; + } createSegment() { - let segment = { + const segment = { id: 408125543 /* Segment */, size: this.format._options.streamable ? -1 : SEGMENT_SIZE_BYTES, data: [ @@ -2867,10 +2976,11 @@ var Metamuxer = (() => { const release = await this.mutex.acquire(); try { const trackData = this.getVideoTrackData(track, meta); - let data = new Uint8Array(chunk.byteLength); + const data = new Uint8Array(chunk.byteLength); chunk.copyTo(data); - let timestamp = this.validateAndNormalizeTimestamp(trackData.track, chunk.timestamp, chunk.type === "key"); - let videoChunk = this.createInternalChunk(data, timestamp, (chunk.duration ?? 0) / 1e6, chunk.type); + const isKeyFrame = chunk.type === "key"; + const timestamp = this.validateAndNormalizeTimestamp(trackData.track, chunk.timestamp, isKeyFrame); + const videoChunk = this.createInternalChunk(data, timestamp, (chunk.duration ?? 0) / 1e6, chunk.type); if (track.source._codec === "vp9") this.fixVP9ColorSpace(trackData, videoChunk); trackData.chunkQueue.push(videoChunk); await this.interleaveChunks(); @@ -2882,10 +2992,12 @@ var Metamuxer = (() => { const release = await this.mutex.acquire(); try { const trackData = this.getAudioTrackData(track, meta); - let data = new Uint8Array(chunk.byteLength); + const data = new Uint8Array(chunk.byteLength); chunk.copyTo(data); - let timestamp = this.validateAndNormalizeTimestamp(trackData.track, chunk.timestamp, chunk.type === "key"); - let audioChunk = this.createInternalChunk(data, timestamp, (chunk.duration ?? 0) / 1e6, chunk.type); + const chunkType = chunk.type; + const isKeyFrame = chunkType === "key"; + const timestamp = this.validateAndNormalizeTimestamp(trackData.track, chunk.timestamp, isKeyFrame); + const audioChunk = this.createInternalChunk(data, timestamp, (chunk.duration ?? 0) / 1e6, chunkType); trackData.chunkQueue.push(audioChunk); await this.interleaveChunks(); } finally { @@ -2901,15 +3013,21 @@ var Metamuxer = (() => { const timestampMs = Math.floor(timestamp * 1e3); inlineTimestampRegex.lastIndex = 0; bodyText = bodyText.replace(inlineTimestampRegex, (match) => { - let time = parseSubtitleTimestamp(match.slice(1, -1)); - let offsetTime = time - timestampMs; + const time = parseSubtitleTimestamp(match.slice(1, -1)); + const offsetTime = time - timestampMs; return `<${formatSubtitleTimestamp(offsetTime)}>`; }); const body = textEncoder.encode(bodyText); const additions = `${cue.settings ?? ""} ${cue.identifier ?? ""} ${cue.notes ?? ""}`; - let subtitleChunk = this.createInternalChunk(body, timestamp, cue.duration, "key", additions.trim() ? textEncoder.encode(additions) : null); + const subtitleChunk = this.createInternalChunk( + body, + timestamp, + cue.duration, + "key", + additions.trim() ? textEncoder.encode(additions) : null + ); trackData.chunkQueue.push(subtitleChunk); await this.interleaveChunks(); } finally { @@ -2926,7 +3044,7 @@ ${cue.notes ?? ""}`; while (true) { let trackWithMinTimestamp = null; let minTimestamp = Infinity; - for (let trackData of this.trackDatas) { + for (const trackData of this.trackDatas) { if (trackData.chunkQueue.length === 0 && !trackData.track.source._closed) { break outer; } @@ -2938,7 +3056,7 @@ ${cue.notes ?? ""}`; if (!trackWithMinTimestamp) { break; } - let chunk = trackWithMinTimestamp.chunkQueue.shift(); + const chunk = trackWithMinTimestamp.chunkQueue.shift(); this.writeBlock(trackWithMinTimestamp, chunk); } await this.writer.flush(); @@ -2952,31 +3070,31 @@ ${cue.notes ?? ""}`; let i = 0; if (readBits(chunk.data, 0, 2) !== 2) return; i += 2; - let profile = (readBits(chunk.data, i + 1, i + 2) << 1) + readBits(chunk.data, i + 0, i + 1); + const profile = (readBits(chunk.data, i + 1, i + 2) << 1) + readBits(chunk.data, i + 0, i + 1); i += 2; if (profile === 3) i++; - let showExistingFrame = readBits(chunk.data, i + 0, i + 1); + const showExistingFrame = readBits(chunk.data, i + 0, i + 1); i++; if (showExistingFrame) return; - let frameType = readBits(chunk.data, i + 0, i + 1); + const frameType = readBits(chunk.data, i + 0, i + 1); i++; if (frameType !== 0) return; i += 2; - let syncCode = readBits(chunk.data, i + 0, i + 24); + const syncCode = readBits(chunk.data, i + 0, i + 24); i += 24; if (syncCode !== 4817730) return; if (profile >= 2) i++; - let colorSpaceID = { - "rgb": 7, - "bt709": 2, - "bt470bg": 1, - "smpte170m": 3 + const colorSpaceID = { + rgb: 7, + bt709: 2, + bt470bg: 1, + smpte170m: 3 }[trackData.info.decoderConfig.colorSpace.matrix]; writeBits(chunk.data, i + 0, i + 3, colorSpaceID); } /** Converts a read-only external chunk into an internal one for easier use. */ createInternalChunk(data, timestamp, duration, type, additions = null) { - let internalChunk = { + const internalChunk = { data, type, timestamp, @@ -2991,7 +3109,7 @@ ${cue.notes ?? ""}`; this.createTracks(); this.createSegment(); } - let msTimestamp = Math.floor(1e3 * chunk.timestamp); + const msTimestamp = Math.floor(1e3 * chunk.timestamp); const keyFrameQueuedEverywhere = this.trackDatas.every((otherTrackData) => { if (otherTrackData.track.source._closed) { return true; @@ -3005,35 +3123,38 @@ ${cue.notes ?? ""}`; if (!this.currentCluster || keyFrameQueuedEverywhere && msTimestamp - this.currentClusterMsTimestamp >= 1e3) { this.createNewCluster(msTimestamp); } - let relativeTimestamp = msTimestamp - this.currentClusterMsTimestamp; + const relativeTimestamp = msTimestamp - this.currentClusterMsTimestamp; if (relativeTimestamp < 0) { return; } - let clusterIsTooLong = relativeTimestamp >= MAX_CHUNK_LENGTH_MS; + const clusterIsTooLong = relativeTimestamp >= MAX_CHUNK_LENGTH_MS; if (clusterIsTooLong) { throw new Error( `Current Matroska cluster exceeded its maximum allowed length of ${MAX_CHUNK_LENGTH_MS} milliseconds. In order to produce a correct WebM file, you must pass in a key frame at least every ${MAX_CHUNK_LENGTH_MS} milliseconds.` ); } - let prelude = new Uint8Array(4); - let view2 = new DataView(prelude.buffer); + const prelude = new Uint8Array(4); + const view2 = new DataView(prelude.buffer); view2.setUint8(0, 128 | trackData.track.id); view2.setInt16(1, relativeTimestamp, false); - let msDuration = Math.floor(1e3 * chunk.duration); + const msDuration = Math.floor(1e3 * chunk.duration); if (msDuration === 0 && !chunk.additions) { view2.setUint8(3, Number(chunk.type === "key") << 7); - let simpleBlock = { id: 163 /* SimpleBlock */, data: [ + const simpleBlock = { id: 163 /* SimpleBlock */, data: [ prelude, chunk.data ] }; this.writeEBML(simpleBlock); } else { - let blockGroup = { id: 160 /* BlockGroup */, data: [ + const blockGroup = { id: 160 /* BlockGroup */, data: [ { id: 161 /* Block */, data: [ prelude, chunk.data ] }, - chunk.type === "delta" ? { id: 251 /* ReferenceBlock */, data: new EBMLSignedInt(trackData.lastWrittenMsTimestamp - msTimestamp) } : null, + chunk.type === "delta" ? { + id: 251 /* ReferenceBlock */, + data: new EBMLSignedInt(trackData.lastWrittenMsTimestamp - msTimestamp) + } : null, chunk.additions ? { id: 30113 /* BlockAdditions */, data: [ { id: 166 /* BlockMore */, data: [ { id: 165 /* BlockAdditional */, data: chunk.additions }, @@ -3066,12 +3187,12 @@ ${cue.notes ?? ""}`; } finalizeCurrentCluster() { assert(this.currentCluster); - let clusterSize = this.writer.getPos() - this.dataOffsets.get(this.currentCluster); - let endPos = this.writer.getPos(); + const clusterSize = this.writer.getPos() - this.dataOffsets.get(this.currentCluster); + const endPos = this.writer.getPos(); this.writer.seek(this.offsets.get(this.currentCluster) + 4); this.writeEBMLVarInt(clusterSize, CLUSTER_SIZE_BYTES); this.writer.seek(endPos); - let clusterOffsetFromSegment = this.offsets.get(this.currentCluster) - this.segmentDataOffset; + const clusterOffsetFromSegment = this.offsets.get(this.currentCluster) - this.segmentDataOffset; assert(this.cues); this.cues.data.push({ id: 187 /* CuePoint */, data: [ { id: 179 /* CueTime */, data: this.currentClusterMsTimestamp }, @@ -3084,6 +3205,7 @@ ${cue.notes ?? ""}`; }) ] }); } + // eslint-disable-next-line @typescript-eslint/no-misused-promises async onTrackClose() { const release = await this.mutex.acquire(); await this.interleaveChunks(); @@ -3096,7 +3218,7 @@ ${cue.notes ?? ""}`; this.createTracks(); this.createSegment(); } - for (let trackData of this.trackDatas) { + for (const trackData of this.trackDatas) { while (trackData.chunkQueue.length > 0) { this.writeBlock(trackData, trackData.chunkQueue.shift()); } @@ -3107,8 +3229,8 @@ ${cue.notes ?? ""}`; assert(this.cues); this.writeEBML(this.cues); if (!this.format._options.streamable) { - let endPos = this.writer.getPos(); - let segmentSize = this.writer.getPos() - this.segmentDataOffset; + const endPos = this.writer.getPos(); + const segmentSize = this.writer.getPos() - this.segmentDataOffset; this.writer.seek(this.offsets.get(this.segment) + 4); this.writeEBMLVarInt(segmentSize, SEGMENT_SIZE_BYTES); this.segmentDuration.data = new EBMLFloat64(this.duration); @@ -3248,7 +3370,7 @@ ${cue.notes ?? ""}`; if (!Number.isInteger(config.bitrate) || config.bitrate <= 0) { throw new TypeError("config.bitrate must be a positive integer."); } - if (config.latencyMode !== void 0 && ["quality", "realtime"].includes(config.latencyMode)) { + if (config.latencyMode !== void 0 && !["quality", "realtime"].includes(config.latencyMode)) { throw new TypeError("config.latencyMode, when provided, must be 'quality' or 'realtime'."); } }; @@ -3267,7 +3389,9 @@ ${cue.notes ?? ""}`; this.source._ensureValidDigest(); 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}.`); + 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; @@ -3276,7 +3400,9 @@ ${cue.notes ?? ""}`; this.ensureEncoder(videoFrame); assert(this.encoder); const multipleOfKeyFrameInterval = Math.floor(videoFrame.timestamp / 1e6 / KEY_FRAME_INTERVAL); - this.encoder.encode(videoFrame, { keyFrame: multipleOfKeyFrameInterval !== this.lastMultipleOfKeyFrameInterval }); + this.encoder.encode(videoFrame, { + keyFrame: multipleOfKeyFrameInterval !== this.lastMultipleOfKeyFrameInterval + }); this.lastMultipleOfKeyFrameInterval = multipleOfKeyFrameInterval; if (this.encoder.encodeQueueSize >= 4) { await new Promise((resolve) => this.encoder.addEventListener("dequeue", resolve, { once: true })); @@ -3288,11 +3414,16 @@ ${cue.notes ?? ""}`; return; } this.encoder = new VideoEncoder({ - output: (chunk, meta) => this.muxer.addEncodedVideoChunk(this.source._connectedTrack, chunk, meta), + 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), + codec: buildVideoCodecString( + this.codecConfig.codec, + videoFrame.codedWidth, + videoFrame.codedHeight, + this.codecConfig.bitrate + ), width: videoFrame.codedWidth, height: videoFrame.codedHeight, bitrate: this.codecConfig.bitrate, @@ -3379,7 +3510,7 @@ ${cue.notes ?? ""}`; const processor = new MediaStreamTrackProcessor({ track: this._track }); const consumer = new WritableStream({ write: (videoFrame) => { - this._encoder.digest(videoFrame); + void this._encoder.digest(videoFrame); videoFrame.close(); } }); @@ -3447,7 +3578,9 @@ ${cue.notes ?? ""}`; this.source._ensureValidDigest(); 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.`); + 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; @@ -3466,7 +3599,7 @@ ${cue.notes ?? ""}`; return; } this.encoder = new AudioEncoder({ - output: (chunk, meta) => this.muxer.addEncodedAudioChunk(this.source._connectedTrack, chunk, meta), + output: (chunk, meta) => void this.muxer.addEncodedAudioChunk(this.source._connectedTrack, chunk, meta), error: (error) => console.error("Audio encode error:", error) }); this.encoder.configure({ @@ -3558,7 +3691,7 @@ ${cue.notes ?? ""}`; const processor = new MediaStreamTrackProcessor({ track: this._track }); const consumer = new WritableStream({ write: (audioData) => { - this._encoder.digest(audioData); + void this._encoder.digest(audioData); audioData.close(); } }); diff --git a/dist/metamuxer.min.js b/dist/metamuxer.min.js index 6e057fb..9f2561c 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 wt=(t,i)=>{for(var e in i)Pe(t,e,{get:i[e],enumerable:!0})},Tt=(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=>Tt(Pe({},"__esModule",{value:!0}),t);var _r={};wt(_r,{AUDIO_CODECS:()=>Y,ArrayBufferTarget:()=>K,AudioBufferSource:()=>Ee,AudioDataSource:()=>Oe,AudioSource:()=>E,CanvasSource:()=>ve,EncodedAudioChunkSource:()=>_e,EncodedVideoChunkSource:()=>Se,MediaSource:()=>W,MediaStreamAudioTrackSource:()=>Me,MediaStreamVideoTrackSource:()=>Ae,MkvOutputFormat:()=>se,Mp4OutputFormat:()=>ge,Output:()=>ze,OutputFormat:()=>z,SUBTITLE_CODECS:()=>ke,StreamTarget:()=>he,SubtitleSource:()=>R,Target:()=>V,TextSubtitleSource:()=>Ve,VIDEO_CODECS:()=>G,VideoFrameSource:()=>ye,VideoSource:()=>O,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>n;r<<=1,r|=u}return r},Qe=(t,i,e,r)=>{for(let s=i;s>e-s-1<t instanceof ArrayBuffer?new Uint8Array(t):new Uint8Array(t.buffer,t.byteOffset,t.byteLength),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},ne=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,gt=/^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 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 `,` `).replaceAll("\r",` -`),Z.lastIndex=0;let e;if(!this.preambleText){if(!gt.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],a=e.index+e[0].length,o=i.indexOf(` -`,a)+1,n=i.slice(a,o).trim(),u=i.indexOf(` +`),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(` -`,a);u===-1&&(u=i.length);let c=le(e[2]),f=le(e[3])-c,w=i.slice(o,u).trim();i=i.slice(u).trimStart(),Z.lastIndex=0;let M={timestamp:c/1e3,duration:f/1e3,text:w,identifier:s,settings:n,notes:r},C={};this.preambleEmitted||(C.config={description:this.preambleText},this.preambleEmitted=!0),this.options.output(M,C)}}},kt=/(?:(\d{2}):)?(\d{2}):(\d{2}).(\d{3})/,le=t=>{let i=kt.exec(t);if(!i)throw new Error("Expected match.");return 60*60*1e3*Number(i[1]||"0")+60*1e3*Number(i[2])+1e3*Number(i[3])+Number(i[4])},ce=t=>{let i=Math.floor(t/36e5),e=Math.floor(t%(60*60*1e3)/(60*1e3)),r=Math.floor(t%(60*1e3)/1e3),s=t%1e3;return i.toString().padStart(2,"0")+":"+e.toString().padStart(2,"0")+":"+r.toString().padStart(2,"0")+"."+s.toString().padStart(3,"0")};var J=class{constructor(i){this.writer=i;this.helper=new Uint8Array(8);this.helperView=new DataView(this.helper.buffer);this.offsets=new WeakMap}writeU32(i){this.helperView.setUint32(0,i,!1),this.writer.write(this.helper.subarray(0,4))}writeU64(i){this.helperView.setUint32(0,Math.floor(i/2**32),!1),this.helperView.setUint32(4,i,!1),this.writer.write(this.helper.subarray(0,8))}writeAscii(i){for(let e=0;e[(t%256+256)%256],h=t=>(_.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]]),l=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()},k=(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,[T(i),je(e),r??[]],s),Ge=t=>{let i=512;return t.fragmented?b("ftyp",[k("iso5"),l(i),k("iso5"),k("iso6"),k("mp41")]):b("ftyp",[k("isom"),l(i),k("isom"),t.holdsAvc?k("avc1"):[],k("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(o=>o.samples.length>0).map(o=>{let n=We(o.samples);return n.timestamp+n.duration})),de),r=Math.max(0,...i.map(o=>o.track.id))+1,s=!P(t)||!P(e),a=s?U:l;return x("mvhd",+s,0,[a(t),a(t),l(de),a(e),A(1),De(1),Array(10).fill(0),Xe(Ke),Array(24).fill(0),l(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),a=s?U:l,o;if(t.type==="video"){let n=t.track.metadata.rotation;o=n===void 0||typeof n=="number"?qe(n??0):n}else o=Ke;return x("tkhd",+s,3,[a(i),a(i),l(t.track.id),l(0),a(r),Array(8).fill(0),h(0),h(t.track.id),De(t.type==="audio"?1:0),h(0),Xe(o),A(t.type==="video"?t.info.width:0),A(t.type==="video"?t.info.height:0)])},_t=(t,i)=>b("mdia",void 0,[Ot(t,i),Vt(t),zt(t)]),Ot=(t,i)=>{let e=We(t.samples),r=v(e?e.timestamp+e.duration:0,t.timescale),s=!P(i)||!P(r),a=s?U:l;return x("mdhd",+s,0,[a(i),a(i),l(t.timescale),a(r),h(21956),h(0)])},Et={video:"vide",audio:"soun",subtitle:"text"},Mt={video:"VideoHandler",audio:"SoundHandler",subtitle:"TextHandler"},Vt=t=>x("hdlr",0,0,[k("mhlr"),k(Et[t.type]),l(0),l(0),l(0),k(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,[l(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,[l(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),l(4718592),l(4718592),l(0),h(1),Array(32).fill(0),h(24),St(65535)],[pr[i.track.source._codec](i),ne(i.info.decoderConfig.colorSpace)?Qt(i):null]),Qt=t=>b("colr",[k("nclx"),h(H[t.info.decoderConfig.colorSpace.primaries]),h(Q[t.info.decoderConfig.colorSpace.transfer]),h($[t.info.decoderConfig.colorSpace.matrix]),T((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]),n=(Number(e[3])<<4)+(0<<1)+Number(i.colorSpace.fullRange);return x("vpcC",1,0,[T(r),T(s),T(n),T(2),T(2),T(2),h(0)])},Lt=()=>{let e=(1<<7)+1;return b("av1C",[e,0,0,0])},qt=(t,i)=>b(t,[Array(6).fill(0),h(1),h(0),h(0),l(0),h(i.info.numberOfChannels),h(16),h(0),h(0),A(i.info.sampleRate)],[br[i.track.source._codec](i)]),Kt=t=>{let e=[...I(t.info.decoderConfig.description??new ArrayBuffer(0))];return e=[...T(64),...T(21),...je(0),...l(0),...l(0),...T(5),...Ue(e.length),...e],e=[...h(1),...T(0),...T(4),...Ue(e.length),...e,...T(6),...T(1),...T(2)],e=[...T(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",[T(0),T(t.info.numberOfChannels),h(i),l(t.info.sampleRate),De(e),T(0)])},Gt=(t,i)=>b(t,[Array(6).fill(0),h(1)],[wr[i.track.source._codec](i)]),Yt=t=>b("vttC",[...y.encode(t.info.config.description)]);var Zt=t=>x("stts",0,0,[l(t.timeToSampleTable.length),t.timeToSampleTable.map(i=>[l(i.sampleCount),l(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,[l(i.length),i.map(([e])=>l(e+1))])},er=t=>x("stsc",0,0,[l(t.compactlyCodedChunkTable.length),t.compactlyCodedChunkTable.map(i=>[l(i.firstChunk),l(i.samplesPerChunk),l(1)])]),tr=t=>x("stsz",0,0,[l(0),l(t.samples.length),t.samples.map(i=>l(i.size))]),rr=t=>t.finalizedChunks.length>0&&S(t.finalizedChunks).offset>=2**32?x("co64",0,0,[l(t.finalizedChunks.length),t.finalizedChunks.map(i=>U(i.offset))]):x("stco",0,0,[l(t.finalizedChunks.length),t.finalizedChunks.map(i=>l(i.offset))]),ir=t=>x("ctts",0,0,[l(t.compositionTimeOffsetTable.length),t.compositionTimeOffsetTable.map(i=>[l(i.sampleCount),l(i.sampleCompositionTimeOffset)])]),sr=t=>b("mvex",void 0,t.map(ar)),ar=t=>x("trex",0,0,[l(t.track.id),l(1),l(0),l(0),l(0)]),Re=(t,i)=>b("moof",void 0,[or(t),...i.map(nr)]),or=t=>x("mfhd",0,0,[l(t)]),Ze=t=>{let i=0,e=0,r=0,s=0,a=t.type==="delta";return e|=+a,a?i|=1:i|=2,i<<24|e<<16|r<<8|s},nr=t=>b("traf",void 0,[ur(t),lr(t),cr(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,[l(t.track.id),l(r.duration),l(r.size),l(r.flags)])},lr=t=>(d(t.currentChunk),x("tfdt",1,0,[U(v(t.currentChunk.startTimestamp,t.timescale))])),cr=t=>{d(t.currentChunk);let i=t.currentChunk.samples.map(g=>g.timescaleUnitsToNextSample),e=t.currentChunk.samples.map(g=>g.size),r=t.currentChunk.samples.map(Ze),s=t.currentChunk.samples.map(g=>v(g.timestamp-g.decodeTimestamp,t.timescale)),a=new Set(i),o=new Set(e),n=new Set(r),u=new Set(s),c=n.size===2&&r[0]!==r[1],m=a.size>1,f=o.size>1,w=!c&&n.size>1,M=u.size>1||[...u].some(g=>g!==0),C=0;return C|=1,C|=4*+c,C|=256*+m,C|=512*+f,C|=1024*+w,C|=2048*+M,x("trun",1,C,[l(t.currentChunk.samples.length),l(t.currentChunk.offset-t.currentChunk.moofOffset||0),c?l(r[0]):[],t.currentChunk.samples.map((g,F)=>[m?l(i[F]):[],f?l(e[F]):[],w?l(r[F]):[],M?Le(s[F]):[]])])},Je=t=>b("mfra",void 0,[...t.map(dr),mr()]),dr=(t,i)=>x("tfra",1,0,[l(t.track.id),l(63),l(t.finalizedChunks.length),t.finalizedChunks.map(r=>[U(v(r.startTimestamp,t.timescale)),U(r.moofOffset),l(i+1),l(1),l(1)])]),mr=()=>x("mfro",0,0,[l(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(ce(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"},wr={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,a=this.trackTimestampInfo.get(i);if(!a){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).`);a={timestampOffset:s,maxTimestamp:i.source._offsetTimestamps?0:s,lastKeyFrameTimestamp:i.source._offsetTimestamps?0:s},this.trackTimestampInfo.set(i,a)}if(i.source._offsetTimestamps&&(s-=a.timestampOffset),s<0)throw new Error(`Timestamps must be non-negative (got ${s}s).`);if(ss.start-a.start);e.push({start:r[0].start,size:r[0].data.byteLength});for(let s=1;sc.start<=r&&rCr){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 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((a,o)=>a.start-o.start),this.chunks.indexOf(s)}queueChunksForFlush(e=!1){d(this.writer);for(let r=0;r{if(t==="avc"){let a=Math.ceil(i/16)*Math.ceil(e/16),o=it.find(f=>a<=f.maxMacroblocks&&r<=f.maxBitrate)??S(it),n=o?o.level:0,u="64".padStart(2,"0"),c="00",m=n.toString(16).padStart(2,"0");return`avc1.${u}${c}${m}`}else if(t==="hevc"){let s=0,a=1,o="6",n=i*e,u=st.find(f=>n<=f.maxPictureSize&&r<=f.maxBitrate)??S(st);return`hev1.${s===0?"":String.fromCharCode(65+s-1)}${a}.${o}.${u.tier}${u.level}.B0`}else{if(t==="vp8")return"vp8";if(t==="vp9"){let s="00",a=i*e,o=at.find(u=>a<=u.maxPictureSize&&r<=u.maxBitrate)??S(at);return`vp09.${s}.${o.level}.08`}else if(t==="av1"){let a=i*e,o=ot.find(u=>a<=u.maxPictureSize&&r<=u.maxBitrate)??S(ot);return`av01.0.${o.level.toString().padStart(2,"0")}${o.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}'.`)},lt=t=>t==="avc"?{avc:{format:"avc"}}:t==="hevc"?{hevc:{format:"hevc"}}:{},ct=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.")},we=t=>{if(!t)throw new TypeError("Subtitle metadata must be provided.");if(typeof t!="object")throw new TypeError("Subtitle metadata must be an object.");if(!t.config)throw new TypeError("Subtitle metadata must include a config object.");if(typeof t.config!="object")throw new TypeError("Subtitle metadata config must be an object.");if(typeof t.config.description!="string")throw new TypeError("Subtitle metadata config description must be a string.")};var de=1e3,gr=2082844800,v=(t,i,e=!0)=>{let r=t*i;return e?Math.round(r):r},Te=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)+gr;this.finalizedChunks=[];this.nextFragmentNumber=1;this.writer=e._writer,this.boxWriter=new J(this.writer),this.fastStart=r._options.fastStart??(this.writer instanceof q?"in-memory":!1),(this.fastStart==="in-memory"||this.fastStart==="fragmented")&&(this.writer.ensureMonotonicity=!0)}async start(){let e=await this.mutex.acquire(),r=this.output._tracks.some(s=>s.type==="video"&&s.source._codec==="avc");this.boxWriter.writeBox(Ge({holdsAvc:r,fragmented:this.fastStart==="fragmented"})),this.ftypSize=this.writer.getPos(),this.fastStart==="in-memory"?this.mdat=me(!1):this.fastStart==="fragmented"||(this.mdat=me(!0),this.boxWriter.writeBox(this.mdat)),await this.writer.flush(),e()}getVideoTrackData(e,r){let s=this.trackDatas.find(o=>o.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 a={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(a),this.trackDatas.sort((o,n)=>o.track.id-n.track.id),a}getAudioTrackData(e,r){let s=this.trackDatas.find(o=>o.track===e);if(s)return s;xe(r),d(r),d(r.decoderConfig);let a={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(a),this.trackDatas.sort((o,n)=>o.track.id-n.track.id),a}getSubtitleTrackData(e,r){let s=this.trackDatas.find(o=>o.track===e);if(s)return s;we(r),d(r),d(r.config);let a={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(a),this.trackDatas.sort((o,n)=>o.track.id-n.track.id),this.validateAndNormalizeTimestamp(e,0,!0),a}async addEncodedVideoChunk(e,r,s){let a=await this.mutex.acquire();try{let o=this.getVideoTrackData(e,s),n=new Uint8Array(r.byteLength);r.copyTo(n);let u=this.validateAndNormalizeTimestamp(o.track,r.timestamp,r.type==="key"),c=this.createSampleForTrack(o,n,u,(r.duration??0)/1e6,r.type);await this.registerSample(o,c)}finally{a()}}async addEncodedAudioChunk(e,r,s){let a=await this.mutex.acquire();try{let o=this.getAudioTrackData(e,s),n=new Uint8Array(r.byteLength);r.copyTo(n);let u=this.validateAndNormalizeTimestamp(o.track,r.timestamp,r.type==="key"),c=this.createSampleForTrack(o,n,u,(r.duration??0)/1e6,r.type);await this.registerSample(o,c)}finally{a()}}async addSubtitleCue(e,r,s){let a=await this.mutex.acquire();try{let o=this.getSubtitleTrackData(e,s);this.validateAndNormalizeTimestamp(o.track,1e6*r.timestamp,!0),e.source._codec==="webvtt"&&(o.cueQueue.push(r),await this.processWebVTTCues(o,r.timestamp))}finally{a()}}async processWebVTTCues(e,r){for(;e.cueQueue.length>0;){let s=new Set([]);for(let m of e.cueQueue)d(m.timestamp<=r),d(e.lastCueEndTimestamp<=m.timestamp+m.duration),s.add(Math.max(m.timestamp,e.lastCueEndTimestamp)),s.add(m.timestamp+m.duration);let a=[...s].sort((m,f)=>m-f),o=a[0],n=a[1]??o;if(r=n)break;j.lastIndex=0;let w=j.test(f.text),M=f.timestamp+f.duration,C=e.cueToSourceId.get(f);if(C===void 0&&ns.timestamp).sort((s,a)=>s-a);for(let s=0;s{if(e===n)return r.type==="key";let u=n.sampleQueue[0];return u&&u.type==="key"});a>=1&&o&&(s=!0,await this.finalizeFragment())}else s=a>=.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 a of this.trackDatas){if(a.sampleQueue.length===0&&!a.track.source._closed)break e;a.sampleQueue.length>0&&a.sampleQueue[0].timestamp=2**32&&(u.largeSize=!0,m=this.boxWriter.measureBox(u)+c),u.size=m,this.boxWriter.writeBox(u)}for(let u of this.trackDatas){u.currentChunk.offset=this.writer.getPos(),u.currentChunk.moofOffset=s;for(let c of u.currentChunk.samples)this.writer.write(c.data),c.data=null}let o=this.writer.getPos();this.writer.seek(this.boxWriter.offsets.get(a));let n=Re(r,this.trackDatas);this.boxWriter.writeBox(n),this.writer.seek(o);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(a=>a.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 a=0;a<2;a++){let o=ee(this.trackDatas,this.creationTime),n=this.boxWriter.measureBox(o);r=this.boxWriter.measureBox(this.mdat);let u=this.writer.getPos()+n+r;for(let c of this.finalizedChunks){c.offset=u;for(let{data:m}of c.samples)d(m),u+=m.byteLength,r+=m.byteLength}if(u<2**32)break;r>=2**32&&(this.mdat.largeSize=!0)}let s=ee(this.trackDatas,this.creationTime);this.boxWriter.writeBox(s),this.mdat.size=r,this.boxWriter.writeBox(this.mdat);for(let a of this.finalizedChunks)for(let o of a.samples)d(o.data),this.writer.write(o.data),o.data=null}else if(this.fastStart==="fragmented"){let r=this.writer.getPos(),s=Je(this.trackDatas);this.boxWriter.writeBox(s);let a=this.writer.getPos()-r;this.writer.seek(this.writer.getPos()-4),this.boxWriter.writeU32(a)}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 a=ee(this.trackDatas,this.creationTime);if(typeof this.fastStart=="object"){this.writer.seek(this.ftypSize),this.boxWriter.writeBox(a);let o=r-this.writer.getPos();this.boxWriter.writeBox(Ye(o))}else this.boxWriter.writeBox(a)}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,kr={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 a=this.writer.getPos();if(this.dataOffsets.set(e,a),this.writeEBML(e.data),e.size!==-1){let o=this.writer.getPos()-a,n=this.writer.getPos();this.writer.seek(r),this.writeEBMLVarInt(o,s),this.writer.seek(n)}}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]),a={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=a}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:kr[r.track.source._codec]},...r.type==="video"?[r.info.decoderConfig.description?{id:25506,data:I(r.info.decoderConfig.description)}:null,r.track.metadata.frameRate?{id:2352003,data:1e9/r.track.metadata.frameRate}:null,{id:224,data:[{id:176,data:r.info.width},{id:186,data:r.info.height},(()=>{if(r.info.decoderConfig.colorSpace){let s=r.info.decoderConfig.colorSpace;return ne(s)?{id:21936,data:[{id:21937,data:$[s.matrix]},{id:21946,data:Q[s.transfer]},{id:21947,data:H[s.primaries]},{id:21945,data:[1,2][Number(s.fullRange)]}]}:null}return null})()]}]:[],...r.type==="audio"?[r.info.decoderConfig.description?{id:25506,data:I(r.info.decoderConfig.description)}:null,{id:225,data:[{id:181,data:new re(r.info.sampleRate)},{id:159,data:r.info.numberOfChannels}]}]:[],...r.type==="subtitle"?[{id:25506,data:y.encode(r.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(o=>o.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 a={track:e,type:"video",info:{width:r.decoderConfig.codedWidth,height:r.decoderConfig.codedHeight,decoderConfig:r.decoderConfig},chunkQueue:[],lastWrittenMsTimestamp:null};return this.trackDatas.push(a),this.trackDatas.sort((o,n)=>o.track.id-n.track.id),a}getAudioTrackData(e,r){let s=this.trackDatas.find(o=>o.track===e);if(s)return s;xe(r),d(r),d(r.decoderConfig);let a={track:e,type:"audio",info:{numberOfChannels:r.decoderConfig.numberOfChannels,sampleRate:r.decoderConfig.sampleRate,decoderConfig:r.decoderConfig},chunkQueue:[],lastWrittenMsTimestamp:null};return this.trackDatas.push(a),this.trackDatas.sort((o,n)=>o.track.id-n.track.id),a}getSubtitleTrackData(e,r){let s=this.trackDatas.find(o=>o.track===e);if(s)return s;we(r),d(r),d(r.config);let a={track:e,type:"subtitle",info:{config:r.config},chunkQueue:[],lastWrittenMsTimestamp:null};return this.trackDatas.push(a),this.trackDatas.sort((o,n)=>o.track.id-n.track.id),a}async addEncodedVideoChunk(e,r,s){let a=await this.mutex.acquire();try{let o=this.getVideoTrackData(e,s),n=new Uint8Array(r.byteLength);r.copyTo(n);let u=this.validateAndNormalizeTimestamp(o.track,r.timestamp,r.type==="key"),c=this.createInternalChunk(n,u,(r.duration??0)/1e6,r.type);e.source._codec==="vp9"&&this.fixVP9ColorSpace(o,c),o.chunkQueue.push(c),await this.interleaveChunks()}finally{a()}}async addEncodedAudioChunk(e,r,s){let a=await this.mutex.acquire();try{let o=this.getAudioTrackData(e,s),n=new Uint8Array(r.byteLength);r.copyTo(n);let u=this.validateAndNormalizeTimestamp(o.track,r.timestamp,r.type==="key"),c=this.createInternalChunk(n,u,(r.duration??0)/1e6,r.type);o.chunkQueue.push(c),await this.interleaveChunks()}finally{a()}}async addSubtitleCue(e,r,s){let a=await this.mutex.acquire();try{let o=this.getSubtitleTrackData(e,s),n=this.validateAndNormalizeTimestamp(o.track,1e6*r.timestamp,!0),u=r.text,c=Math.floor(n*1e3);j.lastIndex=0,u=u.replace(j,M=>{let g=le(M.slice(1,-1))-c;return`<${ce(g)}>`});let m=y.encode(u),f=`${r.settings??""} +`,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??""}`,w=this.createInternalChunk(m,n,r.duration,"key",f.trim()?y.encode(f):null);o.chunkQueue.push(w),await this.interleaveChunks()}finally{a()}}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 a of this.trackDatas){if(a.chunkQueue.length===0&&!a.track.source._closed)break e;a.chunkQueue.length>0&&a.chunkQueue[0].timestamp=2&&s++;let c={rgb:7,bt709:2,bt470bg:1,smpte170m:3}[e.info.decoderConfig.colorSpace.matrix];Qe(r.data,s+0,s+3,c)}createInternalChunk(e,r,s,a,o=null){return{data:e,type:a,timestamp:r,duration:s,additions:o}}writeBlock(e,r){this.segment||(this.createTracks(),this.createSegment());let s=Math.floor(1e3*r.timestamp),a=this.trackDatas.every(f=>{if(f.track.source._closed)return!0;if(e===f)return r.type==="key";let w=f.chunkQueue[0];return w&&w.type==="key"});(!this.currentCluster||a&&s-this.currentClusterMsTimestamp>=1e3)&&this.createNewCluster(s);let o=s-this.currentClusterMsTimestamp;if(o<0)return;if(o>=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),c=new DataView(u.buffer);c.setUint8(0,128|e.track.id),c.setInt16(1,o,!1);let m=Math.floor(1e3*r.duration);if(m===0&&!r.additions){c.setUint8(3,+(r.type==="key")<<7);let f={id:163,data:[u,r.data]};this.writeEBML(f)}else{let f={id:160,data:[{id:161,data:[u,r.data]},r.type==="delta"?{id:251,data:new ie(e.lastWrittenMsTimestamp-s)}:null,r.additions?{id:30113,data:[{id:166,data:[{id:165,data:r.additions},{id:238,data:1}]}]}:null,m>0?{id:155,data:m}:null]};this.writeEBML(f)}this.duration=Math.max(this.duration,s+m),e.lastWrittenMsTimestamp=s,this.trackDatasInCurrentCluster.add(e)}createNewCluster(e){this.currentCluster&&!this.format._options.streamable&&this.finalizeCurrentCluster(),this.currentCluster={id:524531317,size:this.format._options.streamable?-1: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(a=>({id:183,data:[{id:247,data:a.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{},ge=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 Te(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"],ke=["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)}},O=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 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)}},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'.")},ae=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)=>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,...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())}},ye=class extends O{constructor(i){super(i.codec),this._encoder=new ae(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 O{constructor(i,e){if(!(i instanceof HTMLCanvasElement))throw new TypeError("canvas must be an HTMLCanvasElement.");super(e.codec),this._encoder=new ae(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 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 ae(this,r),this._track=e}_start(){this._abortController=new AbortController;let e=new MediaStreamTrackProcessor({track:this._track}),r=new WritableStream({write:s=>{this._encoder.digest(s),s.close()}});e.readable.pipeTo(r,{signal:this._abortController.signal}).catch(s=>{s instanceof DOMException&&s.name==="AbortError"||console.error("Pipe error:",s)})}async _flush(){this._abortController&&(this._abortController.abort(),this._abortController=null),await this._encoder.flush()}},E=class extends W{constructor(e){super();this._connectedTrack=null;if(!Y.includes(e))throw new TypeError(`Invalid audio codec '${e}'. Must be one of: ${Y.join(", ")}.`);this._codec=e}},_e=class extends E{constructor(i){super(i)}digest(i,e){if(!(i instanceof EncodedAudioChunk))throw new TypeError("chunk must be an EncodedAudioChunk.");return this._ensureValidDigest(),this._connectedTrack.output._muxer.addEncodedAudioChunk(this._connectedTrack,i,e)}},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.")},oe=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)=>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,...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())}},Oe=class extends E{constructor(i){super(i.codec),this._encoder=new oe(this,i)}digest(i){if(!(i instanceof AudioData))throw new TypeError("audioData must be an AudioData.");return this._encoder.digest(i)}_flush(){return this._encoder.flush()}},Ee=class extends E{constructor(e){super(e.codec);this._accumulatedFrameCount=0;this._encoder=new oe(this,e)}digest(e){if(!(e instanceof AudioBuffer))throw new TypeError("audioBuffer must be an AudioBuffer.");let r=e.numberOfChannels,s=e.sampleRate,a=e.length,o=new Float32Array(r*a);for(let c=0;c{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(!ke.includes(e))throw new TypeError(`Invalid subtitle codec '${e}'. Must be one of: ${ke.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 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 E))throw new TypeError("source must be an AudioSource.");if(!e||typeof e!="object")throw new TypeError("metadata must be an object.");this._addTrack("audio",i,e)}addSubtitleTrack(i,e={}){if(!(i instanceof R))throw new TypeError("source must be a SubtitleSource.");if(!e||typeof e!="object")throw new TypeError("metadata must be an object.");this._addTrack("subtitle",i,e)}_addTrack(i,e,r){if(this._started)throw new Error("Cannot add track after output has started.");if(e._connectedTrack)throw new Error("Source is already used for a track.");let s={id:this._tracks.length+1,output:this,type:i,source:e,metadata:r};this._muxer.beforeTrackAdd(s),this._tracks.push(s),e._connectedTrack=s}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);})(); +${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);})(); 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 b4b7e67..c0a015d 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>n;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},oe=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,ne=class{constructor(i){this.preambleText=null;this.preambleEmitted=!1;this.options=i}parse(i){i=i.replaceAll(`\r +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 `,` `).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],a=e.index+e[0].length,o=i.indexOf(` -`,a)+1,n=i.slice(a,o).trim(),u=i.indexOf(` +`),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(` -`,a);u===-1&&(u=i.length);let c=ue(e[2]),f=ue(e[3])-c,w=i.slice(o,u).trim();i=i.slice(u).trimStart(),X.lastIndex=0;let O={timestamp:c/1e3,duration:f/1e3,text:w,identifier:s,settings:n,notes:r},C={};this.preambleEmitted||(C.config={description:this.preambleText},this.preambleEmitted=!0),this.options.output(O,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])},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 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]]),l=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]]),ge=t=>(_.setInt32(0,2**30*t,!1),[p[0],p[1],p[2],p[3]]),ke=(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()},k=(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]),ge(t[2]),A(t[3]),A(t[4]),ge(t[5]),A(t[6]),A(t[7]),ge(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,[T(i),$e(e),r??[]],s),Xe=t=>{let i=512;return t.fragmented?b("ftyp",[k("iso5"),l(i),k("iso5"),k("iso6"),k("mp41")]):b("ftyp",[k("isom"),l(i),k("isom"),t.holdsAvc?k("avc1"):[],k("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=>wt(r,i)),e?Yt(t):null]),xt=(t,i)=>{let e=v(Math.max(0,...i.filter(o=>o.samples.length>0).map(o=>{let n=ye(o.samples);return n.timestamp+n.duration})),ce),r=Math.max(0,...i.map(o=>o.track.id))+1,s=!V(t)||!V(e),a=s?B:l;return x("mvhd",+s,0,[a(t),a(t),l(ce),a(e),A(1),Se(1),Array(10).fill(0),Ke(qe),Array(24).fill(0),l(r)])},wt=(t,i)=>b("trak",void 0,[Tt(t,i),Ct(t,i)]),Tt=(t,i)=>{let e=ye(t.samples),r=v(e?e.timestamp+e.duration:0,ce),s=!V(i)||!V(r),a=s?B:l,o;if(t.type==="video"){let n=t.track.metadata.rotation;o=n===void 0||typeof n=="number"?Le(n??0):n}else o=qe;return x("tkhd",+s,3,[a(i),a(i),l(t.track.id),l(0),a(r),Array(8).fill(0),h(0),h(t.track.id),Se(t.type==="audio"?1:0),h(0),Ke(o),A(t.type==="video"?t.info.width:0),A(t.type==="video"?t.info.height:0)])},Ct=(t,i)=>b("mdia",void 0,[gt(t,i),yt(t),vt(t)]),gt=(t,i)=>{let e=ye(t.samples),r=v(e?e.timestamp+e.duration:0,t.timescale),s=!V(i)||!V(r),a=s?B:l;return x("mdhd",+s,0,[a(i),a(i),l(t.timescale),a(r),h(21956),h(0)])},kt={video:"vide",audio:"soun",subtitle:"text"},St={video:"VideoHandler",audio:"SoundHandler",subtitle:"TextHandler"},yt=t=>x("hdlr",0,0,[k("mhlr"),k(kt[t.type]),l(0),l(0),l(0),k(St[t.type],!0)]),vt=t=>b("minf",void 0,[Et[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)]),Ot=()=>x("nmhd",0,0),Et={video:At,audio:_t,subtitle:Ot},Mt=()=>b("dinf",void 0,[Vt()]),Vt=()=>x("dref",0,0,[l(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(or[t.track.source._codec],t):t.type==="audio"?i=Ft(ur[t.track.source._codec],t):t.type==="subtitle"&&(i=Qt(cr[t.track.source._codec],t)),d(i),x("stsd",0,0,[l(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),l(4718592),l(4718592),l(0),h(1),Array(32).fill(0),h(24),bt(65535)],[nr[i.track.source._codec](i),oe(i.info.decoderConfig.colorSpace)?Ut(i):null]),Ut=t=>b("colr",[k("nclx"),h(R[t.info.decoderConfig.colorSpace.primaries]),h(F[t.info.decoderConfig.colorSpace.transfer]),h(N[t.info.decoderConfig.colorSpace.matrix]),T((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]),n=(Number(e[3])<<4)+(0<<1)+Number(i.colorSpace.fullRange);return x("vpcC",1,0,[T(r),T(s),T(n),T(2),T(2),T(2),h(0)])},Rt=()=>{let e=(1<<7)+1;return b("av1C",[e,0,0,0])},Ft=(t,i)=>b(t,[Array(6).fill(0),h(1),h(0),h(0),l(0),h(i.info.numberOfChannels),h(16),h(0),h(0),A(i.info.sampleRate)],[lr[i.track.source._codec](i)]),Nt=t=>{let e=[...P(t.info.decoderConfig.description??new ArrayBuffer(0))];return e=[...T(64),...T(21),...$e(0),...l(0),...l(0),...T(5),...ke(e.length),...e],e=[...h(1),...T(0),...T(4),...ke(e.length),...e,...T(6),...T(1),...T(2)],e=[...T(3),...ke(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",[T(0),T(t.info.numberOfChannels),h(i),l(t.info.sampleRate),Se(e),T(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,[l(t.timeToSampleTable.length),t.timeToSampleTable.map(i=>[l(i.sampleCount),l(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,[l(i.length),i.map(([e])=>l(e+1))])},qt=t=>x("stsc",0,0,[l(t.compactlyCodedChunkTable.length),t.compactlyCodedChunkTable.map(i=>[l(i.firstChunk),l(i.samplesPerChunk),l(1)])]),Kt=t=>x("stsz",0,0,[l(0),l(t.samples.length),t.samples.map(i=>l(i.size))]),Xt=t=>t.finalizedChunks.length>0&&S(t.finalizedChunks).offset>=2**32?x("co64",0,0,[l(t.finalizedChunks.length),t.finalizedChunks.map(i=>B(i.offset))]):x("stco",0,0,[l(t.finalizedChunks.length),t.finalizedChunks.map(i=>l(i.offset))]),Gt=t=>x("ctts",0,0,[l(t.compositionTimeOffsetTable.length),t.compositionTimeOffsetTable.map(i=>[l(i.sampleCount),l(i.sampleCompositionTimeOffset)])]),Yt=t=>b("mvex",void 0,t.map(Zt)),Zt=t=>x("trex",0,0,[l(t.track.id),l(1),l(0),l(0),l(0)]),ve=(t,i)=>b("moof",void 0,[Jt(t),...i.map(er)]),Jt=t=>x("mfhd",0,0,[l(t)]),Ye=t=>{let i=0,e=0,r=0,s=0,a=t.type==="delta";return e|=+a,a?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,[l(t.track.id),l(r.duration),l(r.size),l(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(g=>g.timescaleUnitsToNextSample),e=t.currentChunk.samples.map(g=>g.size),r=t.currentChunk.samples.map(Ye),s=t.currentChunk.samples.map(g=>v(g.timestamp-g.decodeTimestamp,t.timescale)),a=new Set(i),o=new Set(e),n=new Set(r),u=new Set(s),c=n.size===2&&r[0]!==r[1],m=a.size>1,f=o.size>1,w=!c&&n.size>1,O=u.size>1||[...u].some(g=>g!==0),C=0;return C|=1,C|=4*+c,C|=256*+m,C|=512*+f,C|=1024*+w,C|=2048*+O,x("trun",1,C,[l(t.currentChunk.samples.length),l(t.currentChunk.offset-t.currentChunk.moofOffset||0),c?l(r[0]):[],t.currentChunk.samples.map((g,D)=>[m?l(i[D]):[],f?l(e[D]):[],w?l(r[D]):[],O?je(s[D]):[]])])},Ze=t=>b("mfra",void 0,[...t.map(sr),ar()]),sr=(t,i)=>x("tfra",1,0,[l(t.track.id),l(63),l(t.finalizedChunks.length),t.finalizedChunks.map(r=>[B(v(r.startTimestamp,t.timescale)),B(r.moofOffset),l(i+1),l(1),l(1)])]),ar=()=>x("mfro",0,0,[l(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(le(i))]):null,r!==null?b("sttg",[...y.encode(r)]):null,b("payl",[...y.encode(t)])]),tt=t=>b("vtta",[...y.encode(t)]),or={avc:"avc1",hevc:"hvc1",vp8:"vp08",vp9:"vp09",av1:"av01"},nr={avc:Dt,hevc:Wt,vp8:Qe,vp9:Qe,av1:Rt},ur={aac:"mp4a",opus:"Opus"},lr={aac:Nt,opus:Ht},cr={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,a=this.trackTimestampInfo.get(i);if(!a){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).`);a={timestampOffset:s,maxTimestamp:i.source._offsetTimestamps?0:s,lastKeyFrameTimestamp:i.source._offsetTimestamps?0:s},this.trackTimestampInfo.set(i,a)}if(i.source._offsetTimestamps&&(s-=a.timestampOffset),s<0)throw new Error(`Timestamps must be non-negative (got ${s}s).`);if(ss.start-a.start);e.push({start:r[0].start,size:r[0].data.byteLength});for(let s=1;sc.start<=r&&rfr){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 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((a,o)=>a.start-o.start),this.chunks.indexOf(s)}queueChunksForFlush(e=!1){d(this.writer);for(let r=0;r{if(t==="avc"){let a=Math.ceil(i/16)*Math.ceil(e/16),o=rt.find(f=>a<=f.maxMacroblocks&&r<=f.maxBitrate)??S(rt),n=o?o.level:0,u="64".padStart(2,"0"),c="00",m=n.toString(16).padStart(2,"0");return`avc1.${u}${c}${m}`}else if(t==="hevc"){let s=0,a=1,o="6",n=i*e,u=it.find(f=>n<=f.maxPictureSize&&r<=f.maxBitrate)??S(it);return`hev1.${s===0?"":String.fromCharCode(65+s-1)}${a}.${o}.${u.tier}${u.level}.B0`}else{if(t==="vp8")return"vp8";if(t==="vp9"){let s="00",a=i*e,o=st.find(u=>a<=u.maxPictureSize&&r<=u.maxBitrate)??S(st);return`vp09.${s}.${o.level}.08`}else if(t==="av1"){let a=i*e,o=at.find(u=>a<=u.maxPictureSize&&r<=u.maxBitrate)??S(at);return`av01.0.${o.level.toString().padStart(2,"0")}${o.tier}.08`}}throw new TypeError(`Unhandled codec '${t}'.`)},nt=(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"}}:{},lt=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 ce=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),this.fastStart=r._options.fastStart??(this.writer instanceof $?"in-memory":!1),(this.fastStart==="in-memory"||this.fastStart==="fragmented")&&(this.writer.ensureMonotonicity=!0)}async start(){let e=await this.mutex.acquire(),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(o=>o.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 a={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(a),this.trackDatas.sort((o,n)=>o.track.id-n.track.id),a}getAudioTrackData(e,r){let s=this.trackDatas.find(o=>o.track===e);if(s)return s;he(r),d(r),d(r.decoderConfig);let a={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(a),this.trackDatas.sort((o,n)=>o.track.id-n.track.id),a}getSubtitleTrackData(e,r){let s=this.trackDatas.find(o=>o.track===e);if(s)return s;be(r),d(r),d(r.config);let a={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(a),this.trackDatas.sort((o,n)=>o.track.id-n.track.id),this.validateAndNormalizeTimestamp(e,0,!0),a}async addEncodedVideoChunk(e,r,s){let a=await this.mutex.acquire();try{let o=this.getVideoTrackData(e,s),n=new Uint8Array(r.byteLength);r.copyTo(n);let u=this.validateAndNormalizeTimestamp(o.track,r.timestamp,r.type==="key"),c=this.createSampleForTrack(o,n,u,(r.duration??0)/1e6,r.type);await this.registerSample(o,c)}finally{a()}}async addEncodedAudioChunk(e,r,s){let a=await this.mutex.acquire();try{let o=this.getAudioTrackData(e,s),n=new Uint8Array(r.byteLength);r.copyTo(n);let u=this.validateAndNormalizeTimestamp(o.track,r.timestamp,r.type==="key"),c=this.createSampleForTrack(o,n,u,(r.duration??0)/1e6,r.type);await this.registerSample(o,c)}finally{a()}}async addSubtitleCue(e,r,s){let a=await this.mutex.acquire();try{let o=this.getSubtitleTrackData(e,s);this.validateAndNormalizeTimestamp(o.track,1e6*r.timestamp,!0),e.source._codec==="webvtt"&&(o.cueQueue.push(r),await this.processWebVTTCues(o,r.timestamp))}finally{a()}}async processWebVTTCues(e,r){for(;e.cueQueue.length>0;){let s=new Set([]);for(let m of e.cueQueue)d(m.timestamp<=r),d(e.lastCueEndTimestamp<=m.timestamp+m.duration),s.add(Math.max(m.timestamp,e.lastCueEndTimestamp)),s.add(m.timestamp+m.duration);let a=[...s].sort((m,f)=>m-f),o=a[0],n=a[1]??o;if(r=n)break;H.lastIndex=0;let w=H.test(f.text),O=f.timestamp+f.duration,C=e.cueToSourceId.get(f);if(C===void 0&&ns.timestamp).sort((s,a)=>s-a);for(let s=0;s{if(e===n)return r.type==="key";let u=n.sampleQueue[0];return u&&u.type==="key"});a>=1&&o&&(s=!0,await this.finalizeFragment())}else s=a>=.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 a of this.trackDatas){if(a.sampleQueue.length===0&&!a.track.source._closed)break e;a.sampleQueue.length>0&&a.sampleQueue[0].timestamp=2**32&&(u.largeSize=!0,m=this.boxWriter.measureBox(u)+c),u.size=m,this.boxWriter.writeBox(u)}for(let u of this.trackDatas){u.currentChunk.offset=this.writer.getPos(),u.currentChunk.moofOffset=s;for(let c of u.currentChunk.samples)this.writer.write(c.data),c.data=null}let o=this.writer.getPos();this.writer.seek(this.boxWriter.offsets.get(a));let n=ve(r,this.trackDatas);this.boxWriter.writeBox(n),this.writer.seek(o);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(a=>a.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 a=0;a<2;a++){let o=Y(this.trackDatas,this.creationTime),n=this.boxWriter.measureBox(o);r=this.boxWriter.measureBox(this.mdat);let u=this.writer.getPos()+n+r;for(let c of this.finalizedChunks){c.offset=u;for(let{data:m}of c.samples)d(m),u+=m.byteLength,r+=m.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 a of this.finalizedChunks)for(let o of a.samples)d(o.data),this.writer.write(o.data),o.data=null}else if(this.fastStart==="fragmented"){let r=this.writer.getPos(),s=Ze(this.trackDatas);this.boxWriter.writeBox(s);let a=this.writer.getPos()-r;this.writer.seek(this.writer.getPos()-4),this.boxWriter.writeU32(a)}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 a=Y(this.trackDatas,this.creationTime);if(typeof this.fastStart=="object"){this.writer.seek(this.ftypSize),this.boxWriter.writeBox(a);let o=r-this.writer.getPos();this.boxWriter.writeBox(Ge(o))}else this.boxWriter.writeBox(a)}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,Oe=t=>t>=-64&&t<64?1:t>=-8192&&t<8192?2:t>=-(1<<20)&&t<1<<20?3:t>=-(1<<27)&&t<1<<27?4:t>=-(2**34)&&t<2**34?5:6,ct=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 Ee=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},we=class extends Q{constructor(e,r){super(e);this.timestampsMustStartAtZero=!1;this.helper=new Uint8Array(8);this.helperView=new DataView(this.helper.buffer);this.offsets=new WeakMap;this.dataOffsets=new WeakMap;this.trackDatas=[];this.segment=null;this.segmentInfo=null;this.seekHead=null;this.tracksElement=null;this.segmentDuration=null;this.cues=null;this.currentCluster=null;this.currentClusterMsTimestamp=null;this.trackDatasInCurrentCluster=new Set;this.duration=0;this.writer=e._writer,this.format=r,this.format._options.streamable&&(this.writer.ensureMonotonicity=!0)}writeByte(e){this.helperView.setUint8(0,e),this.writer.write(this.helper.subarray(0,1))}writeFloat32(e){this.helperView.setFloat32(0,e,!1),this.writer.write(this.helper.subarray(0,4))}writeFloat64(e){this.helperView.setFloat64(0,e,!1),this.writer.write(this.helper)}writeUnsignedInt(e,r=_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=Oe(e)){e<0&&(e+=2**(r*8)),this.writeUnsignedInt(e,r)}writeEBMLVarInt(e,r=ct(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 a=this.writer.getPos();if(this.dataOffsets.set(e,a),this.writeEBML(e.data),e.size!==-1){let o=this.writer.getPos()-a,n=this.writer.getPos();this.writer.seek(r),this.writeEBMLVarInt(o,s),this.writer.seek(n)}}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??Oe(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]),a={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=a}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"?[r.info.decoderConfig.description?{id:25506,data:P(r.info.decoderConfig.description)}:null,r.track.metadata.frameRate?{id:2352003,data:1e9/r.track.metadata.frameRate}:null,{id:224,data:[{id:176,data:r.info.width},{id:186,data:r.info.height},(()=>{if(r.info.decoderConfig.colorSpace){let s=r.info.decoderConfig.colorSpace;return oe(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:[1,2][Number(s.fullRange)]}]}:null}return null})()]}]:[],...r.type==="audio"?[r.info.decoderConfig.description?{id:25506,data:P(r.info.decoderConfig.description)}:null,{id:225,data:[{id:181,data:new ee(r.info.sampleRate)},{id:159,data:r.info.numberOfChannels}]}]:[],...r.type==="subtitle"?[{id:25506,data:y.encode(r.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(o=>o.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 a={track:e,type:"video",info:{width:r.decoderConfig.codedWidth,height:r.decoderConfig.codedHeight,decoderConfig:r.decoderConfig},chunkQueue:[],lastWrittenMsTimestamp:null};return this.trackDatas.push(a),this.trackDatas.sort((o,n)=>o.track.id-n.track.id),a}getAudioTrackData(e,r){let s=this.trackDatas.find(o=>o.track===e);if(s)return s;he(r),d(r),d(r.decoderConfig);let a={track:e,type:"audio",info:{numberOfChannels:r.decoderConfig.numberOfChannels,sampleRate:r.decoderConfig.sampleRate,decoderConfig:r.decoderConfig},chunkQueue:[],lastWrittenMsTimestamp:null};return this.trackDatas.push(a),this.trackDatas.sort((o,n)=>o.track.id-n.track.id),a}getSubtitleTrackData(e,r){let s=this.trackDatas.find(o=>o.track===e);if(s)return s;be(r),d(r),d(r.config);let a={track:e,type:"subtitle",info:{config:r.config},chunkQueue:[],lastWrittenMsTimestamp:null};return this.trackDatas.push(a),this.trackDatas.sort((o,n)=>o.track.id-n.track.id),a}async addEncodedVideoChunk(e,r,s){let a=await this.mutex.acquire();try{let o=this.getVideoTrackData(e,s),n=new Uint8Array(r.byteLength);r.copyTo(n);let u=this.validateAndNormalizeTimestamp(o.track,r.timestamp,r.type==="key"),c=this.createInternalChunk(n,u,(r.duration??0)/1e6,r.type);e.source._codec==="vp9"&&this.fixVP9ColorSpace(o,c),o.chunkQueue.push(c),await this.interleaveChunks()}finally{a()}}async addEncodedAudioChunk(e,r,s){let a=await this.mutex.acquire();try{let o=this.getAudioTrackData(e,s),n=new Uint8Array(r.byteLength);r.copyTo(n);let u=this.validateAndNormalizeTimestamp(o.track,r.timestamp,r.type==="key"),c=this.createInternalChunk(n,u,(r.duration??0)/1e6,r.type);o.chunkQueue.push(c),await this.interleaveChunks()}finally{a()}}async addSubtitleCue(e,r,s){let a=await this.mutex.acquire();try{let o=this.getSubtitleTrackData(e,s),n=this.validateAndNormalizeTimestamp(o.track,1e6*r.timestamp,!0),u=r.text,c=Math.floor(n*1e3);H.lastIndex=0,u=u.replace(H,O=>{let g=ue(O.slice(1,-1))-c;return`<${le(g)}>`});let m=y.encode(u),f=`${r.settings??""} +`,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??""}`,w=this.createInternalChunk(m,n,r.duration,"key",f.trim()?y.encode(f):null);o.chunkQueue.push(w),await this.interleaveChunks()}finally{a()}}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 a of this.trackDatas){if(a.chunkQueue.length===0&&!a.track.source._closed)break e;a.chunkQueue.length>0&&a.chunkQueue[0].timestamp=2&&s++;let c={rgb:7,bt709:2,bt470bg:1,smpte170m:3}[e.info.decoderConfig.colorSpace.matrix];He(r.data,s+0,s+3,c)}createInternalChunk(e,r,s,a,o=null){return{data:e,type:a,timestamp:r,duration:s,additions:o}}writeBlock(e,r){this.segment||(this.createTracks(),this.createSegment());let s=Math.floor(1e3*r.timestamp),a=this.trackDatas.every(f=>{if(f.track.source._closed)return!0;if(e===f)return r.type==="key";let w=f.chunkQueue[0];return w&&w.type==="key"});(!this.currentCluster||a&&s-this.currentClusterMsTimestamp>=1e3)&&this.createNewCluster(s);let o=s-this.currentClusterMsTimestamp;if(o<0)return;if(o>=Ee)throw new Error(`Current Matroska cluster exceeded its maximum allowed length of ${Ee} milliseconds. In order to produce a correct WebM file, you must pass in a key frame at least every ${Ee} milliseconds.`);let u=new Uint8Array(4),c=new DataView(u.buffer);c.setUint8(0,128|e.track.id),c.setInt16(1,o,!1);let m=Math.floor(1e3*r.duration);if(m===0&&!r.additions){c.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,m>0?{id:155,data:m}:null]};this.writeEBML(f)}this.duration=Math.max(this.duration,s+m),e.lastWrittenMsTimestamp=s,this.trackDatasInCurrentCluster.add(e)}createNewCluster(e){this.currentCluster&&!this.format._options.streamable&&this.finalizeCurrentCluster(),this.currentCluster={id:524531317,size:this.format._options.streamable?-1: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(a=>({id:183,data:[{id:247,data:a.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)}},Te=class extends U{constructor(i={}){if(!i||typeof i!="object")throw new TypeError("options must be an object.");if(i.streamable!==void 0&&typeof i.streamable!="boolean")throw new TypeError("options.streamable, when provided, must be a boolean.");super(),this._options=i}_createMuxer(i){return new we(i,this)}},L=class extends Te{};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)}},E=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 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)}},xr=5,wr=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;wr(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)=>this.muxer.addEncodedVideoChunk(this.source._connectedTrack,e,r),error:e=>console.error("Video encode error:",e)}),this.encoder.configure({codec:ot(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 E{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 E{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 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 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)}},Tr=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.")},ae=class{constructor(i,e){this.source=i;this.codecConfig=e;this.encoder=null;this.muxer=null;this.lastNumberOfChannels=null;this.lastSampleRate=null;Tr(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)=>this.muxer.addEncodedAudioChunk(this.source._connectedTrack,e,r),error:e=>console.error("Audio encode error:",e)}),this.encoder.configure({codec:nt(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())}},De=class extends M{constructor(i){super(i.codec),this._encoder=new ae(this,i)}digest(i){if(!(i instanceof AudioData))throw new TypeError("audioData must be an AudioData.");return this._encoder.digest(i)}_flush(){return this._encoder.flush()}},We=class extends M{constructor(e){super(e.codec);this._accumulatedFrameCount=0;this._encoder=new ae(this,e)}digest(e){if(!(e instanceof AudioBuffer))throw new TypeError("audioBuffer must be an AudioBuffer.");let r=e.numberOfChannels,s=e.sampleRate,a=e.length,o=new Float32Array(r*a);for(let c=0;c{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 ne({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 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 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,Te 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,E as VideoSource,L as WebMOutputFormat}; +${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}; diff --git a/dist/metamuxer.mjs b/dist/metamuxer.mjs index d7608e0..13fbb5f 100644 --- a/dist/metamuxer.mjs +++ b/dist/metamuxer.mjs @@ -13,10 +13,10 @@ var isU32 = (value) => { var readBits = (bytes2, start, end) => { let result = 0; for (let i = start; i < end; i++) { - let byteIndex = Math.floor(i / 8); - let byte = bytes2[byteIndex]; - let bitIndex = 7 - (i & 7); - let bit = (byte & 1 << bitIndex) >> bitIndex; + const byteIndex = Math.floor(i / 8); + const byte = bytes2[byteIndex]; + const bitIndex = 7 - (i & 7); + const bit = (byte & 1 << bitIndex) >> bitIndex; result <<= 1; result |= bit; } @@ -24,9 +24,9 @@ var readBits = (bytes2, start, end) => { }; var writeBits = (bytes2, start, end, value) => { for (let i = start; i < end; i++) { - let byteIndex = Math.floor(i / 8); + const byteIndex = Math.floor(i / 8); let byte = bytes2[byteIndex]; - let bitIndex = 7 - (i & 7); + const bitIndex = 7 - (i & 7); byte &= ~(1 << bitIndex); byte |= (value & 1 << end - i - 1) >> end - i - 1 << bitIndex; bytes2[byteIndex] = byte; @@ -41,11 +41,11 @@ var toUint8Array = (source) => { }; var textEncoder = new TextEncoder(); var COLOR_PRIMARIES_MAP = { - "bt709": 1, + bt709: 1, // ITU-R BT.709 - "bt470bg": 5, + bt470bg: 5, // ITU-R BT.470BG - "smpte170m": 6 + smpte170m: 6 // ITU-R BT.601 525 - SMPTE 170M }; var TRANSFER_CHARACTERISTICS_MAP = { @@ -57,13 +57,13 @@ var TRANSFER_CHARACTERISTICS_MAP = { // IEC 61966-2-1 }; var MATRIX_COEFFICIENTS_MAP = { - "rgb": 0, + rgb: 0, // Identity - "bt709": 1, + bt709: 1, // ITU-R BT.709 - "bt470bg": 5, + bt470bg: 5, // ITU-R BT.470BG - "smpte170m": 6 + smpte170m: 6 // SMPTE 170M }; var colorSpaceIsComplete = (colorSpace) => { @@ -78,10 +78,10 @@ var AsyncMutex = class { } async acquire() { let resolver; - let nextPromise = new Promise((resolve) => { + const nextPromise = new Promise((resolve) => { resolver = resolve; }); - let currentPromiseAlias = this.currentPromise; + const currentPromiseAlias = this.currentPromise; this.currentPromise = nextPromise; await currentPromiseAlias; return resolver; @@ -104,14 +104,14 @@ var SubtitleParser = class { let match; if (!this.preambleText) { if (!preambleStartRegex.test(text)) { - let error = new Error("WebVTT preamble incorrect."); + const error = new Error("WebVTT preamble incorrect."); this.options.error(error); throw error; } match = cueBlockHeaderRegex.exec(text); - let preamble = text.slice(0, match?.index ?? text.length).trimEnd(); + const preamble = text.slice(0, match?.index ?? text.length).trimEnd(); if (!preamble) { - let error = new Error("No WebVTT preamble provided."); + const error = new Error("No WebVTT preamble provided."); this.options.error(error); throw error; } @@ -122,20 +122,20 @@ var SubtitleParser = class { } } while (match = cueBlockHeaderRegex.exec(text)) { - let notes = text.slice(0, match.index); - let cueIdentifier = match[1]; - let matchEnd = match.index + match[0].length; - let bodyStart = text.indexOf("\n", matchEnd) + 1; - let cueSettings = text.slice(matchEnd, bodyStart).trim(); + const notes = text.slice(0, match.index); + const cueIdentifier = match[1]; + const matchEnd = match.index + match[0].length; + const bodyStart = text.indexOf("\n", matchEnd) + 1; + const cueSettings = text.slice(matchEnd, bodyStart).trim(); let bodyEnd = text.indexOf("\n\n", matchEnd); if (bodyEnd === -1) bodyEnd = text.length; - let startTime = parseSubtitleTimestamp(match[2]); - let endTime = parseSubtitleTimestamp(match[3]); - let duration = endTime - startTime; - let body = text.slice(bodyStart, bodyEnd).trim(); + const startTime = parseSubtitleTimestamp(match[2]); + const endTime = parseSubtitleTimestamp(match[3]); + const duration = endTime - startTime; + const body = text.slice(bodyStart, bodyEnd).trim(); text = text.slice(bodyEnd).trimStart(); cueBlockHeaderRegex.lastIndex = 0; - let cue = { + const cue = { timestamp: startTime / 1e3, duration: duration / 1e3, text: body, @@ -143,7 +143,7 @@ var SubtitleParser = class { settings: cueSettings, notes }; - let meta = {}; + const meta = {}; if (!this.preambleEmitted) { meta.config = { description: this.preambleText @@ -156,15 +156,15 @@ var SubtitleParser = class { }; var timestampRegex = /(?:(\d{2}):)?(\d{2}):(\d{2}).(\d{3})/; var parseSubtitleTimestamp = (string) => { - let match = timestampRegex.exec(string); + const match = timestampRegex.exec(string); if (!match) throw new Error("Expected match."); return 60 * 60 * 1e3 * Number(match[1] || "0") + 60 * 1e3 * Number(match[2]) + 1e3 * Number(match[3]) + Number(match[4]); }; var formatSubtitleTimestamp = (timestamp) => { - let hours = Math.floor(timestamp / (60 * 60 * 1e3)); - let minutes = Math.floor(timestamp % (60 * 60 * 1e3) / (60 * 1e3)); - let seconds = Math.floor(timestamp % (60 * 1e3) / 1e3); - let milliseconds = timestamp % 1e3; + const hours = Math.floor(timestamp / (60 * 60 * 1e3)); + const minutes = Math.floor(timestamp % (60 * 60 * 1e3) / (60 * 1e3)); + const seconds = Math.floor(timestamp % (60 * 1e3) / 1e3); + const milliseconds = timestamp % 1e3; return hours.toString().padStart(2, "0") + ":" + minutes.toString().padStart(2, "0") + ":" + seconds.toString().padStart(2, "0") + "." + milliseconds.toString().padStart(3, "0"); }; @@ -204,14 +204,14 @@ var IsobmffBoxWriter = class { this.writeBoxHeader(box2, box2.size ?? box2.contents.byteLength + 8); this.writer.write(box2.contents); } else { - let startPos = this.writer.getPos(); + const startPos = this.writer.getPos(); this.writeBoxHeader(box2, 0); if (box2.contents) this.writer.write(box2.contents); if (box2.children) { - for (let child of box2.children) if (child) this.writeBox(child); + for (const child of box2.children) if (child) this.writeBox(child); } - let endPos = this.writer.getPos(); - let size = box2.size ?? endPos - startPos; + const endPos = this.writer.getPos(); + const size = box2.size ?? endPos - startPos; this.writer.seek(startPos); this.writeBoxHeader(box2, size); this.writer.seek(endPos); @@ -228,20 +228,20 @@ var IsobmffBoxWriter = class { patchBox(box2) { const boxOffset = this.offsets.get(box2); assert(boxOffset !== void 0); - let endPos = this.writer.getPos(); + const endPos = this.writer.getPos(); this.writer.seek(boxOffset); this.writeBox(box2); this.writer.seek(endPos); } measureBox(box2) { if (box2.contents && !box2.children) { - let headerSize = this.measureBoxHeader(box2); + const headerSize = this.measureBoxHeader(box2); return headerSize + box2.contents.byteLength; } else { let result = this.measureBoxHeader(box2); if (box2.contents) result += box2.contents.byteLength; if (box2.children) { - for (let child of box2.children) if (child) result += this.measureBox(child); + for (const child of box2.children) if (child) result += this.measureBox(child); } return result; } @@ -290,7 +290,7 @@ var fixed_2_30 = (value) => { return [bytes[0], bytes[1], bytes[2], bytes[3]]; }; var variableUnsignedInt = (value, byteLength) => { - let bytes2 = []; + const bytes2 = []; let remaining = value; do { let byte = remaining & 127; @@ -306,13 +306,13 @@ var variableUnsignedInt = (value, byteLength) => { return bytes2.reverse(); }; var ascii = (text, nullTerminated = false) => { - let bytes2 = Array(text.length).fill(null).map((_, i) => text.charCodeAt(i)); + const bytes2 = Array(text.length).fill(null).map((_, i) => text.charCodeAt(i)); if (nullTerminated) bytes2.push(0); return bytes2; }; var lastPresentedSample = (samples) => { let result = null; - for (let sample of samples) { + for (const sample of samples) { if (!result || sample.timestamp > result.timestamp) { result = sample; } @@ -320,9 +320,9 @@ var lastPresentedSample = (samples) => { return result; }; var rotationMatrix = (rotationInDegrees) => { - let theta = rotationInDegrees * (Math.PI / 180); - let cosTheta = Math.cos(theta); - let sinTheta = Math.sin(theta); + const theta = rotationInDegrees * (Math.PI / 180); + const cosTheta = Math.cos(theta); + const sinTheta = Math.sin(theta); return [ cosTheta, sinTheta, @@ -360,7 +360,7 @@ var fullBox = (type, version, flags, contents, children) => box( children ); var ftyp = (details) => { - let minorVersion = 512; + const minorVersion = 512; if (details.fragmented) return box("ftyp", [ ascii("iso5"), // Major brand @@ -390,16 +390,16 @@ var moov = (trackDatas, creationTime, fragmented = false) => box("moov", void 0, fragmented ? mvex(trackDatas) : null ]); var mvhd = (creationTime, trackDatas) => { - let duration = intoTimescale(Math.max( + const duration = intoTimescale(Math.max( 0, ...trackDatas.filter((x) => x.samples.length > 0).map((x) => { const lastSample = lastPresentedSample(x.samples); return lastSample.timestamp + lastSample.duration; }) ), GLOBAL_TIMESCALE); - let nextTrackId = Math.max(0, ...trackDatas.map((x) => x.track.id)) + 1; - let needsU64 = !isU32(creationTime) || !isU32(duration); - let u32OrU64 = needsU64 ? u64 : u32; + const nextTrackId = Math.max(0, ...trackDatas.map((x) => x.track.id)) + 1; + const needsU64 = !isU32(creationTime) || !isU32(duration); + const u32OrU64 = needsU64 ? u64 : u32; return fullBox("mvhd", +needsU64, 0, [ u32OrU64(creationTime), // Creation time @@ -428,13 +428,13 @@ var trak = (trackData, creationTime) => box("trak", void 0, [ mdia(trackData, creationTime) ]); var tkhd = (trackData, creationTime) => { - let lastSample = lastPresentedSample(trackData.samples); - let durationInGlobalTimescale = intoTimescale( + const lastSample = lastPresentedSample(trackData.samples); + const durationInGlobalTimescale = intoTimescale( lastSample ? lastSample.timestamp + lastSample.duration : 0, GLOBAL_TIMESCALE ); - let needsU64 = !isU32(creationTime) || !isU32(durationInGlobalTimescale); - let u32OrU64 = needsU64 ? u64 : u32; + const needsU64 = !isU32(creationTime) || !isU32(durationInGlobalTimescale); + const u32OrU64 = needsU64 ? u64 : u32; let matrix; if (trackData.type === "video") { const rotation = trackData.track.metadata.rotation; @@ -477,13 +477,13 @@ var mdia = (trackData, creationTime) => box("mdia", void 0, [ minf(trackData) ]); var mdhd = (trackData, creationTime) => { - let lastSample = lastPresentedSample(trackData.samples); - let localDuration = intoTimescale( + const lastSample = lastPresentedSample(trackData.samples); + const localDuration = intoTimescale( lastSample ? lastSample.timestamp + lastSample.duration : 0, trackData.timescale ); - let needsU64 = !isU32(creationTime) || !isU32(localDuration); - let u32OrU64 = needsU64 ? u64 : u32; + const needsU64 = !isU32(creationTime) || !isU32(localDuration); + const u32OrU64 = needsU64 ? u64 : u32; return fullBox("mdhd", +needsU64, 0, [ u32OrU64(creationTime), // Creation time @@ -655,17 +655,17 @@ var vpcC = (trackData) => { if (!trackData.info.decoderConfig) { return null; } - let decoderConfig = trackData.info.decoderConfig; + const decoderConfig = trackData.info.decoderConfig; assert(decoderConfig.colorSpace); - let parts = decoderConfig.codec.split("."); - let profile = Number(parts[1]); - let level = Number(parts[2]); - let bitDepth = Number(parts[3]); - let chromaSubsampling = 0; - let thirdByte = (bitDepth << 4) + (chromaSubsampling << 1) + Number(decoderConfig.colorSpace.fullRange); - let colourPrimaries = 2; - let transferCharacteristics = 2; - let matrixCoefficients = 2; + const parts = decoderConfig.codec.split("."); + const profile = Number(parts[1]); + const level = Number(parts[2]); + const bitDepth = Number(parts[3]); + const chromaSubsampling = 0; + const thirdByte = (bitDepth << 4) + (chromaSubsampling << 1) + Number(decoderConfig.colorSpace.fullRange); + const colourPrimaries = 2; + const transferCharacteristics = 2; + const matrixCoefficients = 2; return fullBox("vpcC", 1, 0, [ u8(profile), // Profile @@ -684,9 +684,9 @@ var vpcC = (trackData) => { ]); }; var av1C = () => { - let marker = 1; - let version = 1; - let firstByte = (marker << 7) + version; + const marker = 1; + const version = 1; + const firstByte = (marker << 7) + version; return box("av1C", [ firstByte, 0, @@ -719,7 +719,7 @@ var soundSampleDescription = (compressionType, trackData) => box(compressionType AUDIO_CODEC_TO_CONFIGURATION_BOX[trackData.track.source._codec](trackData) ]); var esds = (trackData) => { - let description = toUint8Array(trackData.info.decoderConfig.description ?? new ArrayBuffer(0)); + const description = toUint8Array(trackData.info.decoderConfig.description ?? new ArrayBuffer(0)); let bytes2 = [ ...description ]; @@ -813,7 +813,7 @@ var stts = (trackData) => { }; var stss = (trackData) => { if (trackData.samples.every((x) => x.type === "key")) return null; - let keySamples = [...trackData.samples.entries()].filter(([, sample]) => sample.type === "key"); + const keySamples = [...trackData.samples.entries()].filter(([, sample]) => sample.type === "key"); return fullBox("stss", 0, 0, [ u32(keySamples.length), // Number of entries @@ -905,9 +905,9 @@ var mfhd = (sequenceNumber) => { var fragmentSampleFlags = (sample) => { let byte1 = 0; let byte2 = 0; - let byte3 = 0; - let byte4 = 0; - let sampleIsDifferenceSample = sample.type === "delta"; + const byte3 = 0; + const byte4 = 0; + const sampleIsDifferenceSample = sample.type === "delta"; byte2 |= +sampleIsDifferenceSample; if (sampleIsDifferenceSample) { byte1 |= 1; @@ -930,8 +930,8 @@ var tfhd = (trackData) => { tfFlags |= 16; tfFlags |= 32; tfFlags |= 131072; - let referenceSample = trackData.currentChunk.samples[1] ?? trackData.currentChunk.samples[0]; - let referenceSampleInfo = { + const referenceSample = trackData.currentChunk.samples[1] ?? trackData.currentChunk.samples[0]; + const referenceSampleInfo = { duration: referenceSample.timescaleUnitsToNextSample, size: referenceSample.size, flags: fragmentSampleFlags(referenceSample) @@ -956,19 +956,19 @@ var tfdt = (trackData) => { }; var trun = (trackData) => { assert(trackData.currentChunk); - let allSampleDurations = trackData.currentChunk.samples.map((x) => x.timescaleUnitsToNextSample); - let allSampleSizes = trackData.currentChunk.samples.map((x) => x.size); - let allSampleFlags = trackData.currentChunk.samples.map(fragmentSampleFlags); - let allSampleCompositionTimeOffsets = trackData.currentChunk.samples.map((x) => intoTimescale(x.timestamp - x.decodeTimestamp, trackData.timescale)); - let uniqueSampleDurations = new Set(allSampleDurations); - let uniqueSampleSizes = new Set(allSampleSizes); - let uniqueSampleFlags = new Set(allSampleFlags); - let uniqueSampleCompositionTimeOffsets = new Set(allSampleCompositionTimeOffsets); - let firstSampleFlagsPresent = uniqueSampleFlags.size === 2 && allSampleFlags[0] !== allSampleFlags[1]; - let sampleDurationPresent = uniqueSampleDurations.size > 1; - let sampleSizePresent = uniqueSampleSizes.size > 1; - let sampleFlagsPresent = !firstSampleFlagsPresent && uniqueSampleFlags.size > 1; - let sampleCompositionTimeOffsetsPresent = uniqueSampleCompositionTimeOffsets.size > 1 || [...uniqueSampleCompositionTimeOffsets].some((x) => x !== 0); + const allSampleDurations = trackData.currentChunk.samples.map((x) => x.timescaleUnitsToNextSample); + const allSampleSizes = trackData.currentChunk.samples.map((x) => x.size); + const allSampleFlags = trackData.currentChunk.samples.map(fragmentSampleFlags); + const allSampleCompositionTimeOffsets = trackData.currentChunk.samples.map((x) => intoTimescale(x.timestamp - x.decodeTimestamp, trackData.timescale)); + const uniqueSampleDurations = new Set(allSampleDurations); + const uniqueSampleSizes = new Set(allSampleSizes); + const uniqueSampleFlags = new Set(allSampleFlags); + const uniqueSampleCompositionTimeOffsets = new Set(allSampleCompositionTimeOffsets); + const firstSampleFlagsPresent = uniqueSampleFlags.size === 2 && allSampleFlags[0] !== allSampleFlags[1]; + const sampleDurationPresent = uniqueSampleDurations.size > 1; + const sampleSizePresent = uniqueSampleSizes.size > 1; + const sampleFlagsPresent = !firstSampleFlagsPresent && uniqueSampleFlags.size > 1; + const sampleCompositionTimeOffsetsPresent = uniqueSampleCompositionTimeOffsets.size > 1 || [...uniqueSampleCompositionTimeOffsets].some((x) => x !== 0); let flags = 0; flags |= 1; flags |= 4 * +firstSampleFlagsPresent; @@ -1001,7 +1001,7 @@ var mfra = (trackDatas) => { ]); }; var tfra = (trackData, trackIndex) => { - let version = 1; + const version = 1; return fullBox("tfra", version, 0, [ u32(trackData.track.id), // Track ID @@ -1041,32 +1041,32 @@ var vttc = (payload, timestamp, identifier, settings, sourceId) => box("vttc", v ]); var vtta = (notes) => box("vtta", [...textEncoder.encode(notes)]); var VIDEO_CODEC_TO_BOX_NAME = { - "avc": "avc1", - "hevc": "hvc1", - "vp8": "vp08", - "vp9": "vp09", - "av1": "av01" + avc: "avc1", + hevc: "hvc1", + vp8: "vp08", + vp9: "vp09", + av1: "av01" }; var VIDEO_CODEC_TO_CONFIGURATION_BOX = { - "avc": avcC, - "hevc": hvcC, - "vp8": vpcC, - "vp9": vpcC, - "av1": av1C + avc: avcC, + hevc: hvcC, + vp8: vpcC, + vp9: vpcC, + av1: av1C }; var AUDIO_CODEC_TO_BOX_NAME = { - "aac": "mp4a", - "opus": "Opus" + aac: "mp4a", + opus: "Opus" }; var AUDIO_CODEC_TO_CONFIGURATION_BOX = { - "aac": esds, - "opus": dOps + aac: esds, + opus: dOps }; var SUBTITLE_CODEC_TO_BOX_NAME = { - "webvtt": "wvtt" + webvtt: "wvtt" }; var SUBTITLE_CODEC_TO_CONFIGURATION_BOX = { - "webvtt": vttC + webvtt: vttC }; // src/muxer.ts @@ -1076,8 +1076,10 @@ var Muxer = class { this.trackTimestampInfo = /* @__PURE__ */ new WeakMap(); this.output = output; } + // eslint-disable-next-line @typescript-eslint/no-unused-vars beforeTrackAdd(track) { } + // eslint-disable-next-line @typescript-eslint/no-unused-vars onTrackClose(track) { } validateAndNormalizeTimestamp(track, rawTimestampInUs, isKeyFrame) { @@ -1104,11 +1106,15 @@ var Muxer = class { throw new Error(`Timestamps must be non-negative (got ${timestampInSeconds}s).`); } if (timestampInSeconds < timestampInfo.lastKeyFrameTimestamp) { - throw new Error(`Timestamp cannot be smaller than last key frame's timestamp (got ${timestampInSeconds}s, last key frame at ${timestampInfo.lastKeyFrameTimestamp}s).`); + throw new Error( + `Timestamp cannot be smaller than last key frame's timestamp (got ${timestampInSeconds}s, last key frame at ${timestampInfo.lastKeyFrameTimestamp}s).` + ); } if (isKeyFrame) { if (timestampInSeconds < timestampInfo.maxTimestamp) { - throw new Error(`Key frame timestamps cannot be smaller than any timestamp that came before (got ${timestampInSeconds}s, max timestamp was ${timestampInfo.maxTimestamp}s).`); + throw new Error( + `Key frame timestamps cannot be smaller than any timestamp that came before (got ${timestampInSeconds}s, max timestamp was ${timestampInfo.maxTimestamp}s).` + ); } timestampInfo.lastKeyFrameTimestamp = timestampInSeconds; } @@ -1139,8 +1145,8 @@ var ArrayBufferTargetWriter = class extends Writer { let newLength = this.buffer.byteLength; while (newLength < size) newLength *= 2; if (newLength === this.buffer.byteLength) return; - let newBuffer = new ArrayBuffer(newLength); - let newBytes = new Uint8Array(newBuffer); + const newBuffer = new ArrayBuffer(newLength); + const newBytes = new Uint8Array(newBuffer); newBytes.set(this.bytes, 0); this.buffer = newBuffer; this.bytes = newBytes; @@ -1159,6 +1165,7 @@ 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)); @@ -1195,15 +1202,15 @@ var StreamTargetWriter = class extends Writer { async flush() { assert(this.writer); if (this.sections.length === 0) return; - let chunks = []; - let sorted = [...this.sections].sort((a, b) => a.start - b.start); + const chunks = []; + const sorted = [...this.sections].sort((a, b) => a.start - b.start); chunks.push({ start: sorted[0].start, size: sorted[0].data.byteLength }); for (let i = 1; i < sorted.length; i++) { - let lastChunk = chunks[chunks.length - 1]; - let section = sorted[i]; + const lastChunk = chunks[chunks.length - 1]; + const section = sorted[i]; if (section.start <= lastChunk.start + lastChunk.size) { lastChunk.size = Math.max(lastChunk.size, section.start + section.data.byteLength - lastChunk.start); } else { @@ -1213,9 +1220,9 @@ var StreamTargetWriter = class extends Writer { }); } } - for (let chunk of chunks) { + for (const chunk of chunks) { chunk.data = new Uint8Array(chunk.size); - for (let section of this.sections) { + for (const section of this.sections) { if (chunk.start <= section.start && section.start < chunk.start + chunk.size) { chunk.data.set(section.data, section.start - chunk.start); } @@ -1226,7 +1233,7 @@ var StreamTargetWriter = class extends Writer { if (this.writer.desiredSize !== null && this.writer.desiredSize <= 0) { await this.writer.ready; } - this.writer.write({ + void this.writer.write({ type: "write", data: chunk.data, position: chunk.start @@ -1277,11 +1284,11 @@ var ChunkedStreamTargetWriter = class extends Writer { writeDataIntoChunks(data, position) { let chunkIndex = this.chunks.findIndex((x) => x.start <= position && position < x.start + this.chunkSize); if (chunkIndex === -1) chunkIndex = this.createChunk(position); - let chunk = this.chunks[chunkIndex]; - let relativePosition = position - chunk.start; - let toWrite = data.subarray(0, Math.min(this.chunkSize - relativePosition, data.byteLength)); + const chunk = this.chunks[chunkIndex]; + const relativePosition = position - chunk.start; + const toWrite = data.subarray(0, Math.min(this.chunkSize - relativePosition, data.byteLength)); chunk.data.set(toWrite, relativePosition); - let section = { + const section = { start: relativePosition, end: relativePosition + toWrite.byteLength }; @@ -1304,7 +1311,7 @@ var ChunkedStreamTargetWriter = class extends Writer { let high = chunk.written.length - 1; let index = -1; while (low <= high) { - let mid = Math.floor(low + (high - low + 1) / 2); + const mid = Math.floor(low + (high - low + 1) / 2); if (chunk.written[mid].start <= section.start) { low = mid + 1; index = mid; @@ -1320,8 +1327,8 @@ var ChunkedStreamTargetWriter = class extends Writer { } } createChunk(includesPosition) { - let start = Math.floor(includesPosition / this.chunkSize) * this.chunkSize; - let chunk = { + const start = Math.floor(includesPosition / this.chunkSize) * this.chunkSize; + const chunk = { start, data: new Uint8Array(this.chunkSize), written: [], @@ -1334,9 +1341,9 @@ var ChunkedStreamTargetWriter = class extends Writer { queueChunksForFlush(force = false) { assert(this.writer); for (let i = 0; i < this.chunks.length; i++) { - let chunk = this.chunks[i]; + const chunk = this.chunks[i]; if (!chunk.shouldFlush && !force) continue; - for (let section of chunk.written) { + for (const section of chunk.written) { if (this.ensureMonotonicity && chunk.start + section.start !== this.lastFlushEnd) { throw new Error("Internal error: Monotonicity violation."); } @@ -1353,11 +1360,11 @@ var ChunkedStreamTargetWriter = class extends Writer { async flush() { assert(this.writer); if (this.flushedChunkQueue.length === 0) return; - for (let chunk of this.flushedChunkQueue) { + for (const chunk of this.flushedChunkQueue) { if (this.writer.desiredSize !== null && this.writer.desiredSize <= 0) { await this.writer.ready; } - this.writer.write(chunk); + void this.writer.write(chunk); } this.flushedChunkQueue.length = 0; } @@ -1588,15 +1595,14 @@ var buildVideoCodecString = (codec, width, height, bitrate) => { const hexLevelIndication = levelIndication.toString(16).padStart(2, "0"); return `avc1.${hexProfileIndication}${hexProfileCompatibility}${hexLevelIndication}`; } else if (codec === "hevc") { - let profileSpace = 0; - let profileIdc = 1; + const profilePrefix = ""; + const profileIdc = 1; const compatibilityFlags = "6"; const pictureSize = width * height; const levelInfo = HEVC_LEVEL_TABLE.find( (level) => pictureSize <= level.maxPictureSize && bitrate <= level.maxBitrate ) ?? last(HEVC_LEVEL_TABLE); const constraintFlags = "B0"; - const profilePrefix = profileSpace === 0 ? "" : String.fromCharCode(65 + profileSpace - 1); return `hev1.${profilePrefix}${profileIdc}.${compatibilityFlags}.${levelInfo.tier}${levelInfo.level}.${constraintFlags}`; } else if (codec === "vp8") { return "vp8"; @@ -1687,42 +1693,62 @@ var validateVideoChunkMetadata = (metadata) => { throw new TypeError("Video chunk metadata decoder configuration must specify a codec string."); } if (!Number.isInteger(metadata.decoderConfig.codedWidth) || metadata.decoderConfig.codedWidth <= 0) { - throw new TypeError("Video chunk metadata decoder configuration must specify a valid codedWidth (positive integer)."); + throw new TypeError( + "Video chunk metadata decoder configuration must specify a valid codedWidth (positive integer)." + ); } if (!Number.isInteger(metadata.decoderConfig.codedHeight) || metadata.decoderConfig.codedHeight <= 0) { - throw new TypeError("Video chunk metadata decoder configuration must specify a valid codedHeight (positive integer)."); + throw new TypeError( + "Video chunk metadata decoder configuration must specify a valid codedHeight (positive integer)." + ); } if (metadata.decoderConfig.description !== void 0) { if (!isAllowSharedBufferSource(metadata.decoderConfig.description)) { - throw new TypeError("Video chunk metadata decoder configuration description, when defined, must be an ArrayBuffer or an ArrayBuffer view."); + throw new TypeError( + "Video chunk metadata decoder configuration description, when defined, must be an ArrayBuffer or an ArrayBuffer view." + ); } } if (metadata.decoderConfig.colorSpace !== void 0) { - let { colorSpace } = metadata.decoderConfig; + const { colorSpace } = metadata.decoderConfig; if (typeof colorSpace !== "object") { - throw new TypeError("Video chunk metadata decoder configuration colorSpace, when provided, must be an object."); + throw new TypeError( + "Video chunk metadata decoder configuration colorSpace, when provided, must be an object." + ); } - let primariesValues = Object.keys(COLOR_PRIMARIES_MAP); + const primariesValues = Object.keys(COLOR_PRIMARIES_MAP); if (colorSpace.primaries != null && !primariesValues.includes(colorSpace.primaries)) { - throw new TypeError(`Video chunk metadata decoder configuration colorSpace primaries, when defined, must be one of ${primariesValues.join(", ")}.`); + throw new TypeError( + `Video chunk metadata decoder configuration colorSpace primaries, when defined, must be one of ${primariesValues.join(", ")}.` + ); } - let transferValues = Object.keys(TRANSFER_CHARACTERISTICS_MAP); + const transferValues = Object.keys(TRANSFER_CHARACTERISTICS_MAP); if (colorSpace.transfer != null && !transferValues.includes(colorSpace.transfer)) { - throw new TypeError(`Video chunk metadata decoder configuration colorSpace transfer, when defined, must be one of ${transferValues.join(", ")}.`); + throw new TypeError( + `Video chunk metadata decoder configuration colorSpace transfer, when defined, must be one of ${transferValues.join(", ")}.` + ); } - let matrixValues = Object.keys(MATRIX_COEFFICIENTS_MAP); + const matrixValues = Object.keys(MATRIX_COEFFICIENTS_MAP); if (colorSpace.matrix != null && !matrixValues.includes(colorSpace.matrix)) { - throw new TypeError(`Video chunk metadata decoder configuration colorSpace matrix, when defined, must be one of ${matrixValues.join(", ")}.`); + throw new TypeError( + `Video chunk metadata decoder configuration colorSpace matrix, when defined, must be one of ${matrixValues.join(", ")}.` + ); } if (colorSpace.fullRange != null && typeof colorSpace.fullRange !== "boolean") { - throw new TypeError("Video chunk metadata decoder configuration colorSpace fullRange, when defined, must be a boolean."); + throw new TypeError( + "Video chunk metadata decoder configuration colorSpace fullRange, when defined, must be a boolean." + ); } } if ((metadata.decoderConfig.codec.startsWith("avc1") || metadata.decoderConfig.codec.startsWith("avc3")) && !metadata.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."); + 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 ((metadata.decoderConfig.codec.startsWith("hev1") || metadata.decoderConfig.codec.startsWith("hvc1")) && !metadata.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."); + 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 ((metadata.decoderConfig.codec === "vp8" || metadata.decoderConfig.codec.startsWith("vp09")) && metadata.decoderConfig.colorSpace === void 0) { throw new TypeError("Video chunk metadata decoder configuration for VP8/VP9 must include a colorSpace."); @@ -1745,18 +1771,26 @@ var validateAudioChunkMetadata = (metadata) => { throw new TypeError("Audio chunk metadata decoder configuration must specify a codec string."); } if (!Number.isInteger(metadata.decoderConfig.sampleRate) || metadata.decoderConfig.sampleRate <= 0) { - throw new TypeError("Audio chunk metadata decoder configuration must specify a valid sampleRate (positive integer)."); + throw new TypeError( + "Audio chunk metadata decoder configuration must specify a valid sampleRate (positive integer)." + ); } if (!Number.isInteger(metadata.decoderConfig.numberOfChannels) || metadata.decoderConfig.numberOfChannels <= 0) { - throw new TypeError("Audio chunk metadata decoder configuration must specify a valid numberOfChannels (positive integer)."); + throw new TypeError( + "Audio chunk metadata decoder configuration must specify a valid numberOfChannels (positive integer)." + ); } if (metadata.decoderConfig.description !== void 0) { if (!isAllowSharedBufferSource(metadata.decoderConfig.description)) { - throw new TypeError("Audio chunk metadata decoder configuration description, when defined, must be an ArrayBuffer or an ArrayBuffer view."); + throw new TypeError( + "Audio chunk metadata decoder configuration description, when defined, must be an ArrayBuffer or an ArrayBuffer view." + ); } } if (metadata.decoderConfig.codec.startsWith("mp4a") && !metadata.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."); + 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 (metadata.decoderConfig.codec === "opus" && metadata.decoderConfig.description && metadata.decoderConfig.description.byteLength < 18) { throw new TypeError("Invalid decoder description provided for Opus; must be at least 18 bytes long."); @@ -1784,7 +1818,7 @@ var validateSubtitleMetadata = (metadata) => { var GLOBAL_TIMESCALE = 1e3; var TIMESTAMP_OFFSET = 2082844800; var intoTimescale = (timeInSeconds, timescale, round = true) => { - let value = timeInSeconds * timescale; + const value = timeInSeconds * timescale; return round ? Math.round(value) : value; }; var IsobmffMuxer = class extends Muxer { @@ -1802,7 +1836,8 @@ var IsobmffMuxer = class extends Muxer { this.nextFragmentNumber = 1; this.writer = output._writer; this.boxWriter = new IsobmffBoxWriter(this.writer); - this.fastStart = format._options.fastStart ?? (this.writer instanceof ArrayBufferTargetWriter ? "in-memory" : false); + const fastStartDefault = this.writer instanceof ArrayBufferTargetWriter ? "in-memory" : false; + this.fastStart = format._options.fastStart ?? fastStartDefault; if (this.fastStart === "in-memory" || this.fastStart === "fragmented") { this.writer.ensureMonotonicity = true; } @@ -1931,10 +1966,20 @@ var IsobmffMuxer = class extends Muxer { const release = await this.mutex.acquire(); try { const trackData = this.getVideoTrackData(track, meta); - let data = new Uint8Array(chunk.byteLength); + const data = new Uint8Array(chunk.byteLength); chunk.copyTo(data); - let timestamp = this.validateAndNormalizeTimestamp(trackData.track, chunk.timestamp, chunk.type === "key"); - let sample = this.createSampleForTrack(trackData, data, timestamp, (chunk.duration ?? 0) / 1e6, chunk.type); + const timestamp = this.validateAndNormalizeTimestamp( + trackData.track, + chunk.timestamp, + chunk.type === "key" + ); + const sample = this.createSampleForTrack( + trackData, + data, + timestamp, + (chunk.duration ?? 0) / 1e6, + chunk.type + ); await this.registerSample(trackData, sample); } finally { release(); @@ -1944,10 +1989,21 @@ var IsobmffMuxer = class extends Muxer { const release = await this.mutex.acquire(); try { const trackData = this.getAudioTrackData(track, meta); - let data = new Uint8Array(chunk.byteLength); + const data = new Uint8Array(chunk.byteLength); chunk.copyTo(data); - let timestamp = this.validateAndNormalizeTimestamp(trackData.track, chunk.timestamp, chunk.type === "key"); - let sample = this.createSampleForTrack(trackData, data, timestamp, (chunk.duration ?? 0) / 1e6, chunk.type); + const chunkType = chunk.type; + const timestamp = this.validateAndNormalizeTimestamp( + trackData.track, + chunk.timestamp, + chunkType === "key" + ); + const sample = this.createSampleForTrack( + trackData, + data, + timestamp, + (chunk.duration ?? 0) / 1e6, + chunkType + ); await this.registerSample(trackData, sample); } finally { release(); @@ -1969,60 +2025,72 @@ var IsobmffMuxer = class extends Muxer { } async processWebVTTCues(trackData, until) { while (trackData.cueQueue.length > 0) { - let timestamps = /* @__PURE__ */ new Set([]); - for (let cue of trackData.cueQueue) { + const timestamps = /* @__PURE__ */ new Set([]); + for (const cue of trackData.cueQueue) { assert(cue.timestamp <= until); assert(trackData.lastCueEndTimestamp <= cue.timestamp + cue.duration); timestamps.add(Math.max(cue.timestamp, trackData.lastCueEndTimestamp)); timestamps.add(cue.timestamp + cue.duration); } - let sortedTimestamps = [...timestamps].sort((a, b) => a - b); - let sampleStart = sortedTimestamps[0]; - let sampleEnd = sortedTimestamps[1] ?? sampleStart; + const sortedTimestamps = [...timestamps].sort((a, b) => a - b); + const sampleStart = sortedTimestamps[0]; + const sampleEnd = sortedTimestamps[1] ?? sampleStart; if (until < sampleEnd) { break; } if (trackData.lastCueEndTimestamp < sampleStart) { this.auxWriter.seek(0); - let box2 = vtte(); + const box2 = vtte(); this.auxBoxWriter.writeBox(box2); - let body2 = this.auxWriter.getSlice(0, this.auxWriter.getPos()); - let sample2 = this.createSampleForTrack(trackData, body2, trackData.lastCueEndTimestamp, sampleStart - trackData.lastCueEndTimestamp, "key"); + const body2 = this.auxWriter.getSlice(0, this.auxWriter.getPos()); + const sample2 = this.createSampleForTrack( + trackData, + body2, + trackData.lastCueEndTimestamp, + sampleStart - trackData.lastCueEndTimestamp, + "key" + ); await this.registerSample(trackData, sample2); trackData.lastCueEndTimestamp = sampleStart; } this.auxWriter.seek(0); for (let i = 0; i < trackData.cueQueue.length; i++) { - let cue = trackData.cueQueue[i]; + const cue = trackData.cueQueue[i]; if (cue.timestamp >= sampleEnd) { break; } inlineTimestampRegex.lastIndex = 0; - let containsTimestamp = inlineTimestampRegex.test(cue.text); - let endTimestamp = cue.timestamp + cue.duration; + const containsTimestamp = inlineTimestampRegex.test(cue.text); + const endTimestamp = cue.timestamp + cue.duration; let sourceId = trackData.cueToSourceId.get(cue); if (sourceId === void 0 && sampleEnd < endTimestamp) { sourceId = trackData.nextSourceId++; trackData.cueToSourceId.set(cue, sourceId); } if (cue.notes) { - let box3 = vtta(cue.notes); + const box3 = vtta(cue.notes); this.auxBoxWriter.writeBox(box3); } - let box2 = vttc(cue.text, containsTimestamp ? sampleStart : null, cue.identifier ?? null, cue.settings ?? null, sourceId ?? null); + const box2 = vttc( + cue.text, + containsTimestamp ? sampleStart : null, + cue.identifier ?? null, + cue.settings ?? null, + sourceId ?? null + ); this.auxBoxWriter.writeBox(box2); if (endTimestamp === sampleEnd) { trackData.cueQueue.splice(i--, 1); } } - let body = this.auxWriter.getSlice(0, this.auxWriter.getPos()); - let sample = this.createSampleForTrack(trackData, body, sampleStart, sampleEnd - sampleStart, "key"); + const body = this.auxWriter.getSlice(0, this.auxWriter.getPos()); + const sample = this.createSampleForTrack(trackData, body, sampleStart, sampleEnd - sampleStart, "key"); await this.registerSample(trackData, sample); trackData.lastCueEndTimestamp = sampleEnd; } } createSampleForTrack(trackData, data, timestamp, duration, type) { - let sample = { + const sample = { timestamp, decodeTimestamp: timestamp, // This may be refined later @@ -2047,8 +2115,8 @@ var IsobmffMuxer = class extends Muxer { const durationInTimescale = intoTimescale(sample.duration, trackData.timescale); if (trackData.lastTimescaleUnits !== null) { assert(trackData.lastSample); - let timescaleUnits = intoTimescale(sample.decodeTimestamp, trackData.timescale, false); - let delta = Math.round(timescaleUnits - trackData.lastTimescaleUnits); + const timescaleUnits = intoTimescale(sample.decodeTimestamp, trackData.timescale, false); + const delta = Math.round(timescaleUnits - trackData.lastTimescaleUnits); trackData.lastTimescaleUnits += delta; trackData.lastSample.timescaleUnitsToNextSample = delta; if (this.fastStart !== "fragmented") { @@ -2056,7 +2124,7 @@ var IsobmffMuxer = class extends Muxer { assert(lastTableEntry); if (lastTableEntry.sampleCount === 1) { lastTableEntry.sampleDelta = delta; - let entryBefore = trackData.timeToSampleTable[trackData.timeToSampleTable.length - 2]; + const entryBefore = trackData.timeToSampleTable[trackData.timeToSampleTable.length - 2]; if (entryBefore && entryBefore.sampleDelta === delta) { entryBefore.sampleCount++; trackData.timeToSampleTable.pop(); @@ -2124,7 +2192,7 @@ var IsobmffMuxer = class extends Muxer { if (!trackData.currentChunk) { beginNewChunk = true; } else { - let currentChunkDuration = sample.timestamp - trackData.currentChunk.startTimestamp; + const currentChunkDuration = sample.timestamp - trackData.currentChunk.startTimestamp; if (this.fastStart === "fragmented") { const keyFrameQueuedEverywhere = this.trackDatas.every((otherTrackData) => { if (trackData === otherTrackData) { @@ -2173,7 +2241,7 @@ var IsobmffMuxer = class extends Muxer { return; } trackData.currentChunk.offset = this.writer.getPos(); - for (let sample of trackData.currentChunk.samples) { + for (const sample of trackData.currentChunk.samples) { assert(sample.data); this.writer.write(sample.data); sample.data = null; @@ -2191,7 +2259,7 @@ var IsobmffMuxer = class extends Muxer { while (true) { let trackWithMinTimestamp = null; let minTimestamp = Infinity; - for (let trackData of this.trackDatas) { + for (const trackData of this.trackDatas) { if (trackData.sampleQueue.length === 0 && !trackData.track.source._closed) { break outer; } @@ -2203,26 +2271,26 @@ var IsobmffMuxer = class extends Muxer { if (!trackWithMinTimestamp) { break; } - let sample = trackWithMinTimestamp.sampleQueue.shift(); + const sample = trackWithMinTimestamp.sampleQueue.shift(); await this.addSampleToTrack(trackWithMinTimestamp, sample); } } async finalizeFragment(flushWriter = true) { assert(this.fastStart === "fragmented"); - let fragmentNumber = this.nextFragmentNumber++; + const fragmentNumber = this.nextFragmentNumber++; if (fragmentNumber === 1) { - let movieBox = moov(this.trackDatas, this.creationTime, true); + const movieBox = moov(this.trackDatas, this.creationTime, true); this.boxWriter.writeBox(movieBox); } - let moofOffset = this.writer.getPos(); - let moofBox = moof(fragmentNumber, this.trackDatas); + const moofOffset = this.writer.getPos(); + const moofBox = moof(fragmentNumber, this.trackDatas); this.boxWriter.writeBox(moofBox); { - let mdatBox = mdat(false); + const mdatBox = mdat(false); let totalTrackSampleSize = 0; - for (let trackData of this.trackDatas) { + for (const trackData of this.trackDatas) { assert(trackData.currentChunk); - for (let sample of trackData.currentChunk.samples) { + for (const sample of trackData.currentChunk.samples) { totalTrackSampleSize += sample.size; } } @@ -2234,20 +2302,20 @@ var IsobmffMuxer = class extends Muxer { mdatBox.size = mdatSize; this.boxWriter.writeBox(mdatBox); } - for (let trackData of this.trackDatas) { + for (const trackData of this.trackDatas) { trackData.currentChunk.offset = this.writer.getPos(); trackData.currentChunk.moofOffset = moofOffset; - for (let sample of trackData.currentChunk.samples) { + for (const sample of trackData.currentChunk.samples) { this.writer.write(sample.data); sample.data = null; } } - let endPos = this.writer.getPos(); + const endPos = this.writer.getPos(); this.writer.seek(this.boxWriter.offsets.get(moofBox)); - let newMoofBox = moof(fragmentNumber, this.trackDatas); + const newMoofBox = moof(fragmentNumber, this.trackDatas); this.boxWriter.writeBox(newMoofBox); this.writer.seek(endPos); - for (let trackData of this.trackDatas) { + for (const trackData of this.trackDatas) { trackData.finalizedChunks.push(trackData.currentChunk); this.finalizedChunks.push(trackData.currentChunk); trackData.currentChunk = null; @@ -2256,10 +2324,11 @@ var IsobmffMuxer = class extends Muxer { await this.writer.flush(); } } + // eslint-disable-next-line @typescript-eslint/no-misused-promises async onTrackClose(track) { const release = await this.mutex.acquire(); if (track.type === "subtitle" && track.source._codec === "webvtt") { - let trackData = this.trackDatas.find((x) => x.track === track); + const trackData = this.trackDatas.find((x) => x.track === track); if (trackData) { await this.processWebVTTCues(trackData, Infinity); } @@ -2272,21 +2341,21 @@ var IsobmffMuxer = class extends Muxer { /** Finalizes the file, making it ready for use. Must be called after all video and audio chunks have been added. */ async finalize() { const release = await this.mutex.acquire(); - for (let trackData of this.trackDatas) { + for (const trackData of this.trackDatas) { if (trackData.type === "subtitle" && trackData.track.source._codec === "webvtt") { await this.processWebVTTCues(trackData, Infinity); } } if (this.fastStart === "fragmented") { - for (let trackData of this.trackDatas) { - for (let sample of trackData.sampleQueue) { + for (const trackData of this.trackDatas) { + for (const sample of trackData.sampleQueue) { await this.addSampleToTrack(trackData, sample); } this.processTimestamps(trackData); } await this.finalizeFragment(false); } else { - for (let trackData of this.trackDatas) { + for (const trackData of this.trackDatas) { this.processTimestamps(trackData); await this.finalizeCurrentChunk(trackData); } @@ -2295,13 +2364,13 @@ var IsobmffMuxer = class extends Muxer { assert(this.mdat); let mdatSize; for (let i = 0; i < 2; i++) { - let movieBox2 = moov(this.trackDatas, this.creationTime); - let movieBoxSize = this.boxWriter.measureBox(movieBox2); + const movieBox2 = moov(this.trackDatas, this.creationTime); + const movieBoxSize = this.boxWriter.measureBox(movieBox2); mdatSize = this.boxWriter.measureBox(this.mdat); let currentChunkPos = this.writer.getPos() + movieBoxSize + mdatSize; - for (let chunk of this.finalizedChunks) { + for (const chunk of this.finalizedChunks) { chunk.offset = currentChunkPos; - for (let { data } of chunk.samples) { + for (const { data } of chunk.samples) { assert(data); currentChunkPos += data.byteLength; mdatSize += data.byteLength; @@ -2310,38 +2379,38 @@ var IsobmffMuxer = class extends Muxer { if (currentChunkPos < 2 ** 32) break; if (mdatSize >= 2 ** 32) this.mdat.largeSize = true; } - let movieBox = moov(this.trackDatas, this.creationTime); + const movieBox = moov(this.trackDatas, this.creationTime); this.boxWriter.writeBox(movieBox); this.mdat.size = mdatSize; this.boxWriter.writeBox(this.mdat); - for (let chunk of this.finalizedChunks) { - for (let sample of chunk.samples) { + for (const chunk of this.finalizedChunks) { + for (const sample of chunk.samples) { assert(sample.data); this.writer.write(sample.data); sample.data = null; } } } else if (this.fastStart === "fragmented") { - let startPos = this.writer.getPos(); - let mfraBox = mfra(this.trackDatas); + const startPos = this.writer.getPos(); + const mfraBox = mfra(this.trackDatas); this.boxWriter.writeBox(mfraBox); - let mfraBoxSize = this.writer.getPos() - startPos; + const mfraBoxSize = this.writer.getPos() - startPos; this.writer.seek(this.writer.getPos() - 4); this.boxWriter.writeU32(mfraBoxSize); } else { assert(this.mdat); assert(this.ftypSize !== null); - let mdatPos = this.boxWriter.offsets.get(this.mdat); + const mdatPos = this.boxWriter.offsets.get(this.mdat); assert(mdatPos !== void 0); - let mdatSize = this.writer.getPos() - mdatPos; + const mdatSize = this.writer.getPos() - mdatPos; this.mdat.size = mdatSize; this.mdat.largeSize = mdatSize >= 2 ** 32; this.boxWriter.patchBox(this.mdat); - let movieBox = moov(this.trackDatas, this.creationTime); + const movieBox = moov(this.trackDatas, this.creationTime); if (typeof this.fastStart === "object") { this.writer.seek(this.ftypSize); this.boxWriter.writeBox(movieBox); - let remainingBytes = mdatPos - this.writer.getPos(); + const remainingBytes = mdatPos - this.writer.getPos(); this.boxWriter.writeBox(free(remainingBytes)); } else { this.boxWriter.writeBox(movieBox); @@ -2482,14 +2551,19 @@ var MatroskaMuxer = class extends Muxer { switch (width) { case 6: this.helperView.setUint8(pos++, value / 2 ** 40 | 0); + // eslint-disable-next-line no-fallthrough case 5: this.helperView.setUint8(pos++, value / 2 ** 32 | 0); + // eslint-disable-next-line no-fallthrough case 4: this.helperView.setUint8(pos++, value >> 24); + // eslint-disable-next-line no-fallthrough case 3: this.helperView.setUint8(pos++, value >> 16); + // eslint-disable-next-line no-fallthrough case 2: this.helperView.setUint8(pos++, value >> 8); + // eslint-disable-next-line no-fallthrough case 1: this.helperView.setUint8(pos++, value); break; @@ -2554,32 +2628,32 @@ var MatroskaMuxer = class extends Muxer { if (data instanceof Uint8Array) { this.writer.write(data); } else if (Array.isArray(data)) { - for (let elem of data) { + for (const elem of data) { this.writeEBML(elem); } } else { this.offsets.set(data, this.writer.getPos()); this.writeUnsignedInt(data.id); if (Array.isArray(data.data)) { - let sizePos = this.writer.getPos(); - let sizeSize = data.size === -1 ? 1 : data.size ?? 4; + const sizePos = this.writer.getPos(); + const sizeSize = data.size === -1 ? 1 : data.size ?? 4; if (data.size === -1) { this.writeByte(255); } else { this.writer.seek(this.writer.getPos() + sizeSize); } - let startPos = this.writer.getPos(); + const startPos = this.writer.getPos(); this.dataOffsets.set(data, startPos); this.writeEBML(data.data); if (data.size !== -1) { - let size = this.writer.getPos() - startPos; - let endPos = this.writer.getPos(); + const size = this.writer.getPos() - startPos; + const endPos = this.writer.getPos(); this.writer.seek(sizePos); this.writeEBMLVarInt(size, sizeSize); this.writer.seek(endPos); } } else if (typeof data.data === "number") { - let size = data.size ?? measureUnsignedInt(data.data); + const size = data.size ?? measureUnsignedInt(data.data); this.writeEBMLVarInt(size); this.writeUnsignedInt(data.data, size); } else if (typeof data.data === "string") { @@ -2595,7 +2669,7 @@ var MatroskaMuxer = class extends Muxer { this.writeEBMLVarInt(8); this.writeFloat64(data.data.value); } else if (data.data instanceof EBMLSignedInt) { - let size = data.size ?? measureSignedInt(data.data.value); + const size = data.size ?? measureSignedInt(data.data.value); this.writeEBMLVarInt(size); this.writeSignedInt(data.data.value, size); } @@ -2607,18 +2681,26 @@ var MatroskaMuxer = class extends Muxer { } if (track.type === "video") { if (!["vp8", "vp9", "av1"].includes(track.source._codec)) { - throw new Error(`WebM only supports VP8, VP9 and AV1 as video codecs. Switching to MKV removes this restriction.`); + throw new Error( + `WebM only supports VP8, VP9 and AV1 as video codecs. Switching to MKV removes this restriction.` + ); } } else if (track.type === "audio") { if (!["opus", "vorbis"].includes(track.source._codec)) { - throw new Error(`WebM only supports Opus and Vorbis as audio codecs. Switching to MKV removes this restriction.`); + throw new Error( + `WebM only supports Opus and Vorbis as audio codecs. Switching to MKV removes this restriction.` + ); } } else if (track.type === "subtitle") { if (track.source._codec !== "webvtt") { - throw new Error(`WebM only supports WebVTT as subtitle codec. Switching to MKV removes this restriction.`); + 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."); + throw new Error( + "WebM only supports video, audio and subtitle tracks. Switching to MKV removes this restriction." + ); } } async start() { @@ -2633,7 +2715,7 @@ var MatroskaMuxer = class extends Muxer { release(); } writeEBMLHeader() { - let ebmlHeader = { id: 440786851 /* EBML */, data: [ + const ebmlHeader = { id: 440786851 /* EBML */, data: [ { id: 17030 /* EBMLVersion */, data: 1 }, { id: 17143 /* EBMLReadVersion */, data: 1 }, { id: 17138 /* EBMLMaxIDLength */, data: 4 }, @@ -2652,7 +2734,7 @@ var MatroskaMuxer = class extends Muxer { const kaxCues = new Uint8Array([28, 83, 187, 107]); const kaxInfo = new Uint8Array([21, 73, 169, 102]); const kaxTracks = new Uint8Array([22, 84, 174, 107]); - let seekHead = { id: 290298740 /* SeekHead */, data: [ + const seekHead = { id: 290298740 /* SeekHead */, data: [ { id: 19899 /* Seek */, data: [ { id: 21419 /* SeekID */, data: kaxCues }, { id: 21420 /* SeekPosition */, size: 5, data: 0 } @@ -2669,9 +2751,9 @@ var MatroskaMuxer = class extends Muxer { this.seekHead = seekHead; } createSegmentInfo() { - let segmentDuration = { id: 17545 /* Duration */, data: new EBMLFloat64(0) }; + const segmentDuration = { id: 17545 /* Duration */, data: new EBMLFloat64(0) }; this.segmentDuration = segmentDuration; - let segmentInfo = { id: 357149030 /* Info */, data: [ + const segmentInfo = { id: 357149030 /* Info */, data: [ { id: 2807729 /* TimestampScale */, data: 1e6 }, { id: 19840 /* MuxingApp */, data: APP_NAME }, { id: 22337 /* WritingApp */, data: APP_NAME }, @@ -2680,53 +2762,80 @@ var MatroskaMuxer = class extends Muxer { this.segmentInfo = segmentInfo; } createTracks() { - let tracksElement = { id: 374648427 /* Tracks */, data: [] }; + const tracksElement = { id: 374648427 /* Tracks */, data: [] }; this.tracksElement = tracksElement; - for (let trackData of this.trackDatas) { + for (const trackData of this.trackDatas) { tracksElement.data.push({ id: 174 /* TrackEntry */, data: [ { id: 215 /* TrackNumber */, data: trackData.track.id }, { id: 29637 /* TrackUID */, data: trackData.track.id }, { id: 131 /* TrackType */, data: TRACK_TYPE_MAP[trackData.type] }, { id: 134 /* CodecID */, data: CODEC_STRING_MAP[trackData.track.source._codec] }, - ...trackData.type === "video" ? [ - trackData.info.decoderConfig.description ? { id: 25506 /* CodecPrivate */, data: toUint8Array(trackData.info.decoderConfig.description) } : null, - trackData.track.metadata.frameRate ? { id: 2352003 /* DefaultDuration */, data: 1e9 / trackData.track.metadata.frameRate } : null, - { id: 224 /* Video */, data: [ - { id: 176 /* PixelWidth */, data: trackData.info.width }, - { id: 186 /* PixelHeight */, data: trackData.info.height }, - (() => { - if (trackData.info.decoderConfig.colorSpace) { - let colorSpace = trackData.info.decoderConfig.colorSpace; - if (!colorSpaceIsComplete(colorSpace)) { - return null; - } - return { id: 21936 /* Colour */, data: [ - { id: 21937 /* MatrixCoefficients */, data: MATRIX_COEFFICIENTS_MAP[colorSpace.matrix] }, - { id: 21946 /* TransferCharacteristics */, data: TRANSFER_CHARACTERISTICS_MAP[colorSpace.transfer] }, - { id: 21947 /* Primaries */, data: COLOR_PRIMARIES_MAP[colorSpace.primaries] }, - { id: 21945 /* Range */, data: [1, 2][Number(colorSpace.fullRange)] } - ] }; - } - return null; - })() - ] } - ] : [], - ...trackData.type === "audio" ? [ - trackData.info.decoderConfig.description ? { id: 25506 /* CodecPrivate */, data: toUint8Array(trackData.info.decoderConfig.description) } : null, - { id: 225 /* Audio */, data: [ - { id: 181 /* SamplingFrequency */, data: new EBMLFloat32(trackData.info.sampleRate) }, - { id: 159 /* Channels */, data: trackData.info.numberOfChannels } - // Bit depth for when PCM is a thing - ] } - ] : [], - ...trackData.type === "subtitle" ? [ - { id: 25506 /* CodecPrivate */, data: textEncoder.encode(trackData.info.config.description) } - ] : [] + trackData.type === "video" ? this.videoSpecificTrackInfo(trackData) : null, + trackData.type === "audio" ? this.audioSpecificTrackInfo(trackData) : null, + trackData.type === "subtitle" ? this.subtitleSpecificTrackInfo(trackData) : null ] }); } } + videoSpecificTrackInfo(trackData) { + const elements = [ + trackData.info.decoderConfig.description ? { + id: 25506 /* CodecPrivate */, + data: toUint8Array(trackData.info.decoderConfig.description) + } : null, + trackData.track.metadata.frameRate ? { + id: 2352003 /* DefaultDuration */, + data: 1e9 / trackData.track.metadata.frameRate + } : null + ]; + const colorSpace = trackData.info.decoderConfig.colorSpace; + const videoElement = { id: 224 /* Video */, data: [ + { id: 176 /* PixelWidth */, data: trackData.info.width }, + { id: 186 /* PixelHeight */, data: trackData.info.height }, + colorSpaceIsComplete(colorSpace) ? { + id: 21936 /* Colour */, + data: [ + { + id: 21937 /* MatrixCoefficients */, + data: MATRIX_COEFFICIENTS_MAP[colorSpace.matrix] + }, + { + id: 21946 /* TransferCharacteristics */, + data: TRANSFER_CHARACTERISTICS_MAP[colorSpace.transfer] + }, + { + id: 21947 /* Primaries */, + data: COLOR_PRIMARIES_MAP[colorSpace.primaries] + }, + { + id: 21945 /* Range */, + data: colorSpace.fullRange ? 2 : 1 + } + ] + } : null + ] }; + elements.push(videoElement); + return elements; + } + audioSpecificTrackInfo(trackData) { + return [ + trackData.info.decoderConfig.description ? { + id: 25506 /* CodecPrivate */, + data: toUint8Array(trackData.info.decoderConfig.description) + } : null, + { id: 225 /* Audio */, data: [ + { id: 181 /* SamplingFrequency */, data: new EBMLFloat32(trackData.info.sampleRate) }, + { id: 159 /* Channels */, data: trackData.info.numberOfChannels } + // TODO Bit depth for when PCM is a thing + ] } + ]; + } + subtitleSpecificTrackInfo(trackData) { + return [ + { id: 25506 /* CodecPrivate */, data: textEncoder.encode(trackData.info.config.description) } + ]; + } createSegment() { - let segment = { + const segment = { id: 408125543 /* Segment */, size: this.format._options.streamable ? -1 : SEGMENT_SIZE_BYTES, data: [ @@ -2818,10 +2927,11 @@ var MatroskaMuxer = class extends Muxer { const release = await this.mutex.acquire(); try { const trackData = this.getVideoTrackData(track, meta); - let data = new Uint8Array(chunk.byteLength); + const data = new Uint8Array(chunk.byteLength); chunk.copyTo(data); - let timestamp = this.validateAndNormalizeTimestamp(trackData.track, chunk.timestamp, chunk.type === "key"); - let videoChunk = this.createInternalChunk(data, timestamp, (chunk.duration ?? 0) / 1e6, chunk.type); + const isKeyFrame = chunk.type === "key"; + const timestamp = this.validateAndNormalizeTimestamp(trackData.track, chunk.timestamp, isKeyFrame); + const videoChunk = this.createInternalChunk(data, timestamp, (chunk.duration ?? 0) / 1e6, chunk.type); if (track.source._codec === "vp9") this.fixVP9ColorSpace(trackData, videoChunk); trackData.chunkQueue.push(videoChunk); await this.interleaveChunks(); @@ -2833,10 +2943,12 @@ var MatroskaMuxer = class extends Muxer { const release = await this.mutex.acquire(); try { const trackData = this.getAudioTrackData(track, meta); - let data = new Uint8Array(chunk.byteLength); + const data = new Uint8Array(chunk.byteLength); chunk.copyTo(data); - let timestamp = this.validateAndNormalizeTimestamp(trackData.track, chunk.timestamp, chunk.type === "key"); - let audioChunk = this.createInternalChunk(data, timestamp, (chunk.duration ?? 0) / 1e6, chunk.type); + const chunkType = chunk.type; + const isKeyFrame = chunkType === "key"; + const timestamp = this.validateAndNormalizeTimestamp(trackData.track, chunk.timestamp, isKeyFrame); + const audioChunk = this.createInternalChunk(data, timestamp, (chunk.duration ?? 0) / 1e6, chunkType); trackData.chunkQueue.push(audioChunk); await this.interleaveChunks(); } finally { @@ -2852,15 +2964,21 @@ var MatroskaMuxer = class extends Muxer { const timestampMs = Math.floor(timestamp * 1e3); inlineTimestampRegex.lastIndex = 0; bodyText = bodyText.replace(inlineTimestampRegex, (match) => { - let time = parseSubtitleTimestamp(match.slice(1, -1)); - let offsetTime = time - timestampMs; + const time = parseSubtitleTimestamp(match.slice(1, -1)); + const offsetTime = time - timestampMs; return `<${formatSubtitleTimestamp(offsetTime)}>`; }); const body = textEncoder.encode(bodyText); const additions = `${cue.settings ?? ""} ${cue.identifier ?? ""} ${cue.notes ?? ""}`; - let subtitleChunk = this.createInternalChunk(body, timestamp, cue.duration, "key", additions.trim() ? textEncoder.encode(additions) : null); + const subtitleChunk = this.createInternalChunk( + body, + timestamp, + cue.duration, + "key", + additions.trim() ? textEncoder.encode(additions) : null + ); trackData.chunkQueue.push(subtitleChunk); await this.interleaveChunks(); } finally { @@ -2877,7 +2995,7 @@ ${cue.notes ?? ""}`; while (true) { let trackWithMinTimestamp = null; let minTimestamp = Infinity; - for (let trackData of this.trackDatas) { + for (const trackData of this.trackDatas) { if (trackData.chunkQueue.length === 0 && !trackData.track.source._closed) { break outer; } @@ -2889,7 +3007,7 @@ ${cue.notes ?? ""}`; if (!trackWithMinTimestamp) { break; } - let chunk = trackWithMinTimestamp.chunkQueue.shift(); + const chunk = trackWithMinTimestamp.chunkQueue.shift(); this.writeBlock(trackWithMinTimestamp, chunk); } await this.writer.flush(); @@ -2903,31 +3021,31 @@ ${cue.notes ?? ""}`; let i = 0; if (readBits(chunk.data, 0, 2) !== 2) return; i += 2; - let profile = (readBits(chunk.data, i + 1, i + 2) << 1) + readBits(chunk.data, i + 0, i + 1); + const profile = (readBits(chunk.data, i + 1, i + 2) << 1) + readBits(chunk.data, i + 0, i + 1); i += 2; if (profile === 3) i++; - let showExistingFrame = readBits(chunk.data, i + 0, i + 1); + const showExistingFrame = readBits(chunk.data, i + 0, i + 1); i++; if (showExistingFrame) return; - let frameType = readBits(chunk.data, i + 0, i + 1); + const frameType = readBits(chunk.data, i + 0, i + 1); i++; if (frameType !== 0) return; i += 2; - let syncCode = readBits(chunk.data, i + 0, i + 24); + const syncCode = readBits(chunk.data, i + 0, i + 24); i += 24; if (syncCode !== 4817730) return; if (profile >= 2) i++; - let colorSpaceID = { - "rgb": 7, - "bt709": 2, - "bt470bg": 1, - "smpte170m": 3 + const colorSpaceID = { + rgb: 7, + bt709: 2, + bt470bg: 1, + smpte170m: 3 }[trackData.info.decoderConfig.colorSpace.matrix]; writeBits(chunk.data, i + 0, i + 3, colorSpaceID); } /** Converts a read-only external chunk into an internal one for easier use. */ createInternalChunk(data, timestamp, duration, type, additions = null) { - let internalChunk = { + const internalChunk = { data, type, timestamp, @@ -2942,7 +3060,7 @@ ${cue.notes ?? ""}`; this.createTracks(); this.createSegment(); } - let msTimestamp = Math.floor(1e3 * chunk.timestamp); + const msTimestamp = Math.floor(1e3 * chunk.timestamp); const keyFrameQueuedEverywhere = this.trackDatas.every((otherTrackData) => { if (otherTrackData.track.source._closed) { return true; @@ -2956,35 +3074,38 @@ ${cue.notes ?? ""}`; if (!this.currentCluster || keyFrameQueuedEverywhere && msTimestamp - this.currentClusterMsTimestamp >= 1e3) { this.createNewCluster(msTimestamp); } - let relativeTimestamp = msTimestamp - this.currentClusterMsTimestamp; + const relativeTimestamp = msTimestamp - this.currentClusterMsTimestamp; if (relativeTimestamp < 0) { return; } - let clusterIsTooLong = relativeTimestamp >= MAX_CHUNK_LENGTH_MS; + const clusterIsTooLong = relativeTimestamp >= MAX_CHUNK_LENGTH_MS; if (clusterIsTooLong) { throw new Error( `Current Matroska cluster exceeded its maximum allowed length of ${MAX_CHUNK_LENGTH_MS} milliseconds. In order to produce a correct WebM file, you must pass in a key frame at least every ${MAX_CHUNK_LENGTH_MS} milliseconds.` ); } - let prelude = new Uint8Array(4); - let view2 = new DataView(prelude.buffer); + const prelude = new Uint8Array(4); + const view2 = new DataView(prelude.buffer); view2.setUint8(0, 128 | trackData.track.id); view2.setInt16(1, relativeTimestamp, false); - let msDuration = Math.floor(1e3 * chunk.duration); + const msDuration = Math.floor(1e3 * chunk.duration); if (msDuration === 0 && !chunk.additions) { view2.setUint8(3, Number(chunk.type === "key") << 7); - let simpleBlock = { id: 163 /* SimpleBlock */, data: [ + const simpleBlock = { id: 163 /* SimpleBlock */, data: [ prelude, chunk.data ] }; this.writeEBML(simpleBlock); } else { - let blockGroup = { id: 160 /* BlockGroup */, data: [ + const blockGroup = { id: 160 /* BlockGroup */, data: [ { id: 161 /* Block */, data: [ prelude, chunk.data ] }, - chunk.type === "delta" ? { id: 251 /* ReferenceBlock */, data: new EBMLSignedInt(trackData.lastWrittenMsTimestamp - msTimestamp) } : null, + chunk.type === "delta" ? { + id: 251 /* ReferenceBlock */, + data: new EBMLSignedInt(trackData.lastWrittenMsTimestamp - msTimestamp) + } : null, chunk.additions ? { id: 30113 /* BlockAdditions */, data: [ { id: 166 /* BlockMore */, data: [ { id: 165 /* BlockAdditional */, data: chunk.additions }, @@ -3017,12 +3138,12 @@ ${cue.notes ?? ""}`; } finalizeCurrentCluster() { assert(this.currentCluster); - let clusterSize = this.writer.getPos() - this.dataOffsets.get(this.currentCluster); - let endPos = this.writer.getPos(); + const clusterSize = this.writer.getPos() - this.dataOffsets.get(this.currentCluster); + const endPos = this.writer.getPos(); this.writer.seek(this.offsets.get(this.currentCluster) + 4); this.writeEBMLVarInt(clusterSize, CLUSTER_SIZE_BYTES); this.writer.seek(endPos); - let clusterOffsetFromSegment = this.offsets.get(this.currentCluster) - this.segmentDataOffset; + const clusterOffsetFromSegment = this.offsets.get(this.currentCluster) - this.segmentDataOffset; assert(this.cues); this.cues.data.push({ id: 187 /* CuePoint */, data: [ { id: 179 /* CueTime */, data: this.currentClusterMsTimestamp }, @@ -3035,6 +3156,7 @@ ${cue.notes ?? ""}`; }) ] }); } + // eslint-disable-next-line @typescript-eslint/no-misused-promises async onTrackClose() { const release = await this.mutex.acquire(); await this.interleaveChunks(); @@ -3047,7 +3169,7 @@ ${cue.notes ?? ""}`; this.createTracks(); this.createSegment(); } - for (let trackData of this.trackDatas) { + for (const trackData of this.trackDatas) { while (trackData.chunkQueue.length > 0) { this.writeBlock(trackData, trackData.chunkQueue.shift()); } @@ -3058,8 +3180,8 @@ ${cue.notes ?? ""}`; assert(this.cues); this.writeEBML(this.cues); if (!this.format._options.streamable) { - let endPos = this.writer.getPos(); - let segmentSize = this.writer.getPos() - this.segmentDataOffset; + const endPos = this.writer.getPos(); + const segmentSize = this.writer.getPos() - this.segmentDataOffset; this.writer.seek(this.offsets.get(this.segment) + 4); this.writeEBMLVarInt(segmentSize, SEGMENT_SIZE_BYTES); this.segmentDuration.data = new EBMLFloat64(this.duration); @@ -3199,7 +3321,7 @@ var validateVideoCodecConfig = (config) => { if (!Number.isInteger(config.bitrate) || config.bitrate <= 0) { throw new TypeError("config.bitrate must be a positive integer."); } - if (config.latencyMode !== void 0 && ["quality", "realtime"].includes(config.latencyMode)) { + if (config.latencyMode !== void 0 && !["quality", "realtime"].includes(config.latencyMode)) { throw new TypeError("config.latencyMode, when provided, must be 'quality' or 'realtime'."); } }; @@ -3218,7 +3340,9 @@ var VideoEncoderWrapper = class { this.source._ensureValidDigest(); 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}.`); + 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; @@ -3227,7 +3351,9 @@ var VideoEncoderWrapper = class { this.ensureEncoder(videoFrame); assert(this.encoder); const multipleOfKeyFrameInterval = Math.floor(videoFrame.timestamp / 1e6 / KEY_FRAME_INTERVAL); - this.encoder.encode(videoFrame, { keyFrame: multipleOfKeyFrameInterval !== this.lastMultipleOfKeyFrameInterval }); + this.encoder.encode(videoFrame, { + keyFrame: multipleOfKeyFrameInterval !== this.lastMultipleOfKeyFrameInterval + }); this.lastMultipleOfKeyFrameInterval = multipleOfKeyFrameInterval; if (this.encoder.encodeQueueSize >= 4) { await new Promise((resolve) => this.encoder.addEventListener("dequeue", resolve, { once: true })); @@ -3239,11 +3365,16 @@ var VideoEncoderWrapper = class { return; } this.encoder = new VideoEncoder({ - output: (chunk, meta) => this.muxer.addEncodedVideoChunk(this.source._connectedTrack, chunk, meta), + 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), + codec: buildVideoCodecString( + this.codecConfig.codec, + videoFrame.codedWidth, + videoFrame.codedHeight, + this.codecConfig.bitrate + ), width: videoFrame.codedWidth, height: videoFrame.codedHeight, bitrate: this.codecConfig.bitrate, @@ -3330,7 +3461,7 @@ var MediaStreamVideoTrackSource = class extends VideoSource { const processor = new MediaStreamTrackProcessor({ track: this._track }); const consumer = new WritableStream({ write: (videoFrame) => { - this._encoder.digest(videoFrame); + void this._encoder.digest(videoFrame); videoFrame.close(); } }); @@ -3398,7 +3529,9 @@ var AudioEncoderWrapper = class { this.source._ensureValidDigest(); 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.`); + 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; @@ -3417,7 +3550,7 @@ var AudioEncoderWrapper = class { return; } this.encoder = new AudioEncoder({ - output: (chunk, meta) => this.muxer.addEncodedAudioChunk(this.source._connectedTrack, chunk, meta), + output: (chunk, meta) => void this.muxer.addEncodedAudioChunk(this.source._connectedTrack, chunk, meta), error: (error) => console.error("Audio encode error:", error) }); this.encoder.configure({ @@ -3509,7 +3642,7 @@ var MediaStreamAudioTrackSource = class extends AudioSource { const processor = new MediaStreamTrackProcessor({ track: this._track }); const consumer = new WritableStream({ write: (audioData) => { - this._encoder.digest(audioData); + void this._encoder.digest(audioData); audioData.close(); } }); diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..6115d15 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,40 @@ +import eslint from '@eslint/js'; +import tseslint from 'typescript-eslint'; +import stylistic from '@stylistic/eslint-plugin'; + +export default tseslint.config( + eslint.configs.recommended, + tseslint.configs.recommendedTypeChecked, + { + languageOptions: { + parserOptions: { + projectService: true, + tsconfigRootDir: import.meta.dirname, + }, + }, + }, + stylistic.configs.customize({ + indent: 'tab', + quotes: 'single', + semi: true, + braceStyle: '1tbs', + }), + { + rules: { + '@stylistic/max-len': ['error', { + code: 120, + }], + '@typescript-eslint/no-empty-object-type': 'off', + }, + }, + { + ignores: [ + 'dist', + 'build', + 'api-sketch.ts', + 'build.mjs', + 'append-namespace.mjs', + 'eslint.config.mjs', + ] + } +); diff --git a/package-lock.json b/package-lock.json index c778ffd..3116aa6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,9 +13,13 @@ "@types/dom-webcodecs": "^0.1.13" }, "devDependencies": { + "@eslint/js": "^9.16.0", "@microsoft/api-extractor": "^7.48.0", + "@stylistic/eslint-plugin": "^2.11.0", "esbuild": "^0.23.1", - "typescript": "^5.5.4" + "eslint": "^9.16.0", + "typescript": "^5.5.4", + "typescript-eslint": "^8.16.0" } }, "node_modules/@esbuild/aix-ppc64": { @@ -402,6 +406,228 @@ "node": ">=18" } }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", + "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.0.tgz", + "integrity": "sha512-zdHg2FPIFNKPdcHWtiNT+jEFCHYVplAXRDlQDyqy0zGx/q2parwh7brGJSiTxRk/TSMkbM//zt/f5CHgyTyaSQ==", + "dev": true, + "dependencies": { + "@eslint/object-schema": "^2.1.4", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/core": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.9.0.tgz", + "integrity": "sha512-7ATR9F0e4W85D/0w7cU0SNj7qkAexMG+bAHEZOjo9akvGuhHE2m7umzWzfnpa0XAg5Kxc1BWmtPMV67jJ+9VUg==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz", + "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "9.16.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.16.0.tgz", + "integrity": "sha512-tw2HxzQkrbeuvyj1tG2Yqq+0H9wGoI2IMk4EOsQeX+vmd75FtJAzf+gTA69WF+baUKRYQ3x2kbLE08js5OsTVg==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", + "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.3.tgz", + "integrity": "sha512-2b/g5hRmpbb1o4GnTZax9N9m0FXzz9OV42ZzI4rDDMDuHUqigAiQCEWChBWCY4ztAGVRjoWT19v0yMmc5/L5kA==", + "dev": true, + "dependencies": { + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz", + "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==", + "dev": true, + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, "node_modules/@microsoft/api-extractor": { "version": "7.48.0", "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.48.0.tgz", @@ -468,6 +694,41 @@ "resolve": "~1.22.2" } }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/@rushstack/node-core-library": { "version": "5.10.0", "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-5.10.0.tgz", @@ -548,6 +809,37 @@ "string-argv": "~0.3.1" } }, + "node_modules/@stylistic/eslint-plugin": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-2.11.0.tgz", + "integrity": "sha512-PNRHbydNG5EH8NK4c+izdJlxajIR6GxcUhzsYNRsn6Myep4dsZt0qFCz3rCPnkvgO5FYibDcMqgNHUT+zvjYZw==", + "dev": true, + "dependencies": { + "@typescript-eslint/utils": "^8.13.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "estraverse": "^5.3.0", + "picomatch": "^4.0.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": ">=8.40.0" + } + }, + "node_modules/@stylistic/eslint-plugin/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/@types/argparse": { "version": "1.0.38", "resolved": "https://registry.npmjs.org/@types/argparse/-/argparse-1.0.38.tgz", @@ -567,6 +859,265 @@ "resolved": "https://registry.npmjs.org/@types/dom-webcodecs/-/dom-webcodecs-0.1.13.tgz", "integrity": "sha512-O5hkiFIcjjszPIYyUSyvScyvrBoV3NOEEZx/pMlsu44TKzWNkLVBBxnxJz42in5n3QIolYOcBYFCPZZ0h8SkwQ==" }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.16.0.tgz", + "integrity": "sha512-5YTHKV8MYlyMI6BaEG7crQ9BhSc8RxzshOReKwZwRWN0+XvvTOm+L/UYLCYxFpfwYuAAqhxiq4yae0CMFwbL7Q==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.16.0", + "@typescript-eslint/type-utils": "8.16.0", + "@typescript-eslint/utils": "8.16.0", + "@typescript-eslint/visitor-keys": "8.16.0", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", + "eslint": "^8.57.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.16.0.tgz", + "integrity": "sha512-D7DbgGFtsqIPIFMPJwCad9Gfi/hC0PWErRRHFnaCWoEDYi5tQUDiJCTmGUbBiLzjqAck4KcXt9Ayj0CNlIrF+w==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "8.16.0", + "@typescript-eslint/types": "8.16.0", + "@typescript-eslint/typescript-estree": "8.16.0", + "@typescript-eslint/visitor-keys": "8.16.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.16.0.tgz", + "integrity": "sha512-mwsZWubQvBki2t5565uxF0EYvG+FwdFb8bMtDuGQLdCCnGPrDEDvm1gtfynuKlnpzeBRqdFCkMf9jg1fnAK8sg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.16.0", + "@typescript-eslint/visitor-keys": "8.16.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.16.0.tgz", + "integrity": "sha512-IqZHGG+g1XCWX9NyqnI/0CX5LL8/18awQqmkZSl2ynn8F76j579dByc0jhfVSnSnhf7zv76mKBQv9HQFKvDCgg==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "8.16.0", + "@typescript-eslint/utils": "8.16.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.16.0.tgz", + "integrity": "sha512-NzrHj6thBAOSE4d9bsuRNMvk+BvaQvmY4dDglgkgGC0EW/tB3Kelnp3tAKH87GEwzoxgeQn9fNGRyFJM/xd+GQ==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.16.0.tgz", + "integrity": "sha512-E2+9IzzXMc1iaBy9zmo+UYvluE3TW7bCGWSF41hVWUE01o8nzr1rvOQYSxelxr6StUvRcTMe633eY8mXASMaNw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.16.0", + "@typescript-eslint/visitor-keys": "8.16.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.16.0.tgz", + "integrity": "sha512-C1zRy/mOL8Pj157GiX4kaw7iyRLKfJXBR3L82hk5kS/GyHcOFmy4YUq/zfZti72I9wnuQtA/+xzft4wCC8PJdA==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "8.16.0", + "@typescript-eslint/types": "8.16.0", + "@typescript-eslint/typescript-estree": "8.16.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.16.0.tgz", + "integrity": "sha512-pq19gbaMOmFE3CbL0ZB8J8BFCo2ckfHBfaIsaOZgBIF4EoISJIdLX5xRhd0FGB0LlHReNRuzoJoMGpTjq8F2CQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.16.0", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/acorn": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, "node_modules/ajv": { "version": "8.12.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", @@ -614,6 +1165,21 @@ } } }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -639,12 +1205,116 @@ "concat-map": "0.0.1" } }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, "node_modules/esbuild": { "version": "0.23.1", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz", @@ -684,12 +1354,312 @@ "@esbuild/win32-x64": "0.23.1" } }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.16.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.16.0.tgz", + "integrity": "sha512-whp8mSQI4C8VXd+fLgSM0lh3UlmcFtVwUQjyKCFfsp+2ItAIYhlq/hqGahGqHE6cv9unM41VlqKk2VtKYR2TaA==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.19.0", + "@eslint/core": "^0.9.0", + "@eslint/eslintrc": "^3.2.0", + "@eslint/js": "9.16.0", + "@eslint/plugin-kit": "^0.2.3", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.1", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.5", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.2.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", + "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/espree": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "dev": true, + "dependencies": { + "acorn": "^8.14.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz", + "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==", + "dev": true + }, "node_modules/fs-extra": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", @@ -713,12 +1683,42 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -740,6 +1740,31 @@ "node": ">= 0.4" } }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/import-lazy": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz", @@ -749,6 +1774,15 @@ "node": ">=8" } }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, "node_modules/is-core-module": { "version": "2.15.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", @@ -764,18 +1798,84 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, "node_modules/jju": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz", "integrity": "sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==", "dev": true }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/js-yaml/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, "node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, "node_modules/jsonfile": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", @@ -785,12 +1885,55 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -803,6 +1946,28 @@ "node": ">=10" } }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, "node_modules/minimatch": { "version": "3.0.8", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz", @@ -815,12 +1980,122 @@ "node": "*" } }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -830,6 +2105,26 @@ "node": ">=6" } }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -856,6 +2151,48 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, "node_modules/semver": { "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", @@ -871,6 +2208,27 @@ "node": ">=10" } }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -934,6 +2292,42 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-api-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", + "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", + "dev": true, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/typescript": { "version": "5.5.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", @@ -947,6 +2341,32 @@ "node": ">=14.17" } }, + "node_modules/typescript-eslint": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.16.0.tgz", + "integrity": "sha512-wDkVmlY6O2do4V+lZd0GtRfbtXbeD0q9WygwXXSJnC1xorE8eqyC2L1tJimqpSeFrOzRlYtWnUp/uzgHQOgfBQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.16.0", + "@typescript-eslint/parser": "8.16.0", + "@typescript-eslint/utils": "8.16.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -965,11 +2385,47 @@ "punycode": "^2.1.0" } }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } } } } diff --git a/package.json b/package.json index 193433b..eb5abb6 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,8 @@ "scripts": { "build": "node build.mjs && tsc && api-extractor run && node append-namespace.mjs", "build-local": "node build.mjs && tsc && api-extractor run --local --verbose && node append-namespace.mjs", - "watch": "node build.mjs --watch" + "watch": "node build.mjs --watch", + "lint": "eslint ." }, "author": "", "license": "MIT", @@ -28,8 +29,12 @@ "@types/dom-webcodecs": "^0.1.13" }, "devDependencies": { + "@eslint/js": "^9.16.0", "@microsoft/api-extractor": "^7.48.0", + "@stylistic/eslint-plugin": "^2.11.0", "esbuild": "^0.23.1", - "typescript": "^5.5.4" + "eslint": "^9.16.0", + "typescript": "^5.5.4", + "typescript-eslint": "^8.16.0" } } diff --git a/src/codec.ts b/src/codec.ts index 2f852ce..cc97e09 100644 --- a/src/codec.ts +++ b/src/codec.ts @@ -1,160 +1,168 @@ -import { COLOR_PRIMARIES_MAP, isAllowSharedBufferSource, last, MATRIX_COEFFICIENTS_MAP, TRANSFER_CHARACTERISTICS_MAP } from "./misc"; -import { AudioCodec, VideoCodec } from "./source"; -import { SubtitleMetadata } from "./subtitles"; +import { + COLOR_PRIMARIES_MAP, + MATRIX_COEFFICIENTS_MAP, + TRANSFER_CHARACTERISTICS_MAP, + isAllowSharedBufferSource, + last, +} from './misc'; +import { AudioCodec, VideoCodec } from './source'; +import { SubtitleMetadata } from './subtitles'; // https://en.wikipedia.org/wiki/Advanced_Video_Coding const AVC_LEVEL_TABLE = [ - { maxMacroblocks: 99, maxBitrate: 64000, level: 0x0A }, // Level 1 - { maxMacroblocks: 396, maxBitrate: 192000, level: 0x0B }, // Level 1.1 - { maxMacroblocks: 396, maxBitrate: 384000, level: 0x0C }, // Level 1.2 - { maxMacroblocks: 396, maxBitrate: 768000, level: 0x0D }, // Level 1.3 - { maxMacroblocks: 396, maxBitrate: 2000000, level: 0x14 }, // Level 2 - { maxMacroblocks: 792, maxBitrate: 4000000, level: 0x15 }, // Level 2.1 - { maxMacroblocks: 1620, maxBitrate: 4000000, level: 0x16 }, // Level 2.2 - { maxMacroblocks: 1620, maxBitrate: 10000000, level: 0x1E }, // Level 3 - { maxMacroblocks: 3600, maxBitrate: 14000000, level: 0x1F }, // Level 3.1 - { maxMacroblocks: 5120, maxBitrate: 20000000, level: 0x20 }, // Level 3.2 - { maxMacroblocks: 8192, maxBitrate: 20000000, level: 0x28 }, // Level 4 - { maxMacroblocks: 8192, maxBitrate: 50000000, level: 0x29 }, // Level 4.1 - { maxMacroblocks: 8704, maxBitrate: 50000000, level: 0x2A }, // Level 4.2 - { maxMacroblocks: 22080, maxBitrate: 135000000, level: 0x32 }, // Level 5 - { maxMacroblocks: 36864, maxBitrate: 240000000, level: 0x33 }, // Level 5.1 - { maxMacroblocks: 36864, maxBitrate: 240000000, level: 0x34 }, // Level 5.2 - { maxMacroblocks: 139264, maxBitrate: 240000000, level: 0x3C }, // Level 6 - { maxMacroblocks: 139264, maxBitrate: 480000000, level: 0x3D }, // Level 6.1 - { maxMacroblocks: 139264, maxBitrate: 800000000, level: 0x3E }, // Level 6.2 + { maxMacroblocks: 99, maxBitrate: 64000, level: 0x0A }, // Level 1 + { maxMacroblocks: 396, maxBitrate: 192000, level: 0x0B }, // Level 1.1 + { maxMacroblocks: 396, maxBitrate: 384000, level: 0x0C }, // Level 1.2 + { maxMacroblocks: 396, maxBitrate: 768000, level: 0x0D }, // Level 1.3 + { maxMacroblocks: 396, maxBitrate: 2000000, level: 0x14 }, // Level 2 + { maxMacroblocks: 792, maxBitrate: 4000000, level: 0x15 }, // Level 2.1 + { maxMacroblocks: 1620, maxBitrate: 4000000, level: 0x16 }, // Level 2.2 + { maxMacroblocks: 1620, maxBitrate: 10000000, level: 0x1E }, // Level 3 + { maxMacroblocks: 3600, maxBitrate: 14000000, level: 0x1F }, // Level 3.1 + { maxMacroblocks: 5120, maxBitrate: 20000000, level: 0x20 }, // Level 3.2 + { maxMacroblocks: 8192, maxBitrate: 20000000, level: 0x28 }, // Level 4 + { maxMacroblocks: 8192, maxBitrate: 50000000, level: 0x29 }, // Level 4.1 + { maxMacroblocks: 8704, maxBitrate: 50000000, level: 0x2A }, // Level 4.2 + { maxMacroblocks: 22080, maxBitrate: 135000000, level: 0x32 }, // Level 5 + { maxMacroblocks: 36864, maxBitrate: 240000000, level: 0x33 }, // Level 5.1 + { maxMacroblocks: 36864, maxBitrate: 240000000, level: 0x34 }, // Level 5.2 + { maxMacroblocks: 139264, maxBitrate: 240000000, level: 0x3C }, // Level 6 + { maxMacroblocks: 139264, maxBitrate: 480000000, level: 0x3D }, // Level 6.1 + { maxMacroblocks: 139264, maxBitrate: 800000000, level: 0x3E }, // Level 6.2 ]; // https://en.wikipedia.org/wiki/High_Efficiency_Video_Coding const HEVC_LEVEL_TABLE = [ - { maxPictureSize: 36864, maxBitrate: 128000, tier: 'L', level: 30 }, // Level 1 (Low Tier) - { maxPictureSize: 122880, maxBitrate: 1500000, tier: 'L', level: 60 }, // Level 2 (Low Tier) - { maxPictureSize: 245760, maxBitrate: 3000000, tier: 'L', level: 63 }, // Level 2.1 (Low Tier) - { maxPictureSize: 552960, maxBitrate: 6000000, tier: 'L', level: 90 }, // Level 3 (Low Tier) - { maxPictureSize: 983040, maxBitrate: 10000000, tier: 'L', level: 93 }, // Level 3.1 (Low Tier) - { maxPictureSize: 2228224, maxBitrate: 12000000, tier: 'L', level: 120 }, // Level 4 (Low Tier) - { maxPictureSize: 2228224, maxBitrate: 30000000, tier: 'H', level: 120 }, // Level 4 (High Tier) - { maxPictureSize: 2228224, maxBitrate: 20000000, tier: 'L', level: 123 }, // Level 4.1 (Low Tier) - { maxPictureSize: 2228224, maxBitrate: 50000000, tier: 'H', level: 123 }, // Level 4.1 (High Tier) - { maxPictureSize: 8912896, maxBitrate: 25000000, tier: 'L', level: 150 }, // Level 5 (Low Tier) - { maxPictureSize: 8912896, maxBitrate: 100000000, tier: 'H', level: 150 }, // Level 5 (High Tier) - { maxPictureSize: 8912896, maxBitrate: 40000000, tier: 'L', level: 153 }, // Level 5.1 (Low Tier) - { maxPictureSize: 8912896, maxBitrate: 160000000, tier: 'H', level: 153 }, // Level 5.1 (High Tier) - { maxPictureSize: 8912896, maxBitrate: 60000000, tier: 'L', level: 156 }, // Level 5.2 (Low Tier) - { maxPictureSize: 8912896, maxBitrate: 240000000, tier: 'H', level: 156 }, // Level 5.2 (High Tier) - { maxPictureSize: 35651584, maxBitrate: 60000000, tier: 'L', level: 180 }, // Level 6 (Low Tier) - { maxPictureSize: 35651584, maxBitrate: 240000000, tier: 'H', level: 180 }, // Level 6 (High Tier) - { maxPictureSize: 35651584, maxBitrate: 120000000, tier: 'L', level: 183 }, // Level 6.1 (Low Tier) - { maxPictureSize: 35651584, maxBitrate: 480000000, tier: 'H', level: 183 }, // Level 6.1 (High Tier) - { maxPictureSize: 35651584, maxBitrate: 240000000, tier: 'L', level: 186 }, // Level 6.2 (Low Tier) - { maxPictureSize: 35651584, maxBitrate: 800000000, tier: 'H', level: 186 }, // Level 6.2 (High Tier) + { maxPictureSize: 36864, maxBitrate: 128000, tier: 'L', level: 30 }, // Level 1 (Low Tier) + { maxPictureSize: 122880, maxBitrate: 1500000, tier: 'L', level: 60 }, // Level 2 (Low Tier) + { maxPictureSize: 245760, maxBitrate: 3000000, tier: 'L', level: 63 }, // Level 2.1 (Low Tier) + { maxPictureSize: 552960, maxBitrate: 6000000, tier: 'L', level: 90 }, // Level 3 (Low Tier) + { maxPictureSize: 983040, maxBitrate: 10000000, tier: 'L', level: 93 }, // Level 3.1 (Low Tier) + { maxPictureSize: 2228224, maxBitrate: 12000000, tier: 'L', level: 120 }, // Level 4 (Low Tier) + { maxPictureSize: 2228224, maxBitrate: 30000000, tier: 'H', level: 120 }, // Level 4 (High Tier) + { maxPictureSize: 2228224, maxBitrate: 20000000, tier: 'L', level: 123 }, // Level 4.1 (Low Tier) + { maxPictureSize: 2228224, maxBitrate: 50000000, tier: 'H', level: 123 }, // Level 4.1 (High Tier) + { maxPictureSize: 8912896, maxBitrate: 25000000, tier: 'L', level: 150 }, // Level 5 (Low Tier) + { maxPictureSize: 8912896, maxBitrate: 100000000, tier: 'H', level: 150 }, // Level 5 (High Tier) + { maxPictureSize: 8912896, maxBitrate: 40000000, tier: 'L', level: 153 }, // Level 5.1 (Low Tier) + { maxPictureSize: 8912896, maxBitrate: 160000000, tier: 'H', level: 153 }, // Level 5.1 (High Tier) + { maxPictureSize: 8912896, maxBitrate: 60000000, tier: 'L', level: 156 }, // Level 5.2 (Low Tier) + { maxPictureSize: 8912896, maxBitrate: 240000000, tier: 'H', level: 156 }, // Level 5.2 (High Tier) + { maxPictureSize: 35651584, maxBitrate: 60000000, tier: 'L', level: 180 }, // Level 6 (Low Tier) + { maxPictureSize: 35651584, maxBitrate: 240000000, tier: 'H', level: 180 }, // Level 6 (High Tier) + { maxPictureSize: 35651584, maxBitrate: 120000000, tier: 'L', level: 183 }, // Level 6.1 (Low Tier) + { maxPictureSize: 35651584, maxBitrate: 480000000, tier: 'H', level: 183 }, // Level 6.1 (High Tier) + { maxPictureSize: 35651584, maxBitrate: 240000000, tier: 'L', level: 186 }, // Level 6.2 (Low Tier) + { maxPictureSize: 35651584, maxBitrate: 800000000, tier: 'H', level: 186 }, // Level 6.2 (High Tier) ]; // https://en.wikipedia.org/wiki/VP9 const VP9_LEVEL_TABLE = [ - { maxPictureSize: 36864, maxBitrate: 200000, level: 10 }, // Level 1 - { maxPictureSize: 73728, maxBitrate: 800000, level: 11 }, // Level 1.1 - { maxPictureSize: 122880, maxBitrate: 1800000, level: 20 }, // Level 2 - { maxPictureSize: 245760, maxBitrate: 3600000, level: 21 }, // Level 2.1 - { maxPictureSize: 552960, maxBitrate: 7200000, level: 30 }, // Level 3 - { maxPictureSize: 983040, maxBitrate: 12000000, level: 31 }, // Level 3.1 - { maxPictureSize: 2228224, maxBitrate: 18000000, level: 40 }, // Level 4 - { maxPictureSize: 2228224, maxBitrate: 30000000, level: 41 }, // Level 4.1 - { maxPictureSize: 8912896, maxBitrate: 60000000, level: 50 }, // Level 5 - { maxPictureSize: 8912896, maxBitrate: 120000000, level: 51 }, // Level 5.1 - { maxPictureSize: 8912896, maxBitrate: 180000000, level: 52 }, // Level 5.2 - { maxPictureSize: 35651584, maxBitrate: 180000000, level: 60 }, // Level 6 - { maxPictureSize: 35651584, maxBitrate: 240000000, level: 61 }, // Level 6.1 - { maxPictureSize: 35651584, maxBitrate: 480000000, level: 62 }, // Level 6.2 + { maxPictureSize: 36864, maxBitrate: 200000, level: 10 }, // Level 1 + { maxPictureSize: 73728, maxBitrate: 800000, level: 11 }, // Level 1.1 + { maxPictureSize: 122880, maxBitrate: 1800000, level: 20 }, // Level 2 + { maxPictureSize: 245760, maxBitrate: 3600000, level: 21 }, // Level 2.1 + { maxPictureSize: 552960, maxBitrate: 7200000, level: 30 }, // Level 3 + { maxPictureSize: 983040, maxBitrate: 12000000, level: 31 }, // Level 3.1 + { maxPictureSize: 2228224, maxBitrate: 18000000, level: 40 }, // Level 4 + { maxPictureSize: 2228224, maxBitrate: 30000000, level: 41 }, // Level 4.1 + { maxPictureSize: 8912896, maxBitrate: 60000000, level: 50 }, // Level 5 + { maxPictureSize: 8912896, maxBitrate: 120000000, level: 51 }, // Level 5.1 + { maxPictureSize: 8912896, maxBitrate: 180000000, level: 52 }, // Level 5.2 + { maxPictureSize: 35651584, maxBitrate: 180000000, level: 60 }, // Level 6 + { maxPictureSize: 35651584, maxBitrate: 240000000, level: 61 }, // Level 6.1 + { maxPictureSize: 35651584, maxBitrate: 480000000, level: 62 }, // Level 6.2 ]; // https://en.wikipedia.org/wiki/AV1 const AV1_LEVEL_TABLE = [ - { maxPictureSize: 147456, maxBitrate: 1500000, tier: 'M', level: 0 }, // Level 2.0 (Main Tier) - { maxPictureSize: 278784, maxBitrate: 3000000, tier: 'M', level: 1 }, // Level 2.1 (Main Tier) - { maxPictureSize: 665856, maxBitrate: 6000000, tier: 'M', level: 4 }, // Level 3.0 (Main Tier) - { maxPictureSize: 1065024, maxBitrate: 10000000, tier: 'M', level: 5 }, // Level 3.1 (Main Tier) - { maxPictureSize: 2359296, maxBitrate: 12000000, tier: 'M', level: 8 }, // Level 4.0 (Main Tier) - { maxPictureSize: 2359296, maxBitrate: 30000000, tier: 'H', level: 8 }, // Level 4.0 (High Tier) - { maxPictureSize: 2359296, maxBitrate: 20000000, tier: 'M', level: 9 }, // Level 4.1 (Main Tier) - { maxPictureSize: 2359296, maxBitrate: 50000000, tier: 'H', level: 9 }, // Level 4.1 (High Tier) - { maxPictureSize: 8912896, maxBitrate: 30000000, tier: 'M', level: 12 }, // Level 5.0 (Main Tier) - { maxPictureSize: 8912896, maxBitrate: 100000000, tier: 'H', level: 12 }, // Level 5.0 (High Tier) - { maxPictureSize: 8912896, maxBitrate: 40000000, tier: 'M', level: 13 }, // Level 5.1 (Main Tier) - { maxPictureSize: 8912896, maxBitrate: 160000000, tier: 'H', level: 13 }, // Level 5.1 (High Tier) - { maxPictureSize: 8912896, maxBitrate: 60000000, tier: 'M', level: 14 }, // Level 5.2 (Main Tier) - { maxPictureSize: 8912896, maxBitrate: 240000000, tier: 'H', level: 14 }, // Level 5.2 (High Tier) - { maxPictureSize: 35651584, maxBitrate: 60000000, tier: 'M', level: 15 }, // Level 5.3 (Main Tier) - { maxPictureSize: 35651584, maxBitrate: 240000000, tier: 'H', level: 15 }, // Level 5.3 (High Tier) - { maxPictureSize: 35651584, maxBitrate: 60000000, tier: 'M', level: 16 }, // Level 6.0 (Main Tier) - { maxPictureSize: 35651584, maxBitrate: 240000000, tier: 'H', level: 16 }, // Level 6.0 (High Tier) - { maxPictureSize: 35651584, maxBitrate: 100000000, tier: 'M', level: 17 }, // Level 6.1 (Main Tier) - { maxPictureSize: 35651584, maxBitrate: 480000000, tier: 'H', level: 17 }, // Level 6.1 (High Tier) - { maxPictureSize: 35651584, maxBitrate: 160000000, tier: 'M', level: 18 }, // Level 6.2 (Main Tier) - { maxPictureSize: 35651584, maxBitrate: 800000000, tier: 'H', level: 18 }, // Level 6.2 (High Tier) - { maxPictureSize: 35651584, maxBitrate: 160000000, tier: 'M', level: 19 }, // Level 6.3 (Main Tier) - { maxPictureSize: 35651584, maxBitrate: 800000000, tier: 'H', level: 19 }, // Level 6.3 (High Tier) + { maxPictureSize: 147456, maxBitrate: 1500000, tier: 'M', level: 0 }, // Level 2.0 (Main Tier) + { maxPictureSize: 278784, maxBitrate: 3000000, tier: 'M', level: 1 }, // Level 2.1 (Main Tier) + { maxPictureSize: 665856, maxBitrate: 6000000, tier: 'M', level: 4 }, // Level 3.0 (Main Tier) + { maxPictureSize: 1065024, maxBitrate: 10000000, tier: 'M', level: 5 }, // Level 3.1 (Main Tier) + { maxPictureSize: 2359296, maxBitrate: 12000000, tier: 'M', level: 8 }, // Level 4.0 (Main Tier) + { maxPictureSize: 2359296, maxBitrate: 30000000, tier: 'H', level: 8 }, // Level 4.0 (High Tier) + { maxPictureSize: 2359296, maxBitrate: 20000000, tier: 'M', level: 9 }, // Level 4.1 (Main Tier) + { maxPictureSize: 2359296, maxBitrate: 50000000, tier: 'H', level: 9 }, // Level 4.1 (High Tier) + { maxPictureSize: 8912896, maxBitrate: 30000000, tier: 'M', level: 12 }, // Level 5.0 (Main Tier) + { maxPictureSize: 8912896, maxBitrate: 100000000, tier: 'H', level: 12 }, // Level 5.0 (High Tier) + { maxPictureSize: 8912896, maxBitrate: 40000000, tier: 'M', level: 13 }, // Level 5.1 (Main Tier) + { maxPictureSize: 8912896, maxBitrate: 160000000, tier: 'H', level: 13 }, // Level 5.1 (High Tier) + { maxPictureSize: 8912896, maxBitrate: 60000000, tier: 'M', level: 14 }, // Level 5.2 (Main Tier) + { maxPictureSize: 8912896, maxBitrate: 240000000, tier: 'H', level: 14 }, // Level 5.2 (High Tier) + { maxPictureSize: 35651584, maxBitrate: 60000000, tier: 'M', level: 15 }, // Level 5.3 (Main Tier) + { maxPictureSize: 35651584, maxBitrate: 240000000, tier: 'H', level: 15 }, // Level 5.3 (High Tier) + { maxPictureSize: 35651584, maxBitrate: 60000000, tier: 'M', level: 16 }, // Level 6.0 (Main Tier) + { maxPictureSize: 35651584, maxBitrate: 240000000, tier: 'H', level: 16 }, // Level 6.0 (High Tier) + { maxPictureSize: 35651584, maxBitrate: 100000000, tier: 'M', level: 17 }, // Level 6.1 (Main Tier) + { maxPictureSize: 35651584, maxBitrate: 480000000, tier: 'H', level: 17 }, // Level 6.1 (High Tier) + { maxPictureSize: 35651584, maxBitrate: 160000000, tier: 'M', level: 18 }, // Level 6.2 (Main Tier) + { maxPictureSize: 35651584, maxBitrate: 800000000, tier: 'H', level: 18 }, // Level 6.2 (High Tier) + { maxPictureSize: 35651584, maxBitrate: 160000000, tier: 'M', level: 19 }, // Level 6.3 (Main Tier) + { maxPictureSize: 35651584, maxBitrate: 800000000, tier: 'H', level: 19 }, // Level 6.3 (High Tier) ]; export const buildVideoCodecString = (codec: VideoCodec, width: number, height: number, bitrate: number) => { - if (codec === 'avc') { - const profileIndication = 0x64; // High Profile - const totalMacroblocks = Math.ceil(width / 16) * Math.ceil(height / 16); + if (codec === 'avc') { + const profileIndication = 0x64; // High Profile + const totalMacroblocks = Math.ceil(width / 16) * Math.ceil(height / 16); - // Determine the level based on the table - const levelInfo = AVC_LEVEL_TABLE.find( - (level) => totalMacroblocks <= level.maxMacroblocks && bitrate <= level.maxBitrate - ) ?? last(AVC_LEVEL_TABLE)!; - const levelIndication = levelInfo ? levelInfo.level : 0; + // Determine the level based on the table + const levelInfo = AVC_LEVEL_TABLE.find( + level => totalMacroblocks <= level.maxMacroblocks && bitrate <= level.maxBitrate, + ) ?? last(AVC_LEVEL_TABLE)!; + const levelIndication = levelInfo ? levelInfo.level : 0; - const hexProfileIndication = profileIndication.toString(16).padStart(2, '0'); - const hexProfileCompatibility = '00'; - const hexLevelIndication = levelIndication.toString(16).padStart(2, '0'); + const hexProfileIndication = profileIndication.toString(16).padStart(2, '0'); + const hexProfileCompatibility = '00'; + const hexLevelIndication = levelIndication.toString(16).padStart(2, '0'); + + return `avc1.${hexProfileIndication}${hexProfileCompatibility}${hexLevelIndication}`; + } else if (codec === 'hevc') { + const profilePrefix = ''; // Profile space 0 + const profileIdc = 1; // Main Profile - return `avc1.${hexProfileIndication}${hexProfileCompatibility}${hexLevelIndication}`; - } else if (codec === 'hevc') { - let profileSpace = 0; - let profileIdc = 1; // Main Profile - const compatibilityFlags = '6'; // Taken from the example in ISO 14496-15 const pictureSize = width * height; const levelInfo = HEVC_LEVEL_TABLE.find( - (level) => pictureSize <= level.maxPictureSize && bitrate <= level.maxBitrate + level => pictureSize <= level.maxPictureSize && bitrate <= level.maxBitrate, ) ?? last(HEVC_LEVEL_TABLE)!; - + const constraintFlags = 'B0'; // Progressive source flag - - const profilePrefix = profileSpace === 0 ? '' : - String.fromCharCode(65 + profileSpace - 1); - - return `hev1.${profilePrefix}${profileIdc}.${compatibilityFlags}.${levelInfo.tier}${levelInfo.level}.${constraintFlags}`; + + return 'hev1.' + + `${profilePrefix}${profileIdc}.` + + `${compatibilityFlags}.` + + `${levelInfo.tier}${levelInfo.level}.` + + `${constraintFlags}`; } else if (codec === 'vp8') { return 'vp8'; // Easy, this one } else if (codec === 'vp9') { - const profile = "00"; // Profile 0 - + const profile = '00'; // Profile 0 + const pictureSize = width * height; const levelInfo = VP9_LEVEL_TABLE.find( - (level) => pictureSize <= level.maxPictureSize && bitrate <= level.maxBitrate + level => pictureSize <= level.maxPictureSize && bitrate <= level.maxBitrate, ) ?? last(VP9_LEVEL_TABLE)!; - - const bitDepth = "08"; // 8-bit - + + const bitDepth = '08'; // 8-bit + return `vp09.${profile}.${levelInfo.level}.${bitDepth}`; } else if (codec === 'av1') { const profile = 0; // Main Profile - + const pictureSize = width * height; const levelInfo = AV1_LEVEL_TABLE.find( - (level) => pictureSize <= level.maxPictureSize && bitrate <= level.maxBitrate + level => pictureSize <= level.maxPictureSize && bitrate <= level.maxBitrate, ) ?? last(AV1_LEVEL_TABLE)!; - - const bitDepth = "08"; // 8-bit - + + const bitDepth = '08'; // 8-bit + return `av01.${profile}.${levelInfo.level.toString().padStart(2, '0')}${levelInfo.tier}.${bitDepth}`; } + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions throw new TypeError(`Unhandled codec '${codec}'.`); }; @@ -178,6 +186,7 @@ export const buildAudioCodecString = (codec: AudioCodec, numberOfChannels: numbe return 'vorbis'; // Also easy, this one } + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions throw new TypeError(`Unhandled codec '${codec}'.`); }; @@ -185,14 +194,14 @@ export const getVideoEncoderConfigExtension = (codec: VideoCodec) => { if (codec === 'avc') { return { avc: { - format: 'avc' as const // Ensure the format is not Annex B - } + format: 'avc' as const, // Ensure the format is not Annex B + }, }; } else if (codec === 'hevc') { return { hevc: { - format: 'hevc' as const // Ensure the format is not Annex B - } + format: 'hevc' as const, // Ensure the format is not Annex B + }, }; } @@ -203,14 +212,14 @@ export const getAudioEncoderConfigExtension = (codec: AudioCodec) => { if (codec === 'aac') { return { aac: { - format: 'aac' as const // Ensure the format is not ADTS - } + format: 'aac' as const, // Ensure the format is not ADTS + }, }; } else if (codec === 'opus') { return { opus: { - format: 'opus' as const - } + format: 'opus' as const, + }, }; } @@ -219,120 +228,169 @@ export const getAudioEncoderConfigExtension = (codec: AudioCodec) => { export const validateVideoChunkMetadata = (metadata: EncodedVideoChunkMetadata | undefined) => { if (!metadata) { - throw new TypeError("Video chunk metadata must be provided."); + throw new TypeError('Video chunk metadata must be provided.'); } if (typeof metadata !== 'object') { - throw new TypeError("Video chunk metadata must be an object."); + throw new TypeError('Video chunk metadata must be an object.'); } if (!metadata.decoderConfig) { - throw new TypeError("Video chunk metadata must include a decoder configuration."); + throw new TypeError('Video chunk metadata must include a decoder configuration.'); } if (typeof metadata.decoderConfig !== 'object') { - throw new TypeError("Video chunk metadata decoder configuration must be an object."); + throw new TypeError('Video chunk metadata decoder configuration must be an object.'); } if (typeof metadata.decoderConfig.codec !== 'string') { - throw new TypeError("Video chunk metadata decoder configuration must specify a codec string."); + throw new TypeError('Video chunk metadata decoder configuration must specify a codec string.'); } if (!Number.isInteger(metadata.decoderConfig.codedWidth) || metadata.decoderConfig.codedWidth! <= 0) { - throw new TypeError("Video chunk metadata decoder configuration must specify a valid codedWidth (positive integer)."); + throw new TypeError( + 'Video chunk metadata decoder configuration must specify a valid codedWidth (positive integer).', + ); } if (!Number.isInteger(metadata.decoderConfig.codedHeight) || metadata.decoderConfig.codedHeight! <= 0) { - throw new TypeError("Video chunk metadata decoder configuration must specify a valid codedHeight (positive integer)."); + throw new TypeError( + 'Video chunk metadata decoder configuration must specify a valid codedHeight (positive integer).', + ); } if (metadata.decoderConfig.description !== undefined) { if (!isAllowSharedBufferSource(metadata.decoderConfig.description)) { - throw new TypeError("Video chunk metadata decoder configuration description, when defined, must be an ArrayBuffer or an ArrayBuffer view."); + throw new TypeError( + 'Video chunk metadata decoder configuration description, when defined, must be an ArrayBuffer or an' + + ' ArrayBuffer view.', + ); } } if (metadata.decoderConfig.colorSpace !== undefined) { - let { colorSpace } = metadata.decoderConfig; + const { colorSpace } = metadata.decoderConfig; if (typeof colorSpace !== 'object') { - throw new TypeError("Video chunk metadata decoder configuration colorSpace, when provided, must be an object."); + throw new TypeError( + 'Video chunk metadata decoder configuration colorSpace, when provided, must be an object.', + ); } - let primariesValues = Object.keys(COLOR_PRIMARIES_MAP); + const primariesValues = Object.keys(COLOR_PRIMARIES_MAP); if (colorSpace.primaries != null && !primariesValues.includes(colorSpace.primaries)) { - throw new TypeError(`Video chunk metadata decoder configuration colorSpace primaries, when defined, must be one of ${primariesValues.join(', ')}.`); + throw new TypeError( + `Video chunk metadata decoder configuration colorSpace primaries, when defined, must be one of` + + ` ${primariesValues.join(', ')}.`, + ); } - let transferValues = Object.keys(TRANSFER_CHARACTERISTICS_MAP); + const transferValues = Object.keys(TRANSFER_CHARACTERISTICS_MAP); if (colorSpace.transfer != null && !transferValues.includes(colorSpace.transfer)) { - throw new TypeError(`Video chunk metadata decoder configuration colorSpace transfer, when defined, must be one of ${transferValues.join(', ')}.`); + throw new TypeError( + `Video chunk metadata decoder configuration colorSpace transfer, when defined, must be one of` + + ` ${transferValues.join(', ')}.`, + ); } - let matrixValues = Object.keys(MATRIX_COEFFICIENTS_MAP); + const matrixValues = Object.keys(MATRIX_COEFFICIENTS_MAP); if (colorSpace.matrix != null && !matrixValues.includes(colorSpace.matrix)) { - throw new TypeError(`Video chunk metadata decoder configuration colorSpace matrix, when defined, must be one of ${matrixValues.join(', ')}.`); + throw new TypeError( + `Video chunk metadata decoder configuration colorSpace matrix, when defined, must be one of` + + ` ${matrixValues.join(', ')}.`, + ); } if (colorSpace.fullRange != null && typeof colorSpace.fullRange !== 'boolean') { - throw new TypeError("Video chunk metadata decoder configuration colorSpace fullRange, when defined, must be a boolean."); + throw new TypeError( + 'Video chunk metadata decoder configuration colorSpace fullRange, when defined, must be a boolean.', + ); } } - if ((metadata.decoderConfig.codec.startsWith('avc1') || metadata.decoderConfig.codec.startsWith('avc3')) && !metadata.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 ( + (metadata.decoderConfig.codec.startsWith('avc1') || metadata.decoderConfig.codec.startsWith('avc3')) + && !metadata.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 ((metadata.decoderConfig.codec.startsWith('hev1') || metadata.decoderConfig.codec.startsWith('hvc1')) && !metadata.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 ( + (metadata.decoderConfig.codec.startsWith('hev1') || metadata.decoderConfig.codec.startsWith('hvc1')) + && !metadata.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 ((metadata.decoderConfig.codec === 'vp8' || metadata.decoderConfig.codec.startsWith('vp09')) && metadata.decoderConfig.colorSpace === undefined) { - throw new TypeError("Video chunk metadata decoder configuration for VP8/VP9 must include a colorSpace."); + if ( + (metadata.decoderConfig.codec === 'vp8' || metadata.decoderConfig.codec.startsWith('vp09')) + && metadata.decoderConfig.colorSpace === undefined + ) { + throw new TypeError('Video chunk metadata decoder configuration for VP8/VP9 must include a colorSpace.'); } // No added requirements for AV1 (based) }; export const validateAudioChunkMetadata = (metadata: EncodedAudioChunkMetadata | undefined) => { if (!metadata) { - throw new TypeError("Audio chunk metadata must be provided."); + throw new TypeError('Audio chunk metadata must be provided.'); } if (typeof metadata !== 'object') { - throw new TypeError("Audio chunk metadata must be an object."); + throw new TypeError('Audio chunk metadata must be an object.'); } if (!metadata.decoderConfig) { - throw new TypeError("Audio chunk metadata must include a decoder configuration."); + throw new TypeError('Audio chunk metadata must include a decoder configuration.'); } if (typeof metadata.decoderConfig !== 'object') { - throw new TypeError("Audio chunk metadata decoder configuration must be an object."); + throw new TypeError('Audio chunk metadata decoder configuration must be an object.'); } if (typeof metadata.decoderConfig.codec !== 'string') { - throw new TypeError("Audio chunk metadata decoder configuration must specify a codec string."); + throw new TypeError('Audio chunk metadata decoder configuration must specify a codec string.'); } - if (!Number.isInteger(metadata.decoderConfig.sampleRate) || metadata.decoderConfig.sampleRate! <= 0) { - throw new TypeError("Audio chunk metadata decoder configuration must specify a valid sampleRate (positive integer)."); + if (!Number.isInteger(metadata.decoderConfig.sampleRate) || metadata.decoderConfig.sampleRate <= 0) { + throw new TypeError( + 'Audio chunk metadata decoder configuration must specify a valid sampleRate (positive integer).', + ); } - if (!Number.isInteger(metadata.decoderConfig.numberOfChannels) || metadata.decoderConfig.numberOfChannels! <= 0) { - throw new TypeError("Audio chunk metadata decoder configuration must specify a valid numberOfChannels (positive integer)."); + if (!Number.isInteger(metadata.decoderConfig.numberOfChannels) || metadata.decoderConfig.numberOfChannels <= 0) { + throw new TypeError( + 'Audio chunk metadata decoder configuration must specify a valid numberOfChannels (positive integer).', + ); } if (metadata.decoderConfig.description !== undefined) { if (!isAllowSharedBufferSource(metadata.decoderConfig.description)) { - throw new TypeError("Audio chunk metadata decoder configuration description, when defined, must be an ArrayBuffer or an ArrayBuffer view."); + throw new TypeError( + 'Audio chunk metadata decoder configuration description, when defined, must be an ArrayBuffer or an' + + ' ArrayBuffer view.', + ); } } if (metadata.decoderConfig.codec.startsWith('mp4a') && !metadata.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."); + 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 (metadata.decoderConfig.codec === 'opus' && metadata.decoderConfig.description && metadata.decoderConfig.description.byteLength < 18) { + if ( + metadata.decoderConfig.codec === 'opus' + && metadata.decoderConfig.description + && metadata.decoderConfig.description.byteLength < 18 + ) { throw new TypeError('Invalid decoder description provided for Opus; must be at least 18 bytes long.'); } }; export const validateSubtitleMetadata = (metadata: SubtitleMetadata | undefined) => { if (!metadata) { - throw new TypeError("Subtitle metadata must be provided."); + throw new TypeError('Subtitle metadata must be provided.'); } if (typeof metadata !== 'object') { - throw new TypeError("Subtitle metadata must be an object."); + throw new TypeError('Subtitle metadata must be an object.'); } if (!metadata.config) { - throw new TypeError("Subtitle metadata must include a config object."); + throw new TypeError('Subtitle metadata must include a config object.'); } if (typeof metadata.config !== 'object') { - throw new TypeError("Subtitle metadata config must be an object."); + throw new TypeError('Subtitle metadata config must be an object.'); } if (typeof metadata.config.description !== 'string') { - throw new TypeError("Subtitle metadata config description must be a string."); + throw new TypeError('Subtitle metadata config description must be a string.'); } -} \ No newline at end of file +}; diff --git a/src/index.ts b/src/index.ts index c0c505a..5f3642f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,35 @@ export { Output, OutputOptions, VideoTrackMetadata, AudioTrackMetadata, SubtitleTrackMetadata } from './output'; -export { OutputFormat, Mp4OutputFormat, Mp4OutputFormatOptions, MkvOutputFormat, MkvOutputFormatOptions, WebMOutputFormat, WebMOutputFormatOptions as WebmOutputFormatOptions } from './output-format'; -export { VIDEO_CODECS, VideoCodec, VideoCodecConfig, AUDIO_CODECS, AudioCodec, AudioCodecConfig, SUBTITLE_CODECS, SubtitleCodec, MediaSource, VideoSource, EncodedVideoChunkSource, VideoFrameSource, CanvasSource, MediaStreamVideoTrackSource, AudioSource, EncodedAudioChunkSource, AudioDataSource, AudioBufferSource, MediaStreamAudioTrackSource, SubtitleSource, TextSubtitleSource } from './source'; +export { + OutputFormat, + Mp4OutputFormat, + Mp4OutputFormatOptions, + MkvOutputFormat, + MkvOutputFormatOptions, + WebMOutputFormat, + WebMOutputFormatOptions, +} from './output-format'; +export { + VIDEO_CODECS, + VideoCodec, + VideoCodecConfig, + AUDIO_CODECS, + AudioCodec, + AudioCodecConfig, + SUBTITLE_CODECS, + SubtitleCodec, + MediaSource, + VideoSource, + EncodedVideoChunkSource, + VideoFrameSource, + CanvasSource, + MediaStreamVideoTrackSource, + AudioSource, + EncodedAudioChunkSource, + AudioDataSource, + AudioBufferSource, + MediaStreamAudioTrackSource, + SubtitleSource, + TextSubtitleSource, +} from './source'; export { Target, ArrayBufferTarget, StreamTarget, StreamTargetChunk, StreamTargetOptions } from './target'; -export { TransformationMatrix } from './misc'; \ No newline at end of file +export { TransformationMatrix } from './misc'; diff --git a/src/isobmff/isobmff-boxes.ts b/src/isobmff/isobmff-boxes.ts index 80d6256..99b1853 100644 --- a/src/isobmff/isobmff-boxes.ts +++ b/src/isobmff/isobmff-boxes.ts @@ -1,8 +1,27 @@ -import { toUint8Array, assert, isU32, last, TransformationMatrix, textEncoder, COLOR_PRIMARIES_MAP, TRANSFER_CHARACTERISTICS_MAP, MATRIX_COEFFICIENTS_MAP, colorSpaceIsComplete } from '../misc'; -import { AudioCodec, AudioSource, SubtitleCodec, VideoCodec, VideoSource } from '../source'; +import { + toUint8Array, + assert, + isU32, + last, + TransformationMatrix, + textEncoder, + COLOR_PRIMARIES_MAP, + TRANSFER_CHARACTERISTICS_MAP, + MATRIX_COEFFICIENTS_MAP, + colorSpaceIsComplete, +} from '../misc'; +import { AudioCodec, SubtitleCodec, VideoCodec } from '../source'; import { formatSubtitleTimestamp } from '../subtitles'; import { Writer } from '../writer'; -import { GLOBAL_TIMESCALE, intoTimescale, IsobmffAudioTrackData, IsobmffSubtitleTrackData, IsobmffTrackData, IsobmffVideoTrackData, Sample } from './isobmff-muxer'; +import { + GLOBAL_TIMESCALE, + intoTimescale, + IsobmffAudioTrackData, + IsobmffSubtitleTrackData, + IsobmffTrackData, + IsobmffVideoTrackData, + Sample, +} from './isobmff-muxer'; export class IsobmffBoxWriter { private helper = new Uint8Array(8); @@ -22,7 +41,7 @@ export class IsobmffBoxWriter { } writeU64(value: number) { - this.helperView.setUint32(0, Math.floor(value / 2**32), false); + this.helperView.setUint32(0, Math.floor(value / 2 ** 32), false); this.helperView.setUint32(4, value, false); this.writer.write(this.helper.subarray(0, 8)); } @@ -45,14 +64,14 @@ export class IsobmffBoxWriter { this.writeBoxHeader(box, box.size ?? box.contents.byteLength + 8); this.writer.write(box.contents); } else { - let startPos = this.writer.getPos(); + const startPos = this.writer.getPos(); this.writeBoxHeader(box, 0); if (box.contents) this.writer.write(box.contents); - if (box.children) for (let child of box.children) if (child) this.writeBox(child); + if (box.children) for (const child of box.children) if (child) this.writeBox(child); - let endPos = this.writer.getPos(); - let size = box.size ?? endPos - startPos; + const endPos = this.writer.getPos(); + const size = box.size ?? endPos - startPos; this.writer.seek(startPos); this.writeBoxHeader(box, size); this.writer.seek(endPos); @@ -73,7 +92,7 @@ export class IsobmffBoxWriter { const boxOffset = this.offsets.get(box); assert(boxOffset !== undefined); - let endPos = this.writer.getPos(); + const endPos = this.writer.getPos(); this.writer.seek(boxOffset); this.writeBox(box); this.writer.seek(endPos); @@ -81,20 +100,20 @@ export class IsobmffBoxWriter { measureBox(box: Box) { if (box.contents && !box.children) { - let headerSize = this.measureBoxHeader(box); + const headerSize = this.measureBoxHeader(box); return headerSize + box.contents.byteLength; } else { let result = this.measureBoxHeader(box); if (box.contents) result += box.contents.byteLength; - if (box.children) for (let child of box.children) if (child) result += this.measureBox(child); + if (box.children) for (const child of box.children) if (child) result += this.measureBox(child); return result; } } } -let bytes = new Uint8Array(8); -let view = new DataView(bytes.buffer); +const bytes = new Uint8Array(8); +const view = new DataView(bytes.buffer); const u8 = (value: number) => { return [(value % 0x100 + 0x100) % 0x100]; @@ -126,40 +145,40 @@ const i32 = (value: number) => { }; const u64 = (value: number) => { - view.setUint32(0, Math.floor(value / 2**32), false); + view.setUint32(0, Math.floor(value / 2 ** 32), false); view.setUint32(4, value, false); return [bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7]] as number[]; }; const fixed_8_8 = (value: number) => { - view.setInt16(0, 2**8 * value, false); + view.setInt16(0, 2 ** 8 * value, false); return [bytes[0], bytes[1]] as number[]; }; const fixed_16_16 = (value: number) => { - view.setInt32(0, 2**16 * value, false); + view.setInt32(0, 2 ** 16 * value, false); return [bytes[0], bytes[1], bytes[2], bytes[3]] as number[]; }; const fixed_2_30 = (value: number) => { - view.setInt32(0, 2**30 * value, false); + view.setInt32(0, 2 ** 30 * value, false); return [bytes[0], bytes[1], bytes[2], bytes[3]] as number[]; }; const variableUnsignedInt = (value: number, byteLength?: number) => { - let bytes: number[] = []; + const bytes: number[] = []; let remaining = value; do { let byte = remaining & 0x7f; remaining >>= 7; - + // If this isn't the first byte we're adding (meaning there will be more bytes after it // when we reverse the array), set the continuation bit if (bytes.length > 0) { byte |= 0x80; } - + bytes.push(byte); if (byteLength !== undefined) { @@ -172,7 +191,7 @@ const variableUnsignedInt = (value: number, byteLength?: number) => { }; const ascii = (text: string, nullTerminated = false) => { - let bytes = Array(text.length).fill(null).map((_, i) => text.charCodeAt(i)); + const bytes = Array(text.length).fill(null).map((_, i) => text.charCodeAt(i)); if (nullTerminated) bytes.push(0x00); return bytes; }; @@ -180,7 +199,7 @@ const ascii = (text: string, nullTerminated = false) => { const lastPresentedSample = (samples: Sample[]) => { let result: Sample | null = null; - for (let sample of samples) { + for (const sample of samples) { if (!result || sample.timestamp > result.timestamp) { result = sample; } @@ -190,15 +209,15 @@ const lastPresentedSample = (samples: Sample[]) => { }; const rotationMatrix = (rotationInDegrees: number): TransformationMatrix => { - let theta = rotationInDegrees * (Math.PI / 180); - let cosTheta = Math.cos(theta); - let sinTheta = Math.sin(theta); + 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 + 0, 0, 1, ]; }; @@ -208,16 +227,16 @@ const matrixToBytes = (matrix: TransformationMatrix) => { return [ fixed_16_16(matrix[0]), fixed_16_16(matrix[1]), fixed_2_30(matrix[2]), fixed_16_16(matrix[3]), fixed_16_16(matrix[4]), fixed_2_30(matrix[5]), - fixed_16_16(matrix[6]), fixed_16_16(matrix[7]), fixed_2_30(matrix[8]) + fixed_16_16(matrix[6]), fixed_16_16(matrix[7]), fixed_2_30(matrix[8]), ]; }; export interface Box { - type: string, - contents?: Uint8Array, - children?: (Box | null)[], - size?: number, - largeSize?: boolean + type: string; + contents?: Uint8Array; + children?: (Box | null)[]; + size?: number; + largeSize?: boolean; } type NestedNumberArray = (number | NestedNumberArray)[]; @@ -225,7 +244,7 @@ type NestedNumberArray = (number | NestedNumberArray)[]; export const box = (type: string, contents?: NestedNumberArray, children?: (Box | null)[]): Box => ({ type, contents: contents && new Uint8Array(contents.flat(10) as number[]), - children + children, }); /** A FullBox always starts with a version byte, followed by three flag bytes. */ @@ -234,11 +253,11 @@ export const fullBox = ( version: number, flags: number, contents?: NestedNumberArray, - children?: Box[] + children?: Box[], ) => box( type, [u8(version), u24(flags), contents ?? []], - children + children, ); /** @@ -246,14 +265,14 @@ export const fullBox = ( * reader understands. */ export const ftyp = (details: { - holdsAvc: boolean, - fragmented: boolean + holdsAvc: boolean; + fragmented: boolean; }) => { // You can find the full logic for this at // https://github.com/FFmpeg/FFmpeg/blob/de2fb43e785773738c660cdafb9309b1ef1bc80d/libavformat/movenc.c#L5518 // Obviously, this lib only needs a small subset of that logic. - let minorVersion = 0x200; + const minorVersion = 0x200; if (details.fragmented) return box('ftyp', [ ascii('iso5'), // Major brand @@ -261,7 +280,7 @@ export const ftyp = (details: { // Compatible brands ascii('iso5'), ascii('iso6'), - ascii('mp41') + ascii('mp41'), ]); return box('ftyp', [ @@ -270,7 +289,7 @@ export const ftyp = (details: { // Compatible brands ascii('isom'), details.holdsAvc ? ascii('avc1') : [], - ascii('mp41') + ascii('mp41'), ]); }; @@ -284,31 +303,35 @@ export const free = (size: number): Box => ({ type: 'free', size }); * Movie Box: Used to specify the information that defines a movie - that is, the information that allows * an application to interpret the sample data that is stored elsewhere. */ -export const moov = (trackDatas: IsobmffTrackData[], creationTime: number, fragmented = false) => box('moov', undefined, [ +export const moov = ( + trackDatas: IsobmffTrackData[], + creationTime: number, + fragmented = false, +) => box('moov', undefined, [ mvhd(creationTime, trackDatas), ...trackDatas.map(x => trak(x, creationTime)), - fragmented ? mvex(trackDatas) : null + fragmented ? mvex(trackDatas) : null, ]); /** Movie Header Box: Used to specify the characteristics of the entire movie, such as timescale and duration. */ export const mvhd = ( creationTime: number, - trackDatas: IsobmffTrackData[] + trackDatas: IsobmffTrackData[], ) => { - let duration = intoTimescale(Math.max( + const duration = intoTimescale(Math.max( 0, - ...trackDatas. - filter(x => x.samples.length > 0). - map(x => { + ...trackDatas + .filter(x => x.samples.length > 0) + .map((x) => { const lastSample = lastPresentedSample(x.samples)!; return lastSample.timestamp + lastSample.duration; - }) + }), ), GLOBAL_TIMESCALE); - let nextTrackId = Math.max(0, ...trackDatas.map(x => x.track.id)) + 1; + const nextTrackId = Math.max(0, ...trackDatas.map(x => x.track.id)) + 1; // Conditionally use u64 if u32 isn't enough - let needsU64 = !isU32(creationTime) || !isU32(duration); - let u32OrU64 = needsU64 ? u64 : u32; + const needsU64 = !isU32(creationTime) || !isU32(duration); + const u32OrU64 = needsU64 ? u64 : u32; return fullBox('mvhd', +needsU64, 0, [ u32OrU64(creationTime), // Creation time @@ -320,7 +343,7 @@ export const mvhd = ( Array(10).fill(0), // Reserved matrixToBytes(IDENTITY_MATRIX), // Matrix Array(24).fill(0), // Pre-defined - u32(nextTrackId) // Next track ID + u32(nextTrackId), // Next track ID ]); }; @@ -331,22 +354,22 @@ export const mvhd = ( */ export const trak = (trackData: IsobmffTrackData, creationTime: number) => box('trak', undefined, [ tkhd(trackData, creationTime), - mdia(trackData, creationTime) + mdia(trackData, creationTime), ]); /** Track Header Box: Specifies the characteristics of a single track within a movie. */ export const tkhd = ( trackData: IsobmffTrackData, - creationTime: number + creationTime: number, ) => { - let lastSample = lastPresentedSample(trackData.samples); - let durationInGlobalTimescale = intoTimescale( + const lastSample = lastPresentedSample(trackData.samples); + const durationInGlobalTimescale = intoTimescale( lastSample ? lastSample.timestamp + lastSample.duration : 0, - GLOBAL_TIMESCALE + GLOBAL_TIMESCALE, ); - let needsU64 = !isU32(creationTime) || !isU32(durationInGlobalTimescale); - let u32OrU64 = needsU64 ? u64 : u32; + const needsU64 = !isU32(creationTime) || !isU32(durationInGlobalTimescale); + const u32OrU64 = needsU64 ? u64 : u32; let matrix: TransformationMatrix; if (trackData.type === 'video') { @@ -369,7 +392,7 @@ export const tkhd = ( u16(0), // Reserved matrixToBytes(matrix), // Matrix fixed_16_16(trackData.type === 'video' ? trackData.info.width : 0), // Track width - fixed_16_16(trackData.type === 'video' ? trackData.info.height : 0) // Track height + fixed_16_16(trackData.type === 'video' ? trackData.info.height : 0), // Track height ]); }; @@ -377,22 +400,22 @@ export const tkhd = ( export const mdia = (trackData: IsobmffTrackData, creationTime: number) => box('mdia', undefined, [ mdhd(trackData, creationTime), hdlr(trackData), - minf(trackData) + minf(trackData), ]); /** Media Header Box: Specifies the characteristics of a media, including timescale and duration. */ export const mdhd = ( trackData: IsobmffTrackData, - creationTime: number + creationTime: number, ) => { - let lastSample = lastPresentedSample(trackData.samples); - let localDuration = intoTimescale( + const lastSample = lastPresentedSample(trackData.samples); + const localDuration = intoTimescale( lastSample ? lastSample.timestamp + lastSample.duration : 0, - trackData.timescale + trackData.timescale, ); - let needsU64 = !isU32(creationTime) || !isU32(localDuration); - let u32OrU64 = needsU64 ? u64 : u32; + const needsU64 = !isU32(creationTime) || !isU32(localDuration); + const u32OrU64 = needsU64 ? u64 : u32; return fullBox('mdhd', +needsU64, 0, [ u32OrU64(creationTime), // Creation time @@ -400,20 +423,20 @@ export const mdhd = ( u32(trackData.timescale), // Timescale u32OrU64(localDuration), // Duration u16(0b01010101_11000100), // Language ("und", undetermined) - u16(0) // Quality + u16(0), // Quality ]); }; const TRACK_TYPE_TO_COMPONENT_SUBTYPE: Record = { video: 'vide', audio: 'soun', - subtitle: 'text' + subtitle: 'text', }; const TRACK_TYPE_TO_HANDLER_NAME: Record = { video: 'VideoHandler', audio: 'SoundHandler', - subtitle: 'TextHandler' + subtitle: 'TextHandler', }; /** Handler Reference Box: Specifies the media handler component that is to be used to interpret the media's data. */ @@ -423,7 +446,7 @@ export const hdlr = (trackData: IsobmffTrackData) => fullBox('hdlr', 0, 0, [ u32(0), // Component manufacturer u32(0), // Component flags u32(0), // Component flags mask - ascii(TRACK_TYPE_TO_HANDLER_NAME[trackData.type], true) // Component name + ascii(TRACK_TYPE_TO_HANDLER_NAME[trackData.type], true), // Component name ]); /** @@ -433,7 +456,7 @@ export const hdlr = (trackData: IsobmffTrackData) => fullBox('hdlr', 0, 0, [ export const minf = (trackData: IsobmffTrackData) => box('minf', undefined, [ TRACK_TYPE_TO_HEADER_BOX[trackData.type](), dinf(), - stbl(trackData) + stbl(trackData), ]); /** Video Media Information Header Box: Defines specific color and graphics mode information. */ @@ -441,22 +464,22 @@ export const vmhd = () => fullBox('vmhd', 0, 1, [ u16(0), // Graphics mode u16(0), // Opcolor R u16(0), // Opcolor G - u16(0) // Opcolor B + u16(0), // Opcolor B ]); /** Sound Media Information Header Box: Stores the sound media's control information, such as balance. */ export const smhd = () => fullBox('smhd', 0, 0, [ u16(0), // Balance - u16(0) // Reserved + u16(0), // Reserved ]); /** Null Media Header Box. */ -export const nmhd = () => fullBox('nmhd', 0, 0); +export const nmhd = () => fullBox('nmhd', 0, 0); const TRACK_TYPE_TO_HEADER_BOX: Record Box> = { video: vmhd, audio: smhd, - subtitle: nmhd + subtitle: nmhd, }; /** @@ -464,16 +487,16 @@ const TRACK_TYPE_TO_HEADER_BOX: Record Box> = { * media data. The data handler component uses the Data Information Box to interpret the media's data. */ export const dinf = () => box('dinf', undefined, [ - dref() + dref(), ]); /** * Data Reference Box: Contains tabular data that instructs the data handler component how to access the media's data. */ export const dref = () => fullBox('dref', 0, 0, [ - u32(1) // Entry count + u32(1), // Entry count ], [ - url() + url(), ]); export const url = () => fullBox('url ', 0, 1); // Self-reference flag enabled @@ -483,8 +506,8 @@ export const url = () => fullBox('url ', 0, 1); // Self-reference flag enabled * also indicates how to interpret the sample (for example, whether to decompress the video data and, if so, how). */ export const stbl = (trackData: IsobmffTrackData) => { - const needsCtts = trackData.compositionTimeOffsetTable.length > 1 || - trackData.compositionTimeOffsetTable.some((x) => x.sampleCompositionTimeOffset !== 0); + const needsCtts = trackData.compositionTimeOffsetTable.length > 1 + || trackData.compositionTimeOffsetTable.some(x => x.sampleCompositionTimeOffset !== 0); return box('stbl', undefined, [ stsd(trackData), @@ -493,7 +516,7 @@ export const stbl = (trackData: IsobmffTrackData) => { stsc(trackData), stsz(trackData), stco(trackData), - needsCtts ? ctts(trackData) : null + needsCtts ? ctts(trackData) : null, ]); }; @@ -507,33 +530,33 @@ export const stsd = (trackData: IsobmffTrackData) => { if (trackData.type === 'video') { sampleDescription = videoSampleDescription( VIDEO_CODEC_TO_BOX_NAME[trackData.track.source._codec], - trackData - ) + trackData, + ); } else if (trackData.type === 'audio') { sampleDescription = soundSampleDescription( AUDIO_CODEC_TO_BOX_NAME[trackData.track.source._codec], - trackData + trackData, ); } else if (trackData.type === 'subtitle') { sampleDescription = subtitleSampleDescription( SUBTITLE_CODEC_TO_BOX_NAME[trackData.track.source._codec], - trackData + trackData, ); } assert(sampleDescription!); return fullBox('stsd', 0, 0, [ - u32(1) // Entry count + u32(1), // Entry count ], [ - sampleDescription + sampleDescription, ]); }; /** Video Sample Description Box: Contains information that defines how to interpret video media data. */ export const videoSampleDescription = ( compressionType: string, - trackData: IsobmffVideoTrackData + trackData: IsobmffVideoTrackData, ) => box(compressionType, [ Array(6).fill(0), // Reserved u16(1), // Data reference index @@ -548,10 +571,10 @@ export const videoSampleDescription = ( u16(1), // Frame count Array(32).fill(0), // Compressor name u16(0x0018), // Depth - i16(0xffff) // Pre-defined + i16(0xffff), // Pre-defined ], [ VIDEO_CODEC_TO_CONFIGURATION_BOX[trackData.track.source._codec](trackData), - colorSpaceIsComplete(trackData.info.decoderConfig.colorSpace) ? colr(trackData) : null + colorSpaceIsComplete(trackData.info.decoderConfig.colorSpace) ? colr(trackData) : null, ]); /** Colour Information Box: Specifies the color space of the video. */ @@ -560,19 +583,19 @@ export const colr = (trackData: IsobmffVideoTrackData) => box('colr', [ u16(COLOR_PRIMARIES_MAP[trackData.info.decoderConfig.colorSpace!.primaries!]), // Colour primaries u16(TRANSFER_CHARACTERISTICS_MAP[trackData.info.decoderConfig.colorSpace!.transfer!]), // Transfer characteristics u16(MATRIX_COEFFICIENTS_MAP[trackData.info.decoderConfig.colorSpace!.matrix!]), // Matrix coefficients - u8((trackData.info.decoderConfig.colorSpace!.fullRange ? 1 : 0) << 7) // Full range flag + u8((trackData.info.decoderConfig.colorSpace!.fullRange ? 1 : 0) << 7), // Full range flag ]); /** AVC Configuration Box: Provides additional information to the decoder. */ export const avcC = (trackData: IsobmffVideoTrackData) => trackData.info.decoderConfig && box('avcC', [ // For AVC, description is an AVCDecoderConfigurationRecord, so nothing else to do here - ...toUint8Array(trackData.info.decoderConfig.description!) + ...toUint8Array(trackData.info.decoderConfig.description!), ]); /** HEVC Configuration Box: Provides additional information to the decoder. */ export const hvcC = (trackData: IsobmffVideoTrackData) => trackData.info.decoderConfig && box('hvcC', [ // For HEVC, description is an HEVCDecoderConfigurationRecord, so nothing else to do here - ...toUint8Array(trackData.info.decoderConfig.description!) + ...toUint8Array(trackData.info.decoderConfig.description!), ]); /** VP Configuration Box: Provides additional information to the decoder. */ @@ -583,21 +606,21 @@ export const vpcC = (trackData: IsobmffVideoTrackData) => { return null; } - let decoderConfig = trackData.info.decoderConfig; + const decoderConfig = trackData.info.decoderConfig; assert(decoderConfig.colorSpace); // This is guaranteed by an earlier validation step - let parts = decoderConfig.codec.split('.'); - let profile = Number(parts[1]); - let level = Number(parts[2]); + const parts = decoderConfig.codec.split('.'); + const profile = Number(parts[1]); + const level = Number(parts[2]); - let bitDepth = Number(parts[3]); - let chromaSubsampling = 0; - let thirdByte = (bitDepth << 4) + (chromaSubsampling << 1) + Number(decoderConfig.colorSpace.fullRange); + const bitDepth = Number(parts[3]); + const chromaSubsampling = 0; + const thirdByte = (bitDepth << 4) + (chromaSubsampling << 1) + Number(decoderConfig.colorSpace.fullRange); // Set all to undetermined. We could determine them using the codec color space info, but there's no need. - let colourPrimaries = 2; - let transferCharacteristics = 2; - let matrixCoefficients = 2; + const colourPrimaries = 2; + const transferCharacteristics = 2; + const matrixCoefficients = 2; return fullBox('vpcC', 1, 0, [ u8(profile), // Profile @@ -606,7 +629,7 @@ export const vpcC = (trackData: IsobmffVideoTrackData) => { u8(colourPrimaries), // Colour primaries u8(transferCharacteristics), // Transfer characteristics u8(matrixCoefficients), // Matrix coefficients - u16(0) // Codec initialization data size + u16(0), // Codec initialization data size ]); }; @@ -614,9 +637,9 @@ export const vpcC = (trackData: IsobmffVideoTrackData) => { export const av1C = () => { // Reference: https://aomediacodec.github.io/av1-isobmff/ - let marker = 1; - let version = 1; - let firstByte = (marker << 7) + version; + const marker = 1; + const version = 1; + const firstByte = (marker << 7) + version; // The box contents are not correct like this, but its length is. Getting the values for the last three bytes // requires peeking into the bitstream of the coded chunks. Might come back later. @@ -624,14 +647,14 @@ export const av1C = () => { firstByte, 0, 0, - 0 + 0, ]); }; /** Sound Sample Description Box: Contains information that defines how to interpret sound media data. */ export const soundSampleDescription = ( compressionType: string, - trackData: IsobmffAudioTrackData + trackData: IsobmffAudioTrackData, ) => box(compressionType, [ Array(6).fill(0), // Reserved u16(1), // Data reference index @@ -642,20 +665,20 @@ export const soundSampleDescription = ( u16(16), // Sample size (bits) u16(0), // Compression ID u16(0), // Packet size - fixed_16_16(trackData.info.sampleRate) // Sample rate + fixed_16_16(trackData.info.sampleRate), // Sample rate ], [ - AUDIO_CODEC_TO_CONFIGURATION_BOX[trackData.track.source._codec](trackData) + AUDIO_CODEC_TO_CONFIGURATION_BOX[trackData.track.source._codec](trackData), ]); /** MPEG-4 Elementary Stream Descriptor Box. */ export const esds = (trackData: IsobmffAudioTrackData) => { - let description = toUint8Array(trackData.info.decoderConfig.description ?? new ArrayBuffer(0)); + const description = toUint8Array(trackData.info.decoderConfig.description ?? new ArrayBuffer(0)); // Adapted from https://stackoverflow.com/a/54803118 // We build up the bytes in a layered way which reflects the nested structure let bytes = [ - ...description + ...description, ]; bytes = [ ...u8(0x40), // MPEG-4 Audio @@ -665,7 +688,7 @@ export const esds = (trackData: IsobmffAudioTrackData) => { ...u32(0), // avg bitrate ...u8(0x05), // TAG(5) = ASC ([2],[3]) embedded in above OD ...variableUnsignedInt(bytes.length), - ...bytes + ...bytes, ]; bytes = [ ...u16(1), // ES_ID = 1 @@ -675,12 +698,12 @@ export const esds = (trackData: IsobmffAudioTrackData) => { ...bytes, ...u8(0x06), // TAG(6) ...u8(0x01), // length - ...u8(0x02) // data + ...u8(0x02), // data ]; bytes = [ ...u8(0x03), // TAG(3) = Object Descriptor ([2]) ...variableUnsignedInt(bytes.length), - ...bytes + ...bytes, ]; return fullBox('esds', 0, 0, bytes); @@ -711,26 +734,26 @@ export const dOps = (trackData: IsobmffAudioTrackData) => { u16(preskip), u32(trackData.info.sampleRate), // InputSampleRate fixed_8_8(gain), // OutputGain - u8(0) // ChannelMappingFamily + u8(0), // ChannelMappingFamily ]); }; export const subtitleSampleDescription = ( compressionType: string, - trackData: IsobmffSubtitleTrackData + trackData: IsobmffSubtitleTrackData, ) => box(compressionType, [ Array(6).fill(0), // Reserved u16(1), // Data reference index ], [ - SUBTITLE_CODEC_TO_CONFIGURATION_BOX[trackData.track.source._codec](trackData) -]) + SUBTITLE_CODEC_TO_CONFIGURATION_BOX[trackData.track.source._codec](trackData), +]); export const vttC = (trackData: IsobmffSubtitleTrackData) => box('vttC', [ - ...textEncoder.encode(trackData.info.config.description) + ...textEncoder.encode(trackData.info.config.description), ]); export const txtC = (textConfig: Uint8Array) => fullBox('txtC', 0, 0, [ - ...textConfig, 0 // Text config (null-terminated) + ...textConfig, 0, // Text config (null-terminated) ]); /** @@ -743,8 +766,8 @@ export const stts = (trackData: IsobmffTrackData) => { u32(trackData.timeToSampleTable.length), // Number of entries trackData.timeToSampleTable.map(x => [ // Time-to-sample table u32(x.sampleCount), // Sample count - u32(x.sampleDelta) // Sample duration - ]) + u32(x.sampleDelta), // Sample duration + ]), ]); }; @@ -752,10 +775,10 @@ export const stts = (trackData: IsobmffTrackData) => { export const stss = (trackData: IsobmffTrackData) => { if (trackData.samples.every(x => x.type === 'key')) return null; // No stss box -> every frame is a key frame - let keySamples = [...trackData.samples.entries()].filter(([, sample]) => sample.type === 'key'); + const keySamples = [...trackData.samples.entries()].filter(([, sample]) => sample.type === 'key'); return fullBox('stss', 0, 0, [ u32(keySamples.length), // Number of entries - keySamples.map(([index]) => u32(index + 1)) // Sync sample table + keySamples.map(([index]) => u32(index + 1)), // Sync sample table ]); }; @@ -771,8 +794,8 @@ export const stsc = (trackData: IsobmffTrackData) => { trackData.compactlyCodedChunkTable.map(x => [ // Sample-to-chunk table u32(x.firstChunk), // First chunk u32(x.samplesPerChunk), // Samples per chunk - u32(1) // Sample description index - ]) + u32(1), // Sample description index + ]), ]); }; @@ -780,22 +803,22 @@ export const stsc = (trackData: IsobmffTrackData) => { export const stsz = (trackData: IsobmffTrackData) => fullBox('stsz', 0, 0, [ u32(0), // Sample size (0 means non-constant size) u32(trackData.samples.length), // Number of entries - trackData.samples.map(x => u32(x.size)) // Sample size table + trackData.samples.map(x => u32(x.size)), // Sample size table ]); /** Chunk Offset Box: Identifies the location of each chunk of data in the media's data stream, relative to the file. */ export const stco = (trackData: IsobmffTrackData) => { - if (trackData.finalizedChunks.length > 0 && last(trackData.finalizedChunks)!.offset! >= 2**32) { + if (trackData.finalizedChunks.length > 0 && last(trackData.finalizedChunks)!.offset! >= 2 ** 32) { // If the file is large, use the co64 box return fullBox('co64', 0, 0, [ u32(trackData.finalizedChunks.length), // Number of entries - trackData.finalizedChunks.map(x => u64(x.offset!)) // Chunk offset table + trackData.finalizedChunks.map(x => u64(x.offset!)), // Chunk offset table ]); } return fullBox('stco', 0, 0, [ u32(trackData.finalizedChunks.length), // Number of entries - trackData.finalizedChunks.map(x => u32(x.offset!)) // Chunk offset table + trackData.finalizedChunks.map(x => u32(x.offset!)), // Chunk offset table ]); }; @@ -807,8 +830,8 @@ export const ctts = (trackData: IsobmffTrackData) => { u32(trackData.compositionTimeOffsetTable.length), // Number of entries trackData.compositionTimeOffsetTable.map(x => [ // Time-to-sample table u32(x.sampleCount), // Sample count - u32(x.sampleCompositionTimeOffset) // Sample offset - ]) + u32(x.sampleCompositionTimeOffset), // Sample offset + ]), ]); }; @@ -827,7 +850,7 @@ export const trex = (trackData: IsobmffTrackData) => { u32(1), // Default sample description index u32(0), // Default sample duration u32(0), // Default sample size - u32(0) // Default sample flags + u32(0), // Default sample flags ]); }; @@ -838,24 +861,24 @@ export const trex = (trackData: IsobmffTrackData) => { export const moof = (sequenceNumber: number, trackDatas: IsobmffTrackData[]) => { return box('moof', undefined, [ mfhd(sequenceNumber), - ...trackDatas.map(traf) + ...trackDatas.map(traf), ]); }; /** Movie Fragment Header Box: Contains a sequence number as a safety check. */ export const mfhd = (sequenceNumber: number) => { return fullBox('mfhd', 0, 0, [ - u32(sequenceNumber) // Sequence number + u32(sequenceNumber), // Sequence number ]); }; const fragmentSampleFlags = (sample: Sample) => { let byte1 = 0; let byte2 = 0; - let byte3 = 0; - let byte4 = 0; + const byte3 = 0; + const byte4 = 0; - let sampleIsDifferenceSample = sample.type === 'delta'; + const sampleIsDifferenceSample = sample.type === 'delta'; byte2 |= +sampleIsDifferenceSample; if (sampleIsDifferenceSample) { @@ -873,7 +896,7 @@ export const traf = (trackData: IsobmffTrackData) => { return box('traf', undefined, [ tfhd(trackData), tfdt(trackData), - trun(trackData) + trun(trackData), ]); }; @@ -888,18 +911,18 @@ export const tfhd = (trackData: IsobmffTrackData) => { tfFlags |= 0x20000; // Default base is moof // Prefer the second sample over the first one, as the first one is a sync sample and therefore the "odd one out" - let referenceSample = trackData.currentChunk.samples[1] ?? trackData.currentChunk.samples[0]!; - let referenceSampleInfo = { + const referenceSample = trackData.currentChunk.samples[1] ?? trackData.currentChunk.samples[0]!; + const referenceSampleInfo = { duration: referenceSample.timescaleUnitsToNextSample, size: referenceSample.size, - flags: fragmentSampleFlags(referenceSample) + flags: fragmentSampleFlags(referenceSample), }; return fullBox('tfhd', 0, tfFlags, [ u32(trackData.track.id), // Track ID u32(referenceSampleInfo.duration), // Default sample duration u32(referenceSampleInfo.size), // Default sample size - u32(referenceSampleInfo.flags) // Default sample flags + u32(referenceSampleInfo.flags), // Default sample flags ]); }; @@ -911,7 +934,7 @@ export const tfdt = (trackData: IsobmffTrackData) => { assert(trackData.currentChunk); return fullBox('tfdt', 1, 0, [ - u64(intoTimescale(trackData.currentChunk.startTimestamp, trackData.timescale)) // Base Media Decode Time + u64(intoTimescale(trackData.currentChunk.startTimestamp, trackData.timescale)), // Base Media Decode Time ]); }; @@ -919,23 +942,23 @@ export const tfdt = (trackData: IsobmffTrackData) => { export const trun = (trackData: IsobmffTrackData) => { assert(trackData.currentChunk); - let allSampleDurations = trackData.currentChunk.samples.map(x => x.timescaleUnitsToNextSample); - let allSampleSizes = trackData.currentChunk.samples.map(x => x.size); - let allSampleFlags = trackData.currentChunk.samples.map(fragmentSampleFlags); - let allSampleCompositionTimeOffsets = trackData.currentChunk.samples. - map(x => intoTimescale(x.timestamp - x.decodeTimestamp, trackData.timescale)); + const allSampleDurations = trackData.currentChunk.samples.map(x => x.timescaleUnitsToNextSample); + const allSampleSizes = trackData.currentChunk.samples.map(x => x.size); + const allSampleFlags = trackData.currentChunk.samples.map(fragmentSampleFlags); + const allSampleCompositionTimeOffsets = trackData.currentChunk.samples + .map(x => intoTimescale(x.timestamp - x.decodeTimestamp, trackData.timescale)); - let uniqueSampleDurations = new Set(allSampleDurations); - let uniqueSampleSizes = new Set(allSampleSizes); - let uniqueSampleFlags = new Set(allSampleFlags); - let uniqueSampleCompositionTimeOffsets = new Set(allSampleCompositionTimeOffsets); + const uniqueSampleDurations = new Set(allSampleDurations); + const uniqueSampleSizes = new Set(allSampleSizes); + const uniqueSampleFlags = new Set(allSampleFlags); + const uniqueSampleCompositionTimeOffsets = new Set(allSampleCompositionTimeOffsets); - let firstSampleFlagsPresent = uniqueSampleFlags.size === 2 && allSampleFlags[0] !== allSampleFlags[1]; - let sampleDurationPresent = uniqueSampleDurations.size > 1; - let sampleSizePresent = uniqueSampleSizes.size > 1; - let sampleFlagsPresent = !firstSampleFlagsPresent && uniqueSampleFlags.size > 1; - let sampleCompositionTimeOffsetsPresent = - uniqueSampleCompositionTimeOffsets.size > 1 || [...uniqueSampleCompositionTimeOffsets].some(x => x !== 0); + const firstSampleFlagsPresent = uniqueSampleFlags.size === 2 && allSampleFlags[0] !== allSampleFlags[1]; + const sampleDurationPresent = uniqueSampleDurations.size > 1; + const sampleSizePresent = uniqueSampleSizes.size > 1; + const sampleFlagsPresent = !firstSampleFlagsPresent && uniqueSampleFlags.size > 1; + const sampleCompositionTimeOffsetsPresent + = uniqueSampleCompositionTimeOffsets.size > 1 || [...uniqueSampleCompositionTimeOffsets].some(x => x !== 0); let flags = 0; flags |= 0x0001; // Data offset present @@ -954,8 +977,8 @@ export const trun = (trackData: IsobmffTrackData) => { sampleSizePresent ? u32(allSampleSizes[i]!) : [], // Sample size sampleFlagsPresent ? u32(allSampleFlags[i]!) : [], // Sample flags // Sample composition time offsets - sampleCompositionTimeOffsetsPresent ? i32(allSampleCompositionTimeOffsets[i]!) : [] - ]) + sampleCompositionTimeOffsetsPresent ? i32(allSampleCompositionTimeOffsets[i]!) : [], + ]), ]); }; @@ -966,13 +989,13 @@ export const trun = (trackData: IsobmffTrackData) => { export const mfra = (trackDatas: IsobmffTrackData[]) => { return box('mfra', undefined, [ ...trackDatas.map(tfra), - mfro() + mfro(), ]); }; /** Track Fragment Random Access Box: Provides pointers to sync samples within the file for random access. */ export const tfra = (trackData: IsobmffTrackData, trackIndex: number) => { - let version = 1; // Using this version allows us to use 64-bit time and offset values + const version = 1; // Using this version allows us to use 64-bit time and offset values return fullBox('tfra', version, 0, [ u32(trackData.track.id), // Track ID @@ -983,8 +1006,8 @@ export const tfra = (trackData: IsobmffTrackData, trackIndex: number) => { u64(chunk.moofOffset!), // moof offset u32(trackIndex + 1), // traf number u32(1), // trun number - u32(1) // Sample number - ]) + u32(1), // Sample number + ]), ]); }; @@ -996,7 +1019,7 @@ export const mfro = () => { return fullBox('mfro', 0, 0, [ // This value needs to be overwritten manually from the outside, where the actual size of the enclosing mfra box // is known - u32(0) // Size + u32(0), // Size ]); }; @@ -1004,47 +1027,56 @@ export const mfro = () => { export const vtte = () => box('vtte'); /** VTT Cue Box */ -export const vttc = (payload: string, timestamp: number | null, identifier: string | null, settings: string | null, sourceId: number | null) => box('vttc', undefined, [ +export const vttc = ( + payload: string, + timestamp: number | null, + identifier: string | null, + settings: string | null, + sourceId: number | null, +) => box('vttc', undefined, [ sourceId !== null ? box('vsid', [i32(sourceId)]) : null, identifier !== null ? box('iden', [...textEncoder.encode(identifier)]) : null, timestamp !== null ? box('ctim', [...textEncoder.encode(formatSubtitleTimestamp(timestamp))]) : null, settings !== null ? box('sttg', [...textEncoder.encode(settings)]) : null, - box('payl', [...textEncoder.encode(payload)]) + box('payl', [...textEncoder.encode(payload)]), ]); /** VTT Additional Text Box */ export const vtta = (notes: string) => box('vtta', [...textEncoder.encode(notes)]); const VIDEO_CODEC_TO_BOX_NAME: Record = { - 'avc': 'avc1', - 'hevc': 'hvc1', - 'vp8': 'vp08', - 'vp9': 'vp09', - 'av1': 'av01' + avc: 'avc1', + hevc: 'hvc1', + vp8: 'vp08', + vp9: 'vp09', + av1: 'av01', }; const VIDEO_CODEC_TO_CONFIGURATION_BOX: Record Box | null> = { - 'avc': avcC, - 'hevc': hvcC, - 'vp8': vpcC, - 'vp9': vpcC, - 'av1': av1C + avc: avcC, + hevc: hvcC, + vp8: vpcC, + vp9: vpcC, + av1: av1C, }; const AUDIO_CODEC_TO_BOX_NAME: Record = { - 'aac': 'mp4a', - 'opus': 'Opus' + aac: 'mp4a', + opus: 'Opus', }; const AUDIO_CODEC_TO_CONFIGURATION_BOX: Record Box | null> = { - 'aac': esds, - 'opus': dOps + aac: esds, + opus: dOps, }; const SUBTITLE_CODEC_TO_BOX_NAME: Record = { - 'webvtt': 'wvtt' + webvtt: 'wvtt', }; -const SUBTITLE_CODEC_TO_CONFIGURATION_BOX: Record Box | null> = { - 'webvtt': vttC -}; \ No newline at end of file +const SUBTITLE_CODEC_TO_CONFIGURATION_BOX: Record< + SubtitleCodec, + (trackData: IsobmffSubtitleTrackData) => Box | null +> = { + webvtt: vttC, +}; diff --git a/src/isobmff/isobmff-muxer.ts b/src/isobmff/isobmff-muxer.ts index de85c7d..3f1b5a2 100644 --- a/src/isobmff/isobmff-muxer.ts +++ b/src/isobmff/isobmff-muxer.ts @@ -2,7 +2,7 @@ import { Box, free, ftyp, IsobmffBoxWriter, mdat, mfra, moof, moov, vtta, vttc, import { Muxer } from '../muxer'; import { Output, OutputAudioTrack, OutputSubtitleTrack, OutputTrack, OutputVideoTrack } from '../output'; import { ArrayBufferTargetWriter, Writer } from '../writer'; -import { assert, last, TransformationMatrix } from '../misc'; +import { assert, last } from '../misc'; import { Mp4OutputFormat, Mp4OutputFormatOptions } from '../output-format'; import { inlineTimestampRegex, SubtitleConfig, SubtitleCue, SubtitleMetadata } from '../subtitles'; import { ArrayBufferTarget } from '../target'; @@ -12,66 +12,66 @@ export const GLOBAL_TIMESCALE = 1000; const TIMESTAMP_OFFSET = 2_082_844_800; // Seconds between Jan 1 1904 and Jan 1 1970 export type Sample = { - timestamp: number, - decodeTimestamp: number, - duration: number, - data: Uint8Array | null, - size: number, - type: 'key' | 'delta', - timescaleUnitsToNextSample: number + timestamp: number; + decodeTimestamp: number; + duration: number; + data: Uint8Array | null; + size: number; + type: 'key' | 'delta'; + timescaleUnitsToNextSample: number; }; type Chunk = { - startTimestamp: number, - samples: Sample[], - offset: number | null, + startTimestamp: number; + samples: Sample[]; + offset: number | null; // In the case of a fragmented file, this indicates the position of the moof box pointing to the data in this chunk - moofOffset: number | null + moofOffset: number | null; }; export type IsobmffTrackData = { - timescale: number, - samples: Sample[], - sampleQueue: Sample[], // For fragmented files - timestampProcessingQueue: Sample[], + timescale: number; + samples: Sample[]; + sampleQueue: Sample[]; // For fragmented files + timestampProcessingQueue: Sample[]; - timeToSampleTable: { sampleCount: number, sampleDelta: number }[]; - compositionTimeOffsetTable: { sampleCount: number, sampleCompositionTimeOffset: number }[]; - lastTimescaleUnits: number | null, - lastSample: Sample | null, + timeToSampleTable: { sampleCount: number; sampleDelta: number }[]; + compositionTimeOffsetTable: { sampleCount: number; sampleCompositionTimeOffset: number }[]; + lastTimescaleUnits: number | null; + lastSample: Sample | null; - finalizedChunks: Chunk[], - currentChunk: Chunk | null, + finalizedChunks: Chunk[]; + currentChunk: Chunk | null; compactlyCodedChunkTable: { - firstChunk: number, - samplesPerChunk: number - }[] + firstChunk: number; + samplesPerChunk: number; + }[]; } & ({ - track: OutputVideoTrack, - type: 'video', + track: OutputVideoTrack; + type: 'video'; info: { - width: number, - height: number, - decoderConfig: VideoDecoderConfig - } + width: number; + height: number; + decoderConfig: VideoDecoderConfig; + }; } | { - track: OutputAudioTrack, - type: 'audio', + track: OutputAudioTrack; + type: 'audio'; info: { - numberOfChannels: number, - sampleRate: number, - decoderConfig: AudioDecoderConfig - } + numberOfChannels: number; + sampleRate: number; + decoderConfig: AudioDecoderConfig; + }; } | { - track: OutputSubtitleTrack, - type: 'subtitle', + track: OutputSubtitleTrack; + type: 'subtitle'; info: { - config: SubtitleConfig - }, - lastCueEndTimestamp: number, - cueQueue: SubtitleCue[], - nextSourceId: number, - cueToSourceId: WeakMap + config: SubtitleConfig; + }; + lastCueEndTimestamp: number; + cueQueue: SubtitleCue[]; + nextSourceId: number; + cueToSourceId: WeakMap; }); export type IsobmffVideoTrackData = IsobmffTrackData & { type: 'video' }; @@ -79,7 +79,7 @@ export type IsobmffAudioTrackData = IsobmffTrackData & { type: 'audio' }; export type IsobmffSubtitleTrackData = IsobmffTrackData & { type: 'subtitle' }; export const intoTimescale = (timeInSeconds: number, timescale: number, round = true) => { - let value = timeInSeconds * timescale; + const value = timeInSeconds * timescale; return round ? Math.round(value) : value; }; @@ -112,7 +112,8 @@ export class IsobmffMuxer extends Muxer { // If the fastStart option isn't defined, enable in-memory fast start if the target is an ArrayBuffer, as the // memory usage remains identical - this.fastStart = format._options.fastStart ?? (this.writer instanceof ArrayBufferTargetWriter ? 'in-memory' : false); + const fastStartDefault = this.writer instanceof ArrayBufferTargetWriter ? 'in-memory' : false; + this.fastStart = format._options.fastStart ?? fastStartDefault; if (this.fastStart === 'in-memory' || this.fastStart === 'fragmented') { this.writer.ensureMonotonicity = true; @@ -123,11 +124,11 @@ export class IsobmffMuxer extends Muxer { const release = await this.mutex.acquire(); const holdsAvc = this.output._tracks.some(x => x.type === 'video' && x.source._codec === 'avc'); - + // Write the header this.boxWriter.writeBox(ftyp({ holdsAvc: holdsAvc, - fragmented: this.fastStart === 'fragmented' + fragmented: this.fastStart === 'fragmented', })); this.ftypSize = this.writer.getPos(); @@ -165,7 +166,7 @@ export class IsobmffMuxer extends Muxer { info: { width: meta.decoderConfig.codedWidth, height: meta.decoderConfig.codedHeight, - decoderConfig: meta.decoderConfig + decoderConfig: meta.decoderConfig, }, timescale: track.metadata.frameRate ?? 57600, samples: [], @@ -177,7 +178,7 @@ export class IsobmffMuxer extends Muxer { lastSample: null, finalizedChunks: [], currentChunk: null, - compactlyCodedChunkTable: [] + compactlyCodedChunkTable: [], }; this.trackDatas.push(newTrackData); @@ -203,7 +204,7 @@ export class IsobmffMuxer extends Muxer { info: { numberOfChannels: meta.decoderConfig.numberOfChannels, sampleRate: meta.decoderConfig.sampleRate, - decoderConfig: meta.decoderConfig + decoderConfig: meta.decoderConfig, }, timescale: meta.decoderConfig.sampleRate, samples: [], @@ -215,7 +216,7 @@ export class IsobmffMuxer extends Muxer { lastSample: null, finalizedChunks: [], currentChunk: null, - compactlyCodedChunkTable: [] + compactlyCodedChunkTable: [], }; this.trackDatas.push(newTrackData); @@ -239,7 +240,7 @@ export class IsobmffMuxer extends Muxer { track, type: 'subtitle', info: { - config: meta.config + config: meta.config, }, timescale: 1000, // Reasonable samples: [], @@ -255,7 +256,7 @@ export class IsobmffMuxer extends Muxer { lastCueEndTimestamp: 0, cueQueue: [], nextSourceId: 0, - cueToSourceId: new WeakMap() + cueToSourceId: new WeakMap(), }; this.trackDatas.push(newTrackData); @@ -273,13 +274,23 @@ export class IsobmffMuxer extends Muxer { try { const trackData = this.getVideoTrackData(track, meta); - - let data = new Uint8Array(chunk.byteLength); + + const data = new Uint8Array(chunk.byteLength); chunk.copyTo(data); - - let timestamp = this.validateAndNormalizeTimestamp(trackData.track, chunk.timestamp, chunk.type === 'key'); - let sample = this.createSampleForTrack(trackData, data, timestamp, (chunk.duration ?? 0) / 1e6, chunk.type); - + + const timestamp = this.validateAndNormalizeTimestamp( + trackData.track, + chunk.timestamp, + chunk.type === 'key', + ); + const sample = this.createSampleForTrack( + trackData, + data, + timestamp, + (chunk.duration ?? 0) / 1e6, + chunk.type, + ); + await this.registerSample(trackData, sample); } finally { release(); @@ -291,13 +302,24 @@ export class IsobmffMuxer extends Muxer { try { const trackData = this.getAudioTrackData(track, meta); - - let data = new Uint8Array(chunk.byteLength); + + const data = new Uint8Array(chunk.byteLength); chunk.copyTo(data); - - let timestamp = this.validateAndNormalizeTimestamp(trackData.track, chunk.timestamp, chunk.type === 'key'); - let sample = this.createSampleForTrack(trackData, data, timestamp, (chunk.duration ?? 0) / 1e6, chunk.type); - + + const chunkType = chunk.type as 'key' | 'delta'; // Types are weird for EncodedAudioChunk + const timestamp = this.validateAndNormalizeTimestamp( + trackData.track, + chunk.timestamp, + chunkType === 'key', + ); + const sample = this.createSampleForTrack( + trackData, + data, + timestamp, + (chunk.duration ?? 0) / 1e6, + chunkType, + ); + await this.registerSample(trackData, sample); } finally { release(); @@ -309,9 +331,9 @@ export class IsobmffMuxer extends Muxer { try { const trackData = this.getSubtitleTrackData(track, meta); - + this.validateAndNormalizeTimestamp(trackData.track, 1e6 * cue.timestamp, true); - + if (track.source._codec === 'webvtt') { trackData.cueQueue.push(cue); await this.processWebVTTCues(trackData, cue.timestamp); @@ -328,8 +350,8 @@ export class IsobmffMuxer extends Muxer { // overlapping samples require special logic. The algorithm produces the format specified in ISO 14496-30. while (trackData.cueQueue.length > 0) { - let timestamps = new Set([]); - for (let cue of trackData.cueQueue) { + const timestamps = new Set([]); + for (const cue of trackData.cueQueue) { assert(cue.timestamp <= until); assert(trackData.lastCueEndTimestamp <= cue.timestamp + cue.duration); @@ -337,11 +359,11 @@ export class IsobmffMuxer extends Muxer { timestamps.add(cue.timestamp + cue.duration); // End timestamp } - let sortedTimestamps = [...timestamps].sort((a, b) => a - b); + const sortedTimestamps = [...timestamps].sort((a, b) => a - b); // These are the timestamps of the next sample we'll create: - let sampleStart = sortedTimestamps[0]!; - let sampleEnd = sortedTimestamps[1] ?? sampleStart; + const sampleStart = sortedTimestamps[0]!; + const sampleEnd = sortedTimestamps[1] ?? sampleStart; if (until < sampleEnd) { break; @@ -350,11 +372,17 @@ export class IsobmffMuxer extends Muxer { // We may need to pad out empty space with an vtte box if (trackData.lastCueEndTimestamp < sampleStart) { this.auxWriter.seek(0); - let box = vtte(); + const box = vtte(); this.auxBoxWriter.writeBox(box); - let body = this.auxWriter.getSlice(0, this.auxWriter.getPos()); - let sample = this.createSampleForTrack(trackData, body, trackData.lastCueEndTimestamp, sampleStart - trackData.lastCueEndTimestamp, 'key'); + const body = this.auxWriter.getSlice(0, this.auxWriter.getPos()); + const sample = this.createSampleForTrack( + trackData, + body, + trackData.lastCueEndTimestamp, + sampleStart - trackData.lastCueEndTimestamp, + 'key', + ); await this.registerSample(trackData, sample); trackData.lastCueEndTimestamp = sampleStart; @@ -363,16 +391,16 @@ export class IsobmffMuxer extends Muxer { this.auxWriter.seek(0); for (let i = 0; i < trackData.cueQueue.length; i++) { - let cue = trackData.cueQueue[i]! + const cue = trackData.cueQueue[i]!; if (cue.timestamp >= sampleEnd) { break; } inlineTimestampRegex.lastIndex = 0; - let containsTimestamp = inlineTimestampRegex.test(cue.text); + const containsTimestamp = inlineTimestampRegex.test(cue.text); - let endTimestamp = cue.timestamp + cue.duration; + const endTimestamp = cue.timestamp + cue.duration; let sourceId = trackData.cueToSourceId.get(cue); if (sourceId === undefined && sampleEnd < endTimestamp) { // We know this cue will appear in more than one sample, therefore we need to mark it with a @@ -383,11 +411,17 @@ export class IsobmffMuxer extends Muxer { if (cue.notes) { // Any notes/comments are included in a special vtta box - let box = vtta(cue.notes); + const box = vtta(cue.notes); this.auxBoxWriter.writeBox(box); } - let box = vttc(cue.text, containsTimestamp ? sampleStart : null, cue.identifier ?? null, cue.settings ?? null, sourceId ?? null); + const box = vttc( + cue.text, + containsTimestamp ? sampleStart : null, + cue.identifier ?? null, + cue.settings ?? null, + sourceId ?? null, + ); this.auxBoxWriter.writeBox(box); if (endTimestamp === sampleEnd) { @@ -396,9 +430,9 @@ export class IsobmffMuxer extends Muxer { } } - let body = this.auxWriter.getSlice(0, this.auxWriter.getPos()); - let sample = this.createSampleForTrack(trackData, body, sampleStart, sampleEnd - sampleStart, 'key'); - + const body = this.auxWriter.getSlice(0, this.auxWriter.getPos()); + const sample = this.createSampleForTrack(trackData, body, sampleStart, sampleEnd - sampleStart, 'key'); + await this.registerSample(trackData, sample); trackData.lastCueEndTimestamp = sampleEnd; } @@ -409,9 +443,9 @@ export class IsobmffMuxer extends Muxer { data: Uint8Array, timestamp: number, duration: number, - type: 'key' | 'delta' + type: 'key' | 'delta', ) { - let sample: Sample = { + const sample: Sample = { timestamp, decodeTimestamp: timestamp, // This may be refined later duration, @@ -419,7 +453,7 @@ export class IsobmffMuxer extends Muxer { size: data.byteLength, type, // Will be refined once the next sample comes in - timescaleUnitsToNextSample: intoTimescale(duration, trackData.timescale) + timescaleUnitsToNextSample: intoTimescale(duration, trackData.timescale), }; return sample; @@ -441,18 +475,18 @@ export class IsobmffMuxer extends Muxer { // model it. sample.decodeTimestamp = sortedTimestamps[i]!; - const sampleCompositionTimeOffset = - intoTimescale(sample.timestamp - sample.decodeTimestamp, trackData.timescale); + const sampleCompositionTimeOffset + = intoTimescale(sample.timestamp - sample.decodeTimestamp, trackData.timescale); const durationInTimescale = intoTimescale(sample.duration, trackData.timescale); if (trackData.lastTimescaleUnits !== null) { assert(trackData.lastSample); - - let timescaleUnits = intoTimescale(sample.decodeTimestamp, trackData.timescale, false); - let delta = Math.round(timescaleUnits - trackData.lastTimescaleUnits); + + const timescaleUnits = intoTimescale(sample.decodeTimestamp, trackData.timescale, false); + const delta = Math.round(timescaleUnits - trackData.lastTimescaleUnits); trackData.lastTimescaleUnits += delta; trackData.lastSample.timescaleUnitsToNextSample = delta; - + if (this.fastStart !== 'fragmented') { let lastTableEntry = last(trackData.timeToSampleTable); assert(lastTableEntry); @@ -460,7 +494,7 @@ export class IsobmffMuxer extends Muxer { if (lastTableEntry.sampleCount === 1) { lastTableEntry.sampleDelta = delta; - let entryBefore = trackData.timeToSampleTable[trackData.timeToSampleTable.length - 2]; + const entryBefore = trackData.timeToSampleTable[trackData.timeToSampleTable.length - 2]; if (entryBefore && entryBefore.sampleDelta === delta) { // If the delta is the same as the previous one, merge the two entries entryBefore.sampleCount++; @@ -472,7 +506,7 @@ export class IsobmffMuxer extends Muxer { lastTableEntry.sampleCount--; trackData.timeToSampleTable.push(lastTableEntry = { sampleCount: 1, - sampleDelta: delta + sampleDelta: delta, }); } @@ -483,14 +517,16 @@ export class IsobmffMuxer extends Muxer { // Add a new entry in order to maintain the last sample's true duration trackData.timeToSampleTable.push({ sampleCount: 1, - sampleDelta: durationInTimescale + sampleDelta: durationInTimescale, }); } - + const lastCompositionTimeOffsetTableEntry = last(trackData.compositionTimeOffsetTable); assert(lastCompositionTimeOffsetTableEntry); - - if (lastCompositionTimeOffsetTableEntry.sampleCompositionTimeOffset === sampleCompositionTimeOffset) { + + if ( + lastCompositionTimeOffsetTableEntry.sampleCompositionTimeOffset === sampleCompositionTimeOffset + ) { // Simply increment the count lastCompositionTimeOffsetTableEntry.sampleCount++; } else { @@ -498,21 +534,21 @@ export class IsobmffMuxer extends Muxer { // offset trackData.compositionTimeOffsetTable.push({ sampleCount: 1, - sampleCompositionTimeOffset: sampleCompositionTimeOffset + sampleCompositionTimeOffset: sampleCompositionTimeOffset, }); } } } else { trackData.lastTimescaleUnits = 0; - + if (this.fastStart !== 'fragmented') { trackData.timeToSampleTable.push({ sampleCount: 1, - sampleDelta: durationInTimescale + sampleDelta: durationInTimescale, }); trackData.compositionTimeOffsetTable.push({ sampleCount: 1, - sampleCompositionTimeOffset: sampleCompositionTimeOffset + sampleCompositionTimeOffset: sampleCompositionTimeOffset, }); } } @@ -545,12 +581,12 @@ export class IsobmffMuxer extends Muxer { if (!trackData.currentChunk) { beginNewChunk = true; } else { - let currentChunkDuration = sample.timestamp - trackData.currentChunk.startTimestamp; + const currentChunkDuration = sample.timestamp - trackData.currentChunk.startTimestamp; if (this.fastStart === 'fragmented') { // We can only finalize this fragment (and begin a new one) if we know that each track will be able to // start the new one with a key frame. - const keyFrameQueuedEverywhere = this.trackDatas.every(otherTrackData => { + const keyFrameQueuedEverywhere = this.trackDatas.every((otherTrackData) => { if (trackData === otherTrackData) { return sample.type === 'key'; } @@ -577,7 +613,7 @@ export class IsobmffMuxer extends Muxer { startTimestamp: sample.timestamp, samples: [], offset: null, - moofOffset: null + moofOffset: null, }; } @@ -600,7 +636,7 @@ export class IsobmffMuxer extends Muxer { ) { trackData.compactlyCodedChunkTable.push({ firstChunk: trackData.finalizedChunks.length, // 1-indexed - samplesPerChunk: trackData.currentChunk.samples.length + samplesPerChunk: trackData.currentChunk.samples.length, }); } @@ -611,7 +647,7 @@ export class IsobmffMuxer extends Muxer { // Write out the data trackData.currentChunk.offset = this.writer.getPos(); - for (let sample of trackData.currentChunk.samples) { + for (const sample of trackData.currentChunk.samples) { assert(sample.data); this.writer.write(sample.data); sample.data = null; // Can be GC'd @@ -634,7 +670,7 @@ export class IsobmffMuxer extends Muxer { let trackWithMinTimestamp: IsobmffTrackData | null = null; let minTimestamp = Infinity; - for (let trackData of this.trackDatas) { + for (const trackData of this.trackDatas) { if (trackData.sampleQueue.length === 0 && !trackData.track.source._closed) { break outer; } @@ -649,7 +685,7 @@ export class IsobmffMuxer extends Muxer { break; } - let sample = trackWithMinTimestamp.sampleQueue.shift()!; + const sample = trackWithMinTimestamp.sampleQueue.shift()!; await this.addSampleToTrack(trackWithMinTimestamp, sample); } } @@ -657,34 +693,34 @@ export class IsobmffMuxer extends Muxer { private async finalizeFragment(flushWriter = true) { assert(this.fastStart === 'fragmented'); - let fragmentNumber = this.nextFragmentNumber++; + const fragmentNumber = this.nextFragmentNumber++; if (fragmentNumber === 1) { // Write the moov box now that we have all decoder configs - let movieBox = moov(this.trackDatas, this.creationTime, true); + const movieBox = moov(this.trackDatas, this.creationTime, true); this.boxWriter.writeBox(movieBox); } // Write out an initial moof box; will be overwritten later once actual chunk offsets are known - let moofOffset = this.writer.getPos(); - let moofBox = moof(fragmentNumber, this.trackDatas); + const moofOffset = this.writer.getPos(); + const moofBox = moof(fragmentNumber, this.trackDatas); this.boxWriter.writeBox(moofBox); // Create the mdat box { - let mdatBox = mdat(false); // Initially assume no fragment is larger than 4 GiB + const mdatBox = mdat(false); // Initially assume no fragment is larger than 4 GiB let totalTrackSampleSize = 0; // Compute the size of the mdat box - for (let trackData of this.trackDatas) { + for (const trackData of this.trackDatas) { assert(trackData.currentChunk); - for (let sample of trackData.currentChunk.samples) { + for (const sample of trackData.currentChunk.samples) { totalTrackSampleSize += sample.size; } } let mdatSize = this.boxWriter.measureBox(mdatBox) + totalTrackSampleSize; - if (mdatSize >= 2**32) { + if (mdatSize >= 2 ** 32) { // Fragment is larger than 4 GiB, we need to use the large size mdatBox.largeSize = true; mdatSize = this.boxWriter.measureBox(mdatBox) + totalTrackSampleSize; @@ -695,24 +731,24 @@ export class IsobmffMuxer extends Muxer { } // Write sample data - for (let trackData of this.trackDatas) { + for (const trackData of this.trackDatas) { trackData.currentChunk!.offset = this.writer.getPos(); trackData.currentChunk!.moofOffset = moofOffset; - for (let sample of trackData.currentChunk!.samples) { + for (const sample of trackData.currentChunk!.samples) { this.writer.write(sample.data!); sample.data = null; // Can be GC'd } } // Now that we set the actual chunk offsets, fix the moof box - let endPos = this.writer.getPos(); + const endPos = this.writer.getPos(); this.writer.seek(this.boxWriter.offsets.get(moofBox)!); - let newMoofBox = moof(fragmentNumber, this.trackDatas); + const newMoofBox = moof(fragmentNumber, this.trackDatas); this.boxWriter.writeBox(newMoofBox); this.writer.seek(endPos); - for (let trackData of this.trackDatas) { + for (const trackData of this.trackDatas) { trackData.finalizedChunks.push(trackData.currentChunk!); this.finalizedChunks.push(trackData.currentChunk!); trackData.currentChunk = null; @@ -723,11 +759,12 @@ export class IsobmffMuxer extends Muxer { } } + // eslint-disable-next-line @typescript-eslint/no-misused-promises override async onTrackClose(track: OutputTrack) { const release = await this.mutex.acquire(); if (track.type === 'subtitle' && track.source._codec === 'webvtt') { - let trackData = this.trackDatas.find(x => x.track === track) as IsobmffSubtitleTrackData; + const trackData = this.trackDatas.find(x => x.track === track) as IsobmffSubtitleTrackData; if (trackData) { await this.processWebVTTCues(trackData, Infinity); } @@ -745,24 +782,24 @@ export class IsobmffMuxer extends Muxer { async finalize() { const release = await this.mutex.acquire(); - for (let trackData of this.trackDatas) { + for (const trackData of this.trackDatas) { if (trackData.type === 'subtitle' && trackData.track.source._codec === 'webvtt') { await this.processWebVTTCues(trackData, Infinity); } } if (this.fastStart === 'fragmented') { - for (let trackData of this.trackDatas) { - for (let sample of trackData.sampleQueue) { + for (const trackData of this.trackDatas) { + for (const sample of trackData.sampleQueue) { await this.addSampleToTrack(trackData, sample); } this.processTimestamps(trackData); } - await this.finalizeFragment(false); // Don't flush the last fragment as we will flush it with the mfra box soon + await this.finalizeFragment(false); // Don't flush the last fragment as we will flush it with the mfra box } else { - for (let trackData of this.trackDatas) { + for (const trackData of this.trackDatas) { this.processTimestamps(trackData); await this.finalizeCurrentChunk(trackData); } @@ -781,32 +818,32 @@ export class IsobmffMuxer extends Muxer { // size of the moov box and can compute the proper chunk positions. for (let i = 0; i < 2; i++) { - let movieBox = moov(this.trackDatas, this.creationTime); - let movieBoxSize = this.boxWriter.measureBox(movieBox); + const movieBox = moov(this.trackDatas, this.creationTime); + const movieBoxSize = this.boxWriter.measureBox(movieBox); mdatSize = this.boxWriter.measureBox(this.mdat); let currentChunkPos = this.writer.getPos() + movieBoxSize + mdatSize; - for (let chunk of this.finalizedChunks) { + for (const chunk of this.finalizedChunks) { chunk.offset = currentChunkPos; - for (let { data } of chunk.samples) { + for (const { data } of chunk.samples) { assert(data); currentChunkPos += data.byteLength; mdatSize += data.byteLength; } } - if (currentChunkPos < 2**32) break; - if (mdatSize >= 2**32) this.mdat.largeSize = true; + if (currentChunkPos < 2 ** 32) break; + if (mdatSize >= 2 ** 32) this.mdat.largeSize = true; } - let movieBox = moov(this.trackDatas, this.creationTime); + const movieBox = moov(this.trackDatas, this.creationTime); this.boxWriter.writeBox(movieBox); this.mdat.size = mdatSize!; this.boxWriter.writeBox(this.mdat); - for (let chunk of this.finalizedChunks) { - for (let sample of chunk.samples) { + for (const chunk of this.finalizedChunks) { + for (const sample of chunk.samples) { assert(sample.data); this.writer.write(sample.data); sample.data = null; @@ -814,32 +851,32 @@ export class IsobmffMuxer extends Muxer { } } else if (this.fastStart === 'fragmented') { // Append the mfra box to the end of the file for better random access - let startPos = this.writer.getPos(); - let mfraBox = mfra(this.trackDatas); + const startPos = this.writer.getPos(); + const mfraBox = mfra(this.trackDatas); this.boxWriter.writeBox(mfraBox); // Patch the 'size' field of the mfro box at the end of the mfra box now that we know its actual size - let mfraBoxSize = this.writer.getPos() - startPos; + const mfraBoxSize = this.writer.getPos() - startPos; this.writer.seek(this.writer.getPos() - 4); this.boxWriter.writeU32(mfraBoxSize); } else { assert(this.mdat); assert(this.ftypSize !== null); - let mdatPos = this.boxWriter.offsets.get(this.mdat); + const mdatPos = this.boxWriter.offsets.get(this.mdat); assert(mdatPos !== undefined); - let mdatSize = this.writer.getPos() - mdatPos; + const mdatSize = this.writer.getPos() - mdatPos; this.mdat.size = mdatSize; - this.mdat.largeSize = mdatSize >= 2**32; // Only use the large size if we need it + this.mdat.largeSize = mdatSize >= 2 ** 32; // Only use the large size if we need it this.boxWriter.patchBox(this.mdat); - let movieBox = moov(this.trackDatas, this.creationTime); + const movieBox = moov(this.trackDatas, this.creationTime); if (typeof this.fastStart === 'object') { this.writer.seek(this.ftypSize); this.boxWriter.writeBox(movieBox); - let remainingBytes = mdatPos - this.writer.getPos(); + const remainingBytes = mdatPos - this.writer.getPos(); this.boxWriter.writeBox(free(remainingBytes)); } else { this.boxWriter.writeBox(movieBox); diff --git a/src/matroska/ebml.ts b/src/matroska/ebml.ts index 8683a72..4d943c1 100644 --- a/src/matroska/ebml.ts +++ b/src/matroska/ebml.ts @@ -1,7 +1,7 @@ export interface EBMLElement { - id: number, - size?: number, - data: number | string | Uint8Array | EBMLFloat32 | EBMLFloat64 | EBMLSignedInt | (EBML | null)[] + id: number; + size?: number; + data: number | string | Uint8Array | EBMLFloat32 | EBMLFloat64 | EBMLSignedInt | (EBML | null)[]; } export type EBML = EBMLElement | Uint8Array | (EBML | null)[]; @@ -89,7 +89,7 @@ export enum EBMLId { MatrixCoefficients = 0x55b1, TransferCharacteristics = 0x55ba, Primaries = 0x55bb, - Range = 0x55b9 + Range = 0x55b9, } export const measureUnsignedInt = (value: number) => { @@ -99,9 +99,9 @@ export const measureUnsignedInt = (value: number) => { return 2; } else if (value < (1 << 24)) { return 3; - } else if (value < 2**32) { + } else if (value < 2 ** 32) { return 4; - } else if (value < 2**40) { + } else if (value < 2 ** 40) { return 5; } else { return 6; @@ -117,7 +117,7 @@ export const measureSignedInt = (value: number) => { return 3; } else if (value >= -(1 << 27) && value < (1 << 27)) { return 4; - } else if (value >= -(2**34) && value < 2**34) { + } else if (value >= -(2 ** 34) && value < 2 ** 34) { return 5; } else { return 6; @@ -137,11 +137,11 @@ export const measureEBMLVarInt = (value: number) => { return 3; } else if (value < (1 << 28) - 1) { return 4; - } else if (value < 2**35-1) { + } else if (value < 2 ** 35 - 1) { return 5; - } else if (value < 2**42-1) { + } else if (value < 2 ** 42 - 1) { return 6; } else { throw new Error('EBML VINT size not supported ' + value); } -}; \ No newline at end of file +}; diff --git a/src/matroska/matroska-muxer.ts b/src/matroska/matroska-muxer.ts index 5750129..15453b8 100644 --- a/src/matroska/matroska-muxer.ts +++ b/src/matroska/matroska-muxer.ts @@ -1,67 +1,94 @@ -import { validateAudioChunkMetadata, validateSubtitleMetadata, validateVideoChunkMetadata } from '../codec'; -import { assert, COLOR_PRIMARIES_MAP, colorSpaceIsComplete, MATRIX_COEFFICIENTS_MAP, readBits, textEncoder, toUint8Array, TRANSFER_CHARACTERISTICS_MAP, writeBits } from '../misc'; -import { Muxer } from '../muxer'; -import { Output, OutputAudioTrack, OutputSubtitleTrack, OutputTrack, OutputVideoTrack } from '../output'; -import { MkvOutputFormat, WebMOutputFormat } from '../output-format'; import { AudioCodec, SubtitleCodec, VideoCodec } from '../source'; -import { formatSubtitleTimestamp, inlineTimestampRegex, parseSubtitleTimestamp, SubtitleConfig, SubtitleCue, SubtitleMetadata } from '../subtitles'; +import { + COLOR_PRIMARIES_MAP, + MATRIX_COEFFICIENTS_MAP, + TRANSFER_CHARACTERISTICS_MAP, + assert, + colorSpaceIsComplete, + readBits, + textEncoder, + toUint8Array, + writeBits, +} from '../misc'; +import { + EBML, + EBMLElement, + EBMLFloat32, + EBMLFloat64, + EBMLId, + EBMLSignedInt, + measureEBMLVarInt, + measureSignedInt, + measureUnsignedInt, +} from './ebml'; +import { MkvOutputFormat, WebMOutputFormat } from '../output-format'; +import { Output, OutputAudioTrack, OutputSubtitleTrack, OutputTrack, OutputVideoTrack } from '../output'; +import { + SubtitleConfig, + SubtitleCue, + SubtitleMetadata, + formatSubtitleTimestamp, + inlineTimestampRegex, + parseSubtitleTimestamp, +} from '../subtitles'; +import { validateAudioChunkMetadata, validateSubtitleMetadata, validateVideoChunkMetadata } from '../codec'; +import { Muxer } from '../muxer'; import { Writer } from '../writer'; -import { EBML, EBMLElement, EBMLFloat32, EBMLFloat64, EBMLId, EBMLSignedInt, measureEBMLVarInt, measureSignedInt, measureUnsignedInt } from './ebml'; -const MAX_CHUNK_LENGTH_MS = 2**15; +const MAX_CHUNK_LENGTH_MS = 2 ** 15; const APP_NAME = 'https://github.com/Vanilagy/webm-muxer'; // TODO const SEGMENT_SIZE_BYTES = 6; const CLUSTER_SIZE_BYTES = 5; type InternalMediaChunk = { - data: Uint8Array, - type: 'key' | 'delta', - timestamp: number, - duration: number, - additions: Uint8Array | null, + data: Uint8Array; + type: 'key' | 'delta'; + timestamp: number; + duration: number; + additions: Uint8Array | null; }; type SeekHead = { - id: number, + id: number; data: { - id: number, + id: number; data: ({ - id: number, - data: Uint8Array, - size?: undefined + id: number; + data: Uint8Array; + size?: undefined; } | { - id: number, - size: number, - data: number - })[] - }[] + id: number; + size: number; + data: number; + })[]; + }[]; }; type MatroskaTrackData = { - chunkQueue: InternalMediaChunk[], - lastWrittenMsTimestamp: number | null + chunkQueue: InternalMediaChunk[]; + lastWrittenMsTimestamp: number | null; } & ({ - track: OutputVideoTrack, - type: 'video', + track: OutputVideoTrack; + type: 'video'; info: { - width: number, - height: number, - decoderConfig: VideoDecoderConfig - } + width: number; + height: number; + decoderConfig: VideoDecoderConfig; + }; } | { - track: OutputAudioTrack, - type: 'audio', + track: OutputAudioTrack; + type: 'audio'; info: { - numberOfChannels: number, - sampleRate: number, - decoderConfig: AudioDecoderConfig - } + numberOfChannels: number; + sampleRate: number; + decoderConfig: AudioDecoderConfig; + }; } | { - track: OutputSubtitleTrack, - type: 'subtitle', + track: OutputSubtitleTrack; + type: 'subtitle'; info: { - config: SubtitleConfig - } + config: SubtitleConfig; + }; }); type MatroskaVideoTrackData = MatroskaTrackData & { type: 'video' }; @@ -76,13 +103,13 @@ const CODEC_STRING_MAP: Record av1: 'V_AV1', aac: 'A_AAC', opus: 'A_OPUS', - webvtt: 'S_TEXT/WEBVTT' + webvtt: 'S_TEXT/WEBVTT', }; const TRACK_TYPE_MAP: Record = { video: 1, audio: 2, - subtitle: 17 + subtitle: 17, }; export class MatroskaMuxer extends Muxer { @@ -150,15 +177,20 @@ export class MatroskaMuxer extends Muxer { switch (width) { case 6: // Need to use division to access >32 bits of floating point var - this.helperView.setUint8(pos++, (value / 2**40) | 0); + this.helperView.setUint8(pos++, (value / 2 ** 40) | 0); + // eslint-disable-next-line no-fallthrough case 5: - this.helperView.setUint8(pos++, (value / 2**32) | 0); + this.helperView.setUint8(pos++, (value / 2 ** 32) | 0); + // eslint-disable-next-line no-fallthrough case 4: this.helperView.setUint8(pos++, value >> 24); + // eslint-disable-next-line no-fallthrough case 3: this.helperView.setUint8(pos++, value >> 16); + // eslint-disable-next-line no-fallthrough case 2: this.helperView.setUint8(pos++, value >> 8); + // eslint-disable-next-line no-fallthrough case 1: this.helperView.setUint8(pos++, value); break; @@ -206,15 +238,15 @@ export class MatroskaMuxer extends Muxer { * operations, so we need to do a division by 2^32 instead of a * right-shift of 32 to retain those top 3 bits */ - this.helperView.setUint8(pos++, (1 << 3) | ((value / 2**32) & 0x7)); + this.helperView.setUint8(pos++, (1 << 3) | ((value / 2 ** 32) & 0x7)); this.helperView.setUint8(pos++, value >> 24); this.helperView.setUint8(pos++, value >> 16); this.helperView.setUint8(pos++, value >> 8); this.helperView.setUint8(pos++, value); break; case 6: - this.helperView.setUint8(pos++, (1 << 2) | ((value / 2**40) & 0x3)); - this.helperView.setUint8(pos++, (value / 2**32) | 0); + this.helperView.setUint8(pos++, (1 << 2) | ((value / 2 ** 40) & 0x3)); + this.helperView.setUint8(pos++, (value / 2 ** 32) | 0); this.helperView.setUint8(pos++, value >> 24); this.helperView.setUint8(pos++, value >> 16); this.helperView.setUint8(pos++, value >> 8); @@ -238,7 +270,7 @@ export class MatroskaMuxer extends Muxer { if (data instanceof Uint8Array) { this.writer.write(data); } else if (Array.isArray(data)) { - for (let elem of data) { + for (const elem of data) { this.writeEBML(elem); } } else { @@ -247,8 +279,8 @@ export class MatroskaMuxer extends Muxer { this.writeUnsignedInt(data.id); // ID field if (Array.isArray(data.data)) { - let sizePos = this.writer.getPos(); - let sizeSize = data.size === -1 ? 1 : (data.size ?? 4); + const sizePos = this.writer.getPos(); + const sizeSize = data.size === -1 ? 1 : (data.size ?? 4); if (data.size === -1) { // Write the reserved all-one-bits marker for unknown/unbounded size. @@ -257,19 +289,19 @@ export class MatroskaMuxer extends Muxer { this.writer.seek(this.writer.getPos() + sizeSize); } - let startPos = this.writer.getPos(); + const startPos = this.writer.getPos(); this.dataOffsets.set(data, startPos); this.writeEBML(data.data); if (data.size !== -1) { - let size = this.writer.getPos() - startPos; - let endPos = this.writer.getPos(); + const size = this.writer.getPos() - startPos; + const endPos = this.writer.getPos(); this.writer.seek(sizePos); this.writeEBMLVarInt(size, sizeSize); this.writer.seek(endPos); } } else if (typeof data.data === 'number') { - let size = data.size ?? measureUnsignedInt(data.data); + const size = data.size ?? measureUnsignedInt(data.data); this.writeEBMLVarInt(size); this.writeUnsignedInt(data.data, size); } else if (typeof data.data === 'string') { @@ -285,7 +317,7 @@ export class MatroskaMuxer extends Muxer { this.writeEBMLVarInt(8); this.writeFloat64(data.data.value); } else if (data.data instanceof EBMLSignedInt) { - let size = data.size ?? measureSignedInt(data.data.value); + const size = data.size ?? measureSignedInt(data.data.value); this.writeEBMLVarInt(size); this.writeSignedInt(data.data.value, size); } @@ -293,24 +325,32 @@ export class MatroskaMuxer extends Muxer { } override beforeTrackAdd(track: OutputTrack) { - if (!(this.format instanceof WebMOutputFormat)) { + if (!(this.format instanceof WebMOutputFormat)) { return; } if (track.type === 'video') { if (!['vp8', 'vp9', 'av1'].includes(track.source._codec)) { - throw new Error(`WebM only supports VP8, VP9 and AV1 as video codecs. Switching to MKV removes this restriction.`); + throw new Error( + `WebM only supports VP8, VP9 and AV1 as video codecs. Switching to MKV removes this restriction.`, + ); } } else if (track.type === 'audio') { if (!['opus', 'vorbis'].includes(track.source._codec)) { - throw new Error(`WebM only supports Opus and Vorbis as audio codecs. Switching to MKV removes this restriction.`); + throw new Error( + `WebM only supports Opus and Vorbis as audio codecs. Switching to MKV removes this restriction.`, + ); } } else if (track.type === 'subtitle') { if (track.source._codec !== 'webvtt') { - throw new Error(`WebM only supports WebVTT as subtitle codec. Switching to MKV removes this restriction.`); + 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.'); + throw new Error( + 'WebM only supports video, audio and subtitle tracks. Switching to MKV removes this restriction.', + ); } } @@ -332,14 +372,14 @@ export class MatroskaMuxer extends Muxer { } private writeEBMLHeader() { - let ebmlHeader: EBML = { id: EBMLId.EBML, data: [ + const ebmlHeader: EBML = { id: EBMLId.EBML, data: [ { id: EBMLId.EBMLVersion, data: 1 }, { id: EBMLId.EBMLReadVersion, data: 1 }, { id: EBMLId.EBMLMaxIDLength, data: 4 }, { id: EBMLId.EBMLMaxSizeLength, data: 8 }, { id: EBMLId.DocType, data: this.format instanceof WebMOutputFormat ? 'webm' : 'matroska' }, { id: EBMLId.DocTypeVersion, data: 2 }, - { id: EBMLId.DocTypeReadVersion, data: 2 } + { id: EBMLId.DocTypeReadVersion, data: 2 }, ] }; this.writeEBML(ebmlHeader); } @@ -349,99 +389,138 @@ export class MatroskaMuxer extends Muxer { * relevant sections more easily. Since we don't know the positions of those sections yet, we'll set them later. */ private createSeekHead() { - const kaxCues = new Uint8Array([ 0x1c, 0x53, 0xbb, 0x6b ]); - const kaxInfo = new Uint8Array([ 0x15, 0x49, 0xa9, 0x66 ]); - const kaxTracks = new Uint8Array([ 0x16, 0x54, 0xae, 0x6b ]); + const kaxCues = new Uint8Array([0x1c, 0x53, 0xbb, 0x6b]); + const kaxInfo = new Uint8Array([0x15, 0x49, 0xa9, 0x66]); + const kaxTracks = new Uint8Array([0x16, 0x54, 0xae, 0x6b]); - let seekHead = { id: EBMLId.SeekHead, data: [ + const seekHead = { id: EBMLId.SeekHead, data: [ { id: EBMLId.Seek, data: [ { id: EBMLId.SeekID, data: kaxCues }, - { id: EBMLId.SeekPosition, size: 5, data: 0 } + { id: EBMLId.SeekPosition, size: 5, data: 0 }, ] }, { id: EBMLId.Seek, data: [ { id: EBMLId.SeekID, data: kaxInfo }, - { id: EBMLId.SeekPosition, size: 5, data: 0 } + { id: EBMLId.SeekPosition, size: 5, data: 0 }, ] }, { id: EBMLId.Seek, data: [ { id: EBMLId.SeekID, data: kaxTracks }, - { id: EBMLId.SeekPosition, size: 5, data: 0 } - ] } + { id: EBMLId.SeekPosition, size: 5, data: 0 }, + ] }, ] }; this.seekHead = seekHead; } private createSegmentInfo() { - let segmentDuration: EBML = { id: EBMLId.Duration, data: new EBMLFloat64(0) }; + const segmentDuration: EBML = { id: EBMLId.Duration, data: new EBMLFloat64(0) }; this.segmentDuration = segmentDuration; - let segmentInfo: EBML = { id: EBMLId.Info, data: [ + const segmentInfo: EBML = { id: EBMLId.Info, data: [ { id: EBMLId.TimestampScale, data: 1e6 }, { id: EBMLId.MuxingApp, data: APP_NAME }, { id: EBMLId.WritingApp, data: APP_NAME }, - !this.format._options.streamable ? segmentDuration : null + !this.format._options.streamable ? segmentDuration : null, ] }; this.segmentInfo = segmentInfo; } private createTracks() { - let tracksElement = { id: EBMLId.Tracks, data: [] as EBML[] }; + const tracksElement = { id: EBMLId.Tracks, data: [] as EBML[] }; this.tracksElement = tracksElement; - for (let trackData of this.trackDatas) { + for (const trackData of this.trackDatas) { tracksElement.data.push({ id: EBMLId.TrackEntry, data: [ { id: EBMLId.TrackNumber, data: trackData.track.id }, { id: EBMLId.TrackUID, data: trackData.track.id }, { id: EBMLId.TrackType, data: TRACK_TYPE_MAP[trackData.type] }, { id: EBMLId.CodecID, data: CODEC_STRING_MAP[trackData.track.source._codec] }, - ...(trackData.type === 'video' ? [ - (trackData.info.decoderConfig.description ? { id: EBMLId.CodecPrivate, data: toUint8Array(trackData.info.decoderConfig.description) } : null), - (trackData.track.metadata.frameRate ? { id: EBMLId.DefaultDuration, data: 1e9 / trackData.track.metadata.frameRate } : null), - { id: EBMLId.Video, data: [ - { id: EBMLId.PixelWidth, data: trackData.info.width }, - { id: EBMLId.PixelHeight, data: trackData.info.height }, - (() => { - if (trackData.info.decoderConfig.colorSpace) { - let colorSpace = trackData.info.decoderConfig.colorSpace; - if (!colorSpaceIsComplete(colorSpace)) { - return null; - } - - return {id: EBMLId.Colour, data: [ - { id: EBMLId.MatrixCoefficients, data: MATRIX_COEFFICIENTS_MAP[colorSpace.matrix!] }, - { id: EBMLId.TransferCharacteristics, data: TRANSFER_CHARACTERISTICS_MAP[colorSpace.transfer!] }, - { id: EBMLId.Primaries, data: COLOR_PRIMARIES_MAP[colorSpace.primaries!] }, - { id: EBMLId.Range, data: [1, 2][Number(colorSpace.fullRange)]! } - ] }; - } - - return null; - })() - ] } - ] : []), - ...(trackData.type === 'audio' ? [ - (trackData.info.decoderConfig.description ? { id: EBMLId.CodecPrivate, data: toUint8Array(trackData.info.decoderConfig.description) } : null), - { id: EBMLId.Audio, data: [ - { id: EBMLId.SamplingFrequency, data: new EBMLFloat32(trackData.info.sampleRate) }, - { id: EBMLId.Channels, data: trackData.info.numberOfChannels }, - // Bit depth for when PCM is a thing - ] } - ] : []), - ...(trackData.type === 'subtitle' ? [ - { id: EBMLId.CodecPrivate, data: textEncoder.encode(trackData.info.config.description) } - ] : []), - ] }) + (trackData.type === 'video' ? this.videoSpecificTrackInfo(trackData) : null), + (trackData.type === 'audio' ? this.audioSpecificTrackInfo(trackData) : null), + (trackData.type === 'subtitle' ? this.subtitleSpecificTrackInfo(trackData) : null), + ] }); } } + private videoSpecificTrackInfo(trackData: MatroskaVideoTrackData) { + const elements: EBMLElement['data'] = [ + (trackData.info.decoderConfig.description + ? { + id: EBMLId.CodecPrivate, + data: toUint8Array(trackData.info.decoderConfig.description), + } + : null), + (trackData.track.metadata.frameRate + ? { + id: EBMLId.DefaultDuration, + data: 1e9 / trackData.track.metadata.frameRate, + } + : null), + ]; + + const colorSpace = trackData.info.decoderConfig.colorSpace; + const videoElement: EBMLElement = { id: EBMLId.Video, data: [ + { id: EBMLId.PixelWidth, data: trackData.info.width }, + { id: EBMLId.PixelHeight, data: trackData.info.height }, + (colorSpaceIsComplete(colorSpace) + ? { + id: EBMLId.Colour, + data: [ + { + id: EBMLId.MatrixCoefficients, + data: MATRIX_COEFFICIENTS_MAP[colorSpace.matrix], + }, + { + id: EBMLId.TransferCharacteristics, + data: TRANSFER_CHARACTERISTICS_MAP[colorSpace.transfer], + }, + { + id: EBMLId.Primaries, + data: COLOR_PRIMARIES_MAP[colorSpace.primaries], + }, + { + id: EBMLId.Range, + data: colorSpace.fullRange ? 2 : 1, + }, + ], + } + : null), + ] }; + + elements.push(videoElement); + + return elements; + } + + private audioSpecificTrackInfo(trackData: MatroskaAudioTrackData) { + return [ + (trackData.info.decoderConfig.description + ? { + id: EBMLId.CodecPrivate, + data: toUint8Array(trackData.info.decoderConfig.description), + } + : null), + { id: EBMLId.Audio, data: [ + { id: EBMLId.SamplingFrequency, data: new EBMLFloat32(trackData.info.sampleRate) }, + { id: EBMLId.Channels, data: trackData.info.numberOfChannels }, + // TODO Bit depth for when PCM is a thing + ] }, + ]; + } + + private subtitleSpecificTrackInfo(trackData: MatroskaSubtitleTrackData) { + return [ + { id: EBMLId.CodecPrivate, data: textEncoder.encode(trackData.info.config.description) }, + ]; + } + private createSegment() { - let segment: EBML = { + const segment: EBML = { id: EBMLId.Segment, size: this.format._options.streamable ? -1 : SEGMENT_SIZE_BYTES, data: [ !this.format._options.streamable ? this.seekHead as EBML : null, this.segmentInfo, - this.tracksElement - ] + this.tracksElement, + ], }; this.segment = segment; @@ -483,10 +562,10 @@ export class MatroskaMuxer extends Muxer { info: { width: meta.decoderConfig.codedWidth, height: meta.decoderConfig.codedHeight, - decoderConfig: meta.decoderConfig + decoderConfig: meta.decoderConfig, }, chunkQueue: [], - lastWrittenMsTimestamp: null + lastWrittenMsTimestamp: null, }; this.trackDatas.push(newTrackData); @@ -512,10 +591,10 @@ export class MatroskaMuxer extends Muxer { info: { numberOfChannels: meta.decoderConfig.numberOfChannels, sampleRate: meta.decoderConfig.sampleRate, - decoderConfig: meta.decoderConfig + decoderConfig: meta.decoderConfig, }, chunkQueue: [], - lastWrittenMsTimestamp: null + lastWrittenMsTimestamp: null, }; this.trackDatas.push(newTrackData); @@ -539,10 +618,10 @@ export class MatroskaMuxer extends Muxer { track, type: 'subtitle', info: { - config: meta.config + config: meta.config, }, chunkQueue: [], - lastWrittenMsTimestamp: null + lastWrittenMsTimestamp: null, }; this.trackDatas.push(newTrackData); @@ -550,21 +629,22 @@ export class MatroskaMuxer extends Muxer { return newTrackData; } - + async addEncodedVideoChunk(track: OutputVideoTrack, chunk: EncodedVideoChunk, meta?: EncodedVideoChunkMetadata) { const release = await this.mutex.acquire(); try { const trackData = this.getVideoTrackData(track, meta); - - let data = new Uint8Array(chunk.byteLength); + + const data = new Uint8Array(chunk.byteLength); chunk.copyTo(data); - - let timestamp = this.validateAndNormalizeTimestamp(trackData.track, chunk.timestamp, chunk.type === 'key'); - let videoChunk = this.createInternalChunk(data, timestamp, (chunk.duration ?? 0) / 1e6, chunk.type); + + const isKeyFrame = chunk.type === 'key'; + const timestamp = this.validateAndNormalizeTimestamp(trackData.track, chunk.timestamp, isKeyFrame); + const videoChunk = this.createInternalChunk(data, timestamp, (chunk.duration ?? 0) / 1e6, chunk.type); if (track.source._codec === 'vp9') this.fixVP9ColorSpace(trackData, videoChunk); - - trackData.chunkQueue.push(videoChunk); + + trackData.chunkQueue.push(videoChunk); await this.interleaveChunks(); } finally { release(); @@ -576,45 +656,53 @@ export class MatroskaMuxer extends Muxer { try { const trackData = this.getAudioTrackData(track, meta); - - let data = new Uint8Array(chunk.byteLength); + + const data = new Uint8Array(chunk.byteLength); chunk.copyTo(data); - - let timestamp = this.validateAndNormalizeTimestamp(trackData.track, chunk.timestamp, chunk.type === 'key'); - let audioChunk = this.createInternalChunk(data, timestamp, (chunk.duration ?? 0) / 1e6, chunk.type); - + + const chunkType = chunk.type as 'key' | 'delta'; // Types are weird for EncodedAudioChunk + const isKeyFrame = chunkType === 'key'; + const timestamp = this.validateAndNormalizeTimestamp(trackData.track, chunk.timestamp, isKeyFrame); + const audioChunk = this.createInternalChunk(data, timestamp, (chunk.duration ?? 0) / 1e6, chunkType); + trackData.chunkQueue.push(audioChunk); await this.interleaveChunks(); } finally { release(); } } - + async addSubtitleCue(track: OutputSubtitleTrack, cue: SubtitleCue, meta?: SubtitleMetadata) { const release = await this.mutex.acquire(); try { const trackData = this.getSubtitleTrackData(track, meta); - + const timestamp = this.validateAndNormalizeTimestamp(trackData.track, 1e6 * cue.timestamp, true); - + let bodyText = cue.text; const timestampMs = Math.floor(timestamp * 1000); - + // Replace in-body timestamps so that they're relative to the cue start time inlineTimestampRegex.lastIndex = 0; bodyText = bodyText.replace(inlineTimestampRegex, (match) => { - let time = parseSubtitleTimestamp(match.slice(1, -1)); - let offsetTime = time - timestampMs; - + const time = parseSubtitleTimestamp(match.slice(1, -1)); + const offsetTime = time - timestampMs; + return `<${formatSubtitleTimestamp(offsetTime)}>`; }); - + const body = textEncoder.encode(bodyText); const additions = `${cue.settings ?? ''}\n${cue.identifier ?? ''}\n${cue.notes ?? ''}`; - - let subtitleChunk = this.createInternalChunk(body, timestamp, cue.duration, 'key', additions.trim() ? textEncoder.encode(additions) : null); - + + const subtitleChunk = this.createInternalChunk( + body, + timestamp, + cue.duration, + 'key', + additions.trim() ? textEncoder.encode(additions) : null, + ); + trackData.chunkQueue.push(subtitleChunk); await this.interleaveChunks(); } finally { @@ -634,7 +722,7 @@ export class MatroskaMuxer extends Muxer { let trackWithMinTimestamp: MatroskaTrackData | null = null; let minTimestamp = Infinity; - for (let trackData of this.trackDatas) { + for (const trackData of this.trackDatas) { if (trackData.chunkQueue.length === 0 && !trackData.track.source._closed) { break outer; } @@ -649,7 +737,7 @@ export class MatroskaMuxer extends Muxer { break; } - let chunk = trackWithMinTimestamp.chunkQueue.shift()!; + const chunk = trackWithMinTimestamp.chunkQueue.shift()!; this.writeBlock(trackWithMinTimestamp, chunk); } @@ -665,31 +753,36 @@ export class MatroskaMuxer extends Muxer { let i = 0; // Check if it's a "superframe" - if (readBits(chunk.data, 0, 2) !== 0b10) return; i += 2; + if (readBits(chunk.data, 0, 2) !== 0b10) return; + i += 2; - let profile = (readBits(chunk.data, i+1, i+2) << 1) + readBits(chunk.data, i+0, i+1); i += 2; + const profile = (readBits(chunk.data, i + 1, i + 2) << 1) + readBits(chunk.data, i + 0, i + 1); + i += 2; if (profile === 3) i++; - let showExistingFrame = readBits(chunk.data, i+0, i+1); i++; + const showExistingFrame = readBits(chunk.data, i + 0, i + 1); + i++; if (showExistingFrame) return; - let frameType = readBits(chunk.data, i+0, i+1); i++; + const frameType = readBits(chunk.data, i + 0, i + 1); + i++; if (frameType !== 0) return; // Just to be sure i += 2; - let syncCode = readBits(chunk.data, i+0, i+24); i += 24; + const syncCode = readBits(chunk.data, i + 0, i + 24); + i += 24; if (syncCode !== 0x498342) return; if (profile >= 2) i++; - let colorSpaceID = { - 'rgb': 7, - 'bt709': 2, - 'bt470bg': 1, - 'smpte170m': 3 + const colorSpaceID = { + rgb: 7, + bt709: 2, + bt470bg: 1, + smpte170m: 3, }[trackData.info.decoderConfig.colorSpace.matrix]; - writeBits(chunk.data, i+0, i+3, colorSpaceID); + writeBits(chunk.data, i + 0, i + 3, colorSpaceID); } /** Converts a read-only external chunk into an internal one for easier use. */ @@ -698,14 +791,14 @@ export class MatroskaMuxer extends Muxer { timestamp: number, duration: number, type: 'key' | 'delta', - additions: Uint8Array | null = null + additions: Uint8Array | null = null, ) { - let internalChunk: InternalMediaChunk = { + const internalChunk: InternalMediaChunk = { data, type, timestamp, duration, - additions + additions, }; return internalChunk; @@ -719,10 +812,10 @@ export class MatroskaMuxer extends Muxer { this.createSegment(); } - let msTimestamp = Math.floor(1000 * chunk.timestamp); + const msTimestamp = Math.floor(1000 * chunk.timestamp); // We can only finalize this fragment (and begin a new one) if we know that each track will be able to // start the new one with a key frame. - const keyFrameQueuedEverywhere = this.trackDatas.every(otherTrackData => { + const keyFrameQueuedEverywhere = this.trackDatas.every((otherTrackData) => { if (otherTrackData.track.source._closed) { return true; } @@ -736,58 +829,65 @@ export class MatroskaMuxer extends Muxer { }); if ( - !this.currentCluster || - (keyFrameQueuedEverywhere && msTimestamp - this.currentClusterMsTimestamp! >= 1000) + !this.currentCluster + || (keyFrameQueuedEverywhere && msTimestamp - this.currentClusterMsTimestamp! >= 1000) ) { this.createNewCluster(msTimestamp); } - let relativeTimestamp = msTimestamp - this.currentClusterMsTimestamp!; + const relativeTimestamp = msTimestamp - this.currentClusterMsTimestamp!; if (relativeTimestamp < 0) { // The chunk lies outside of the current cluster return; } - let clusterIsTooLong = relativeTimestamp >= MAX_CHUNK_LENGTH_MS; + const clusterIsTooLong = relativeTimestamp >= MAX_CHUNK_LENGTH_MS; if (clusterIsTooLong) { throw new Error( - `Current Matroska cluster exceeded its maximum allowed length of ${MAX_CHUNK_LENGTH_MS} ` + - `milliseconds. In order to produce a correct WebM file, you must pass in a key frame at least every ` + - `${MAX_CHUNK_LENGTH_MS} milliseconds.` + `Current Matroska cluster exceeded its maximum allowed length of ${MAX_CHUNK_LENGTH_MS} ` + + `milliseconds. In order to produce a correct WebM file, you must pass in a key frame at least every ` + + `${MAX_CHUNK_LENGTH_MS} milliseconds.`, ); } - let prelude = new Uint8Array(4); - let view = new DataView(prelude.buffer); + const prelude = new Uint8Array(4); + const view = new DataView(prelude.buffer); // 0x80 to indicate it's the last byte of a multi-byte number view.setUint8(0, 0x80 | trackData.track.id); view.setInt16(1, relativeTimestamp, false); - let msDuration = Math.floor(1000 * chunk.duration); + const msDuration = Math.floor(1000 * chunk.duration); if (msDuration === 0 && !chunk.additions) { // No duration or additions, we can write out a SimpleBlock view.setUint8(3, Number(chunk.type === 'key') << 7); // Flags (keyframe flag only present for SimpleBlock) - let simpleBlock = { id: EBMLId.SimpleBlock, data: [ + const simpleBlock = { id: EBMLId.SimpleBlock, data: [ prelude, - chunk.data + chunk.data, ] }; this.writeEBML(simpleBlock); } else { - let blockGroup = { id: EBMLId.BlockGroup, data: [ + const blockGroup = { id: EBMLId.BlockGroup, data: [ { id: EBMLId.Block, data: [ prelude, - chunk.data + chunk.data, ] }, - chunk.type === 'delta' ? { id: EBMLId.ReferenceBlock, data: new EBMLSignedInt(trackData.lastWrittenMsTimestamp! - msTimestamp) } : null, - chunk.additions ? { id: EBMLId.BlockAdditions, data: [ - { id: EBMLId.BlockMore, data: [ - { id: EBMLId.BlockAdditional, data: chunk.additions }, - { id: EBMLId.BlockAddID, data: 1 } - ] } - ] } : null, - msDuration > 0 ? { id: EBMLId.BlockDuration, data: msDuration } : null + chunk.type === 'delta' + ? { + id: EBMLId.ReferenceBlock, + data: new EBMLSignedInt(trackData.lastWrittenMsTimestamp! - msTimestamp), + } + : null, + chunk.additions + ? { id: EBMLId.BlockAdditions, data: [ + { id: EBMLId.BlockMore, data: [ + { id: EBMLId.BlockAdditional, data: chunk.additions }, + { id: EBMLId.BlockAddID, data: 1 }, + ] }, + ] } + : null, + msDuration > 0 ? { id: EBMLId.BlockDuration, data: msDuration } : null, ] }; this.writeEBML(blockGroup); } @@ -814,8 +914,8 @@ export class MatroskaMuxer extends Muxer { id: EBMLId.Cluster, size: this.format._options.streamable ? -1 : CLUSTER_SIZE_BYTES, data: [ - { id: EBMLId.Timestamp, data: msTimestamp } - ] + { id: EBMLId.Timestamp, data: msTimestamp }, + ], }; this.writeEBML(this.currentCluster); @@ -825,8 +925,8 @@ export class MatroskaMuxer extends Muxer { private finalizeCurrentCluster() { assert(this.currentCluster); - let clusterSize = this.writer.getPos() - this.dataOffsets.get(this.currentCluster)!; - let endPos = this.writer.getPos(); + const clusterSize = this.writer.getPos() - this.dataOffsets.get(this.currentCluster)!; + const endPos = this.writer.getPos(); // Write the size now that we know it this.writer.seek(this.offsets.get(this.currentCluster)! + 4); @@ -840,8 +940,8 @@ export class MatroskaMuxer extends Muxer { } */ - let clusterOffsetFromSegment = - this.offsets.get(this.currentCluster)! - this.segmentDataOffset; + const clusterOffsetFromSegment + = this.offsets.get(this.currentCluster)! - this.segmentDataOffset; assert(this.cues); @@ -849,15 +949,16 @@ export class MatroskaMuxer extends Muxer { (this.cues.data as EBML[]).push({ id: EBMLId.CuePoint, data: [ { id: EBMLId.CueTime, data: this.currentClusterMsTimestamp! }, // We only write out cues for tracks that have at least one chunk in this cluster - ...[...this.trackDatasInCurrentCluster].map(trackData => { + ...[...this.trackDatasInCurrentCluster].map((trackData) => { return { id: EBMLId.CueTrackPositions, data: [ { id: EBMLId.CueTrack, data: trackData.track.id }, - { id: EBMLId.CueClusterPosition, data: clusterOffsetFromSegment } + { id: EBMLId.CueClusterPosition, data: clusterOffsetFromSegment }, ] }; - }) + }), ] }); } + // eslint-disable-next-line @typescript-eslint/no-misused-promises override async onTrackClose() { const release = await this.mutex.acquire(); @@ -877,7 +978,7 @@ export class MatroskaMuxer extends Muxer { } // Flush any remaining queued chunks to the file - for (let trackData of this.trackDatas) { + for (const trackData of this.trackDatas) { while (trackData.chunkQueue.length > 0) { this.writeBlock(trackData, trackData.chunkQueue.shift()!); } @@ -891,32 +992,32 @@ export class MatroskaMuxer extends Muxer { this.writeEBML(this.cues); if (!this.format._options.streamable) { - let endPos = this.writer.getPos(); + const endPos = this.writer.getPos(); // Write the Segment size - let segmentSize = this.writer.getPos() - this.segmentDataOffset; + const segmentSize = this.writer.getPos() - this.segmentDataOffset; this.writer.seek(this.offsets.get(this.segment!)! + 4); this.writeEBMLVarInt(segmentSize, SEGMENT_SIZE_BYTES); // Write the duration of the media to the Segment this.segmentDuration!.data = new EBMLFloat64(this.duration); this.writer.seek(this.offsets.get(this.segmentDuration!)!); - this.writeEBML(this.segmentDuration!); + this.writeEBML(this.segmentDuration); // Fill in SeekHead position data and write it again - 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.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.writeEBML(this.seekHead); this.writer.seek(endPos); } release(); } -} \ No newline at end of file +} diff --git a/src/misc.ts b/src/misc.ts index a1c8ecd..49abcb5 100644 --- a/src/misc.ts +++ b/src/misc.ts @@ -12,17 +12,17 @@ export const last = (arr: T[]) => { }; export const isU32 = (value: number) => { - return value >= 0 && value < 2**32; + return value >= 0 && value < 2 ** 32; }; export const readBits = (bytes: Uint8Array, start: number, end: number) => { let result = 0; for (let i = start; i < end; i++) { - let byteIndex = Math.floor(i / 8); - let byte = bytes[byteIndex]!; - let bitIndex = 0b111 - (i & 0b111); - let bit = (byte & (1 << bitIndex)) >> bitIndex; + const byteIndex = Math.floor(i / 8); + const byte = bytes[byteIndex]!; + const bitIndex = 0b111 - (i & 0b111); + const bit = (byte & (1 << bitIndex)) >> bitIndex; result <<= 1; result |= bit; @@ -33,9 +33,9 @@ export const readBits = (bytes: Uint8Array, start: number, end: number) => { export const writeBits = (bytes: Uint8Array, start: number, end: number, value: number) => { for (let i = start; i < end; i++) { - let byteIndex = Math.floor(i / 8); + const byteIndex = Math.floor(i / 8); let byte = bytes[byteIndex]!; - let bitIndex = 0b111 - (i & 0b111); + const bitIndex = 0b111 - (i & 0b111); byte &= ~(1 << bitIndex); byte |= ((value & (1 << (end - i - 1))) >> (end - i - 1)) << bitIndex; @@ -56,29 +56,45 @@ export const textEncoder = new TextEncoder(); // 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 = { - 'bt709': 1, // ITU-R BT.709 - 'bt470bg': 5, // ITU-R BT.470BG - 'smpte170m': 6 // ITU-R BT.601 525 - SMPTE 170M + bt709: 1, // ITU-R BT.709 + bt470bg: 5, // ITU-R BT.470BG + smpte170m: 6, // ITU-R BT.601 525 - SMPTE 170M }; export const TRANSFER_CHARACTERISTICS_MAP: Record = { - 'bt709': 1, // ITU-R BT.709 - 'smpte170m': 6, // SMPTE 170M - 'iec61966-2-1': 13 // IEC 61966-2-1 + 'bt709': 1, // ITU-R BT.709 + 'smpte170m': 6, // SMPTE 170M + 'iec61966-2-1': 13, // IEC 61966-2-1 }; 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 + rgb: 0, // Identity + bt709: 1, // ITU-R BT.709 + bt470bg: 5, // ITU-R BT.470BG + smpte170m: 6, // SMPTE 170M }; -export const colorSpaceIsComplete = (colorSpace: VideoColorSpaceInit | undefined) => { - return !!colorSpace && !!colorSpace.primaries && !!colorSpace.transfer && !!colorSpace.matrix && colorSpace.fullRange !== undefined; +export type RequiredNonNull = { + [K in keyof T]-?: NonNullable; +}; + +export const colorSpaceIsComplete = ( + colorSpace: VideoColorSpaceInit | undefined, +): colorSpace is RequiredNonNull => { + return ( + !!colorSpace + && !!colorSpace.primaries + && !!colorSpace.transfer + && !!colorSpace.matrix + && colorSpace.fullRange !== undefined + ); }; export const isAllowSharedBufferSource = (x: unknown) => { // Quite a mouthful: - return x instanceof ArrayBuffer || (typeof SharedArrayBuffer !== 'undefined' && x instanceof SharedArrayBuffer) || (ArrayBuffer.isView(x) && !(x instanceof DataView)); + return ( + x instanceof ArrayBuffer + || (typeof SharedArrayBuffer !== 'undefined' && x instanceof SharedArrayBuffer) + || (ArrayBuffer.isView(x) && !(x instanceof DataView)) + ); }; export class AsyncMutex { @@ -86,15 +102,15 @@ export class AsyncMutex { async acquire() { let resolver: () => void; - let nextPromise = new Promise(resolve => { + const nextPromise = new Promise((resolve) => { resolver = resolve; }); - let currentPromiseAlias = this.currentPromise; + const currentPromiseAlias = this.currentPromise; this.currentPromise = nextPromise; await currentPromiseAlias; return resolver!; } -} \ No newline at end of file +} diff --git a/src/muxer.ts b/src/muxer.ts index 70fc37c..393b86f 100644 --- a/src/muxer.ts +++ b/src/muxer.ts @@ -1,6 +1,6 @@ -import { AsyncMutex } from "./misc"; -import { Output, OutputAudioTrack, OutputSubtitleTrack, OutputTrack, OutputVideoTrack } from "./output"; -import { SubtitleCue, SubtitleMetadata } from "./subtitles"; +import { AsyncMutex } from './misc'; +import { Output, OutputAudioTrack, OutputSubtitleTrack, OutputTrack, OutputVideoTrack } from './output'; +import { SubtitleCue, SubtitleMetadata } from './subtitles'; export abstract class Muxer { output: Output; @@ -11,18 +11,28 @@ export abstract class Muxer { } abstract start(): Promise; - abstract addEncodedVideoChunk(track: OutputVideoTrack, chunk: EncodedVideoChunk, meta?: EncodedVideoChunkMetadata): Promise; - abstract addEncodedAudioChunk(track: OutputAudioTrack, chunk: EncodedAudioChunk, meta?: EncodedAudioChunkMetadata): Promise; + abstract addEncodedVideoChunk( + track: OutputVideoTrack, + chunk: EncodedVideoChunk, + meta?: EncodedVideoChunkMetadata + ): Promise; + abstract addEncodedAudioChunk( + track: OutputAudioTrack, + chunk: EncodedAudioChunk, + meta?: EncodedAudioChunkMetadata + ): Promise; abstract addSubtitleCue(track: OutputSubtitleTrack, cue: SubtitleCue, meta?: SubtitleMetadata): Promise; abstract finalize(): Promise; + // eslint-disable-next-line @typescript-eslint/no-unused-vars beforeTrackAdd(track: OutputTrack) {} + // eslint-disable-next-line @typescript-eslint/no-unused-vars onTrackClose(track: OutputTrack) {} private trackTimestampInfo = new WeakMap(); abstract timestampsMustStartAtZero: boolean; @@ -42,7 +52,7 @@ export abstract class Muxer { timestampInfo = { timestampOffset: timestampInSeconds, maxTimestamp: track.source._offsetTimestamps ? 0 : timestampInSeconds, - lastKeyFrameTimestamp: track.source._offsetTimestamps ? 0 : timestampInSeconds + lastKeyFrameTimestamp: track.source._offsetTimestamps ? 0 : timestampInSeconds, }; this.trackTimestampInfo.set(track, timestampInfo); } @@ -56,19 +66,25 @@ export abstract class Muxer { } if (timestampInSeconds < timestampInfo.lastKeyFrameTimestamp) { - throw new Error(`Timestamp cannot be smaller than last key frame's timestamp (got ${timestampInSeconds}s, last key frame at ${timestampInfo.lastKeyFrameTimestamp}s).`); + throw new Error( + `Timestamp cannot be smaller than last key frame's timestamp (got ${timestampInSeconds}s,` + + ` last key frame at ${timestampInfo.lastKeyFrameTimestamp}s).`, + ); } if (isKeyFrame) { if (timestampInSeconds < timestampInfo.maxTimestamp) { - throw new Error(`Key frame timestamps cannot be smaller than any timestamp that came before (got ${timestampInSeconds}s, max timestamp was ${timestampInfo.maxTimestamp}s).`); + throw new Error( + `Key frame timestamps cannot be smaller than any timestamp that came before` + + ` (got ${timestampInSeconds}s, max timestamp was ${timestampInfo.maxTimestamp}s).`, + ); } timestampInfo.lastKeyFrameTimestamp = timestampInSeconds; } - + timestampInfo.maxTimestamp = Math.max(timestampInfo.maxTimestamp, timestampInSeconds); return timestampInSeconds; } -} \ No newline at end of file +} diff --git a/src/output-format.ts b/src/output-format.ts index 957f1b7..efe9da6 100644 --- a/src/output-format.ts +++ b/src/output-format.ts @@ -1,7 +1,7 @@ -import { IsobmffMuxer } from "./isobmff/isobmff-muxer"; -import { MatroskaMuxer } from "./matroska/matroska-muxer"; -import { Muxer } from "./muxer"; -import { Output } from "./output"; +import { IsobmffMuxer } from './isobmff/isobmff-muxer'; +import { MatroskaMuxer } from './matroska/matroska-muxer'; +import { Muxer } from './muxer'; +import { Output } from './output'; /** @public */ export abstract class OutputFormat { @@ -11,7 +11,7 @@ export abstract class OutputFormat { /** @public */ export type Mp4OutputFormatOptions = { - fastStart?: false | 'in-memory' | 'fragmented' + fastStart?: false | 'in-memory' | 'fragmented'; }; /** @public */ @@ -40,7 +40,7 @@ export class Mp4OutputFormat extends OutputFormat { /** @public */ export type MkvOutputFormatOptions = { - streamable?: boolean + streamable?: boolean; }; /** @public */ @@ -71,4 +71,4 @@ export class MkvOutputFormat extends OutputFormat { export type WebMOutputFormatOptions = MkvOutputFormatOptions; /** @public */ -export class WebMOutputFormat extends MkvOutputFormat {} \ No newline at end of file +export class WebMOutputFormat extends MkvOutputFormat {} diff --git a/src/output.ts b/src/output.ts index 106d6ae..be22d59 100644 --- a/src/output.ts +++ b/src/output.ts @@ -1,31 +1,31 @@ -import { AsyncMutex, TransformationMatrix } from "./misc"; -import { Muxer } from "./muxer"; -import { OutputFormat } from "./output-format"; -import { AudioSource, MediaSource, SubtitleSource, VideoSource } from "./source"; -import { Target } from "./target"; -import { Writer } from "./writer"; +import { AsyncMutex, TransformationMatrix } from './misc'; +import { Muxer } from './muxer'; +import { OutputFormat } from './output-format'; +import { AudioSource, MediaSource, SubtitleSource, VideoSource } from './source'; +import { Target } from './target'; +import { Writer } from './writer'; /** @public */ export type OutputOptions = { - format: OutputFormat, - target: Target + format: OutputFormat; + target: Target; }; export type OutputTrack = { - id: number, - output: Output + id: number; + output: Output; } & ({ - type: 'video', - source: VideoSource, - metadata: VideoTrackMetadata + type: 'video'; + source: VideoSource; + metadata: VideoTrackMetadata; } | { - type: 'audio', - source: AudioSource, - metadata: AudioTrackMetadata + type: 'audio'; + source: AudioSource; + metadata: AudioTrackMetadata; } | { - type: 'subtitle', - source: SubtitleSource, - metadata: SubtitleTrackMetadata + type: 'subtitle'; + source: SubtitleSource; + metadata: SubtitleTrackMetadata; }); export type OutputVideoTrack = OutputTrack & { type: 'video' }; @@ -34,8 +34,8 @@ export type OutputSubtitleTrack = OutputTrack & { type: 'subtitle' }; /** @public */ export type VideoTrackMetadata = { - rotation?: 0 | 90 | 180 | 270 | TransformationMatrix, // TODO respect this field for Matroska - frameRate?: number + rotation?: 0 | 90 | 180 | 270 | TransformationMatrix; // TODO respect this field for Matroska + frameRate?: number; }; /** @public */ export type AudioTrackMetadata = {}; @@ -87,17 +87,17 @@ export class Output { if (typeof metadata.rotation === 'number' && ![0, 90, 180, 270].includes(metadata.rotation)) { throw new TypeError(`Invalid video rotation: ${metadata.rotation}. Has to be 0, 90, 180 or 270.`); } else if ( - Array.isArray(metadata.rotation) && - (metadata.rotation.length !== 9 || metadata.rotation.some(value => !Number.isFinite(value))) + Array.isArray(metadata.rotation) + && (metadata.rotation.length !== 9 || metadata.rotation.some(value => !Number.isFinite(value))) ) { throw new TypeError(`Invalid video transformation matrix: ${metadata.rotation.join()}`); } if ( - metadata.frameRate !== undefined && - (!Number.isInteger(metadata.frameRate) || metadata.frameRate <= 0) + metadata.frameRate !== undefined + && (!Number.isInteger(metadata.frameRate) || metadata.frameRate <= 0) ) { throw new TypeError( - `Invalid video frame rate: ${metadata.frameRate}. Must be a positive integer.` + `Invalid video frame rate: ${metadata.frameRate}. Must be a positive integer.`, ); } @@ -139,8 +139,8 @@ export class Output { id: this._tracks.length + 1, output: this, type, - source: source as any, - metadata + source: source as unknown, + metadata, } as OutputTrack; this._muxer.beforeTrackAdd(track); @@ -189,4 +189,4 @@ export class Output { release(); } -} \ No newline at end of file +} diff --git a/src/source.ts b/src/source.ts index bb64481..8a76705 100644 --- a/src/source.ts +++ b/src/source.ts @@ -1,8 +1,13 @@ -import { buildAudioCodecString, buildVideoCodecString, getAudioEncoderConfigExtension, getVideoEncoderConfigExtension } from "./codec"; -import { assert } from "./misc"; -import { Muxer } from "./muxer"; -import { OutputAudioTrack, OutputSubtitleTrack, OutputTrack, OutputVideoTrack } from "./output"; -import { SubtitleParser } from "./subtitles"; +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; @@ -113,9 +118,9 @@ const KEY_FRAME_INTERVAL = 5; /** @public */ export type VideoCodecConfig = { - codec: VideoCodec, - bitrate: number, - latencyMode?: VideoEncoderConfig['latencyMode'] + codec: VideoCodec; + bitrate: number; + latencyMode?: VideoEncoderConfig['latencyMode']; }; const validateVideoCodecConfig = (config: VideoCodecConfig) => { @@ -128,8 +133,8 @@ const validateVideoCodecConfig = (config: VideoCodecConfig) => { 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'."); + if (config.latencyMode !== undefined && !['quality', 'realtime'].includes(config.latencyMode)) { + throw new TypeError('config.latencyMode, when provided, must be \'quality\' or \'realtime\'.'); } }; @@ -150,7 +155,10 @@ class VideoEncoderWrapper { // 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}.`); + 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; @@ -165,7 +173,9 @@ class VideoEncoderWrapper { // 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.encoder.encode(videoFrame, { + keyFrame: multipleOfKeyFrameInterval !== this.lastMultipleOfKeyFrameInterval, + }); this.lastMultipleOfKeyFrameInterval = multipleOfKeyFrameInterval; @@ -183,24 +193,29 @@ class VideoEncoderWrapper { } this.encoder = new VideoEncoder({ - output: (chunk, meta) => this.muxer!.addEncodedVideoChunk(this.source._connectedTrack!, chunk, meta), - error: (error) => console.error('Video encode error:', error), + 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), + 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) + ...getVideoEncoderConfigExtension(this.codecConfig.codec), }); assert(this.source._connectedTrack); this.muxer = this.source._connectedTrack.output._muxer; } - + async flush() { if (this.encoder) { await this.encoder.flush(); @@ -238,9 +253,9 @@ export class CanvasSource extends VideoSource { /** @internal */ private _encoder: VideoEncoderWrapper; /** @internal */ - private _canvas: HTMLCanvasElement; + private _canvas: HTMLCanvasElement | OffscreenCanvas; - constructor(canvas: HTMLCanvasElement, codecConfig: VideoCodecConfig) { + constructor(canvas: HTMLCanvasElement | OffscreenCanvas, codecConfig: VideoCodecConfig) { if (!(canvas instanceof HTMLCanvasElement)) { throw new TypeError('canvas must be an HTMLCanvasElement.'); } @@ -295,7 +310,7 @@ export class MediaStreamVideoTrackSource extends VideoSource { codecConfig = { ...codecConfig, - latencyMode: 'realtime' + latencyMode: 'realtime', }; super(codecConfig.codec); @@ -306,19 +321,19 @@ export class MediaStreamVideoTrackSource extends VideoSource { /** @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 - this._encoder.digest(videoFrame); + void this._encoder.digest(videoFrame); videoFrame.close(); - } + }, }); processor.readable.pipeTo(consumer, { - signal: this._abortController.signal - }).catch(err => { + signal: this._abortController.signal, + }).catch((err) => { // Handle abort error silently if (err instanceof DOMException && err.name === 'AbortError') return; // Handle other errors @@ -373,8 +388,8 @@ export class EncodedAudioChunkSource extends AudioSource { } /** @public */ export type AudioCodecConfig = { - codec: AudioCodec, - bitrate: number + codec: AudioCodec; + bitrate: number; }; const validateAudioCodecConfig = (config: AudioCodecConfig) => { @@ -404,8 +419,15 @@ class AudioEncoderWrapper { // 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.`); + 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; @@ -430,8 +452,8 @@ class AudioEncoderWrapper { } this.encoder = new AudioEncoder({ - output: (chunk, meta) => this.muxer!.addEncodedAudioChunk(this.source._connectedTrack!, chunk, meta), - error: (error) => console.error('Audio encode error:', error), + output: (chunk, meta) => void this.muxer!.addEncodedAudioChunk(this.source._connectedTrack!, chunk, meta), + error: error => console.error('Audio encode error:', error), }); this.encoder.configure({ @@ -439,13 +461,13 @@ class AudioEncoderWrapper { numberOfChannels: audioData.numberOfChannels, sampleRate: audioData.sampleRate, bitrate: this.codecConfig.bitrate, - ...getAudioEncoderConfigExtension(this.codecConfig.codec) + ...getAudioEncoderConfigExtension(this.codecConfig.codec), }); assert(this.source._connectedTrack); this.muxer = this.source._connectedTrack.output._muxer; } - + async flush() { if (this.encoder) { await this.encoder.flush(); @@ -498,7 +520,7 @@ export class AudioBufferSource extends AudioSource { 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++) { @@ -512,7 +534,7 @@ export class AudioBufferSource extends AudioSource { numberOfFrames, numberOfChannels, timestamp: Math.round(1e6 * this._accumulatedFrameCount / sampleRate), - data: data + data: data, }); const promise = this._encoder.digest(audioData); @@ -554,19 +576,19 @@ export class MediaStreamAudioTrackSource extends AudioSource { /** @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 - this._encoder.digest(audioData); + void this._encoder.digest(audioData); audioData.close(); - } + }, }); processor.readable.pipeTo(consumer, { - signal: this._abortController.signal - }).catch(err => { + signal: this._abortController.signal, + }).catch((err) => { // Handle abort error silently if (err instanceof DOMException && err.name === 'AbortError') return; // Handle other errors @@ -613,8 +635,9 @@ export class TextSubtitleSource extends SubtitleSource { 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) + output: (cue, metadata) => + this._connectedTrack?.output._muxer.addSubtitleCue(this._connectedTrack, cue, metadata), + error: error => console.error('Subtitle parse error:', error), }); } @@ -628,4 +651,4 @@ export class TextSubtitleSource extends SubtitleSource { return this._connectedTrack!.output._muxer.mutex.currentPromise; } -} \ No newline at end of file +} diff --git a/src/subtitles.ts b/src/subtitles.ts index c1ff2ae..1bee395 100644 --- a/src/subtitles.ts +++ b/src/subtitles.ts @@ -1,24 +1,24 @@ export type SubtitleCue = { - timestamp: number, // in seconds - duration: number, // in seconds - text: string, - identifier?: string, - settings?: string, - notes?: string + timestamp: number; // in seconds + duration: number; // in seconds + text: string; + identifier?: string; + settings?: string; + notes?: string; }; export type SubtitleConfig = { - description: string + description: string; }; export type SubtitleMetadata = { - config?: SubtitleConfig + config?: SubtitleConfig; }; type SubtitleParserOptions = { - codec: 'webvtt', - output: (cue: SubtitleCue, metadata: SubtitleMetadata) => unknown, - error: (error: Error) => unknown + codec: 'webvtt'; + output: (cue: SubtitleCue, metadata: SubtitleMetadata) => unknown; + error: (error: Error) => unknown; }; const cueBlockHeaderRegex = /(?:(.+?)\n)?((?:\d{2}:)?\d{2}:\d{2}.\d{3})\s+-->\s+((?:\d{2}:)?\d{2}:\d{2}.\d{3})/g; @@ -42,16 +42,16 @@ export class SubtitleParser { if (!this.preambleText) { if (!preambleStartRegex.test(text)) { - let error = new Error('WebVTT preamble incorrect.'); + const error = new Error('WebVTT preamble incorrect.'); this.options.error(error); throw error; } match = cueBlockHeaderRegex.exec(text); - let preamble = text.slice(0, match?.index ?? text.length).trimEnd(); + const preamble = text.slice(0, match?.index ?? text.length).trimEnd(); if (!preamble) { - let error = new Error('No WebVTT preamble provided.'); + const error = new Error('No WebVTT preamble provided.'); this.options.error(error); throw error; } @@ -64,37 +64,37 @@ export class SubtitleParser { } } - while (match = cueBlockHeaderRegex.exec(text)) { - let notes = text.slice(0, match.index); - let cueIdentifier = match[1]; - let matchEnd = match.index! + match[0].length; - let bodyStart = text.indexOf('\n', matchEnd) + 1; - let cueSettings = text.slice(matchEnd, bodyStart).trim(); + while ((match = cueBlockHeaderRegex.exec(text))) { + const notes = text.slice(0, match.index); + const cueIdentifier = match[1]; + const matchEnd = match.index! + match[0].length; + const bodyStart = text.indexOf('\n', matchEnd) + 1; + const cueSettings = text.slice(matchEnd, bodyStart).trim(); let bodyEnd = text.indexOf('\n\n', matchEnd); if (bodyEnd === -1) bodyEnd = text.length; - let startTime = parseSubtitleTimestamp(match[2]!); - let endTime = parseSubtitleTimestamp(match[3]!); - let duration = endTime - startTime; + const startTime = parseSubtitleTimestamp(match[2]!); + const endTime = parseSubtitleTimestamp(match[3]!); + const duration = endTime - startTime; - let body = text.slice(bodyStart, bodyEnd).trim(); + const body = text.slice(bodyStart, bodyEnd).trim(); text = text.slice(bodyEnd).trimStart(); cueBlockHeaderRegex.lastIndex = 0; - let cue: SubtitleCue = { + const cue: SubtitleCue = { timestamp: startTime / 1000, duration: duration / 1000, text: body, identifier: cueIdentifier, settings: cueSettings, - notes + notes, }; - let meta: SubtitleMetadata = {}; + const meta: SubtitleMetadata = {}; if (!this.preambleEmitted) { meta.config = { - description: this.preambleText + description: this.preambleText, }; this.preambleEmitted = true; } @@ -106,23 +106,23 @@ export class SubtitleParser { const timestampRegex = /(?:(\d{2}):)?(\d{2}):(\d{2}).(\d{3})/; export const parseSubtitleTimestamp = (string: string) => { - let match = timestampRegex.exec(string); + const match = timestampRegex.exec(string); if (!match) throw new Error('Expected match.'); - return 60 * 60 * 1000 * Number(match[1] || '0') + - 60 * 1000 * Number(match[2]) + - 1000 * Number(match[3]) + - Number(match[4]); + return 60 * 60 * 1000 * Number(match[1] || '0') + + 60 * 1000 * Number(match[2]) + + 1000 * Number(match[3]) + + Number(match[4]); }; export const formatSubtitleTimestamp = (timestamp: number) => { - let hours = Math.floor(timestamp / (60 * 60 * 1000)); - let minutes = Math.floor((timestamp % (60 * 60 * 1000)) / (60 * 1000)); - let seconds = Math.floor((timestamp % (60 * 1000)) / 1000); - let milliseconds = timestamp % 1000; + const hours = Math.floor(timestamp / (60 * 60 * 1000)); + const minutes = Math.floor((timestamp % (60 * 60 * 1000)) / (60 * 1000)); + const seconds = Math.floor((timestamp % (60 * 1000)) / 1000); + const milliseconds = timestamp % 1000; - return hours.toString().padStart(2, '0') + ':' + - minutes.toString().padStart(2, '0') + ':' + - seconds.toString().padStart(2, '0') + '.' + - milliseconds.toString().padStart(3, '0'); -}; \ No newline at end of file + return hours.toString().padStart(2, '0') + ':' + + minutes.toString().padStart(2, '0') + ':' + + seconds.toString().padStart(2, '0') + '.' + + milliseconds.toString().padStart(3, '0'); +}; diff --git a/src/target.ts b/src/target.ts index 136aa49..2d69844 100644 --- a/src/target.ts +++ b/src/target.ts @@ -1,5 +1,5 @@ -import { Output } from "./output"; -import { ArrayBufferTargetWriter, ChunkedStreamTargetWriter, StreamTargetWriter, Writer } from "./writer"; +import { ArrayBufferTargetWriter, ChunkedStreamTargetWriter, StreamTargetWriter, Writer } from './writer'; +import { Output } from './output'; /** @public */ export abstract class Target { @@ -23,15 +23,15 @@ export class ArrayBufferTarget extends Target { /** @public */ export type StreamTargetChunk = { - type: 'write', // This ensures automatic compatibility with FileSystemWritableFileStream - data: Uint8Array, - position: number + type: 'write'; // This ensures automatic compatibility with FileSystemWritableFileStream + data: Uint8Array; + position: number; }; /** @public */ export type StreamTargetOptions = { - chunked?: boolean, - chunkSize?: number + chunked?: boolean; + chunkSize?: number; }; /** @public */ @@ -43,7 +43,7 @@ export class StreamTarget extends Target { constructor( writable: WritableStream, - options: StreamTargetOptions = {} + options: StreamTargetOptions = {}, ) { super(); @@ -68,4 +68,4 @@ export class StreamTarget extends Target { _createWriter() { return this._options.chunked ? new ChunkedStreamTargetWriter(this) : new StreamTargetWriter(this); } -} \ No newline at end of file +} diff --git a/src/writer.ts b/src/writer.ts index d30338f..def1f6c 100644 --- a/src/writer.ts +++ b/src/writer.ts @@ -1,5 +1,5 @@ -import { assert } from './misc'; import { ArrayBufferTarget, StreamTarget, StreamTargetChunk } from './target'; +import { assert } from './misc'; export abstract class Writer { /** Setting this to true will cause the writer to ensure data is written in a strictly monotonic, streamable way. */ @@ -13,7 +13,7 @@ export abstract class Writer { abstract seek(newPos: number): void; /** Returns the current position. */ abstract getPos(): number; - /** Signals to the writer that it may be time to flush. */ + /** Signals to the writer that it may be time to flush. */ abstract flush(): Promise; /** Called after muxing has finished. */ abstract finalize(): Promise; @@ -26,7 +26,7 @@ export abstract class Writer { export class ArrayBufferTargetWriter extends Writer { private pos = 0; private target: ArrayBufferTarget; - private buffer = new ArrayBuffer(2**16); + private buffer = new ArrayBuffer(2 ** 16); private bytes = new Uint8Array(this.buffer); private maxPos = 0; @@ -42,8 +42,8 @@ export class ArrayBufferTargetWriter extends Writer { if (newLength === this.buffer.byteLength) return; - let newBuffer = new ArrayBuffer(newLength); - let newBytes = new Uint8Array(newBuffer); + const newBuffer = new ArrayBuffer(newLength); + const newBytes = new Uint8Array(newBuffer); newBytes.set(this.bytes, 0); this.buffer = newBuffer; @@ -69,6 +69,7 @@ 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)); @@ -87,9 +88,10 @@ export class StreamTargetWriter extends Writer { private pos = 0; private target: StreamTarget; private sections: { - data: Uint8Array, - start: number + data: Uint8Array; + start: number; }[] = []; + private lastFlushEnd = 0; private writer: WritableStreamDefaultWriter | null = null; @@ -106,7 +108,7 @@ export class StreamTargetWriter extends Writer { write(data: Uint8Array) { this.sections.push({ data: data.slice(), - start: this.pos + start: this.pos, }); this.pos += data.byteLength; } @@ -123,38 +125,38 @@ export class StreamTargetWriter extends Writer { assert(this.writer); if (this.sections.length === 0) return; - let chunks: { - start: number, - size: number, - data?: Uint8Array + const chunks: { + start: number; + size: number; + data?: Uint8Array; }[] = []; - let sorted = [...this.sections].sort((a, b) => a.start - b.start); + const sorted = [...this.sections].sort((a, b) => a.start - b.start); chunks.push({ start: sorted[0]!.start, - size: sorted[0]!.data.byteLength + size: sorted[0]!.data.byteLength, }); // Figure out how many contiguous chunks we have for (let i = 1; i < sorted.length; i++) { - let lastChunk = chunks[chunks.length - 1]!; - let section = sorted[i]!; + const lastChunk = chunks[chunks.length - 1]!; + const section = sorted[i]!; if (section.start <= lastChunk.start + lastChunk.size) { lastChunk.size = Math.max(lastChunk.size, section.start + section.data.byteLength - lastChunk.start); } else { chunks.push({ start: section.start, - size: section.data.byteLength + size: section.data.byteLength, }); } } - for (let chunk of chunks) { + for (const chunk of chunks) { chunk.data = new Uint8Array(chunk.size); // Make sure to write the data in the correct order for correct overwriting - for (let section of this.sections) { + for (const section of this.sections) { // Check if the section is in the chunk if (chunk.start <= section.start && section.start < chunk.start + chunk.size) { chunk.data.set(section.data, section.start - chunk.start); @@ -169,11 +171,11 @@ export class StreamTargetWriter extends Writer { await this.writer.ready; // Allow the writer to apply backpressure } - this.writer.write({ + void this.writer.write({ type: 'write', data: chunk.data, - position: chunk.start - }); + position: chunk.start, + }); this.lastFlushEnd = chunk.start + chunk.data.byteLength; } @@ -186,19 +188,19 @@ export class StreamTargetWriter extends Writer { } } -const DEFAULT_CHUNK_SIZE = 2**24; +const DEFAULT_CHUNK_SIZE = 2 ** 24; const MAX_CHUNKS_AT_ONCE = 2; interface Chunk { - start: number, - written: ChunkSection[], - data: Uint8Array, - shouldFlush: boolean + start: number; + written: ChunkSection[]; + data: Uint8Array; + shouldFlush: boolean; } interface ChunkSection { - start: number, - end: number + start: number; + end: number; } /** @@ -224,7 +226,7 @@ export class ChunkedStreamTargetWriter extends Writer { this.target = target; this.chunkSize = target._options?.chunkSize ?? DEFAULT_CHUNK_SIZE; - if (!Number.isInteger(this.chunkSize) || this.chunkSize < 2**10) { + if (!Number.isInteger(this.chunkSize) || this.chunkSize < 2 ** 10) { throw new Error('Invalid StreamTarget options: chunkSize must be an integer not smaller than 1024.'); } } @@ -252,17 +254,17 @@ export class ChunkedStreamTargetWriter extends Writer { // First, find the chunk to write the data into, or create one if none exists let chunkIndex = this.chunks.findIndex(x => x.start <= position && position < x.start + this.chunkSize); if (chunkIndex === -1) chunkIndex = this.createChunk(position); - let chunk = this.chunks[chunkIndex]!; + const chunk = this.chunks[chunkIndex]!; // Figure out how much to write to the chunk, and then write to the chunk - let relativePosition = position - chunk.start; - let toWrite = data.subarray(0, Math.min(this.chunkSize - relativePosition, data.byteLength)); + const relativePosition = position - chunk.start; + const toWrite = data.subarray(0, Math.min(this.chunkSize - relativePosition, data.byteLength)); chunk.data.set(toWrite, relativePosition); // Create a section describing the region of data that was just written to - let section: ChunkSection = { + const section: ChunkSection = { start: relativePosition, - end: relativePosition + toWrite.byteLength + end: relativePosition + toWrite.byteLength, }; this.insertSectionIntoChunk(chunk, section); @@ -274,7 +276,7 @@ export class ChunkedStreamTargetWriter extends Writer { // Make sure we don't hold too many chunks in memory at once to keep memory usage down if (this.chunks.length > MAX_CHUNKS_AT_ONCE) { // Flush all but the last chunk - for (let i = 0; i < this.chunks.length-1; i++) { + for (let i = 0; i < this.chunks.length - 1; i++) { this.chunks[i]!.shouldFlush = true; } this.queueChunksForFlush(); @@ -293,7 +295,7 @@ export class ChunkedStreamTargetWriter extends Writer { // Do a binary search to find the last section with a start not larger than `section`'s start while (low <= high) { - let mid = Math.floor(low + (high - low + 1) / 2); + const mid = Math.floor(low + (high - low + 1) / 2); if (chunk.written[mid]!.start <= section.start) { low = mid + 1; @@ -315,12 +317,12 @@ export class ChunkedStreamTargetWriter extends Writer { } private createChunk(includesPosition: number) { - let start = Math.floor(includesPosition / this.chunkSize) * this.chunkSize; - let chunk: Chunk = { + const start = Math.floor(includesPosition / this.chunkSize) * this.chunkSize; + const chunk: Chunk = { start, data: new Uint8Array(this.chunkSize), written: [], - shouldFlush: false + shouldFlush: false, }; this.chunks.push(chunk); this.chunks.sort((a, b) => a.start - b.start); @@ -332,10 +334,10 @@ export class ChunkedStreamTargetWriter extends Writer { assert(this.writer); for (let i = 0; i < this.chunks.length; i++) { - let chunk = this.chunks[i]!; + const chunk = this.chunks[i]!; if (!chunk.shouldFlush && !force) continue; - for (let section of chunk.written) { + for (const section of chunk.written) { if (this.ensureMonotonicity && chunk.start + section.start !== this.lastFlushEnd) { throw new Error('Internal error: Monotonicity violation.'); } @@ -343,7 +345,7 @@ export class ChunkedStreamTargetWriter extends Writer { this.flushedChunkQueue.push({ type: 'write', data: chunk.data.subarray(section.start, section.end), - position: chunk.start + section.start + position: chunk.start + section.start, }); this.lastFlushEnd = chunk.start + section.end; } @@ -355,12 +357,12 @@ export class ChunkedStreamTargetWriter extends Writer { assert(this.writer); if (this.flushedChunkQueue.length === 0) return; - for (let chunk of this.flushedChunkQueue) { + for (const chunk of this.flushedChunkQueue) { if (this.writer.desiredSize !== null && this.writer.desiredSize <= 0) { await this.writer.ready; // Allow the writer to apply backpressure } - this.writer.write(chunk); + void this.writer.write(chunk); } this.flushedChunkQueue.length = 0; @@ -374,4 +376,4 @@ export class ChunkedStreamTargetWriter extends Writer { return this.writer.close(); } -} \ No newline at end of file +} diff --git a/tsconfig.json b/tsconfig.json index 4d884cf..40dbf0f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,7 +10,8 @@ "outDir": "build", "declaration": true, "stripInternal": true, - "skipLibCheck": true + "skipLibCheck": true, + "allowJs": true }, "include": [ "src/**/*"