diff --git a/dist/metamuxer.d.ts b/dist/metamuxer.d.ts deleted file mode 100644 index 76c81da..0000000 --- a/dist/metamuxer.d.ts +++ /dev/null @@ -1,400 +0,0 @@ -/** @public */ -export declare const ALL_FORMATS: InputFormat[]; - -/** @public */ -export declare type AnyIterable = Iterable | AsyncIterable; - -/** @public */ -export declare class ArrayBufferTarget extends Target { - buffer: ArrayBuffer | null; -} - -/** @public */ -export declare const AUDIO_CODECS: readonly ["aac", "opus"]; - -/** @public */ -export declare class AudioBufferDrain { - constructor(audioTrack: InputAudioTrack); - getBuffer(timestamp: number): Promise; - buffers(startTimestamp?: number, endTimestamp?: number): AsyncGenerator; - buffersAtTimestamps(timestamps: AnyIterable): AsyncGenerator; -} - -/** @public */ -export declare class AudioBufferSource extends AudioSource { - constructor(codecConfig: AudioCodecConfig); - digest(audioBuffer: AudioBuffer): Promise; -} - -/** @public */ -export declare type AudioCodec = typeof AUDIO_CODECS[number]; - -/** @public */ -export declare type AudioCodecConfig = { - codec: AudioCodec; - bitrate: number; - onEncodingError?: (error: Error) => void; -}; - -/** @public */ -export declare class AudioDataDrain extends BaseMediaFrameDrain { - constructor(audioTrack: InputAudioTrack); - getData(timestamp: number): Promise; - data(startTimestamp?: number, endTimestamp?: number): AsyncGenerator; - dataAtTimestamps(timestamps: AnyIterable): AsyncGenerator; -} - -/** @public */ -export declare class AudioDataSource extends AudioSource { - constructor(codecConfig: AudioCodecConfig); - digest(audioData: AudioData): Promise; -} - -/** @public */ -export declare abstract class AudioSource extends MediaSource_2 { - constructor(codec: AudioCodec); -} - -/** @public */ -export declare type AudioTrackMetadata = {}; - -/** @public */ -export declare abstract class BaseChunkDrain { - abstract getFirstChunk(options?: ChunkRetrievalOptions): Promise; - abstract getChunk(timestamp: number, options?: ChunkRetrievalOptions): Promise; - abstract getNextChunk(chunk: Chunk, options?: ChunkRetrievalOptions): Promise; - abstract getKeyChunk(timestamp: number, options?: ChunkRetrievalOptions): Promise; - abstract getNextKeyChunk(chunk: Chunk, options?: ChunkRetrievalOptions): Promise; - chunks(startChunk?: Chunk, endTimestamp?: number): AsyncGenerator; -} - -/** @public */ -export declare abstract class BaseMediaFrameDrain { - protected mediaFramesAtTimestamps(timestamps: AnyIterable): AsyncGenerator; - protected mediaFramesInRange(startTimestamp?: number, endTimestamp?: number): AsyncGenerator; -} - -/** @public */ -export declare class BlobSource extends Source { - constructor(blob: Blob); -} - -/** @public */ -declare class BufferSource_2 extends Source { - constructor(buffer: ArrayBuffer | Uint8Array); -} -export { BufferSource_2 as BufferSource } - -/** @public */ -export declare class CanvasDrain { - constructor(videoTrack: InputVideoTrack, dimensions?: { - width: number; - height: number; - }); - getCanvas(timestamp: number): Promise; - canvases(startTimestamp?: number, endTimestamp?: number): AsyncGenerator; - canvasesAtTimestamps(timestamps: AnyIterable): AsyncGenerator; -} - -/** @public */ -export declare class CanvasSource extends VideoSource { - constructor(canvas: HTMLCanvasElement | OffscreenCanvas, codecConfig: VideoCodecConfig); - digest(timestamp: number, duration?: number): Promise; -} - -/** @public */ -export declare type ChunkRetrievalOptions = { - metadataOnly?: boolean; -}; - -/** @public */ -export declare class EncodedAudioChunkDrain extends BaseChunkDrain { - constructor(audioTrack: InputAudioTrack); - getFirstChunk(options?: ChunkRetrievalOptions): Promise; - getChunk(timestamp: number, options?: ChunkRetrievalOptions): Promise; - getNextChunk(chunk: EncodedAudioChunk, options?: ChunkRetrievalOptions): Promise; - getKeyChunk(timestamp: number, options?: ChunkRetrievalOptions): Promise; - getNextKeyChunk(chunk: EncodedAudioChunk, options?: ChunkRetrievalOptions): Promise; -} - -/** @public */ -export declare class EncodedAudioChunkSource extends AudioSource { - constructor(codec: AudioCodec); - digest(chunk: EncodedAudioChunk, meta?: EncodedAudioChunkMetadata): Promise; -} - -/** @public */ -export declare class EncodedVideoChunkDrain extends BaseChunkDrain { - constructor(videoTrack: InputVideoTrack); - getFirstChunk(options?: ChunkRetrievalOptions): Promise; - getChunk(timestamp: number, options?: ChunkRetrievalOptions): Promise; - getNextChunk(chunk: EncodedVideoChunk, options?: ChunkRetrievalOptions): Promise; - getKeyChunk(timestamp: number, options?: ChunkRetrievalOptions): Promise; - getNextKeyChunk(chunk: EncodedVideoChunk, options?: ChunkRetrievalOptions): Promise; -} - -/** @public */ -export declare class EncodedVideoChunkSource extends VideoSource { - constructor(codec: VideoCodec); - digest(chunk: EncodedVideoChunk, meta?: EncodedVideoChunkMetadata): Promise; -} - -/** @public */ -export declare class Input { - constructor(options: InputOptions); - getFormat(): Promise; - computeDuration(): Promise; - getTracks(): Promise; - getVideoTracks(): Promise; - getPrimaryVideoTrack(): Promise; - getAudioTracks(): Promise; - getPrimaryAudioTrack(): Promise; - getMimeType(): Promise; -} - -/** @public */ -export declare class InputAudioTrack extends InputTrack { - getCodec(): Promise; - getNumberOfChannels(): Promise; - getSampleRate(): Promise; - getDecoderConfig(): Promise; - getCodecMimeType(): Promise; -} - -/** @public */ -export declare abstract class InputFormat { -} - -/** @public */ -export declare type InputOptions = { - formats: InputFormat[]; - source: Source; -}; - -/** @public */ -export declare abstract class InputTrack { - abstract getCodec(): Promise; - abstract getCodecMimeType(): Promise; - isVideoTrack(): this is InputVideoTrack; - isAudioTrack(): this is InputAudioTrack; - computeDuration(): Promise; -} - -/** @public */ -export declare class InputVideoTrack extends InputTrack { - getCodec(): Promise; - getCodedWidth(): Promise; - getCodedHeight(): Promise; - getRotation(): Promise; - getDisplayWidth(): Promise; - getDisplayHeight(): Promise; - getDecoderConfig(): Promise; - getCodecMimeType(): Promise; -} - -/** @public */ -export declare const ISOBMFF: IsobmffInputFormat; - -/** @public */ -export declare class IsobmffInputFormat extends InputFormat { -} - -/** @public */ -export declare const MATROSKA: MatroskaInputFormat; - -/** @public */ -export declare class MatroskaInputFormat extends InputFormat { -} - -/** @public */ -export declare type MediaCodec = VideoCodec | AudioCodec | SubtitleCodec; - -/** @public */ -declare abstract class MediaSource_2 { - close(): void; -} -export { MediaSource_2 as MediaSource } - -/** @public */ -export declare class MediaStreamAudioTrackSource extends AudioSource { - constructor(track: MediaStreamAudioTrack, codecConfig: AudioCodecConfig); -} - -/** @public */ -export declare class MediaStreamVideoTrackSource extends VideoSource { - constructor(track: MediaStreamVideoTrack, codecConfig: VideoCodecConfig); -} - -/** @public */ -export declare const MKV: MatroskaInputFormat; - -/** @public */ -export declare class MkvOutputFormat extends OutputFormat { - constructor(options?: MkvOutputFormatOptions); -} - -/** @public */ -export declare type MkvOutputFormatOptions = { - streamable?: boolean; -}; - -/** @public */ -export declare const MOV: IsobmffInputFormat; - -/** @public */ -export declare const MP4: IsobmffInputFormat; - -/** @public */ -export declare class Mp4OutputFormat extends OutputFormat { - constructor(options?: Mp4OutputFormatOptions); -} - -/** @public */ -export declare type Mp4OutputFormatOptions = { - fastStart?: false | 'in-memory' | 'fragmented'; -}; - -/** @public */ -export declare class Output { - constructor(options: OutputOptions); - addVideoTrack(source: VideoSource, metadata?: VideoTrackMetadata): void; - addAudioTrack(source: AudioSource, metadata?: AudioTrackMetadata): void; - addSubtitleTrack(source: SubtitleSource, metadata?: SubtitleTrackMetadata): void; - start(): Promise; - finalize(): Promise; -} - -/** @public */ -export declare abstract class OutputFormat { -} - -/** @public */ -export declare type OutputOptions = { - format: OutputFormat; - target: Target; -}; - -/** @public */ -export declare type Rotation = 0 | 90 | 180 | 270; - -/** @public */ -export declare abstract class Source { -} - -/** @public */ -export declare class StreamTarget extends Target { - constructor(writable: WritableStream, options?: StreamTargetOptions); -} - -/** @public */ -export declare type StreamTargetChunk = { - type: 'write'; - data: Uint8Array; - position: number; -}; - -/** @public */ -export declare type StreamTargetOptions = { - chunked?: boolean; - chunkSize?: number; -}; - -/** @public */ -export declare const SUBTITLE_CODECS: readonly ["webvtt"]; - -/** @public */ -export declare type SubtitleCodec = typeof SUBTITLE_CODECS[number]; - -/** @public */ -export declare abstract class SubtitleSource extends MediaSource_2 { - constructor(codec: SubtitleCodec); -} - -/** @public */ -export declare type SubtitleTrackMetadata = {}; - -/** @public */ -export declare abstract class Target { -} - -/** @public */ -export declare class TextSubtitleSource extends SubtitleSource { - constructor(codec: SubtitleCodec); - digest(text: string): Promise; -} - -/** @public */ -export declare type TransformationMatrix = [number, number, number, number, number, number, number, number, number]; - -/** @public */ -export declare class UrlSource extends Source { - constructor(url: string, options?: { - withCredentials?: boolean; - }); -} - -/** @public */ -export declare const VIDEO_CODECS: readonly ["avc", "hevc", "vp8", "vp9", "av1"]; - -/** @public */ -export declare type VideoCodec = typeof VIDEO_CODECS[number]; - -/** @public */ -export declare type VideoCodecConfig = { - codec: VideoCodec; - bitrate: number; - latencyMode?: VideoEncoderConfig['latencyMode']; - onEncodingError?: (error: Error) => void; -}; - -/** @public */ -export declare class VideoFrameDrain extends BaseMediaFrameDrain { - constructor(videoTrack: InputVideoTrack); - getFrame(timestamp: number): Promise; - frames(startTimestamp?: number, endTimestamp?: number): AsyncGenerator; - framesAtTimestamps(timestamps: AnyIterable): AsyncGenerator; -} - -/** @public */ -export declare class VideoFrameSource extends VideoSource { - constructor(codecConfig: VideoCodecConfig); - digest(videoFrame: VideoFrame): Promise; -} - -/** @public */ -export declare abstract class VideoSource extends MediaSource_2 { - constructor(codec: VideoCodec); -} - -/** @public */ -export declare type VideoTrackMetadata = { - rotation?: 0 | 90 | 180 | 270 | TransformationMatrix; - frameRate?: number; -}; - -/** @public */ -export declare const WEBM: MatroskaInputFormat; - -/** @public */ -export declare class WebMOutputFormat extends MkvOutputFormat { -} - -/** @public */ -export declare type WebMOutputFormatOptions = MkvOutputFormatOptions; - -/** @public */ -export declare type WrappedAudioBuffer = { - buffer: AudioBuffer; - timestamp: number; -}; - -/** @public */ -export declare type WrappedCanvas = { - canvas: HTMLCanvasElement; - timestamp: number; - duration: number; -}; - -export { } - -export as namespace Metamuxer; \ No newline at end of file diff --git a/dist/metamuxer.js b/dist/metamuxer.js deleted file mode 100644 index 4c2074a..0000000 --- a/dist/metamuxer.js +++ /dev/null @@ -1,6636 +0,0 @@ -"use strict"; -var Metamuxer = (() => { - var __defProp = Object.defineProperty; - var __getOwnPropDesc = Object.getOwnPropertyDescriptor; - var __getOwnPropNames = Object.getOwnPropertyNames; - var __hasOwnProp = Object.prototype.hasOwnProperty; - var __export = (target, all) => { - for (var name in all) - __defProp(target, name, { get: all[name], enumerable: true }); - }; - var __copyProps = (to, from, except, desc) => { - if (from && typeof from === "object" || typeof from === "function") { - for (let key of __getOwnPropNames(from)) - if (!__hasOwnProp.call(to, key) && key !== except) - __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); - } - return to; - }; - var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); - - // src/index.ts - var src_exports = {}; - __export(src_exports, { - ALL_FORMATS: () => ALL_FORMATS, - AUDIO_CODECS: () => AUDIO_CODECS, - ArrayBufferTarget: () => ArrayBufferTarget, - AudioBufferDrain: () => AudioBufferDrain, - AudioBufferSource: () => AudioBufferSource, - AudioDataDrain: () => AudioDataDrain, - AudioDataSource: () => AudioDataSource, - AudioSource: () => AudioSource, - BaseChunkDrain: () => BaseChunkDrain, - BaseMediaFrameDrain: () => BaseMediaFrameDrain, - BlobSource: () => BlobSource, - BufferSource: () => BufferSource, - CanvasDrain: () => CanvasDrain, - CanvasSource: () => CanvasSource, - EncodedAudioChunkDrain: () => EncodedAudioChunkDrain, - EncodedAudioChunkSource: () => EncodedAudioChunkSource, - EncodedVideoChunkDrain: () => EncodedVideoChunkDrain, - EncodedVideoChunkSource: () => EncodedVideoChunkSource, - ISOBMFF: () => ISOBMFF, - Input: () => Input, - InputAudioTrack: () => InputAudioTrack, - InputFormat: () => InputFormat, - InputTrack: () => InputTrack, - InputVideoTrack: () => InputVideoTrack, - IsobmffInputFormat: () => IsobmffInputFormat, - MATROSKA: () => MATROSKA, - MKV: () => MKV, - MOV: () => MOV, - MP4: () => MP4, - MatroskaInputFormat: () => MatroskaInputFormat, - MediaSource: () => MediaSource, - MediaStreamAudioTrackSource: () => MediaStreamAudioTrackSource, - MediaStreamVideoTrackSource: () => MediaStreamVideoTrackSource, - MkvOutputFormat: () => MkvOutputFormat2, - Mp4OutputFormat: () => Mp4OutputFormat, - Output: () => Output, - OutputFormat: () => OutputFormat, - SUBTITLE_CODECS: () => SUBTITLE_CODECS, - Source: () => Source, - StreamTarget: () => StreamTarget, - SubtitleSource: () => SubtitleSource, - Target: () => Target, - TextSubtitleSource: () => TextSubtitleSource, - UrlSource: () => UrlSource, - VIDEO_CODECS: () => VIDEO_CODECS, - VideoFrameDrain: () => VideoFrameDrain, - VideoFrameSource: () => VideoFrameSource, - VideoSource: () => VideoSource, - WEBM: () => WEBM, - WebMOutputFormat: () => WebMOutputFormat - }); - - // src/misc.ts - function assert(x) { - if (!x) { - throw new Error("Assertion failed."); - } - } - var last = (arr) => { - return arr && arr[arr.length - 1]; - }; - var isU32 = (value) => { - return value >= 0 && value < 2 ** 32; - }; - var readBits = (bytes2, start, end) => { - let result = 0; - for (let i = start; i < end; i++) { - 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; - } - return result; - }; - var writeBits = (bytes2, start, end, value) => { - for (let i = start; i < end; i++) { - const byteIndex = Math.floor(i / 8); - let byte = bytes2[byteIndex]; - const bitIndex = 7 - (i & 7); - byte &= ~(1 << bitIndex); - byte |= (value & 1 << end - i - 1) >> end - i - 1 << bitIndex; - bytes2[byteIndex] = byte; - } - }; - var toUint8Array = (source) => { - if (source instanceof ArrayBuffer) { - return new Uint8Array(source); - } else { - return new Uint8Array(source.buffer, source.byteOffset, source.byteLength); - } - }; - var textEncoder = new TextEncoder(); - var invertObject = (object) => { - return Object.fromEntries(Object.entries(object).map(([key, value]) => [value, key])); - }; - var COLOR_PRIMARIES_MAP = { - bt709: 1, - // ITU-R BT.709 - bt470bg: 5, - // ITU-R BT.470BG - smpte170m: 6 - // ITU-R BT.601 525 - SMPTE 170M - }; - var COLOR_PRIMARIES_MAP_INVERSE = invertObject(COLOR_PRIMARIES_MAP); - var TRANSFER_CHARACTERISTICS_MAP = { - "bt709": 1, - // ITU-R BT.709 - "smpte170m": 6, - // SMPTE 170M - "iec61966-2-1": 13 - // IEC 61966-2-1 - }; - var TRANSFER_CHARACTERISTICS_MAP_INVERSE = invertObject(TRANSFER_CHARACTERISTICS_MAP); - var MATRIX_COEFFICIENTS_MAP = { - rgb: 0, - // Identity - bt709: 1, - // ITU-R BT.709 - bt470bg: 5, - // ITU-R BT.470BG - smpte170m: 6 - // SMPTE 170M - }; - var MATRIX_COEFFICIENTS_MAP_INVERSE = invertObject(MATRIX_COEFFICIENTS_MAP); - var colorSpaceIsComplete = (colorSpace) => { - return !!colorSpace && !!colorSpace.primaries && !!colorSpace.transfer && !!colorSpace.matrix && colorSpace.fullRange !== void 0; - }; - var isAllowSharedBufferSource = (x) => { - return x instanceof ArrayBuffer || typeof SharedArrayBuffer !== "undefined" && x instanceof SharedArrayBuffer || ArrayBuffer.isView(x) && !(x instanceof DataView); - }; - var AsyncMutex = class { - constructor() { - this.currentPromise = Promise.resolve(); - } - async acquire() { - let resolver; - const nextPromise = new Promise((resolve) => { - resolver = resolve; - }); - const currentPromiseAlias = this.currentPromise; - this.currentPromise = nextPromise; - await currentPromiseAlias; - return resolver; - } - }; - var rotationMatrix = (rotationInDegrees) => { - const theta = rotationInDegrees * (Math.PI / 180); - const cosTheta = Math.cos(theta); - const sinTheta = Math.sin(theta); - return [ - cosTheta, - sinTheta, - 0, - -sinTheta, - cosTheta, - 0, - 0, - 0, - 1 - ]; - }; - var IDENTITY_MATRIX = rotationMatrix(0); - var bytesToHexString = (bytes2) => { - return [...bytes2].map((x) => x.toString(16).padStart(2, "0")).join(""); - }; - var reverseBitsU32 = (x) => { - x = x >> 1 & 1431655765 | (x & 1431655765) << 1; - x = x >> 2 & 858993459 | (x & 858993459) << 2; - x = x >> 4 & 252645135 | (x & 252645135) << 4; - x = x >> 8 & 16711935 | (x & 16711935) << 8; - x = x >> 16 & 65535 | (x & 65535) << 16; - return x >>> 0; - }; - var binarySearchExact = (arr, key, valueGetter) => { - let low = 0; - let high = arr.length - 1; - let res = -1; - while (low <= high) { - const mid = low + high >> 1; - const midVal = valueGetter(arr[mid]); - if (midVal === key) { - res = mid; - high = mid - 1; - } else if (midVal < key) { - low = mid + 1; - } else { - high = mid - 1; - } - } - return res; - }; - var binarySearchLessOrEqual = (arr, key, valueGetter) => { - let ans = -1; - let low = 0; - let high = arr.length - 1; - while (low <= high) { - const mid = low + (high - low + 1) / 2 | 0; - const midVal = valueGetter(arr[mid]); - if (midVal <= key) { - ans = mid; - low = mid + 1; - } else { - high = mid - 1; - } - } - return ans; - }; - var promiseWithResolvers = () => { - let resolve; - let reject; - const promise = new Promise((res, rej) => { - resolve = res; - reject = rej; - }); - return { promise, resolve, reject }; - }; - var removeItem = (arr, item) => { - const index = arr.indexOf(item); - if (index !== -1) { - arr.splice(index, 1); - } - }; - var findLastIndex = (arr, predicate) => { - for (let i = arr.length - 1; i >= 0; i--) { - if (predicate(arr[i])) { - return i; - } - } - return -1; - }; - var toAsyncIterator = async function* (source) { - if (Symbol.iterator in source) { - yield* source[Symbol.iterator](); - } else { - yield* source[Symbol.asyncIterator](); - } - }; - var validateAnyIterable = (iterable) => { - if (!(Symbol.iterator in iterable) && !(Symbol.asyncIterator in iterable)) { - throw new TypeError("Argument must be an iterable or async iterable."); - } - }; - - // src/subtitles.ts - var cueBlockHeaderRegex = /(?:(.+?)\n)?((?:\d{2}:)?\d{2}:\d{2}.\d{3})\s+-->\s+((?:\d{2}:)?\d{2}:\d{2}.\d{3})/g; - var preambleStartRegex = /^WEBVTT(.|\n)*?\n{2}/; - var inlineTimestampRegex = /<(?:(\d{2}):)?(\d{2}):(\d{2}).(\d{3})>/g; - var SubtitleParser = class { - constructor(options) { - this.preambleText = null; - this.preambleEmitted = false; - this.options = options; - } - parse(text) { - text = text.replaceAll("\r\n", "\n").replaceAll("\r", "\n"); - cueBlockHeaderRegex.lastIndex = 0; - let match; - if (!this.preambleText) { - if (!preambleStartRegex.test(text)) { - throw new Error("WebVTT preamble incorrect."); - } - match = cueBlockHeaderRegex.exec(text); - const preamble = text.slice(0, match?.index ?? text.length).trimEnd(); - if (!preamble) { - throw new Error("No WebVTT preamble provided."); - } - this.preambleText = preamble; - if (match) { - text = text.slice(match.index); - cueBlockHeaderRegex.lastIndex = 0; - } - } - 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; - 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; - const cue = { - timestamp: startTime / 1e3, - duration: duration / 1e3, - text: body, - identifier: cueIdentifier, - settings: cueSettings, - notes - }; - const meta = {}; - if (!this.preambleEmitted) { - meta.config = { - description: this.preambleText - }; - this.preambleEmitted = true; - } - this.options.output(cue, meta); - } - } - }; - var timestampRegex = /(?:(\d{2}):)?(\d{2}):(\d{2}).(\d{3})/; - var parseSubtitleTimestamp = (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) => { - 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"); - }; - - // src/isobmff/isobmff-boxes.ts - var IsobmffBoxWriter = class { - constructor(writer) { - this.writer = writer; - this.helper = new Uint8Array(8); - this.helperView = new DataView(this.helper.buffer); - /** - * Stores the position from the start of the file to where boxes elements have been written. This is used to - * rewrite/edit elements that were already added before, and to measure sizes of things. - */ - this.offsets = /* @__PURE__ */ new WeakMap(); - } - writeU32(value) { - this.helperView.setUint32(0, value, false); - this.writer.write(this.helper.subarray(0, 4)); - } - writeU64(value) { - this.helperView.setUint32(0, Math.floor(value / 2 ** 32), false); - this.helperView.setUint32(4, value, false); - this.writer.write(this.helper.subarray(0, 8)); - } - writeAscii(text) { - for (let i = 0; i < text.length; i++) { - this.helperView.setUint8(i % 8, text.charCodeAt(i)); - if (i % 8 === 7) this.writer.write(this.helper); - } - if (text.length % 8 !== 0) { - this.writer.write(this.helper.subarray(0, text.length % 8)); - } - } - writeBox(box2) { - this.offsets.set(box2, this.writer.getPos()); - if (box2.contents && !box2.children) { - this.writeBoxHeader(box2, box2.size ?? box2.contents.byteLength + 8); - this.writer.write(box2.contents); - } else { - const startPos = this.writer.getPos(); - this.writeBoxHeader(box2, 0); - if (box2.contents) this.writer.write(box2.contents); - if (box2.children) { - for (const child of box2.children) if (child) this.writeBox(child); - } - const endPos = this.writer.getPos(); - const size = box2.size ?? endPos - startPos; - this.writer.seek(startPos); - this.writeBoxHeader(box2, size); - this.writer.seek(endPos); - } - } - writeBoxHeader(box2, size) { - this.writeU32(box2.largeSize ? 1 : size); - this.writeAscii(box2.type); - if (box2.largeSize) this.writeU64(size); - } - measureBoxHeader(box2) { - return 8 + (box2.largeSize ? 8 : 0); - } - patchBox(box2) { - const boxOffset = this.offsets.get(box2); - assert(boxOffset !== void 0); - const endPos = this.writer.getPos(); - this.writer.seek(boxOffset); - this.writeBox(box2); - this.writer.seek(endPos); - } - measureBox(box2) { - if (box2.contents && !box2.children) { - 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 (const child of box2.children) if (child) result += this.measureBox(child); - } - return result; - } - } - }; - var bytes = new Uint8Array(8); - var view = new DataView(bytes.buffer); - var u8 = (value) => { - return [(value % 256 + 256) % 256]; - }; - var u16 = (value) => { - view.setUint16(0, value, false); - return [bytes[0], bytes[1]]; - }; - var i16 = (value) => { - view.setInt16(0, value, false); - return [bytes[0], bytes[1]]; - }; - var u24 = (value) => { - view.setUint32(0, value, false); - return [bytes[1], bytes[2], bytes[3]]; - }; - var u32 = (value) => { - view.setUint32(0, value, false); - return [bytes[0], bytes[1], bytes[2], bytes[3]]; - }; - var i32 = (value) => { - view.setInt32(0, value, false); - return [bytes[0], bytes[1], bytes[2], bytes[3]]; - }; - var u64 = (value) => { - 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]]; - }; - var fixed_8_8 = (value) => { - view.setInt16(0, 2 ** 8 * value, false); - return [bytes[0], bytes[1]]; - }; - var fixed_16_16 = (value) => { - view.setInt32(0, 2 ** 16 * value, false); - return [bytes[0], bytes[1], bytes[2], bytes[3]]; - }; - var fixed_2_30 = (value) => { - view.setInt32(0, 2 ** 30 * value, false); - return [bytes[0], bytes[1], bytes[2], bytes[3]]; - }; - var variableUnsignedInt = (value, byteLength) => { - const bytes2 = []; - let remaining = value; - do { - let byte = remaining & 127; - remaining >>= 7; - if (bytes2.length > 0) { - byte |= 128; - } - bytes2.push(byte); - if (byteLength !== void 0) { - byteLength--; - } - } while (remaining > 0 || byteLength); - return bytes2.reverse(); - }; - var ascii = (text, nullTerminated = false) => { - 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 (const sample of samples) { - if (!result || sample.timestamp > result.timestamp) { - result = sample; - } - } - return result; - }; - var matrixToBytes = (matrix) => { - 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]) - ]; - }; - var box = (type, contents, children) => ({ - type, - contents: contents && new Uint8Array(contents.flat(10)), - children - }); - var fullBox = (type, version, flags, contents, children) => box( - type, - [u8(version), u24(flags), contents ?? []], - children - ); - var ftyp = (details) => { - const minorVersion = 512; - if (details.fragmented) return box("ftyp", [ - ascii("iso5"), - // Major brand - u32(minorVersion), - // Minor version - // Compatible brands - ascii("iso5"), - ascii("iso6"), - ascii("mp41") - ]); - return box("ftyp", [ - ascii("isom"), - // Major brand - u32(minorVersion), - // Minor version - // Compatible brands - ascii("isom"), - details.holdsAvc ? ascii("avc1") : [], - ascii("mp41") - ]); - }; - var mdat = (reserveLargeSize) => ({ type: "mdat", largeSize: reserveLargeSize }); - var free = (size) => ({ type: "free", size }); - var moov = (trackDatas, creationTime, fragmented = false) => box("moov", void 0, [ - mvhd(creationTime, trackDatas), - ...trackDatas.map((x) => trak(x, creationTime)), - fragmented ? mvex(trackDatas) : null - ]); - var mvhd = (creationTime, trackDatas) => { - 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); - 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 - u32OrU64(creationTime), - // Modification time - u32(GLOBAL_TIMESCALE), - // Timescale - u32OrU64(duration), - // Duration - fixed_16_16(1), - // Preferred rate - fixed_8_8(1), - // Preferred volume - Array(10).fill(0), - // Reserved - matrixToBytes(IDENTITY_MATRIX), - // Matrix - Array(24).fill(0), - // Pre-defined - u32(nextTrackId) - // Next track ID - ]); - }; - var trak = (trackData, creationTime) => box("trak", void 0, [ - tkhd(trackData, creationTime), - mdia(trackData, creationTime) - ]); - var tkhd = (trackData, creationTime) => { - const lastSample = lastPresentedSample(trackData.samples); - const durationInGlobalTimescale = intoTimescale( - lastSample ? lastSample.timestamp + lastSample.duration : 0, - GLOBAL_TIMESCALE - ); - const needsU64 = !isU32(creationTime) || !isU32(durationInGlobalTimescale); - const u32OrU64 = needsU64 ? u64 : u32; - let matrix; - if (trackData.type === "video") { - const rotation = trackData.track.metadata.rotation; - matrix = rotation === void 0 || typeof rotation === "number" ? rotationMatrix(rotation ?? 0) : rotation; - } else { - matrix = IDENTITY_MATRIX; - } - return fullBox("tkhd", +needsU64, 3, [ - u32OrU64(creationTime), - // Creation time - u32OrU64(creationTime), - // Modification time - u32(trackData.track.id), - // Track ID - u32(0), - // Reserved - u32OrU64(durationInGlobalTimescale), - // Duration - Array(8).fill(0), - // Reserved - u16(0), - // Layer - u16(trackData.track.id), - // Alternate group - fixed_8_8(trackData.type === "audio" ? 1 : 0), - // Volume - 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 - ]); - }; - var mdia = (trackData, creationTime) => box("mdia", void 0, [ - mdhd(trackData, creationTime), - hdlr(trackData), - minf(trackData) - ]); - var mdhd = (trackData, creationTime) => { - const lastSample = lastPresentedSample(trackData.samples); - const localDuration = intoTimescale( - lastSample ? lastSample.timestamp + lastSample.duration : 0, - trackData.timescale - ); - const needsU64 = !isU32(creationTime) || !isU32(localDuration); - const u32OrU64 = needsU64 ? u64 : u32; - return fullBox("mdhd", +needsU64, 0, [ - u32OrU64(creationTime), - // Creation time - u32OrU64(creationTime), - // Modification time - u32(trackData.timescale), - // Timescale - u32OrU64(localDuration), - // Duration - u16(21956), - // Language ("und", undetermined) - u16(0) - // Quality - ]); - }; - var TRACK_TYPE_TO_COMPONENT_SUBTYPE = { - video: "vide", - audio: "soun", - subtitle: "text" - }; - var TRACK_TYPE_TO_HANDLER_NAME = { - video: "VideoHandler", - audio: "SoundHandler", - subtitle: "TextHandler" - }; - var hdlr = (trackData) => fullBox("hdlr", 0, 0, [ - ascii("mhlr"), - // Component type - ascii(TRACK_TYPE_TO_COMPONENT_SUBTYPE[trackData.type]), - // Component subtype - u32(0), - // Component manufacturer - u32(0), - // Component flags - u32(0), - // Component flags mask - ascii(TRACK_TYPE_TO_HANDLER_NAME[trackData.type], true) - // Component name - ]); - var minf = (trackData) => box("minf", void 0, [ - TRACK_TYPE_TO_HEADER_BOX[trackData.type](), - dinf(), - stbl(trackData) - ]); - var vmhd = () => fullBox("vmhd", 0, 1, [ - u16(0), - // Graphics mode - u16(0), - // Opcolor R - u16(0), - // Opcolor G - u16(0) - // Opcolor B - ]); - var smhd = () => fullBox("smhd", 0, 0, [ - u16(0), - // Balance - u16(0) - // Reserved - ]); - var nmhd = () => fullBox("nmhd", 0, 0); - var TRACK_TYPE_TO_HEADER_BOX = { - video: vmhd, - audio: smhd, - subtitle: nmhd - }; - var dinf = () => box("dinf", void 0, [ - dref() - ]); - var dref = () => fullBox("dref", 0, 0, [ - u32(1) - // Entry count - ], [ - url() - ]); - var url = () => fullBox("url ", 0, 1); - var stbl = (trackData) => { - const needsCtts = trackData.compositionTimeOffsetTable.length > 1 || trackData.compositionTimeOffsetTable.some((x) => x.sampleCompositionTimeOffset !== 0); - return box("stbl", void 0, [ - stsd(trackData), - stts(trackData), - stss(trackData), - stsc(trackData), - stsz(trackData), - stco(trackData), - needsCtts ? ctts(trackData) : null - ]); - }; - var stsd = (trackData) => { - let sampleDescription; - if (trackData.type === "video") { - sampleDescription = videoSampleDescription( - VIDEO_CODEC_TO_BOX_NAME[trackData.track.source._codec], - trackData - ); - } else if (trackData.type === "audio") { - sampleDescription = soundSampleDescription( - AUDIO_CODEC_TO_BOX_NAME[trackData.track.source._codec], - trackData - ); - } else if (trackData.type === "subtitle") { - sampleDescription = subtitleSampleDescription( - SUBTITLE_CODEC_TO_BOX_NAME[trackData.track.source._codec], - trackData - ); - } - assert(sampleDescription); - return fullBox("stsd", 0, 0, [ - u32(1) - // Entry count - ], [ - sampleDescription - ]); - }; - var videoSampleDescription = (compressionType, trackData) => box(compressionType, [ - Array(6).fill(0), - // Reserved - u16(1), - // Data reference index - u16(0), - // Pre-defined - u16(0), - // Reserved - Array(12).fill(0), - // Pre-defined - u16(trackData.info.width), - // Width - u16(trackData.info.height), - // Height - u32(4718592), - // Horizontal resolution - u32(4718592), - // Vertical resolution - u32(0), - // Reserved - u16(1), - // Frame count - Array(32).fill(0), - // Compressor name - u16(24), - // Depth - i16(65535) - // Pre-defined - ], [ - VIDEO_CODEC_TO_CONFIGURATION_BOX[trackData.track.source._codec](trackData), - colorSpaceIsComplete(trackData.info.decoderConfig.colorSpace) ? colr(trackData) : null - ]); - var colr = (trackData) => box("colr", [ - ascii("nclx"), - // Colour type - 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 - ]); - var avcC = (trackData) => trackData.info.decoderConfig && box("avcC", [ - // For AVC, description is an AVCDecoderConfigurationRecord, so nothing else to do here - ...toUint8Array(trackData.info.decoderConfig.description) - ]); - var hvcC = (trackData) => trackData.info.decoderConfig && box("hvcC", [ - // For HEVC, description is an HEVCDecoderConfigurationRecord, so nothing else to do here - ...toUint8Array(trackData.info.decoderConfig.description) - ]); - var vpcC = (trackData) => { - if (!trackData.info.decoderConfig) { - return null; - } - const decoderConfig = trackData.info.decoderConfig; - assert(decoderConfig.colorSpace); - 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 - u8(level), - // Level - u8(thirdByte), - // Bit depth, chroma subsampling, full range - u8(colourPrimaries), - // Colour primaries - u8(transferCharacteristics), - // Transfer characteristics - u8(matrixCoefficients), - // Matrix coefficients - u16(0) - // Codec initialization data size - ]); - }; - var av1C = () => { - const marker = 1; - const version = 1; - const firstByte = (marker << 7) + version; - return box("av1C", [ - firstByte, - 0, - 0, - 0 - ]); - }; - var soundSampleDescription = (compressionType, trackData) => box(compressionType, [ - Array(6).fill(0), - // Reserved - u16(1), - // Data reference index - u16(0), - // Version - u16(0), - // Revision level - u32(0), - // Vendor - u16(trackData.info.numberOfChannels), - // Number of channels - u16(16), - // Sample size (bits) - u16(0), - // Compression ID - u16(0), - // Packet size - fixed_16_16(trackData.info.sampleRate) - // Sample rate - ], [ - AUDIO_CODEC_TO_CONFIGURATION_BOX[trackData.track.source._codec](trackData) - ]); - var esds = (trackData) => { - const description = toUint8Array(trackData.info.decoderConfig.description ?? new ArrayBuffer(0)); - let bytes2 = [ - ...description - ]; - bytes2 = [ - ...u8(64), - // MPEG-4 Audio - ...u8(21), - // stream type(6bits)=5 audio, flags(2bits)=1 - ...u24(0), - // 24bit buffer size - ...u32(0), - // max bitrate - ...u32(0), - // avg bitrate - ...u8(5), - // TAG(5) = ASC ([2],[3]) embedded in above OD - ...variableUnsignedInt(bytes2.length), - ...bytes2 - ]; - bytes2 = [ - ...u16(1), - // ES_ID = 1 - ...u8(0), - // flags etc = 0 - ...u8(4), - // TAG(4) = ES Descriptor ([2]) embedded in above OD - ...variableUnsignedInt(bytes2.length), - ...bytes2, - ...u8(6), - // TAG(6) - ...u8(1), - // length - ...u8(2) - // data - ]; - bytes2 = [ - ...u8(3), - // TAG(3) = Object Descriptor ([2]) - ...variableUnsignedInt(bytes2.length), - ...bytes2 - ]; - return fullBox("esds", 0, 0, bytes2); - }; - var dOps = (trackData) => { - let preskip = 3840; - let gain = 0; - const description = trackData.info.decoderConfig?.description; - if (description) { - assert(description.byteLength >= 18); - const view2 = ArrayBuffer.isView(description) ? new DataView(description.buffer, description.byteOffset, description.byteLength) : new DataView(description); - preskip = view2.getUint16(10, true); - gain = view2.getInt16(14, true); - } - return box("dOps", [ - u8(0), - // Version - u8(trackData.info.numberOfChannels), - // OutputChannelCount - u16(preskip), - u32(trackData.info.sampleRate), - // InputSampleRate - fixed_8_8(gain), - // OutputGain - u8(0) - // ChannelMappingFamily - ]); - }; - var subtitleSampleDescription = (compressionType, trackData) => box(compressionType, [ - Array(6).fill(0), - // Reserved - u16(1) - // Data reference index - ], [ - SUBTITLE_CODEC_TO_CONFIGURATION_BOX[trackData.track.source._codec](trackData) - ]); - var vttC = (trackData) => box("vttC", [ - ...textEncoder.encode(trackData.info.config.description) - ]); - var stts = (trackData) => { - return fullBox("stts", 0, 0, [ - 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 - ]) - ]); - }; - var stss = (trackData) => { - if (trackData.samples.every((x) => x.type === "key")) return null; - 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 - ]); - }; - var stsc = (trackData) => { - return fullBox("stsc", 0, 0, [ - u32(trackData.compactlyCodedChunkTable.length), - // Number of entries - trackData.compactlyCodedChunkTable.map((x) => [ - // Sample-to-chunk table - u32(x.firstChunk), - // First chunk - u32(x.samplesPerChunk), - // Samples per chunk - u32(1) - // Sample description index - ]) - ]); - }; - var stsz = (trackData) => 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 - ]); - var stco = (trackData) => { - if (trackData.finalizedChunks.length > 0 && last(trackData.finalizedChunks).offset >= 2 ** 32) { - return fullBox("co64", 0, 0, [ - u32(trackData.finalizedChunks.length), - // Number of entries - 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 - ]); - }; - var ctts = (trackData) => { - return fullBox("ctts", 0, 0, [ - 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 - ]) - ]); - }; - var mvex = (trackDatas) => { - return box("mvex", void 0, trackDatas.map(trex)); - }; - var trex = (trackData) => { - return fullBox("trex", 0, 0, [ - u32(trackData.track.id), - // Track ID - u32(1), - // Default sample description index - u32(0), - // Default sample duration - u32(0), - // Default sample size - u32(0) - // Default sample flags - ]); - }; - var moof = (sequenceNumber, trackDatas) => { - return box("moof", void 0, [ - mfhd(sequenceNumber), - ...trackDatas.map(traf) - ]); - }; - var mfhd = (sequenceNumber) => { - return fullBox("mfhd", 0, 0, [ - u32(sequenceNumber) - // Sequence number - ]); - }; - var fragmentSampleFlags = (sample) => { - let byte1 = 0; - let byte2 = 0; - const byte3 = 0; - const byte4 = 0; - const sampleIsDifferenceSample = sample.type === "delta"; - byte2 |= +sampleIsDifferenceSample; - if (sampleIsDifferenceSample) { - byte1 |= 1; - } else { - byte1 |= 2; - } - return byte1 << 24 | byte2 << 16 | byte3 << 8 | byte4; - }; - var traf = (trackData) => { - return box("traf", void 0, [ - tfhd(trackData), - tfdt(trackData), - trun(trackData) - ]); - }; - var tfhd = (trackData) => { - assert(trackData.currentChunk); - let tfFlags = 0; - tfFlags |= 8; - tfFlags |= 16; - tfFlags |= 32; - tfFlags |= 131072; - const referenceSample = trackData.currentChunk.samples[1] ?? trackData.currentChunk.samples[0]; - const referenceSampleInfo = { - duration: referenceSample.timescaleUnitsToNextSample, - size: referenceSample.size, - 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 - ]); - }; - var tfdt = (trackData) => { - assert(trackData.currentChunk); - return fullBox("tfdt", 1, 0, [ - u64(intoTimescale(trackData.currentChunk.startTimestamp, trackData.timescale)) - // Base Media Decode Time - ]); - }; - var trun = (trackData) => { - assert(trackData.currentChunk); - 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; - flags |= 256 * +sampleDurationPresent; - flags |= 512 * +sampleSizePresent; - flags |= 1024 * +sampleFlagsPresent; - flags |= 2048 * +sampleCompositionTimeOffsetsPresent; - return fullBox("trun", 1, flags, [ - u32(trackData.currentChunk.samples.length), - // Sample count - u32(trackData.currentChunk.offset - trackData.currentChunk.moofOffset || 0), - // Data offset - firstSampleFlagsPresent ? u32(allSampleFlags[0]) : [], - trackData.currentChunk.samples.map((_, i) => [ - sampleDurationPresent ? u32(allSampleDurations[i]) : [], - // Sample duration - sampleSizePresent ? u32(allSampleSizes[i]) : [], - // Sample size - sampleFlagsPresent ? u32(allSampleFlags[i]) : [], - // Sample flags - // Sample composition time offsets - sampleCompositionTimeOffsetsPresent ? i32(allSampleCompositionTimeOffsets[i]) : [] - ]) - ]); - }; - var mfra = (trackDatas) => { - return box("mfra", void 0, [ - ...trackDatas.map(tfra), - mfro() - ]); - }; - var tfra = (trackData, trackIndex) => { - const version = 1; - return fullBox("tfra", version, 0, [ - u32(trackData.track.id), - // Track ID - u32(63), - // This specifies that traf number, trun number and sample number are 32-bit ints - u32(trackData.finalizedChunks.length), - // Number of entries - trackData.finalizedChunks.map((chunk) => [ - u64(intoTimescale(chunk.samples[0].timestamp, trackData.timescale)), - // Time (in presentation time) - u64(chunk.moofOffset), - // moof offset - u32(trackIndex + 1), - // traf number - u32(1), - // trun number - u32(1) - // Sample number - ]) - ]); - }; - var 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 - ]); - }; - var vtte = () => box("vtte"); - var vttc = (payload, timestamp, identifier, settings, sourceId) => box("vttc", void 0, [ - 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)]) - ]); - var vtta = (notes) => box("vtta", [...textEncoder.encode(notes)]); - var VIDEO_CODEC_TO_BOX_NAME = { - 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 - }; - var AUDIO_CODEC_TO_BOX_NAME = { - aac: "mp4a", - opus: "Opus" - }; - var AUDIO_CODEC_TO_CONFIGURATION_BOX = { - aac: esds, - opus: dOps - }; - var SUBTITLE_CODEC_TO_BOX_NAME = { - webvtt: "wvtt" - }; - var SUBTITLE_CODEC_TO_CONFIGURATION_BOX = { - webvtt: vttC - }; - - // src/muxer.ts - var Muxer = class { - constructor(output) { - this.mutex = new AsyncMutex(); - 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) { - let timestampInSeconds = rawTimestampInUs / 1e6; - let timestampInfo = this.trackTimestampInfo.get(track); - if (!timestampInfo) { - if (!isKeyFrame) { - throw new Error("First frame must be a key frame."); - } - if (this.timestampsMustStartAtZero && timestampInSeconds > 0) { - throw new Error(`Timestamps must start at zero (got ${timestampInSeconds}s).`); - } - timestampInfo = { - timestampOffset: timestampInSeconds, - maxTimestamp: track.source._offsetTimestamps ? 0 : timestampInSeconds, - lastKeyFrameTimestamp: track.source._offsetTimestamps ? 0 : timestampInSeconds - }; - this.trackTimestampInfo.set(track, timestampInfo); - } - if (track.source._offsetTimestamps) { - timestampInSeconds -= timestampInfo.timestampOffset; - } - if (timestampInSeconds < 0) { - 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).` - ); - } - 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).` - ); - } - timestampInfo.lastKeyFrameTimestamp = timestampInSeconds; - } - timestampInfo.maxTimestamp = Math.max(timestampInfo.maxTimestamp, timestampInSeconds); - return timestampInSeconds; - } - }; - - // src/writer.ts - var Writer = class { - constructor() { - /** Setting this to true will cause the writer to ensure data is written in a strictly monotonic, streamable way. */ - this.ensureMonotonicity = false; - } - start() { - } - }; - var ArrayBufferTargetWriter = class extends Writer { - constructor(target) { - super(); - this.pos = 0; - this.buffer = new ArrayBuffer(2 ** 16); - this.bytes = new Uint8Array(this.buffer); - this.maxPos = 0; - this.target = target; - } - ensureSize(size) { - let newLength = this.buffer.byteLength; - while (newLength < size) newLength *= 2; - if (newLength === this.buffer.byteLength) return; - const newBuffer = new ArrayBuffer(newLength); - const newBytes = new Uint8Array(newBuffer); - newBytes.set(this.bytes, 0); - this.buffer = newBuffer; - this.bytes = newBytes; - } - write(data) { - this.ensureSize(this.pos + data.byteLength); - this.bytes.set(data, this.pos); - this.pos += data.byteLength; - this.maxPos = Math.max(this.maxPos, this.pos); - } - seek(newPos) { - this.pos = newPos; - } - getPos() { - return this.pos; - } - async flush() { - } - async finalize() { - this.ensureSize(this.pos); - this.target.buffer = this.buffer.slice(0, Math.max(this.maxPos, this.pos)); - } - getSlice(start, end) { - return this.bytes.slice(start, end); - } - }; - var StreamTargetWriter = class extends Writer { - constructor(target) { - super(); - this.pos = 0; - this.sections = []; - this.lastFlushEnd = 0; - this.writer = null; - this.target = target; - } - start() { - this.writer = this.target._writable.getWriter(); - } - write(data) { - this.sections.push({ - data: data.slice(), - start: this.pos - }); - this.pos += data.byteLength; - } - seek(newPos) { - this.pos = newPos; - } - getPos() { - return this.pos; - } - async flush() { - assert(this.writer); - if (this.sections.length === 0) return; - 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++) { - 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 - }); - } - } - for (const chunk of chunks) { - chunk.data = new Uint8Array(chunk.size); - 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); - } - } - if (this.ensureMonotonicity && chunk.start !== this.lastFlushEnd) { - throw new Error("Internal error: Monotonicity violation."); - } - if (this.writer.desiredSize !== null && this.writer.desiredSize <= 0) { - await this.writer.ready; - } - void this.writer.write({ - type: "write", - data: chunk.data, - position: chunk.start - }); - this.lastFlushEnd = chunk.start + chunk.data.byteLength; - } - this.sections.length = 0; - } - finalize() { - assert(this.writer); - return this.writer.close(); - } - }; - var DEFAULT_CHUNK_SIZE = 2 ** 24; - var MAX_CHUNKS_AT_ONCE = 2; - var ChunkedStreamTargetWriter = class extends Writer { - constructor(target) { - super(); - this.pos = 0; - /** - * The data is divided up into fixed-size chunks, whose contents are first filled in RAM and then flushed out. - * A chunk is flushed if all of its contents have been written. - */ - this.chunks = []; - this.lastFlushEnd = 0; - this.writer = null; - this.flushedChunkQueue = []; - this.target = target; - this.chunkSize = target._options?.chunkSize ?? DEFAULT_CHUNK_SIZE; - if (!Number.isInteger(this.chunkSize) || this.chunkSize < 2 ** 10) { - throw new Error("Invalid StreamTarget options: chunkSize must be an integer not smaller than 1024."); - } - } - start() { - this.writer = this.target._writable.getWriter(); - } - write(data) { - this.writeDataIntoChunks(data, this.pos); - this.queueChunksForFlush(); - this.pos += data.byteLength; - } - seek(newPos) { - this.pos = newPos; - } - getPos() { - return this.pos; - } - writeDataIntoChunks(data, position) { - let chunkIndex = this.chunks.findIndex((x) => x.start <= position && position < x.start + this.chunkSize); - if (chunkIndex === -1) chunkIndex = this.createChunk(position); - 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); - const section = { - start: relativePosition, - end: relativePosition + toWrite.byteLength - }; - this.insertSectionIntoChunk(chunk, section); - if (chunk.written[0].start === 0 && chunk.written[0].end === this.chunkSize) { - chunk.shouldFlush = true; - } - if (this.chunks.length > MAX_CHUNKS_AT_ONCE) { - for (let i = 0; i < this.chunks.length - 1; i++) { - this.chunks[i].shouldFlush = true; - } - this.queueChunksForFlush(); - } - if (toWrite.byteLength < data.byteLength) { - this.writeDataIntoChunks(data.subarray(toWrite.byteLength), position + toWrite.byteLength); - } - } - insertSectionIntoChunk(chunk, section) { - let low = 0; - let high = chunk.written.length - 1; - let index = -1; - while (low <= high) { - const mid = Math.floor(low + (high - low + 1) / 2); - if (chunk.written[mid].start <= section.start) { - low = mid + 1; - index = mid; - } else { - high = mid - 1; - } - } - chunk.written.splice(index + 1, 0, section); - if (index === -1 || chunk.written[index].end < section.start) index++; - while (index < chunk.written.length - 1 && chunk.written[index].end >= chunk.written[index + 1].start) { - chunk.written[index].end = Math.max(chunk.written[index].end, chunk.written[index + 1].end); - chunk.written.splice(index + 1, 1); - } - } - createChunk(includesPosition) { - const start = Math.floor(includesPosition / this.chunkSize) * this.chunkSize; - const chunk = { - start, - data: new Uint8Array(this.chunkSize), - written: [], - shouldFlush: false - }; - this.chunks.push(chunk); - this.chunks.sort((a, b) => a.start - b.start); - return this.chunks.indexOf(chunk); - } - queueChunksForFlush(force = false) { - assert(this.writer); - for (let i = 0; i < this.chunks.length; i++) { - const chunk = this.chunks[i]; - if (!chunk.shouldFlush && !force) continue; - for (const section of chunk.written) { - if (this.ensureMonotonicity && chunk.start + section.start !== this.lastFlushEnd) { - throw new Error("Internal error: Monotonicity violation."); - } - this.flushedChunkQueue.push({ - type: "write", - data: chunk.data.subarray(section.start, section.end), - position: chunk.start + section.start - }); - this.lastFlushEnd = chunk.start + section.end; - } - this.chunks.splice(i--, 1); - } - } - async flush() { - assert(this.writer); - if (this.flushedChunkQueue.length === 0) return; - for (const chunk of this.flushedChunkQueue) { - if (this.writer.desiredSize !== null && this.writer.desiredSize <= 0) { - await this.writer.ready; - } - void this.writer.write(chunk); - } - this.flushedChunkQueue.length = 0; - } - async finalize() { - assert(this.writer); - this.queueChunksForFlush(true); - await this.flush(); - return this.writer.close(); - } - }; - - // src/target.ts - var Target = class { - constructor() { - /** @internal */ - this._output = null; - } - }; - var ArrayBufferTarget = class extends Target { - constructor() { - super(...arguments); - this.buffer = null; - } - /** @internal */ - _createWriter() { - return new ArrayBufferTargetWriter(this); - } - }; - var StreamTarget = class extends Target { - constructor(writable, options = {}) { - super(); - if (!(writable instanceof WritableStream)) { - throw new TypeError("StreamTarget requires a WritableStream instance."); - } - if (options != null && typeof options !== "object") { - throw new TypeError("StreamTarget options, when provided, must be an object."); - } - if (options.chunked !== void 0 && typeof options.chunked !== "boolean") { - throw new TypeError("options.chunked, when provided, must be a boolean."); - } - if (options.chunkSize !== void 0 && (!Number.isInteger(options.chunkSize) || options.chunkSize <= 0)) { - throw new TypeError("options.chunkSize, when provided, must be a positive integer."); - } - this._writable = writable; - this._options = options; - } - /** @internal */ - _createWriter() { - return this._options.chunked ? new ChunkedStreamTargetWriter(this) : new StreamTargetWriter(this); - } - }; - - // src/codec.ts - var VIDEO_CODECS = ["avc", "hevc", "vp8", "vp9", "av1"]; - var AUDIO_CODECS = ["aac", "opus"]; - var SUBTITLE_CODECS = ["webvtt"]; - var AVC_LEVEL_TABLE = [ - { maxMacroblocks: 99, maxBitrate: 64e3, level: 10 }, - // Level 1 - { maxMacroblocks: 396, maxBitrate: 192e3, level: 11 }, - // Level 1.1 - { maxMacroblocks: 396, maxBitrate: 384e3, level: 12 }, - // Level 1.2 - { maxMacroblocks: 396, maxBitrate: 768e3, level: 13 }, - // Level 1.3 - { maxMacroblocks: 396, maxBitrate: 2e6, level: 20 }, - // Level 2 - { maxMacroblocks: 792, maxBitrate: 4e6, level: 21 }, - // Level 2.1 - { maxMacroblocks: 1620, maxBitrate: 4e6, level: 22 }, - // Level 2.2 - { maxMacroblocks: 1620, maxBitrate: 1e7, level: 30 }, - // Level 3 - { maxMacroblocks: 3600, maxBitrate: 14e6, level: 31 }, - // Level 3.1 - { maxMacroblocks: 5120, maxBitrate: 2e7, level: 32 }, - // Level 3.2 - { maxMacroblocks: 8192, maxBitrate: 2e7, level: 40 }, - // Level 4 - { maxMacroblocks: 8192, maxBitrate: 5e7, level: 41 }, - // Level 4.1 - { maxMacroblocks: 8704, maxBitrate: 5e7, level: 42 }, - // Level 4.2 - { maxMacroblocks: 22080, maxBitrate: 135e6, level: 50 }, - // Level 5 - { maxMacroblocks: 36864, maxBitrate: 24e7, level: 51 }, - // Level 5.1 - { maxMacroblocks: 36864, maxBitrate: 24e7, level: 52 }, - // Level 5.2 - { maxMacroblocks: 139264, maxBitrate: 24e7, level: 60 }, - // Level 6 - { maxMacroblocks: 139264, maxBitrate: 48e7, level: 61 }, - // Level 6.1 - { maxMacroblocks: 139264, maxBitrate: 8e8, level: 62 } - // Level 6.2 - ]; - var HEVC_LEVEL_TABLE = [ - { maxPictureSize: 36864, maxBitrate: 128e3, tier: "L", level: 30 }, - // Level 1 (Low Tier) - { maxPictureSize: 122880, maxBitrate: 15e5, tier: "L", level: 60 }, - // Level 2 (Low Tier) - { maxPictureSize: 245760, maxBitrate: 3e6, tier: "L", level: 63 }, - // Level 2.1 (Low Tier) - { maxPictureSize: 552960, maxBitrate: 6e6, tier: "L", level: 90 }, - // Level 3 (Low Tier) - { maxPictureSize: 983040, maxBitrate: 1e7, tier: "L", level: 93 }, - // Level 3.1 (Low Tier) - { maxPictureSize: 2228224, maxBitrate: 12e6, tier: "L", level: 120 }, - // Level 4 (Low Tier) - { maxPictureSize: 2228224, maxBitrate: 3e7, tier: "H", level: 120 }, - // Level 4 (High Tier) - { maxPictureSize: 2228224, maxBitrate: 2e7, tier: "L", level: 123 }, - // Level 4.1 (Low Tier) - { maxPictureSize: 2228224, maxBitrate: 5e7, tier: "H", level: 123 }, - // Level 4.1 (High Tier) - { maxPictureSize: 8912896, maxBitrate: 25e6, tier: "L", level: 150 }, - // Level 5 (Low Tier) - { maxPictureSize: 8912896, maxBitrate: 1e8, tier: "H", level: 150 }, - // Level 5 (High Tier) - { maxPictureSize: 8912896, maxBitrate: 4e7, tier: "L", level: 153 }, - // Level 5.1 (Low Tier) - { maxPictureSize: 8912896, maxBitrate: 16e7, tier: "H", level: 153 }, - // Level 5.1 (High Tier) - { maxPictureSize: 8912896, maxBitrate: 6e7, tier: "L", level: 156 }, - // Level 5.2 (Low Tier) - { maxPictureSize: 8912896, maxBitrate: 24e7, tier: "H", level: 156 }, - // Level 5.2 (High Tier) - { maxPictureSize: 35651584, maxBitrate: 6e7, tier: "L", level: 180 }, - // Level 6 (Low Tier) - { maxPictureSize: 35651584, maxBitrate: 24e7, tier: "H", level: 180 }, - // Level 6 (High Tier) - { maxPictureSize: 35651584, maxBitrate: 12e7, tier: "L", level: 183 }, - // Level 6.1 (Low Tier) - { maxPictureSize: 35651584, maxBitrate: 48e7, tier: "H", level: 183 }, - // Level 6.1 (High Tier) - { maxPictureSize: 35651584, maxBitrate: 24e7, tier: "L", level: 186 }, - // Level 6.2 (Low Tier) - { maxPictureSize: 35651584, maxBitrate: 8e8, tier: "H", level: 186 } - // Level 6.2 (High Tier) - ]; - var VP9_LEVEL_TABLE = [ - { maxPictureSize: 36864, maxBitrate: 2e5, level: 10 }, - // Level 1 - { maxPictureSize: 73728, maxBitrate: 8e5, level: 11 }, - // Level 1.1 - { maxPictureSize: 122880, maxBitrate: 18e5, level: 20 }, - // Level 2 - { maxPictureSize: 245760, maxBitrate: 36e5, level: 21 }, - // Level 2.1 - { maxPictureSize: 552960, maxBitrate: 72e5, level: 30 }, - // Level 3 - { maxPictureSize: 983040, maxBitrate: 12e6, level: 31 }, - // Level 3.1 - { maxPictureSize: 2228224, maxBitrate: 18e6, level: 40 }, - // Level 4 - { maxPictureSize: 2228224, maxBitrate: 3e7, level: 41 }, - // Level 4.1 - { maxPictureSize: 8912896, maxBitrate: 6e7, level: 50 }, - // Level 5 - { maxPictureSize: 8912896, maxBitrate: 12e7, level: 51 }, - // Level 5.1 - { maxPictureSize: 8912896, maxBitrate: 18e7, level: 52 }, - // Level 5.2 - { maxPictureSize: 35651584, maxBitrate: 18e7, level: 60 }, - // Level 6 - { maxPictureSize: 35651584, maxBitrate: 24e7, level: 61 }, - // Level 6.1 - { maxPictureSize: 35651584, maxBitrate: 48e7, level: 62 } - // Level 6.2 - ]; - var AV1_LEVEL_TABLE = [ - { maxPictureSize: 147456, maxBitrate: 15e5, tier: "M", level: 0 }, - // Level 2.0 (Main Tier) - { maxPictureSize: 278784, maxBitrate: 3e6, tier: "M", level: 1 }, - // Level 2.1 (Main Tier) - { maxPictureSize: 665856, maxBitrate: 6e6, tier: "M", level: 4 }, - // Level 3.0 (Main Tier) - { maxPictureSize: 1065024, maxBitrate: 1e7, tier: "M", level: 5 }, - // Level 3.1 (Main Tier) - { maxPictureSize: 2359296, maxBitrate: 12e6, tier: "M", level: 8 }, - // Level 4.0 (Main Tier) - { maxPictureSize: 2359296, maxBitrate: 3e7, tier: "H", level: 8 }, - // Level 4.0 (High Tier) - { maxPictureSize: 2359296, maxBitrate: 2e7, tier: "M", level: 9 }, - // Level 4.1 (Main Tier) - { maxPictureSize: 2359296, maxBitrate: 5e7, tier: "H", level: 9 }, - // Level 4.1 (High Tier) - { maxPictureSize: 8912896, maxBitrate: 3e7, tier: "M", level: 12 }, - // Level 5.0 (Main Tier) - { maxPictureSize: 8912896, maxBitrate: 1e8, tier: "H", level: 12 }, - // Level 5.0 (High Tier) - { maxPictureSize: 8912896, maxBitrate: 4e7, tier: "M", level: 13 }, - // Level 5.1 (Main Tier) - { maxPictureSize: 8912896, maxBitrate: 16e7, tier: "H", level: 13 }, - // Level 5.1 (High Tier) - { maxPictureSize: 8912896, maxBitrate: 6e7, tier: "M", level: 14 }, - // Level 5.2 (Main Tier) - { maxPictureSize: 8912896, maxBitrate: 24e7, tier: "H", level: 14 }, - // Level 5.2 (High Tier) - { maxPictureSize: 35651584, maxBitrate: 6e7, tier: "M", level: 15 }, - // Level 5.3 (Main Tier) - { maxPictureSize: 35651584, maxBitrate: 24e7, tier: "H", level: 15 }, - // Level 5.3 (High Tier) - { maxPictureSize: 35651584, maxBitrate: 6e7, tier: "M", level: 16 }, - // Level 6.0 (Main Tier) - { maxPictureSize: 35651584, maxBitrate: 24e7, tier: "H", level: 16 }, - // Level 6.0 (High Tier) - { maxPictureSize: 35651584, maxBitrate: 1e8, tier: "M", level: 17 }, - // Level 6.1 (Main Tier) - { maxPictureSize: 35651584, maxBitrate: 48e7, tier: "H", level: 17 }, - // Level 6.1 (High Tier) - { maxPictureSize: 35651584, maxBitrate: 16e7, tier: "M", level: 18 }, - // Level 6.2 (Main Tier) - { maxPictureSize: 35651584, maxBitrate: 8e8, tier: "H", level: 18 }, - // Level 6.2 (High Tier) - { maxPictureSize: 35651584, maxBitrate: 16e7, tier: "M", level: 19 }, - // Level 6.3 (Main Tier) - { maxPictureSize: 35651584, maxBitrate: 8e8, tier: "H", level: 19 } - // Level 6.3 (High Tier) - ]; - var buildVideoCodecString = (codec, width, height, bitrate) => { - if (codec === "avc") { - const profileIndication = 100; - const totalMacroblocks = Math.ceil(width / 16) * Math.ceil(height / 16); - 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"); - return `avc1.${hexProfileIndication}${hexProfileCompatibility}${hexLevelIndication}`; - } else if (codec === "hevc") { - 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"; - return `hev1.${profilePrefix}${profileIdc}.${compatibilityFlags}.${levelInfo.tier}${levelInfo.level}.${constraintFlags}`; - } else if (codec === "vp8") { - return "vp8"; - } else if (codec === "vp9") { - const profile = "00"; - const pictureSize = width * height; - const levelInfo = VP9_LEVEL_TABLE.find( - (level) => pictureSize <= level.maxPictureSize && bitrate <= level.maxBitrate - ) ?? last(VP9_LEVEL_TABLE); - const bitDepth = "08"; - return `vp09.${profile}.${levelInfo.level}.${bitDepth}`; - } else if (codec === "av1") { - const profile = 0; - const pictureSize = width * height; - const levelInfo = AV1_LEVEL_TABLE.find( - (level) => pictureSize <= level.maxPictureSize && bitrate <= level.maxBitrate - ) ?? last(AV1_LEVEL_TABLE); - const bitDepth = "08"; - return `av01.${profile}.${levelInfo.level.toString().padStart(2, "0")}${levelInfo.tier}.${bitDepth}`; - } - throw new TypeError(`Unhandled codec '${codec}'.`); - }; - var extractVideoCodecString = (codec, description) => { - if (codec === "avc") { - if (!description || description.byteLength < 4) { - throw new TypeError("AVC description must be at least 4 bytes long."); - } - return `avc1.${bytesToHexString(description.subarray(1, 4))}`; - } else if (codec === "hevc") { - if (!description) { - throw new TypeError("HEVC description must be provided."); - } - const view2 = new DataView(description.buffer, description.byteOffset, description.byteLength); - let codecString = "hev1."; - const generalProfileSpace = description[1] >> 6 & 3; - const generalProfileIdc = description[1] & 31; - codecString += ["", "A", "B", "C"][generalProfileSpace] + generalProfileIdc; - codecString += "."; - const compatibilityFlags = reverseBitsU32(view2.getUint32(2)); - codecString += compatibilityFlags.toString(16); - codecString += "."; - const generalTierFlag = description[1] >> 5 & 1; - const generalLevelIdc = description[12]; - codecString += generalTierFlag === 0 ? "L" : "H"; - codecString += generalLevelIdc; - codecString += "."; - const constraintFlags = []; - for (let i = 0; i < 6; i++) { - const byte = description[i + 13]; - constraintFlags.push(byte); - } - while (constraintFlags[constraintFlags.length - 1] === 0) { - constraintFlags.pop(); - } - codecString += constraintFlags.map((x) => x.toString(16)).join("."); - return codecString; - } - throw new TypeError(`Unhandled codec '${codec}'.`); - }; - var buildAudioCodecString = (codec, numberOfChannels, sampleRate) => { - if (codec === "aac") { - if (numberOfChannels >= 2 && sampleRate <= 24e3) { - return "mp4a.40.29"; - } - if (sampleRate <= 24e3) { - return "mp4a.40.5"; - } - return "mp4a.40.2"; - } else if (codec === "opus") { - return "opus"; - } else if (codec === "vorbis") { - return "vorbis"; - } - throw new TypeError(`Unhandled codec '${codec}'.`); - }; - var extractAudioCodecString = (codec, description) => { - if (codec === "aac") { - const audioSpecificConfig = parseAacAudioSpecificConfig(description); - return `mp4a.40.${audioSpecificConfig.objectType}`; - } else if (codec === "opus") { - return "opus"; - } else if (codec === "vorbis") { - return "vorbis"; - } - throw new TypeError(`Unhandled codec '${codec}'.`); - }; - var parseAacAudioSpecificConfig = (bytes2) => { - if (!bytes2 || bytes2.byteLength < 2) { - throw new TypeError("AAC description must be at least 2 bytes long."); - } - let bitOffset = 0; - let objectType = readBits(bytes2, bitOffset, bitOffset + 5); - bitOffset += 5; - if (objectType === 31) { - objectType = 32 + readBits(bytes2, bitOffset, bitOffset + 6); - bitOffset += 6; - } - const frequencyIndex = readBits(bytes2, bitOffset, bitOffset + 4); - bitOffset += 4; - let sampleRate = null; - if (frequencyIndex === 15) { - sampleRate = readBits(bytes2, bitOffset, bitOffset + 24); - bitOffset += 24; - } else { - const freqTable = [ - 96e3, - 88200, - 64e3, - 48e3, - 44100, - 32e3, - 24e3, - 22050, - 16e3, - 12e3, - 11025, - 8e3, - 7350 - ]; - if (frequencyIndex < freqTable.length) { - sampleRate = freqTable[frequencyIndex]; - } - } - const channelConfiguration = readBits(bytes2, bitOffset, bitOffset + 4); - bitOffset += 4; - let numberOfChannels = null; - if (channelConfiguration >= 1 && channelConfiguration <= 7) { - const channelMap = { - 1: 1, - 2: 2, - 3: 3, - 4: 4, - 5: 5, - 6: 6, - 7: 8 - }; - numberOfChannels = channelMap[channelConfiguration]; - } - return { - objectType, - frequencyIndex, - sampleRate, - channelConfiguration, - numberOfChannels - }; - }; - var getVideoEncoderConfigExtension = (codec) => { - if (codec === "avc") { - return { - avc: { - format: "avc" - // Ensure the format is not Annex B - } - }; - } else if (codec === "hevc") { - return { - hevc: { - format: "hevc" - // Ensure the format is not Annex B - } - }; - } - return {}; - }; - var getAudioEncoderConfigExtension = (codec) => { - if (codec === "aac") { - return { - aac: { - format: "aac" - // Ensure the format is not ADTS - } - }; - } else if (codec === "opus") { - return { - opus: { - format: "opus" - } - }; - } - return {}; - }; - var validateVideoChunkMetadata = (metadata) => { - if (!metadata) { - throw new TypeError("Video chunk metadata must be provided."); - } - if (typeof metadata !== "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."); - } - if (typeof metadata.decoderConfig !== "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."); - } - 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)." - ); - } - 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)." - ); - } - 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." - ); - } - } - if (metadata.decoderConfig.colorSpace !== void 0) { - const { colorSpace } = metadata.decoderConfig; - if (typeof colorSpace !== "object") { - throw new TypeError( - "Video chunk metadata decoder configuration colorSpace, when provided, must be an object." - ); - } - 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(", ")}.` - ); - } - 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(", ")}.` - ); - } - 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(", ")}.` - ); - } - if (colorSpace.fullRange != null && typeof colorSpace.fullRange !== "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("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 === void 0) { - throw new TypeError("Video chunk metadata decoder configuration for VP8/VP9 must include a colorSpace."); - } - }; - var validateAudioChunkMetadata = (metadata) => { - if (!metadata) { - throw new TypeError("Audio chunk metadata must be provided."); - } - if (typeof metadata !== "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."); - } - if (typeof metadata.decoderConfig !== "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."); - } - 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 (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." - ); - } - } - 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." - ); - } - 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."); - } - }; - var validateSubtitleMetadata = (metadata) => { - if (!metadata) { - throw new TypeError("Subtitle metadata must be provided."); - } - if (typeof metadata !== "object") { - throw new TypeError("Subtitle metadata must be an object."); - } - if (!metadata.config) { - 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."); - } - if (typeof metadata.config.description !== "string") { - throw new TypeError("Subtitle metadata config description must be a string."); - } - }; - - // src/isobmff/isobmff-muxer.ts - var GLOBAL_TIMESCALE = 1e3; - var TIMESTAMP_OFFSET = 2082844800; - var intoTimescale = (timeInSeconds, timescale, round = true) => { - const value = timeInSeconds * timescale; - return round ? Math.round(value) : value; - }; - var IsobmffMuxer = class extends Muxer { - constructor(output, format) { - super(output); - this.timestampsMustStartAtZero = true; - this.auxTarget = new ArrayBufferTarget(); - this.auxWriter = this.auxTarget._createWriter(); - this.auxBoxWriter = new IsobmffBoxWriter(this.auxWriter); - this.ftypSize = null; - this.mdat = null; - this.trackDatas = []; - this.creationTime = Math.floor(Date.now() / 1e3) + TIMESTAMP_OFFSET; - this.finalizedChunks = []; - this.nextFragmentNumber = 1; - this.writer = output._writer; - this.boxWriter = new IsobmffBoxWriter(this.writer); - const fastStartDefault = this.writer instanceof ArrayBufferTargetWriter ? "in-memory" : false; - this.fastStart = format._options.fastStart ?? fastStartDefault; - if (this.fastStart === "in-memory" || this.fastStart === "fragmented") { - this.writer.ensureMonotonicity = true; - } - } - async start() { - const release = await this.mutex.acquire(); - const holdsAvc = this.output._tracks.some((x) => x.type === "video" && x.source._codec === "avc"); - this.boxWriter.writeBox(ftyp({ - holdsAvc, - fragmented: this.fastStart === "fragmented" - })); - this.ftypSize = this.writer.getPos(); - if (this.fastStart === "in-memory") { - this.mdat = mdat(false); - } else if (this.fastStart === "fragmented") { - } else { - this.mdat = mdat(true); - this.boxWriter.writeBox(this.mdat); - } - await this.writer.flush(); - release(); - } - getVideoTrackData(track, meta) { - const existingTrackData = this.trackDatas.find((x) => x.track === track); - if (existingTrackData) { - return existingTrackData; - } - validateVideoChunkMetadata(meta); - assert(meta); - assert(meta.decoderConfig); - assert(meta.decoderConfig.codedWidth !== void 0); - assert(meta.decoderConfig.codedHeight !== void 0); - const newTrackData = { - track, - type: "video", - info: { - width: meta.decoderConfig.codedWidth, - height: meta.decoderConfig.codedHeight, - decoderConfig: meta.decoderConfig - }, - timescale: track.metadata.frameRate ?? 57600, - samples: [], - sampleQueue: [], - timestampProcessingQueue: [], - timeToSampleTable: [], - compositionTimeOffsetTable: [], - lastTimescaleUnits: null, - lastSample: null, - finalizedChunks: [], - currentChunk: null, - compactlyCodedChunkTable: [] - }; - this.trackDatas.push(newTrackData); - this.trackDatas.sort((a, b) => a.track.id - b.track.id); - return newTrackData; - } - getAudioTrackData(track, meta) { - const existingTrackData = this.trackDatas.find((x) => x.track === track); - if (existingTrackData) { - return existingTrackData; - } - validateAudioChunkMetadata(meta); - assert(meta); - assert(meta.decoderConfig); - const newTrackData = { - track, - type: "audio", - info: { - numberOfChannels: meta.decoderConfig.numberOfChannels, - sampleRate: meta.decoderConfig.sampleRate, - decoderConfig: meta.decoderConfig - }, - timescale: meta.decoderConfig.sampleRate, - samples: [], - sampleQueue: [], - timestampProcessingQueue: [], - timeToSampleTable: [], - compositionTimeOffsetTable: [], - lastTimescaleUnits: null, - lastSample: null, - finalizedChunks: [], - currentChunk: null, - compactlyCodedChunkTable: [] - }; - this.trackDatas.push(newTrackData); - this.trackDatas.sort((a, b) => a.track.id - b.track.id); - return newTrackData; - } - getSubtitleTrackData(track, meta) { - const existingTrackData = this.trackDatas.find((x) => x.track === track); - if (existingTrackData) { - return existingTrackData; - } - validateSubtitleMetadata(meta); - assert(meta); - assert(meta.config); - const newTrackData = { - track, - type: "subtitle", - info: { - config: meta.config - }, - timescale: 1e3, - // Reasonable - samples: [], - sampleQueue: [], - timestampProcessingQueue: [], - timeToSampleTable: [], - compositionTimeOffsetTable: [], - lastTimescaleUnits: null, - lastSample: null, - finalizedChunks: [], - currentChunk: null, - compactlyCodedChunkTable: [], - lastCueEndTimestamp: 0, - cueQueue: [], - nextSourceId: 0, - cueToSourceId: /* @__PURE__ */ new WeakMap() - }; - this.trackDatas.push(newTrackData); - this.trackDatas.sort((a, b) => a.track.id - b.track.id); - this.validateAndNormalizeTimestamp(track, 0, true); - return newTrackData; - } - async addEncodedVideoChunk(track, chunk, meta) { - const release = await this.mutex.acquire(); - try { - const trackData = this.getVideoTrackData(track, meta); - const data = new Uint8Array(chunk.byteLength); - chunk.copyTo(data); - 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(); - } - } - async addEncodedAudioChunk(track, chunk, meta) { - const release = await this.mutex.acquire(); - try { - const trackData = this.getAudioTrackData(track, meta); - const data = new Uint8Array(chunk.byteLength); - chunk.copyTo(data); - 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(); - } - } - async addSubtitleCue(track, cue, meta) { - const release = await this.mutex.acquire(); - try { - const trackData = this.getSubtitleTrackData(track, meta); - this.validateAndNormalizeTimestamp(trackData.track, 1e6 * cue.timestamp, true); - if (track.source._codec === "webvtt") { - trackData.cueQueue.push(cue); - await this.processWebVTTCues(trackData, cue.timestamp); - } else { - } - } finally { - release(); - } - } - async processWebVTTCues(trackData, until) { - while (trackData.cueQueue.length > 0) { - 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); - } - 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); - const box2 = vtte(); - this.auxBoxWriter.writeBox(box2); - 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++) { - const cue = trackData.cueQueue[i]; - if (cue.timestamp >= sampleEnd) { - break; - } - inlineTimestampRegex.lastIndex = 0; - 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) { - const box3 = vtta(cue.notes); - this.auxBoxWriter.writeBox(box3); - } - 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); - } - } - 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) { - const sample = { - timestamp, - decodeTimestamp: timestamp, - // This may be refined later - duration, - data, - size: data.byteLength, - type, - // Will be refined once the next sample comes in - timescaleUnitsToNextSample: intoTimescale(duration, trackData.timescale) - }; - return sample; - } - processTimestamps(trackData) { - if (trackData.timestampProcessingQueue.length === 0) { - return; - } - const sortedTimestamps = trackData.timestampProcessingQueue.map((x) => x.timestamp).sort((a, b) => a - b); - for (let i = 0; i < trackData.timestampProcessingQueue.length; i++) { - const sample = trackData.timestampProcessingQueue[i]; - sample.decodeTimestamp = sortedTimestamps[i]; - const sampleCompositionTimeOffset = intoTimescale(sample.timestamp - sample.decodeTimestamp, trackData.timescale); - const durationInTimescale = intoTimescale(sample.duration, trackData.timescale); - if (trackData.lastTimescaleUnits !== null) { - assert(trackData.lastSample); - 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); - if (lastTableEntry.sampleCount === 1) { - lastTableEntry.sampleDelta = delta; - const entryBefore = trackData.timeToSampleTable[trackData.timeToSampleTable.length - 2]; - if (entryBefore && entryBefore.sampleDelta === delta) { - entryBefore.sampleCount++; - trackData.timeToSampleTable.pop(); - lastTableEntry = entryBefore; - } - } else if (lastTableEntry.sampleDelta !== delta) { - lastTableEntry.sampleCount--; - trackData.timeToSampleTable.push(lastTableEntry = { - sampleCount: 1, - sampleDelta: delta - }); - } - if (lastTableEntry.sampleDelta === durationInTimescale) { - lastTableEntry.sampleCount++; - } else { - trackData.timeToSampleTable.push({ - sampleCount: 1, - sampleDelta: durationInTimescale - }); - } - const lastCompositionTimeOffsetTableEntry = last(trackData.compositionTimeOffsetTable); - assert(lastCompositionTimeOffsetTableEntry); - if (lastCompositionTimeOffsetTableEntry.sampleCompositionTimeOffset === sampleCompositionTimeOffset) { - lastCompositionTimeOffsetTableEntry.sampleCount++; - } else { - trackData.compositionTimeOffsetTable.push({ - sampleCount: 1, - sampleCompositionTimeOffset - }); - } - } - } else { - trackData.lastTimescaleUnits = 0; - if (this.fastStart !== "fragmented") { - trackData.timeToSampleTable.push({ - sampleCount: 1, - sampleDelta: durationInTimescale - }); - trackData.compositionTimeOffsetTable.push({ - sampleCount: 1, - sampleCompositionTimeOffset - }); - } - } - trackData.lastSample = sample; - } - trackData.timestampProcessingQueue.length = 0; - } - async registerSample(trackData, sample) { - if (this.fastStart === "fragmented") { - trackData.sampleQueue.push(sample); - await this.interleaveSamples(); - } else { - await this.addSampleToTrack(trackData, sample); - } - } - async addSampleToTrack(trackData, sample) { - if (sample.type === "key") { - this.processTimestamps(trackData); - } - if (this.fastStart !== "fragmented") { - trackData.samples.push(sample); - } - let beginNewChunk = false; - if (!trackData.currentChunk) { - beginNewChunk = true; - } else { - const currentChunkDuration = sample.timestamp - trackData.currentChunk.startTimestamp; - if (this.fastStart === "fragmented") { - const keyFrameQueuedEverywhere = this.trackDatas.every((otherTrackData) => { - if (trackData === otherTrackData) { - return sample.type === "key"; - } - const firstQueuedSample = otherTrackData.sampleQueue[0]; - return firstQueuedSample && firstQueuedSample.type === "key"; - }); - if (currentChunkDuration >= 1 && keyFrameQueuedEverywhere) { - beginNewChunk = true; - await this.finalizeFragment(); - } - } else { - beginNewChunk = currentChunkDuration >= 0.5; - } - } - if (beginNewChunk) { - if (trackData.currentChunk) { - await this.finalizeCurrentChunk(trackData); - } - trackData.currentChunk = { - startTimestamp: sample.timestamp, - samples: [], - offset: null, - moofOffset: null - }; - } - assert(trackData.currentChunk); - trackData.currentChunk.samples.push(sample); - trackData.timestampProcessingQueue.push(sample); - } - async finalizeCurrentChunk(trackData) { - assert(this.fastStart !== "fragmented"); - if (!trackData.currentChunk) return; - trackData.finalizedChunks.push(trackData.currentChunk); - this.finalizedChunks.push(trackData.currentChunk); - if (trackData.compactlyCodedChunkTable.length === 0 || last(trackData.compactlyCodedChunkTable).samplesPerChunk !== trackData.currentChunk.samples.length) { - trackData.compactlyCodedChunkTable.push({ - firstChunk: trackData.finalizedChunks.length, - // 1-indexed - samplesPerChunk: trackData.currentChunk.samples.length - }); - } - if (this.fastStart === "in-memory") { - trackData.currentChunk.offset = 0; - return; - } - trackData.currentChunk.offset = this.writer.getPos(); - for (const sample of trackData.currentChunk.samples) { - assert(sample.data); - this.writer.write(sample.data); - sample.data = null; - } - await this.writer.flush(); - } - async interleaveSamples() { - assert(this.fastStart === "fragmented"); - for (const track of this.output._tracks) { - if (!track.source._closed && !this.trackDatas.some((x) => x.track === track)) { - return; - } - } - outer: - while (true) { - let trackWithMinTimestamp = null; - let minTimestamp = Infinity; - for (const trackData of this.trackDatas) { - if (trackData.sampleQueue.length === 0 && !trackData.track.source._closed) { - break outer; - } - if (trackData.sampleQueue.length > 0 && trackData.sampleQueue[0].timestamp < minTimestamp) { - trackWithMinTimestamp = trackData; - minTimestamp = trackData.sampleQueue[0].timestamp; - } - } - if (!trackWithMinTimestamp) { - break; - } - const sample = trackWithMinTimestamp.sampleQueue.shift(); - await this.addSampleToTrack(trackWithMinTimestamp, sample); - } - } - async finalizeFragment(flushWriter = true) { - assert(this.fastStart === "fragmented"); - const fragmentNumber = this.nextFragmentNumber++; - if (fragmentNumber === 1) { - const movieBox = moov(this.trackDatas, this.creationTime, true); - this.boxWriter.writeBox(movieBox); - } - const tracksInFragment = this.trackDatas.filter((x) => x.currentChunk); - const moofOffset = this.writer.getPos(); - const moofBox = moof(fragmentNumber, tracksInFragment); - this.boxWriter.writeBox(moofBox); - { - const mdatBox = mdat(false); - let totalTrackSampleSize = 0; - for (const trackData of tracksInFragment) { - for (const sample of trackData.currentChunk.samples) { - totalTrackSampleSize += sample.size; - } - } - let mdatSize = this.boxWriter.measureBox(mdatBox) + totalTrackSampleSize; - if (mdatSize >= 2 ** 32) { - mdatBox.largeSize = true; - mdatSize = this.boxWriter.measureBox(mdatBox) + totalTrackSampleSize; - } - mdatBox.size = mdatSize; - this.boxWriter.writeBox(mdatBox); - } - for (const trackData of tracksInFragment) { - trackData.currentChunk.offset = this.writer.getPos(); - trackData.currentChunk.moofOffset = moofOffset; - for (const sample of trackData.currentChunk.samples) { - this.writer.write(sample.data); - sample.data = null; - } - } - const endPos = this.writer.getPos(); - this.writer.seek(this.boxWriter.offsets.get(moofBox)); - const newMoofBox = moof(fragmentNumber, tracksInFragment); - this.boxWriter.writeBox(newMoofBox); - this.writer.seek(endPos); - for (const trackData of tracksInFragment) { - trackData.finalizedChunks.push(trackData.currentChunk); - this.finalizedChunks.push(trackData.currentChunk); - trackData.currentChunk = null; - } - if (flushWriter) { - 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") { - const trackData = this.trackDatas.find((x) => x.track === track); - if (trackData) { - await this.processWebVTTCues(trackData, Infinity); - } - } - if (this.fastStart === "fragmented") { - await this.interleaveSamples(); - } - release(); - } - /** 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 (const trackData of this.trackDatas) { - if (trackData.type === "subtitle" && trackData.track.source._codec === "webvtt") { - await this.processWebVTTCues(trackData, Infinity); - } - } - if (this.fastStart === "fragmented") { - 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 (const trackData of this.trackDatas) { - this.processTimestamps(trackData); - await this.finalizeCurrentChunk(trackData); - } - } - if (this.fastStart === "in-memory") { - assert(this.mdat); - let mdatSize; - for (let i = 0; i < 2; i++) { - 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 (const chunk of this.finalizedChunks) { - chunk.offset = currentChunkPos; - 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; - } - const movieBox = moov(this.trackDatas, this.creationTime); - this.boxWriter.writeBox(movieBox); - this.mdat.size = mdatSize; - this.boxWriter.writeBox(this.mdat); - 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") { - const startPos = this.writer.getPos(); - const mfraBox = mfra(this.trackDatas); - this.boxWriter.writeBox(mfraBox); - 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); - const mdatPos = this.boxWriter.offsets.get(this.mdat); - assert(mdatPos !== void 0); - const mdatSize = this.writer.getPos() - mdatPos; - this.mdat.size = mdatSize; - this.mdat.largeSize = mdatSize >= 2 ** 32; - this.boxWriter.patchBox(this.mdat); - const movieBox = moov(this.trackDatas, this.creationTime); - if (typeof this.fastStart === "object") { - this.writer.seek(this.ftypSize); - this.boxWriter.writeBox(movieBox); - const remainingBytes = mdatPos - this.writer.getPos(); - this.boxWriter.writeBox(free(remainingBytes)); - } else { - this.boxWriter.writeBox(movieBox); - } - } - release(); - } - }; - - // src/matroska/ebml.ts - var EBMLFloat32 = class { - constructor(value) { - this.value = value; - } - }; - var EBMLFloat64 = class { - constructor(value) { - this.value = value; - } - }; - var EBMLSignedInt = class { - constructor(value) { - this.value = value; - } - }; - var measureUnsignedInt = (value) => { - if (value < 1 << 8) { - return 1; - } else if (value < 1 << 16) { - return 2; - } else if (value < 1 << 24) { - return 3; - } else if (value < 2 ** 32) { - return 4; - } else if (value < 2 ** 40) { - return 5; - } else { - return 6; - } - }; - var measureSignedInt = (value) => { - if (value >= -(1 << 6) && value < 1 << 6) { - return 1; - } else if (value >= -(1 << 13) && value < 1 << 13) { - return 2; - } else if (value >= -(1 << 20) && value < 1 << 20) { - return 3; - } else if (value >= -(1 << 27) && value < 1 << 27) { - return 4; - } else if (value >= -(2 ** 34) && value < 2 ** 34) { - return 5; - } else { - return 6; - } - }; - var measureEBMLVarInt = (value) => { - if (value < (1 << 7) - 1) { - return 1; - } else if (value < (1 << 14) - 1) { - return 2; - } else if (value < (1 << 21) - 1) { - return 3; - } else if (value < (1 << 28) - 1) { - return 4; - } else if (value < 2 ** 35 - 1) { - return 5; - } else if (value < 2 ** 42 - 1) { - return 6; - } else { - throw new Error("EBML VINT size not supported " + value); - } - }; - - // src/matroska/matroska-muxer.ts - var MAX_CHUNK_LENGTH_MS = 2 ** 15; - var APP_NAME = "https://github.com/Vanilagy/webm-muxer"; - var SEGMENT_SIZE_BYTES = 6; - var CLUSTER_SIZE_BYTES = 5; - var CODEC_STRING_MAP = { - 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" - }; - var TRACK_TYPE_MAP = { - video: 1, - audio: 2, - subtitle: 17 - }; - var MatroskaMuxer = class extends Muxer { - constructor(output, format) { - super(output); - this.timestampsMustStartAtZero = false; - this.helper = new Uint8Array(8); - this.helperView = new DataView(this.helper.buffer); - /** - * Stores the position from the start of the file to where EBML elements have been written. This is used to - * rewrite/edit elements that were already added before, and to measure sizes of things. - */ - this.offsets = /* @__PURE__ */ new WeakMap(); - /** Same as offsets, but stores position where the element's data starts (after ID and size fields). */ - this.dataOffsets = /* @__PURE__ */ new WeakMap(); - this.trackDatas = []; - this.segment = null; - this.segmentInfo = null; - this.seekHead = null; - this.tracksElement = null; - this.segmentDuration = null; - this.cues = null; - this.currentCluster = null; - this.currentClusterMsTimestamp = null; - this.trackDatasInCurrentCluster = /* @__PURE__ */ new Set(); - this.duration = 0; - this.writer = output._writer; - this.format = format; - if (this.format._options.streamable) { - this.writer.ensureMonotonicity = true; - } - } - writeByte(value) { - this.helperView.setUint8(0, value); - this.writer.write(this.helper.subarray(0, 1)); - } - writeFloat32(value) { - this.helperView.setFloat32(0, value, false); - this.writer.write(this.helper.subarray(0, 4)); - } - writeFloat64(value) { - this.helperView.setFloat64(0, value, false); - this.writer.write(this.helper); - } - writeUnsignedInt(value, width = measureUnsignedInt(value)) { - let pos = 0; - 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; - default: - throw new Error("Bad UINT size " + width); - } - this.writer.write(this.helper.subarray(0, pos)); - } - writeSignedInt(value, width = measureSignedInt(value)) { - if (value < 0) { - value += 2 ** (width * 8); - } - this.writeUnsignedInt(value, width); - } - writeEBMLVarInt(value, width = measureEBMLVarInt(value)) { - let pos = 0; - switch (width) { - case 1: - this.helperView.setUint8(pos++, 1 << 7 | value); - break; - case 2: - this.helperView.setUint8(pos++, 1 << 6 | value >> 8); - this.helperView.setUint8(pos++, value); - break; - case 3: - this.helperView.setUint8(pos++, 1 << 5 | value >> 16); - this.helperView.setUint8(pos++, value >> 8); - this.helperView.setUint8(pos++, value); - break; - case 4: - this.helperView.setUint8(pos++, 1 << 4 | value >> 24); - this.helperView.setUint8(pos++, value >> 16); - this.helperView.setUint8(pos++, value >> 8); - this.helperView.setUint8(pos++, value); - break; - case 5: - this.helperView.setUint8(pos++, 1 << 3 | value / 2 ** 32 & 7); - 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 & 3); - 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); - this.helperView.setUint8(pos++, value); - break; - default: - throw new Error("Bad EBML VINT size " + width); - } - this.writer.write(this.helper.subarray(0, pos)); - } - // Assumes the string is ASCII - writeString(str) { - this.writer.write(new Uint8Array(str.split("").map((x) => x.charCodeAt(0)))); - } - writeEBML(data) { - if (data === null) return; - if (data instanceof Uint8Array) { - this.writer.write(data); - } else if (Array.isArray(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)) { - 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); - } - const startPos = this.writer.getPos(); - this.dataOffsets.set(data, startPos); - this.writeEBML(data.data); - if (data.size !== -1) { - 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") { - const size = data.size ?? measureUnsignedInt(data.data); - this.writeEBMLVarInt(size); - this.writeUnsignedInt(data.data, size); - } else if (typeof data.data === "string") { - this.writeEBMLVarInt(data.data.length); - this.writeString(data.data); - } else if (data.data instanceof Uint8Array) { - this.writeEBMLVarInt(data.data.byteLength, data.size); - this.writer.write(data.data); - } else if (data.data instanceof EBMLFloat32) { - this.writeEBMLVarInt(4); - this.writeFloat32(data.data.value); - } else if (data.data instanceof EBMLFloat64) { - this.writeEBMLVarInt(8); - this.writeFloat64(data.data.value); - } else if (data.data instanceof EBMLSignedInt) { - const size = data.size ?? measureSignedInt(data.data.value); - this.writeEBMLVarInt(size); - this.writeSignedInt(data.data.value, size); - } - } - } - beforeTrackAdd(track) { - 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.` - ); - } - } 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.` - ); - } - } 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.` - ); - } - } else { - throw new Error( - "WebM only supports video, audio and subtitle tracks. Switching to MKV removes this restriction." - ); - } - } - async start() { - const release = await this.mutex.acquire(); - this.writeEBMLHeader(); - if (!this.format._options.streamable) { - this.createSeekHead(); - } - this.createSegmentInfo(); - this.createCues(); - await this.writer.flush(); - release(); - } - writeEBMLHeader() { - const ebmlHeader = { id: 440786851 /* EBML */, data: [ - { id: 17030 /* EBMLVersion */, data: 1 }, - { id: 17143 /* EBMLReadVersion */, data: 1 }, - { id: 17138 /* EBMLMaxIDLength */, data: 4 }, - { id: 17139 /* EBMLMaxSizeLength */, data: 8 }, - { id: 17026 /* DocType */, data: this.format instanceof WebMOutputFormat ? "webm" : "matroska" }, - { id: 17031 /* DocTypeVersion */, data: 2 }, - { id: 17029 /* DocTypeReadVersion */, data: 2 } - ] }; - this.writeEBML(ebmlHeader); - } - /** - * Creates a SeekHead element which is positioned near the start of the file and allows the media player to seek to - * relevant sections more easily. Since we don't know the positions of those sections yet, we'll set them later. - */ - createSeekHead() { - const kaxCues = new Uint8Array([28, 83, 187, 107]); - const kaxInfo = new Uint8Array([21, 73, 169, 102]); - const kaxTracks = new Uint8Array([22, 84, 174, 107]); - const seekHead = { id: 290298740 /* SeekHead */, data: [ - { id: 19899 /* Seek */, data: [ - { id: 21419 /* SeekID */, data: kaxCues }, - { id: 21420 /* SeekPosition */, size: 5, data: 0 } - ] }, - { id: 19899 /* Seek */, data: [ - { id: 21419 /* SeekID */, data: kaxInfo }, - { id: 21420 /* SeekPosition */, size: 5, data: 0 } - ] }, - { id: 19899 /* Seek */, data: [ - { id: 21419 /* SeekID */, data: kaxTracks }, - { id: 21420 /* SeekPosition */, size: 5, data: 0 } - ] } - ] }; - this.seekHead = seekHead; - } - createSegmentInfo() { - const segmentDuration = { id: 17545 /* Duration */, data: new EBMLFloat64(0) }; - this.segmentDuration = segmentDuration; - const segmentInfo = { id: 357149030 /* Info */, data: [ - { id: 2807729 /* TimestampScale */, data: 1e6 }, - { id: 19840 /* MuxingApp */, data: APP_NAME }, - { id: 22337 /* WritingApp */, data: APP_NAME }, - !this.format._options.streamable ? segmentDuration : null - ] }; - this.segmentInfo = segmentInfo; - } - createTracks() { - const tracksElement = { id: 374648427 /* Tracks */, data: [] }; - this.tracksElement = tracksElement; - 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" ? 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() { - const segment = { - id: 408125543 /* Segment */, - size: this.format._options.streamable ? -1 : SEGMENT_SIZE_BYTES, - data: [ - !this.format._options.streamable ? this.seekHead : null, - this.segmentInfo, - this.tracksElement - ] - }; - this.segment = segment; - this.writeEBML(segment); - } - createCues() { - this.cues = { id: 475249515 /* Cues */, data: [] }; - } - get segmentDataOffset() { - assert(this.segment); - return this.dataOffsets.get(this.segment); - } - getVideoTrackData(track, meta) { - const existingTrackData = this.trackDatas.find((x) => x.track === track); - if (existingTrackData) { - return existingTrackData; - } - validateVideoChunkMetadata(meta); - assert(meta); - assert(meta.decoderConfig); - assert(meta.decoderConfig.codedWidth !== void 0); - assert(meta.decoderConfig.codedHeight !== void 0); - const newTrackData = { - track, - type: "video", - info: { - width: meta.decoderConfig.codedWidth, - height: meta.decoderConfig.codedHeight, - decoderConfig: meta.decoderConfig - }, - chunkQueue: [], - lastWrittenMsTimestamp: null - }; - this.trackDatas.push(newTrackData); - this.trackDatas.sort((a, b) => a.track.id - b.track.id); - return newTrackData; - } - getAudioTrackData(track, meta) { - const existingTrackData = this.trackDatas.find((x) => x.track === track); - if (existingTrackData) { - return existingTrackData; - } - validateAudioChunkMetadata(meta); - assert(meta); - assert(meta.decoderConfig); - const newTrackData = { - track, - type: "audio", - info: { - numberOfChannels: meta.decoderConfig.numberOfChannels, - sampleRate: meta.decoderConfig.sampleRate, - decoderConfig: meta.decoderConfig - }, - chunkQueue: [], - lastWrittenMsTimestamp: null - }; - this.trackDatas.push(newTrackData); - this.trackDatas.sort((a, b) => a.track.id - b.track.id); - return newTrackData; - } - getSubtitleTrackData(track, meta) { - const existingTrackData = this.trackDatas.find((x) => x.track === track); - if (existingTrackData) { - return existingTrackData; - } - validateSubtitleMetadata(meta); - assert(meta); - assert(meta.config); - const newTrackData = { - track, - type: "subtitle", - info: { - config: meta.config - }, - chunkQueue: [], - lastWrittenMsTimestamp: null - }; - this.trackDatas.push(newTrackData); - this.trackDatas.sort((a, b) => a.track.id - b.track.id); - return newTrackData; - } - async addEncodedVideoChunk(track, chunk, meta) { - const release = await this.mutex.acquire(); - try { - const trackData = this.getVideoTrackData(track, meta); - const data = new Uint8Array(chunk.byteLength); - chunk.copyTo(data); - 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(); - } finally { - release(); - } - } - async addEncodedAudioChunk(track, chunk, meta) { - const release = await this.mutex.acquire(); - try { - const trackData = this.getAudioTrackData(track, meta); - const data = new Uint8Array(chunk.byteLength); - chunk.copyTo(data); - 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 { - release(); - } - } - async addSubtitleCue(track, cue, meta) { - const release = await this.mutex.acquire(); - try { - const trackData = this.getSubtitleTrackData(track, meta); - const timestamp = this.validateAndNormalizeTimestamp(trackData.track, 1e6 * cue.timestamp, true); - let bodyText = cue.text; - const timestampMs = Math.floor(timestamp * 1e3); - inlineTimestampRegex.lastIndex = 0; - bodyText = bodyText.replace(inlineTimestampRegex, (match) => { - 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 ?? ""}`; - const subtitleChunk = this.createInternalChunk( - body, - timestamp, - cue.duration, - "key", - additions.trim() ? textEncoder.encode(additions) : null - ); - trackData.chunkQueue.push(subtitleChunk); - await this.interleaveChunks(); - } finally { - release(); - } - } - async interleaveChunks() { - for (const track of this.output._tracks) { - if (!track.source._closed && !this.trackDatas.some((x) => x.track === track)) { - return; - } - } - outer: - while (true) { - let trackWithMinTimestamp = null; - let minTimestamp = Infinity; - for (const trackData of this.trackDatas) { - if (trackData.chunkQueue.length === 0 && !trackData.track.source._closed) { - break outer; - } - if (trackData.chunkQueue.length > 0 && trackData.chunkQueue[0].timestamp < minTimestamp) { - trackWithMinTimestamp = trackData; - minTimestamp = trackData.chunkQueue[0].timestamp; - } - } - if (!trackWithMinTimestamp) { - break; - } - const chunk = trackWithMinTimestamp.chunkQueue.shift(); - this.writeBlock(trackWithMinTimestamp, chunk); - } - await this.writer.flush(); - } - /** Due to [a bug in Chromium](https://bugs.chromium.org/p/chromium/issues/detail?id=1377842), VP9 streams often - * lack color space information. This method patches in that information. */ - // http://downloads.webmproject.org/docs/vp9/vp9-bitstream_superframe-and-uncompressed-header_v1.0.pdf - fixVP9ColorSpace(trackData, chunk) { - if (chunk.type !== "key") return; - if (!trackData.info.decoderConfig.colorSpace || !trackData.info.decoderConfig.colorSpace.matrix) return; - let i = 0; - if (readBits(chunk.data, 0, 2) !== 2) return; - 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++; - const showExistingFrame = readBits(chunk.data, i + 0, i + 1); - i++; - if (showExistingFrame) return; - const frameType = readBits(chunk.data, i + 0, i + 1); - i++; - if (frameType !== 0) return; - i += 2; - const syncCode = readBits(chunk.data, i + 0, i + 24); - i += 24; - if (syncCode !== 4817730) return; - if (profile >= 2) i++; - 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) { - const internalChunk = { - data, - type, - timestamp, - duration, - additions - }; - return internalChunk; - } - /** Writes a block containing media data to the file. */ - writeBlock(trackData, chunk) { - if (!this.segment) { - this.createTracks(); - this.createSegment(); - } - const msTimestamp = Math.floor(1e3 * chunk.timestamp); - const keyFrameQueuedEverywhere = this.trackDatas.every((otherTrackData) => { - if (otherTrackData.track.source._closed) { - return true; - } - if (trackData === otherTrackData) { - return chunk.type === "key"; - } - const firstQueuedSample = otherTrackData.chunkQueue[0]; - return firstQueuedSample && firstQueuedSample.type === "key"; - }); - if (!this.currentCluster || keyFrameQueuedEverywhere && msTimestamp - this.currentClusterMsTimestamp >= 1e3) { - this.createNewCluster(msTimestamp); - } - const relativeTimestamp = msTimestamp - this.currentClusterMsTimestamp; - if (relativeTimestamp < 0) { - return; - } - 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.` - ); - } - const prelude = new Uint8Array(4); - const view2 = new DataView(prelude.buffer); - view2.setUint8(0, 128 | trackData.track.id); - view2.setInt16(1, relativeTimestamp, false); - const msDuration = Math.floor(1e3 * chunk.duration); - if (msDuration === 0 && !chunk.additions) { - view2.setUint8(3, Number(chunk.type === "key") << 7); - const simpleBlock = { id: 163 /* SimpleBlock */, data: [ - prelude, - chunk.data - ] }; - this.writeEBML(simpleBlock); - } else { - 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.additions ? { id: 30113 /* BlockAdditions */, data: [ - { id: 166 /* BlockMore */, data: [ - { id: 165 /* BlockAdditional */, data: chunk.additions }, - { id: 238 /* BlockAddID */, data: 1 } - ] } - ] } : null, - msDuration > 0 ? { id: 155 /* BlockDuration */, data: msDuration } : null - ] }; - this.writeEBML(blockGroup); - } - this.duration = Math.max(this.duration, msTimestamp + msDuration); - trackData.lastWrittenMsTimestamp = msTimestamp; - this.trackDatasInCurrentCluster.add(trackData); - } - /** Creates a new Cluster element to contain media chunks. */ - createNewCluster(msTimestamp) { - if (this.currentCluster && !this.format._options.streamable) { - this.finalizeCurrentCluster(); - } - this.currentCluster = { - id: 524531317 /* Cluster */, - size: this.format._options.streamable ? -1 : CLUSTER_SIZE_BYTES, - data: [ - { id: 231 /* Timestamp */, data: msTimestamp } - ] - }; - this.writeEBML(this.currentCluster); - this.currentClusterMsTimestamp = msTimestamp; - this.trackDatasInCurrentCluster.clear(); - } - finalizeCurrentCluster() { - assert(this.currentCluster); - 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); - 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 }, - // We only write out cues for tracks that have at least one chunk in this cluster - ...[...this.trackDatasInCurrentCluster].map((trackData) => { - return { id: 183 /* CueTrackPositions */, data: [ - { id: 247 /* CueTrack */, data: trackData.track.id }, - { id: 241 /* CueClusterPosition */, data: clusterOffsetFromSegment } - ] }; - }) - ] }); - } - // eslint-disable-next-line @typescript-eslint/no-misused-promises - async onTrackClose() { - const release = await this.mutex.acquire(); - await this.interleaveChunks(); - release(); - } - /** Finalizes the file, making it ready for use. Must be called after all media chunks have been added. */ - async finalize() { - const release = await this.mutex.acquire(); - if (!this.segment) { - this.createTracks(); - this.createSegment(); - } - for (const trackData of this.trackDatas) { - while (trackData.chunkQueue.length > 0) { - this.writeBlock(trackData, trackData.chunkQueue.shift()); - } - } - if (!this.format._options.streamable && this.currentCluster) { - this.finalizeCurrentCluster(); - } - assert(this.cues); - this.writeEBML(this.cues); - if (!this.format._options.streamable) { - 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); - 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(endPos); - } - release(); - } - }; - - // src/output-format.ts - var OutputFormat = class { - }; - var Mp4OutputFormat = class extends OutputFormat { - constructor(options = {}) { - if (!options || typeof options !== "object") { - throw new TypeError("options must be an object."); - } - if (options.fastStart !== void 0 && ![false, "in-memory", "fragmented"].includes(options.fastStart)) { - throw new TypeError('options.fastStart, when provided, must be false, "in-memory", or "fragmented".'); - } - super(); - this._options = options; - } - /** @internal */ - _createMuxer(output) { - return new IsobmffMuxer(output, this); - } - }; - var MkvOutputFormat2 = class extends OutputFormat { - constructor(options = {}) { - if (!options || typeof options !== "object") { - throw new TypeError("options must be an object."); - } - if (options.streamable !== void 0 && typeof options.streamable !== "boolean") { - throw new TypeError("options.streamable, when provided, must be a boolean."); - } - super(); - this._options = options; - } - /** @internal */ - _createMuxer(output) { - return new MatroskaMuxer(output, this); - } - }; - var WebMOutputFormat = class extends MkvOutputFormat2 { - }; - - // src/media-source.ts - var MediaSource = class { - constructor() { - /** @internal */ - this._connectedTrack = null; - /** @internal */ - this._closed = false; - /** @internal */ - this._offsetTimestamps = false; - } - /** @internal */ - _ensureValidDigest() { - if (!this._connectedTrack) { - throw new Error("Cannot call digest without connecting the source to an output track."); - } - if (!this._connectedTrack.output._started) { - throw new Error("Cannot call digest before output has been started."); - } - if (this._connectedTrack.output._finalizing) { - throw new Error("Cannot call digest after output has started finalizing."); - } - if (this._closed) { - throw new Error("Cannot call digest after source has been closed."); - } - } - /** @internal */ - _start() { - } - /** @internal */ - async _flush() { - } - close() { - if (this._closed) { - throw new Error("Source already closed."); - } - if (!this._connectedTrack) { - throw new Error("Cannot call close without connecting the source to an output track."); - } - if (!this._connectedTrack.output._started) { - throw new Error("Cannot call close before output has been started."); - } - this._closed = true; - if (this._connectedTrack.output._finalizing) { - return; - } - this._connectedTrack.output._muxer.onTrackClose(this._connectedTrack); - } - }; - var VideoSource = class extends MediaSource { - constructor(codec) { - super(); - /** @internal */ - this._connectedTrack = null; - if (!VIDEO_CODECS.includes(codec)) { - throw new TypeError(`Invalid video codec '${codec}'. Must be one of: ${VIDEO_CODECS.join(", ")}.`); - } - this._codec = codec; - } - }; - var EncodedVideoChunkSource = class extends VideoSource { - constructor(codec) { - super(codec); - } - digest(chunk, meta) { - if (!(chunk instanceof EncodedVideoChunk)) { - throw new TypeError("chunk must be an EncodedVideoChunk."); - } - this._ensureValidDigest(); - return this._connectedTrack.output._muxer.addEncodedVideoChunk(this._connectedTrack, chunk, meta); - } - }; - var KEY_FRAME_INTERVAL = 5; - var validateVideoCodecConfig = (config) => { - if (!config || typeof config !== "object") { - throw new TypeError("Codec config must be an object."); - } - if (!VIDEO_CODECS.includes(config.codec)) { - throw new TypeError(`Invalid video codec '${config.codec}'. Must be one of: ${VIDEO_CODECS.join(", ")}.`); - } - if (!Number.isInteger(config.bitrate) || config.bitrate <= 0) { - throw new TypeError("config.bitrate must be a positive integer."); - } - if (config.latencyMode !== void 0 && !["quality", "realtime"].includes(config.latencyMode)) { - throw new TypeError("config.latencyMode, when provided, must be 'quality' or 'realtime'."); - } - if (config.onEncodingError !== void 0 && typeof config.onEncodingError !== "function") { - throw new TypeError("config.onEncodingError, when provided, must be a function."); - } - }; - var VideoEncoderWrapper = class { - constructor(source, codecConfig) { - this.source = source; - this.codecConfig = codecConfig; - this.encoder = null; - this.muxer = null; - this.lastMultipleOfKeyFrameInterval = -1; - this.lastWidth = null; - this.lastHeight = null; - validateVideoCodecConfig(codecConfig); - } - async digest(videoFrame) { - 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}.` - ); - } - } else { - this.lastWidth = videoFrame.codedWidth; - this.lastHeight = videoFrame.codedHeight; - } - this.ensureEncoder(videoFrame); - assert(this.encoder); - const multipleOfKeyFrameInterval = Math.floor(videoFrame.timestamp / 1e6 / KEY_FRAME_INTERVAL); - this.encoder.encode(videoFrame, { - keyFrame: multipleOfKeyFrameInterval !== this.lastMultipleOfKeyFrameInterval - }); - this.lastMultipleOfKeyFrameInterval = multipleOfKeyFrameInterval; - if (this.encoder.encodeQueueSize >= 4) { - await new Promise((resolve) => this.encoder.addEventListener("dequeue", resolve, { once: true })); - } - await this.muxer.mutex.currentPromise; - } - ensureEncoder(videoFrame) { - if (this.encoder) { - return; - } - this.encoder = new VideoEncoder({ - output: (chunk, meta) => void this.muxer.addEncodedVideoChunk(this.source._connectedTrack, chunk, meta), - error: this.codecConfig.onEncodingError ?? ((error) => console.error("VideoEncoder error:", error)) - }); - this.encoder.configure({ - codec: buildVideoCodecString( - this.codecConfig.codec, - videoFrame.codedWidth, - videoFrame.codedHeight, - this.codecConfig.bitrate - ), - width: videoFrame.codedWidth, - height: videoFrame.codedHeight, - bitrate: this.codecConfig.bitrate, - framerate: this.source._connectedTrack?.metadata.frameRate, - latencyMode: this.codecConfig.latencyMode, - ...getVideoEncoderConfigExtension(this.codecConfig.codec) - }); - assert(this.source._connectedTrack); - this.muxer = this.source._connectedTrack.output._muxer; - } - async flush() { - if (this.encoder) { - await this.encoder.flush(); - this.encoder.close(); - } - } - }; - var VideoFrameSource = class extends VideoSource { - constructor(codecConfig) { - super(codecConfig.codec); - this._encoder = new VideoEncoderWrapper(this, codecConfig); - } - digest(videoFrame) { - if (!(videoFrame instanceof VideoFrame)) { - throw new TypeError("videoFrame must be a VideoFrame."); - } - return this._encoder.digest(videoFrame); - } - /** @internal */ - _flush() { - return this._encoder.flush(); - } - }; - var CanvasSource = class extends VideoSource { - constructor(canvas, codecConfig) { - if (!(canvas instanceof HTMLCanvasElement)) { - throw new TypeError("canvas must be an HTMLCanvasElement."); - } - super(codecConfig.codec); - this._encoder = new VideoEncoderWrapper(this, codecConfig); - this._canvas = canvas; - } - digest(timestamp, duration = 0) { - if (!Number.isFinite(timestamp) || timestamp < 0) { - throw new TypeError("timestamp must be a non-negative number."); - } - if (!Number.isFinite(duration) || duration < 0) { - throw new TypeError("duration must be a non-negative number."); - } - const frame = new VideoFrame(this._canvas, { - timestamp: Math.round(1e6 * timestamp), - duration: Math.round(1e6 * duration), - alpha: "discard" - }); - const promise = this._encoder.digest(frame); - frame.close(); - return promise; - } - /** @internal */ - _flush() { - return this._encoder.flush(); - } - }; - var MediaStreamVideoTrackSource = class extends VideoSource { - constructor(track, codecConfig) { - if (!(track instanceof MediaStreamTrack) || track.kind !== "video") { - throw new TypeError("track must be a video MediaStreamTrack."); - } - codecConfig = { - ...codecConfig, - latencyMode: "realtime" - }; - super(codecConfig.codec); - /** @internal */ - this._abortController = null; - /** @internal */ - this._offsetTimestamps = true; - this._encoder = new VideoEncoderWrapper(this, codecConfig); - this._track = track; - } - /** @internal */ - _start() { - this._abortController = new AbortController(); - const processor = new MediaStreamTrackProcessor({ track: this._track }); - const consumer = new WritableStream({ - write: (videoFrame) => { - void this._encoder.digest(videoFrame); - videoFrame.close(); - } - }); - processor.readable.pipeTo(consumer, { - signal: this._abortController.signal - }).catch((err) => { - if (err instanceof DOMException && err.name === "AbortError") return; - console.error("Pipe error:", err); - }); - } - /** @internal */ - async _flush() { - if (this._abortController) { - this._abortController.abort(); - this._abortController = null; - } - await this._encoder.flush(); - } - }; - var AudioSource = class extends MediaSource { - constructor(codec) { - super(); - /** @internal */ - this._connectedTrack = null; - if (!AUDIO_CODECS.includes(codec)) { - throw new TypeError(`Invalid audio codec '${codec}'. Must be one of: ${AUDIO_CODECS.join(", ")}.`); - } - this._codec = codec; - } - }; - var EncodedAudioChunkSource = class extends AudioSource { - constructor(codec) { - super(codec); - } - digest(chunk, meta) { - if (!(chunk instanceof EncodedAudioChunk)) { - throw new TypeError("chunk must be an EncodedAudioChunk."); - } - this._ensureValidDigest(); - return this._connectedTrack.output._muxer.addEncodedAudioChunk(this._connectedTrack, chunk, meta); - } - }; - var validateAudioCodecConfig = (config) => { - if (!config || typeof config !== "object") { - throw new TypeError("Codec config must be an object."); - } - if (!AUDIO_CODECS.includes(config.codec)) { - throw new TypeError(`Invalid audio codec '${config.codec}'. Must be one of: ${AUDIO_CODECS.join(", ")}.`); - } - if (!Number.isInteger(config.bitrate) || config.bitrate <= 0) { - throw new TypeError("config.bitrate must be a positive integer."); - } - if (config.onEncodingError !== void 0 && typeof config.onEncodingError !== "function") { - throw new TypeError("config.onEncodingError, when provided, must be a function."); - } - }; - var AudioEncoderWrapper = class { - constructor(source, codecConfig) { - this.source = source; - this.codecConfig = codecConfig; - this.encoder = null; - this.muxer = null; - this.lastNumberOfChannels = null; - this.lastSampleRate = null; - validateAudioCodecConfig(codecConfig); - } - async digest(audioData) { - 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.` - ); - } - } else { - this.lastNumberOfChannels = audioData.numberOfChannels; - this.lastSampleRate = audioData.sampleRate; - } - this.ensureEncoder(audioData); - assert(this.encoder); - this.encoder.encode(audioData); - if (this.encoder.encodeQueueSize >= 4) { - await new Promise((resolve) => this.encoder.addEventListener("dequeue", resolve, { once: true })); - } - await this.muxer.mutex.currentPromise; - } - ensureEncoder(audioData) { - if (this.encoder) { - return; - } - this.encoder = new AudioEncoder({ - output: (chunk, meta) => void this.muxer.addEncodedAudioChunk(this.source._connectedTrack, chunk, meta), - error: this.codecConfig.onEncodingError ?? ((error) => console.error("AudioEncoder error:", error)) - }); - this.encoder.configure({ - codec: buildAudioCodecString(this.codecConfig.codec, audioData.numberOfChannels, audioData.sampleRate), - numberOfChannels: audioData.numberOfChannels, - sampleRate: audioData.sampleRate, - bitrate: this.codecConfig.bitrate, - ...getAudioEncoderConfigExtension(this.codecConfig.codec) - }); - assert(this.source._connectedTrack); - this.muxer = this.source._connectedTrack.output._muxer; - } - async flush() { - if (this.encoder) { - await this.encoder.flush(); - this.encoder.close(); - } - } - }; - var AudioDataSource = class extends AudioSource { - constructor(codecConfig) { - super(codecConfig.codec); - this._encoder = new AudioEncoderWrapper(this, codecConfig); - } - digest(audioData) { - if (!(audioData instanceof AudioData)) { - throw new TypeError("audioData must be an AudioData."); - } - return this._encoder.digest(audioData); - } - /** @internal */ - _flush() { - return this._encoder.flush(); - } - }; - var AudioBufferSource = class extends AudioSource { - constructor(codecConfig) { - super(codecConfig.codec); - /** @internal */ - this._accumulatedFrameCount = 0; - this._encoder = new AudioEncoderWrapper(this, codecConfig); - } - digest(audioBuffer) { - if (!(audioBuffer instanceof AudioBuffer)) { - throw new TypeError("audioBuffer must be an AudioBuffer."); - } - const numberOfChannels = audioBuffer.numberOfChannels; - const sampleRate = audioBuffer.sampleRate; - const numberOfFrames = audioBuffer.length; - const data = new Float32Array(numberOfChannels * numberOfFrames); - for (let channel = 0; channel < numberOfChannels; channel++) { - const channelData = audioBuffer.getChannelData(channel); - data.set(channelData, channel * numberOfFrames); - } - const audioData = new AudioData({ - format: "f32-planar", - sampleRate, - numberOfFrames, - numberOfChannels, - timestamp: Math.round(1e6 * this._accumulatedFrameCount / sampleRate), - data - }); - const promise = this._encoder.digest(audioData); - audioData.close(); - this._accumulatedFrameCount += numberOfFrames; - return promise; - } - /** @internal */ - _flush() { - return this._encoder.flush(); - } - }; - var MediaStreamAudioTrackSource = class extends AudioSource { - constructor(track, codecConfig) { - if (!(track instanceof MediaStreamTrack) || track.kind !== "audio") { - throw new TypeError("track must be an audio MediaStreamTrack."); - } - super(codecConfig.codec); - /** @internal */ - this._abortController = null; - /** @internal */ - this._offsetTimestamps = true; - this._encoder = new AudioEncoderWrapper(this, codecConfig); - this._track = track; - } - /** @internal */ - _start() { - this._abortController = new AbortController(); - const processor = new MediaStreamTrackProcessor({ track: this._track }); - const consumer = new WritableStream({ - write: (audioData) => { - void this._encoder.digest(audioData); - audioData.close(); - } - }); - processor.readable.pipeTo(consumer, { - signal: this._abortController.signal - }).catch((err) => { - if (err instanceof DOMException && err.name === "AbortError") return; - console.error("Pipe error:", err); - }); - } - /** @internal */ - async _flush() { - if (this._abortController) { - this._abortController.abort(); - this._abortController = null; - } - await this._encoder.flush(); - } - }; - var SubtitleSource = class extends MediaSource { - constructor(codec) { - super(); - /** @internal */ - this._connectedTrack = null; - if (!SUBTITLE_CODECS.includes(codec)) { - throw new TypeError(`Invalid subtitle codec '${codec}'. Must be one of: ${SUBTITLE_CODECS.join(", ")}.`); - } - this._codec = codec; - } - }; - var TextSubtitleSource = class extends SubtitleSource { - constructor(codec) { - super(codec); - this._parser = new SubtitleParser({ - codec, - output: (cue, metadata) => this._connectedTrack?.output._muxer.addSubtitleCue(this._connectedTrack, cue, metadata) - }); - } - digest(text) { - if (typeof text !== "string") { - throw new TypeError("text must be a string."); - } - this._ensureValidDigest(); - this._parser.parse(text); - return this._connectedTrack.output._muxer.mutex.currentPromise; - } - }; - - // src/output.ts - var Output = class { - constructor(options) { - /** @internal */ - this._tracks = []; - /** @internal */ - this._started = false; - /** @internal */ - this._finalizing = false; - /** @internal */ - this._mutex = new AsyncMutex(); - if (!options || typeof options !== "object") { - throw new TypeError("options must be an object."); - } - if (!(options.format instanceof OutputFormat)) { - throw new TypeError("options.format must be an OutputFormat."); - } - if (!(options.target instanceof Target)) { - throw new TypeError("options.target must be a Target."); - } - if (options.target._output) { - throw new Error("Target is already used for another output."); - } - options.target._output = this; - this._writer = options.target._createWriter(); - this._muxer = options.format._createMuxer(this); - } - addVideoTrack(source, metadata = {}) { - if (!(source instanceof VideoSource)) { - throw new TypeError("source must be a VideoSource."); - } - if (!metadata || typeof metadata !== "object") { - throw new TypeError("metadata must be an object."); - } - 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)))) { - throw new TypeError(`Invalid video transformation matrix: ${metadata.rotation.join()}`); - } - if (metadata.frameRate !== void 0 && (!Number.isInteger(metadata.frameRate) || metadata.frameRate <= 0)) { - throw new TypeError( - `Invalid video frame rate: ${metadata.frameRate}. Must be a positive integer.` - ); - } - this._addTrack("video", source, metadata); - } - addAudioTrack(source, metadata = {}) { - if (!(source instanceof AudioSource)) { - throw new TypeError("source must be an AudioSource."); - } - if (!metadata || typeof metadata !== "object") { - throw new TypeError("metadata must be an object."); - } - this._addTrack("audio", source, metadata); - } - addSubtitleTrack(source, metadata = {}) { - if (!(source instanceof SubtitleSource)) { - throw new TypeError("source must be a SubtitleSource."); - } - if (!metadata || typeof metadata !== "object") { - throw new TypeError("metadata must be an object."); - } - this._addTrack("subtitle", source, metadata); - } - /** @internal */ - _addTrack(type, source, metadata) { - if (this._started) { - throw new Error("Cannot add track after output has started."); - } - if (source._connectedTrack) { - throw new Error("Source is already used for a track."); - } - const track = { - id: this._tracks.length + 1, - output: this, - type, - source, - metadata - }; - this._muxer.beforeTrackAdd(track); - this._tracks.push(track); - source._connectedTrack = track; - } - async start() { - if (this._started) { - throw new Error("Output already started."); - } - this._started = true; - this._writer.start(); - const release = await this._mutex.acquire(); - await this._muxer.start(); - for (const track of this._tracks) { - track.source._start(); - } - release(); - } - async finalize() { - if (!this._started) { - throw new Error("Cannot finalize before starting."); - } - if (this._finalizing) { - throw new Error("Cannot call finalize twice."); - } - this._finalizing = true; - const release = await this._mutex.acquire(); - const promises = this._tracks.map((x) => x.source._flush()); - await Promise.all(promises); - await this._muxer.finalize(); - await this._writer.flush(); - await this._writer.finalize(); - release(); - } - }; - - // src/source.ts - var Source = class { - constructor() { - /** @internal */ - this._sizePromise = null; - } - /** @internal */ - _getSize() { - return this._sizePromise ??= this._retrieveSize(); - } - }; - var BufferSource = class extends Source { - constructor(buffer) { - if (!(buffer instanceof ArrayBuffer) && !(buffer instanceof Uint8Array)) { - throw new TypeError("buffer must be an ArrayBuffer or Uint8Array."); - } - super(); - this._bytes = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer); - } - /** @internal */ - async _read(start, end) { - return this._bytes.subarray(start, end); - } - /** @internal */ - async _retrieveSize() { - return this._bytes.byteLength; - } - }; - var BlobSource = class extends Source { - constructor(blob) { - if (!(blob instanceof Blob)) { - throw new TypeError("blob must be a Blob."); - } - super(); - this._blob = blob; - } - /** @internal */ - async _read(start, end) { - const slice = this._blob.slice(start, end); - const buffer = await slice.arrayBuffer(); - return new Uint8Array(buffer); - } - /** @internal */ - async _retrieveSize() { - return this._blob.size; - } - }; - var UrlSource = class extends Source { - constructor(url2, options = {}) { - if (typeof url2 !== "string") { - throw new TypeError("url must be a string."); - } - if (!options || typeof options !== "object") { - throw new TypeError("options must be an object."); - } - if (options.withCredentials !== void 0 && typeof options.withCredentials !== "boolean") { - throw new TypeError("options.withCredentials, when specified, must be a boolean."); - } - super(); - /** @internal */ - this._fullData = null; - this._url = url2; - this._withCredentials = options.withCredentials ?? false; - } - /** @internal */ - _makeRequest(range) { - return new Promise((resolve, reject) => { - const xhr = new XMLHttpRequest(); - xhr.open("GET", this._url, true); - xhr.responseType = "arraybuffer"; - xhr.withCredentials = this._withCredentials; - if (range) { - xhr.setRequestHeader("Range", `bytes=${range.start}-${range.end - 1}`); - } - xhr.onload = () => { - if (xhr.status >= 200 && xhr.status < 300) { - const buffer = xhr.response; - if (!range) { - this._fullData = buffer; - } - resolve({ - response: buffer, - statusCode: xhr.status - }); - } else { - reject(new Error(`Error fetching ${this._url}: ${xhr.status} ${xhr.statusText}`)); - } - }; - xhr.onerror = () => { - reject(new Error("Network error occurred.")); - }; - xhr.ontimeout = () => { - reject(new Error("Request timed out.")); - }; - xhr.send(); - }); - } - /** @internal */ - async _read(start, end) { - if (this._fullData) { - return new Uint8Array(this._fullData, start, end - start); - } - const { response, statusCode } = await this._makeRequest({ start, end }); - if (statusCode === 200) { - const fullData = new Uint8Array(response); - return fullData.subarray(start, end); - } - return new Uint8Array(response); - } - /** @internal */ - async _retrieveSize() { - if (this._fullData) { - return this._fullData.byteLength; - } - const xhr = new XMLHttpRequest(); - xhr.open("HEAD", this._url, true); - xhr.withCredentials = this._withCredentials; - await new Promise((resolve, reject) => { - xhr.onload = () => { - if (xhr.status >= 200 && xhr.status < 300) { - resolve(); - } else { - reject(new Error(`Error fetching ${this._url} (HEAD): ${xhr.status} ${xhr.statusText}`)); - } - }; - xhr.onerror = () => { - resolve(); - }; - xhr.send(); - }); - const contentLength = xhr.getResponseHeader("Content-Length"); - if (!contentLength) { - const { response } = await this._makeRequest(); - return response.byteLength; - } - return parseInt(contentLength, 10); - } - }; - - // src/demuxer.ts - var Demuxer = class { - constructor(input) { - this.input = input; - } - }; - - // src/input-track.ts - var InputTrack = class { - /** @internal */ - constructor(backing) { - this._backing = backing; - } - isVideoTrack() { - return this instanceof InputVideoTrack; - } - isAudioTrack() { - return this instanceof InputAudioTrack; - } - computeDuration() { - return this._backing.computeDuration(); - } - }; - var InputVideoTrack = class extends InputTrack { - /** @internal */ - constructor(backing) { - super(backing); - this._backing = backing; - } - getCodec() { - return this._backing.getCodec(); - } - getCodedWidth() { - return this._backing.getCodedWidth(); - } - getCodedHeight() { - return this._backing.getCodedHeight(); - } - getRotation() { - return this._backing.getRotation(); - } - async getDisplayWidth() { - const rotation = await this._backing.getRotation(); - return rotation % 180 === 0 ? this._backing.getCodedWidth() : this._backing.getCodedHeight(); - } - async getDisplayHeight() { - const rotation = await this._backing.getRotation(); - return rotation % 180 === 0 ? this._backing.getCodedHeight() : this._backing.getCodedWidth(); - } - getDecoderConfig() { - return this._backing.getDecoderConfig(); - } - async getCodecMimeType() { - const decoderConfig = await this.getDecoderConfig(); - return decoderConfig.codec; - } - }; - var InputAudioTrack = class extends InputTrack { - /** @internal */ - constructor(backing) { - super(backing); - this._backing = backing; - } - getCodec() { - return this._backing.getCodec(); - } - getNumberOfChannels() { - return this._backing.getNumberOfChannels(); - } - getSampleRate() { - return this._backing.getSampleRate(); - } - getDecoderConfig() { - return this._backing.getDecoderConfig(); - } - async getCodecMimeType() { - const decoderConfig = await this.getDecoderConfig(); - return decoderConfig.codec; - } - }; - - // src/reader.ts - var Reader = class { - constructor(source, maxStorableBytes = Infinity) { - this.source = source; - this.maxStorableBytes = maxStorableBytes; - this.loadedSegments = []; - this.loadingSegments = []; - this.sourceSizePromise = null; - this.nextAge = 0; - this.totalStoredBytes = 0; - } - async loadRange(start, end) { - end = Math.min(end, await this.source._getSize()); - const matchingLoadingSegment = this.loadingSegments.find((x) => x.start <= start && x.end >= end); - if (matchingLoadingSegment) { - await matchingLoadingSegment.promise; - return; - } - const encasingSegmentExists = this.loadedSegments.some((x) => x.start <= start && x.end >= end); - if (encasingSegmentExists) { - return; - } - const bytesPromise = this.source._read(start, end); - const loadingSegment = { start, end, promise: bytesPromise }; - this.loadingSegments.push(loadingSegment); - const bytes2 = await bytesPromise; - removeItem(this.loadingSegments, loadingSegment); - this.insertIntoLoadedSegments(start, bytes2); - } - insertIntoLoadedSegments(start, bytes2) { - const segment = { - start, - end: start + bytes2.byteLength, - bytes: bytes2, - view: new DataView(bytes2.buffer), - age: this.nextAge++ - }; - let index = binarySearchLessOrEqual(this.loadedSegments, start, (x) => x.start); - if (index === -1 || this.loadedSegments[index].start < segment.start) { - index++; - } - this.loadedSegments.splice(index, 0, segment); - this.totalStoredBytes += bytes2.byteLength; - for (let i = index + 1; i < this.loadedSegments.length; i++) { - const otherSegment = this.loadedSegments[i]; - if (otherSegment.start >= segment.end) { - break; - } - if (segment.start <= otherSegment.start && otherSegment.end <= segment.end) { - this.loadedSegments.splice(i, 1); - i--; - } - } - while (this.totalStoredBytes > this.maxStorableBytes && this.loadedSegments.length > 1) { - let oldestSegment = null; - let oldestSegmentIndex = -1; - for (let i = 0; i < this.loadedSegments.length; i++) { - const candidate = this.loadedSegments[i]; - if (!oldestSegment || candidate.age < oldestSegment.age) { - oldestSegment = candidate; - oldestSegmentIndex = i; - } - } - assert(oldestSegment); - this.totalStoredBytes -= oldestSegment.bytes.byteLength; - this.loadedSegments.splice(oldestSegmentIndex, 1); - } - } - getViewAndOffset(start, end) { - const startIndex = binarySearchLessOrEqual(this.loadedSegments, start, (x) => x.start); - let segment = null; - if (startIndex !== -1) { - for (let i = startIndex; i < this.loadedSegments.length; i++) { - const candidate = this.loadedSegments[i]; - if (candidate.start > start) { - break; - } - if (end <= candidate.end) { - segment = candidate; - break; - } - } - } - if (!segment) { - throw new Error(`No segment loaded for range [${start}, ${end}).`); - } - segment.age = this.nextAge++; - return { - view: segment.view, - offset: segment.bytes.byteOffset + start - segment.start - }; - } - forgetRange(start, end) { - if (end <= start) { - return; - } - const startIndex = binarySearchLessOrEqual(this.loadedSegments, start, (x) => x.start); - if (startIndex === -1) { - return; - } - const segment = this.loadedSegments[startIndex]; - if (segment.start !== start || segment.end !== end) { - return; - } - this.loadedSegments.splice(startIndex, 1); - this.totalStoredBytes -= segment.bytes.byteLength; - } - }; - - // src/isobmff/isobmff-reader.ts - var IsobmffReader = class { - constructor(reader) { - this.reader = reader; - this.pos = 0; - } - readRange(start, end) { - const { view: view2, offset } = this.reader.getViewAndOffset(start, end); - return new Uint8Array(view2.buffer, offset, end - start); - } - readU8() { - const { view: view2, offset } = this.reader.getViewAndOffset(this.pos, this.pos + 1); - this.pos++; - return view2.getUint8(offset); - } - readU16() { - const { view: view2, offset } = this.reader.getViewAndOffset(this.pos, this.pos + 2); - this.pos += 2; - return view2.getUint16(offset, false); - } - readU24() { - const { view: view2, offset } = this.reader.getViewAndOffset(this.pos, this.pos + 3); - this.pos += 3; - const high = view2.getUint16(offset, false); - const low = view2.getUint8(offset + 2); - return high * 256 + low; - } - readS32() { - const { view: view2, offset } = this.reader.getViewAndOffset(this.pos, this.pos + 4); - this.pos += 4; - return view2.getInt32(offset, false); - } - readU32() { - const { view: view2, offset } = this.reader.getViewAndOffset(this.pos, this.pos + 4); - this.pos += 4; - return view2.getUint32(offset, false); - } - readI32() { - const { view: view2, offset } = this.reader.getViewAndOffset(this.pos, this.pos + 4); - this.pos += 4; - return view2.getInt32(offset, false); - } - readU64() { - const high = this.readU32(); - const low = this.readU32(); - return high * 4294967296 + low; - } - readF64() { - const { view: view2, offset } = this.reader.getViewAndOffset(this.pos, this.pos + 8); - this.pos += 8; - return view2.getFloat64(offset, false); - } - readFixed_16_16() { - return this.readS32() / 65536; - } - readFixed_2_30() { - return this.readS32() / 1073741824; - } - readAscii(length) { - const { view: view2, offset } = this.reader.getViewAndOffset(this.pos, this.pos + length); - this.pos += length; - let str = ""; - for (let i = 0; i < length; i++) { - str += String.fromCharCode(view2.getUint8(offset + i)); - } - return str; - } - readIsomVariableInteger() { - let result = 0; - for (let i = 0; i < 4; i++) { - result <<= 7; - const nextByte = this.readU8(); - result |= nextByte & 127; - if ((nextByte & 128) === 0) { - break; - } - } - return result; - } - readBoxHeader() { - let totalSize = this.readU32(); - const name = this.readAscii(4); - let headerSize = 8; - const hasLargeSize = totalSize === 1; - if (hasLargeSize) { - totalSize = this.readU64(); - headerSize = 16; - } - return { name, totalSize, headerSize, contentSize: totalSize - headerSize }; - } - }; - - // src/isobmff/isobmff-demuxer.ts - var knownMatrixes = [rotationMatrix(0), rotationMatrix(90), rotationMatrix(180), rotationMatrix(270)]; - var IsobmffDemuxer = class extends Demuxer { - constructor(input) { - super(input); - this.currentTrack = null; - this.tracks = []; - this.metadataPromise = null; - this.movieTimescale = -1; - this.movieDurationInTimescale = -1; - this.isFragmented = false; - this.fragmentTrackDefaults = []; - this.fragments = []; - this.currentFragment = null; - this.fragmentLookupMutex = new AsyncMutex(); - this.isobmffReader = new IsobmffReader(input._mainReader); - this.chunkReader = new IsobmffReader(new Reader(input._source, 64 * 2 ** 20)); - } - async computeDuration() { - const tracks = await this.getTracks(); - const trackDurations = await Promise.all(tracks.map((x) => x.computeDuration())); - return Math.max(0, ...trackDurations); - } - async getTracks() { - await this.readMetadata(); - return this.tracks.map((track) => track.inputTrack); - } - async getMimeType() { - await this.readMetadata(); - let string = "video/mp4"; - if (this.tracks.length > 0) { - const codecMimeTypes = await Promise.all(this.tracks.map((x) => x.inputTrack.getCodecMimeType())); - const uniqueCodecMimeTypes = [...new Set(codecMimeTypes)]; - string += `; codecs="${uniqueCodecMimeTypes.join(", ")}"`; - } - return string; - } - readMetadata() { - return this.metadataPromise ??= (async () => { - const sourceSize = await this.isobmffReader.reader.source._getSize(); - while (this.isobmffReader.pos < sourceSize) { - await this.isobmffReader.reader.loadRange(this.isobmffReader.pos, this.isobmffReader.pos + 16); - const startPos = this.isobmffReader.pos; - const boxInfo = this.isobmffReader.readBoxHeader(); - if (boxInfo.name === "moov") { - await this.isobmffReader.reader.loadRange( - this.isobmffReader.pos, - this.isobmffReader.pos + boxInfo.contentSize - ); - this.readContiguousBoxes(boxInfo.contentSize); - break; - } - this.isobmffReader.pos = startPos + boxInfo.totalSize; - } - if (this.isFragmented) { - await this.isobmffReader.reader.loadRange(sourceSize - 4, sourceSize); - this.isobmffReader.pos = sourceSize - 4; - const lastWord = this.isobmffReader.readU32(); - const potentialMfraPos = sourceSize - lastWord; - if (potentialMfraPos >= 0 && potentialMfraPos < sourceSize) { - await this.isobmffReader.reader.loadRange(potentialMfraPos, sourceSize); - this.isobmffReader.pos = potentialMfraPos; - const boxInfo = this.isobmffReader.readBoxHeader(); - if (boxInfo.name === "mfra") { - this.readContiguousBoxes(boxInfo.contentSize); - } - } - } - })(); - } - getSampleTableForTrack(internalTrack) { - if (internalTrack.sampleTable) { - return internalTrack.sampleTable; - } - const sampleTable = { - sampleTimingEntries: [], - sampleCompositionTimeOffsets: [], - sampleSizes: [], - keySampleIndices: null, - chunkOffsets: [], - sampleToChunk: [], - presentationTimestamps: [] - }; - internalTrack.sampleTable = sampleTable; - this.isobmffReader.pos = internalTrack.sampleTableOffset; - this.currentTrack = internalTrack; - this.traverseBox(); - this.currentTrack = null; - for (const entry of sampleTable.sampleTimingEntries) { - for (let i = 0; i < entry.count; i++) { - sampleTable.presentationTimestamps.push({ - presentationTimestamp: entry.startDecodeTimestamp + i * entry.delta, - sampleIndex: entry.startIndex + i - }); - } - } - for (const entry of sampleTable.sampleCompositionTimeOffsets) { - for (let i = 0; i < entry.count; i++) { - const sampleIndex = entry.startIndex + i; - const sample = sampleTable.presentationTimestamps[sampleIndex]; - if (!sample) { - continue; - } - sample.presentationTimestamp += entry.offset; - } - } - sampleTable.presentationTimestamps.sort((a, b) => a.presentationTimestamp - b.presentationTimestamp); - return internalTrack.sampleTable; - } - async readFragment() { - const startPos = this.isobmffReader.pos; - await this.isobmffReader.reader.loadRange(this.isobmffReader.pos, this.isobmffReader.pos + 16); - const moofBoxInfo = this.isobmffReader.readBoxHeader(); - assert(moofBoxInfo.name === "moof"); - await this.isobmffReader.reader.loadRange(startPos, startPos + moofBoxInfo.totalSize); - this.isobmffReader.pos = startPos; - this.traverseBox(); - const index = binarySearchExact(this.fragments, startPos, (x) => x.moofOffset); - assert(index !== -1); - const fragment = this.fragments[index]; - assert(fragment.moofOffset === startPos); - this.isobmffReader.reader.forgetRange(startPos, startPos + moofBoxInfo.totalSize); - for (const [trackId, trackData] of fragment.trackData) { - if (trackData.startTimestampIsFinal) { - continue; - } - const internalTrack = this.tracks.find((x) => x.id === trackId); - this.isobmffReader.pos = 0; - let currentFragment = null; - let lastFragment = null; - const index2 = binarySearchLessOrEqual( - internalTrack.fragments, - startPos - 1, - (x) => x.moofOffset - ); - if (index2 !== -1) { - currentFragment = internalTrack.fragments[index2]; - lastFragment = currentFragment; - this.isobmffReader.pos = currentFragment.moofOffset + currentFragment.moofSize; - } - while (this.isobmffReader.pos < startPos) { - if (currentFragment?.nextFragment) { - currentFragment = currentFragment.nextFragment; - this.isobmffReader.pos = currentFragment.moofOffset + currentFragment.moofSize; - } else { - await this.isobmffReader.reader.loadRange(this.isobmffReader.pos, this.isobmffReader.pos + 16); - const startPos2 = this.isobmffReader.pos; - const boxInfo = this.isobmffReader.readBoxHeader(); - if (boxInfo.name === "moof") { - const index3 = binarySearchExact(this.fragments, startPos2, (x) => x.moofOffset); - if (index3 === -1) { - this.isobmffReader.pos = startPos2; - const fragment2 = await this.readFragment(); - if (currentFragment) currentFragment.nextFragment = fragment2; - currentFragment = fragment2; - } else { - const fragment2 = this.fragments[index3]; - if (currentFragment) currentFragment.nextFragment = fragment2; - currentFragment = fragment2; - } - } - this.isobmffReader.pos = startPos2 + boxInfo.totalSize; - } - if (currentFragment && currentFragment.trackData.has(trackId)) { - lastFragment = currentFragment; - } - } - if (lastFragment) { - const otherTrackData = lastFragment.trackData.get(trackId); - assert(otherTrackData.startTimestampIsFinal); - offsetFragmentTrackDataByTimestamp(trackData, otherTrackData.endTimestamp); - } - trackData.startTimestampIsFinal = true; - } - return fragment; - } - readContiguousBoxes(totalSize) { - const startIndex = this.isobmffReader.pos; - while (this.isobmffReader.pos - startIndex < totalSize) { - this.traverseBox(); - } - } - traverseBox() { - const startPos = this.isobmffReader.pos; - const boxInfo = this.isobmffReader.readBoxHeader(); - const boxEndPos = startPos + boxInfo.totalSize; - switch (boxInfo.name) { - case "mdia": - case "minf": - case "dinf": - case "mfra": - { - this.readContiguousBoxes(boxInfo.contentSize); - } - ; - break; - case "mvhd": - { - const version = this.isobmffReader.readU8(); - this.isobmffReader.pos += 3; - if (version === 1) { - this.isobmffReader.pos += 8 + 8; - this.movieTimescale = this.isobmffReader.readU32(); - this.movieDurationInTimescale = this.isobmffReader.readU64(); - } else { - this.isobmffReader.pos += 4 + 4; - this.movieTimescale = this.isobmffReader.readU32(); - this.movieDurationInTimescale = this.isobmffReader.readU32(); - } - } - ; - break; - case "trak": - { - const track = { - id: -1, - demuxer: this, - inputTrack: null, - info: null, - timescale: -1, - durationInTimescale: -1, - rotation: 0, - sampleTableOffset: -1, - sampleTable: null, - fragmentLookupTable: null, - currentFragmentState: null, - fragments: [] - }; - this.currentTrack = track; - this.readContiguousBoxes(boxInfo.contentSize); - if (track.id !== -1 && track.timescale !== -1 && track.info !== null) { - if (track.info.type === "video" && track.info.codec !== null) { - const videoTrack = track; - track.inputTrack = new InputVideoTrack(new IsobmffVideoTrackBacking(videoTrack)); - this.tracks.push(track); - } else if (track.info.type === "audio" && track.info.codec !== null) { - const audioTrack = track; - track.inputTrack = new InputAudioTrack(new IsobmffAudioTrackBacking(audioTrack)); - this.tracks.push(track); - if (track.info.codec === "aac") { - const audioSpecificConfig = parseAacAudioSpecificConfig(track.info.codecDescription); - if (audioSpecificConfig.numberOfChannels !== null) { - track.info.numberOfChannels = audioSpecificConfig.numberOfChannels; - } - if (audioSpecificConfig.sampleRate !== null) { - track.info.sampleRate = audioSpecificConfig.sampleRate; - } - } - } - } - this.currentTrack = null; - } - ; - break; - case "tkhd": - { - const track = this.currentTrack; - assert(track); - const version = this.isobmffReader.readU8(); - const flags = this.isobmffReader.readU24(); - const trackEnabled = (flags & 1) !== 0; - if (!trackEnabled) { - break; - } - if (version === 0) { - this.isobmffReader.pos += 8; - track.id = this.isobmffReader.readU32(); - this.isobmffReader.pos += 8; - } else if (version === 1) { - this.isobmffReader.pos += 16; - track.id = this.isobmffReader.readU32(); - this.isobmffReader.pos += 12; - } else { - throw new Error(`Incorrect track header version ${version}.`); - } - this.isobmffReader.pos += 2 * 4 + 2 + 2 + 2 + 2; - const rotationMatrix2 = []; - rotationMatrix2.push(this.isobmffReader.readFixed_16_16(), this.isobmffReader.readFixed_16_16()); - this.isobmffReader.pos += 4; - rotationMatrix2.push(this.isobmffReader.readFixed_16_16(), this.isobmffReader.readFixed_16_16()); - const matrixIndex = knownMatrixes.findIndex((x) => x.every((y, i) => y === rotationMatrix2[i])); - if (matrixIndex === -1) { - track.rotation = 0; - } else { - track.rotation = 90 * matrixIndex; - } - } - ; - break; - case "mdhd": - { - const track = this.currentTrack; - assert(track); - const version = this.isobmffReader.readU8(); - this.isobmffReader.pos += 3; - if (version === 0) { - this.isobmffReader.pos += 8; - track.timescale = this.isobmffReader.readU32(); - track.durationInTimescale = this.isobmffReader.readU32(); - } else if (version === 1) { - this.isobmffReader.pos += 16; - track.timescale = this.isobmffReader.readU32(); - track.durationInTimescale = this.isobmffReader.readU64(); - } - } - ; - break; - case "hdlr": - { - const track = this.currentTrack; - assert(track); - this.isobmffReader.pos += 8; - const handlerType = this.isobmffReader.readAscii(4); - if (handlerType === "vide") { - track.info = { - type: "video", - width: -1, - height: -1, - codec: null, - codecDescription: null, - colorSpace: null - }; - } else if (handlerType === "soun") { - track.info = { - type: "audio", - numberOfChannels: -1, - sampleRate: -1, - codec: null, - codecDescription: null - }; - } - } - ; - break; - case "stbl": - { - const track = this.currentTrack; - assert(track); - track.sampleTableOffset = startPos; - this.readContiguousBoxes(boxInfo.contentSize); - } - ; - break; - case "stsd": - { - const track = this.currentTrack; - assert(track); - if (track.info === null || track.sampleTable) { - break; - } - const stsdVersion = this.isobmffReader.readU8(); - this.isobmffReader.pos += 3; - const entries = this.isobmffReader.readU32(); - for (let i = 0; i < entries; i++) { - const sampleBoxInfo = this.isobmffReader.readBoxHeader(); - if (track.info.type === "video") { - if (sampleBoxInfo.name === "avc1") { - track.info.codec = "avc"; - } else if (sampleBoxInfo.name === "hvc1" || sampleBoxInfo.name === "hev1") { - track.info.codec = "hevc"; - } else { - console.warn(`Unsupported video sample entry type ${sampleBoxInfo.name}.`); - break; - } - this.isobmffReader.pos += 6 * 1 + 2 + 2 + 2 + 3 * 4; - track.info.width = this.isobmffReader.readU16(); - track.info.height = this.isobmffReader.readU16(); - this.isobmffReader.pos += 4 + 4 + 4 + 2 + 32 + 2 + 2; - this.readContiguousBoxes(startPos + sampleBoxInfo.totalSize - this.isobmffReader.pos); - } else { - if (sampleBoxInfo.name === "mp4a") { - track.info.codec = "aac"; - } else if (sampleBoxInfo.name.toLowerCase() === "opus") { - track.info.codec = "opus"; - } else { - console.warn(`Unsupported audio sample entry type ${sampleBoxInfo.name}.`); - break; - } - this.isobmffReader.pos += 6 * 1 + 2; - const version = this.isobmffReader.readU16(); - this.isobmffReader.pos += 3 * 2; - let channelCount = this.isobmffReader.readU16(); - this.isobmffReader.pos += 2 + 2 + 2; - let sampleRate = this.isobmffReader.readU32() / 65536; - if (stsdVersion === 0 && version > 0) { - if (version === 1) { - this.isobmffReader.pos += 4 * 4; - } else if (version === 2) { - this.isobmffReader.pos += 4; - sampleRate = this.isobmffReader.readF64(); - channelCount = this.isobmffReader.readU32(); - this.isobmffReader.pos += 4; - const sampleSize = this.isobmffReader.readU32(); - const flags = this.isobmffReader.readU32(); - const bytesPerFrame = this.isobmffReader.readU32(); - const samplesPerFrame = this.isobmffReader.readU32(); - } - } - track.info.numberOfChannels = channelCount; - track.info.sampleRate = sampleRate; - this.readContiguousBoxes(startPos + sampleBoxInfo.totalSize - this.isobmffReader.pos); - } - } - } - ; - break; - case "avcC": - { - const track = this.currentTrack; - assert(track && track.info); - track.info.codecDescription = this.isobmffReader.readRange( - this.isobmffReader.pos, - this.isobmffReader.pos + boxInfo.contentSize - ); - } - ; - break; - case "hvcC": - { - const track = this.currentTrack; - assert(track && track.info); - track.info.codecDescription = this.isobmffReader.readRange( - this.isobmffReader.pos, - this.isobmffReader.pos + boxInfo.contentSize - ); - } - ; - break; - case "colr": - { - const track = this.currentTrack; - assert(track && track.info?.type === "video"); - const colourType = this.isobmffReader.readAscii(4); - if (colourType !== "nclx") { - break; - } - const colourPrimaries = this.isobmffReader.readU16(); - const transferCharacteristics = this.isobmffReader.readU16(); - const matrixCoefficients = this.isobmffReader.readU16(); - const fullRangeFlag = Boolean(this.isobmffReader.readU8() & 128); - track.info.colorSpace = { - primaries: COLOR_PRIMARIES_MAP_INVERSE[colourPrimaries], - transfer: TRANSFER_CHARACTERISTICS_MAP_INVERSE[transferCharacteristics], - matrix: MATRIX_COEFFICIENTS_MAP_INVERSE[matrixCoefficients], - fullRange: fullRangeFlag - }; - } - ; - break; - case "wave": - { - if (boxInfo.totalSize > 8) { - this.readContiguousBoxes(boxInfo.contentSize); - } - } - ; - break; - case "esds": - { - const track = this.currentTrack; - assert(track && track.info); - this.isobmffReader.pos += 4; - const tag = this.isobmffReader.readU8(); - assert(tag === 3); - this.isobmffReader.readIsomVariableInteger(); - this.isobmffReader.pos += 2; - const mixed = this.isobmffReader.readU8(); - const streamDependenceFlag = (mixed & 128) !== 0; - const urlFlag = (mixed & 64) !== 0; - const ocrStreamFlag = (mixed & 32) !== 0; - if (streamDependenceFlag) { - this.isobmffReader.pos += 2; - } - if (urlFlag) { - const urlLength = this.isobmffReader.readU8(); - this.isobmffReader.pos += urlLength; - } - if (ocrStreamFlag) { - this.isobmffReader.pos += 2; - } - const decoderConfigTag = this.isobmffReader.readU8(); - assert(decoderConfigTag === 4); - this.isobmffReader.readIsomVariableInteger(); - const objectTypeIndication = this.isobmffReader.readU8(); - assert(objectTypeIndication === 64); - this.isobmffReader.pos += 1 + 3 + 4 + 4; - const decoderSpecificInfoTag = this.isobmffReader.readU8(); - assert(decoderSpecificInfoTag === 5); - const decoderSpecificInfoLength = this.isobmffReader.readIsomVariableInteger(); - track.info.codecDescription = this.isobmffReader.readRange( - this.isobmffReader.pos, - this.isobmffReader.pos + decoderSpecificInfoLength - ); - } - ; - break; - case "stts": - { - const track = this.currentTrack; - assert(track); - if (!track.sampleTable) { - break; - } - this.isobmffReader.pos += 4; - const entryCount = this.isobmffReader.readU32(); - let currentIndex = 0; - let currentTimestamp = 0; - for (let i = 0; i < entryCount; i++) { - const sampleCount = this.isobmffReader.readU32(); - const sampleDelta = this.isobmffReader.readU32(); - track.sampleTable.sampleTimingEntries.push({ - startIndex: currentIndex, - startDecodeTimestamp: currentTimestamp, - count: sampleCount, - delta: sampleDelta - }); - currentIndex += sampleCount; - currentTimestamp += sampleCount * sampleDelta; - } - } - ; - break; - case "ctts": - { - const track = this.currentTrack; - assert(track); - if (!track.sampleTable) { - break; - } - this.isobmffReader.pos += 1 + 3; - const entryCount = this.isobmffReader.readU32(); - let sampleIndex = 0; - for (let i = 0; i < entryCount; i++) { - const sampleCount = this.isobmffReader.readU32(); - const sampleOffset = this.isobmffReader.readI32(); - track.sampleTable.sampleCompositionTimeOffsets.push({ - startIndex: sampleIndex, - count: sampleCount, - offset: sampleOffset - }); - sampleIndex += sampleCount; - } - } - ; - break; - case "stsz": - { - const track = this.currentTrack; - assert(track); - if (!track.sampleTable) { - break; - } - this.isobmffReader.pos += 4; - const sampleSize = this.isobmffReader.readU32(); - const sampleCount = this.isobmffReader.readU32(); - if (sampleSize === 0) { - for (let i = 0; i < sampleCount; i++) { - const sampleSize2 = this.isobmffReader.readU32(); - track.sampleTable.sampleSizes.push(sampleSize2); - } - } else { - track.sampleTable.sampleSizes.push(sampleSize); - } - } - ; - break; - case "stz2": - { - throw new Error("Unsupported."); - } - ; - case "stss": - { - const track = this.currentTrack; - assert(track); - if (!track.sampleTable) { - break; - } - this.isobmffReader.pos += 4; - track.sampleTable.keySampleIndices = []; - const entryCount = this.isobmffReader.readU32(); - for (let i = 0; i < entryCount; i++) { - const sampleIndex = this.isobmffReader.readU32() - 1; - track.sampleTable.keySampleIndices.push(sampleIndex); - } - } - ; - break; - case "stsc": - { - const track = this.currentTrack; - assert(track); - if (!track.sampleTable) { - break; - } - this.isobmffReader.pos += 4; - const entryCount = this.isobmffReader.readU32(); - for (let i = 0; i < entryCount; i++) { - const startChunkIndex = this.isobmffReader.readU32() - 1; - const samplesPerChunk = this.isobmffReader.readU32(); - const sampleDescriptionIndex = this.isobmffReader.readU32(); - track.sampleTable.sampleToChunk.push({ - startSampleIndex: -1, - startChunkIndex, - samplesPerChunk, - sampleDescriptionIndex - }); - } - let startSampleIndex = 0; - for (let i = 0; i < track.sampleTable.sampleToChunk.length; i++) { - track.sampleTable.sampleToChunk[i].startSampleIndex = startSampleIndex; - if (i < track.sampleTable.sampleToChunk.length - 1) { - const nextChunk = track.sampleTable.sampleToChunk[i + 1]; - const chunkCount = nextChunk.startChunkIndex - track.sampleTable.sampleToChunk[i].startChunkIndex; - startSampleIndex += chunkCount * track.sampleTable.sampleToChunk[i].samplesPerChunk; - } - } - } - ; - break; - case "stco": - { - const track = this.currentTrack; - assert(track); - if (!track.sampleTable) { - break; - } - this.isobmffReader.pos += 4; - const entryCount = this.isobmffReader.readU32(); - for (let i = 0; i < entryCount; i++) { - const chunkOffset = this.isobmffReader.readU32(); - track.sampleTable.chunkOffsets.push(chunkOffset); - } - } - ; - break; - case "co64": - { - const track = this.currentTrack; - assert(track); - if (!track.sampleTable) { - break; - } - this.isobmffReader.pos += 4; - const entryCount = this.isobmffReader.readU32(); - for (let i = 0; i < entryCount; i++) { - const chunkOffset = this.isobmffReader.readU64(); - track.sampleTable.chunkOffsets.push(chunkOffset); - } - } - ; - break; - case "mvex": - { - this.isFragmented = true; - this.readContiguousBoxes(boxInfo.contentSize); - } - ; - break; - case "mehd": - { - const version = this.isobmffReader.readU8(); - this.isobmffReader.pos += 3; - const fragmentDuration = version === 1 ? this.isobmffReader.readU64() : this.isobmffReader.readU32(); - this.movieDurationInTimescale = fragmentDuration; - } - ; - break; - case "trex": - { - this.isobmffReader.pos += 4; - const trackId = this.isobmffReader.readU32(); - const defaultSampleDescriptionIndex = this.isobmffReader.readU32(); - const defaultSampleDuration = this.isobmffReader.readU32(); - const defaultSampleSize = this.isobmffReader.readU32(); - const defaultSampleFlags = this.isobmffReader.readU32(); - this.fragmentTrackDefaults.push({ - trackId, - defaultSampleDescriptionIndex, - defaultSampleDuration, - defaultSampleSize, - defaultSampleFlags - }); - } - ; - break; - case "tfra": - { - const version = this.isobmffReader.readU8(); - this.isobmffReader.pos += 3; - const trackId = this.isobmffReader.readU32(); - const track = this.tracks.find((x2) => x2.id === trackId); - if (!track) { - break; - } - track.fragmentLookupTable = []; - const word = this.isobmffReader.readU32(); - const lengthSizeOfTrafNum = (word & 48) >> 4; - const lengthSizeOfTrunNum = (word & 12) >> 2; - const lengthSizeOfSampleNum = word & 3; - const x = this.isobmffReader; - const functions = [x.readU8.bind(x), x.readU16.bind(x), x.readU24.bind(x), x.readU32.bind(x)]; - const readTrafNum = functions[lengthSizeOfTrafNum]; - const readTrunNum = functions[lengthSizeOfTrunNum]; - const readSampleNum = functions[lengthSizeOfSampleNum]; - const numberOfEntries = this.isobmffReader.readU32(); - for (let i = 0; i < numberOfEntries; i++) { - const time = version === 1 ? this.isobmffReader.readU64() : this.isobmffReader.readU32(); - const moofOffset = version === 1 ? this.isobmffReader.readU64() : this.isobmffReader.readU32(); - const trafNumber = readTrafNum(); - const trunNumber = readTrunNum(); - const sampleNumber = readSampleNum(); - track.fragmentLookupTable.push({ - timestamp: time, - moofOffset - }); - } - } - ; - break; - case "moof": - { - this.currentFragment = { - moofOffset: startPos, - moofSize: boxInfo.totalSize, - implicitBaseDataOffset: startPos, - trackData: /* @__PURE__ */ new Map(), - dataStart: Infinity, - dataEnd: 0, - nextFragment: null - }; - this.readContiguousBoxes(boxInfo.contentSize); - const insertionIndex = binarySearchLessOrEqual( - this.fragments, - this.currentFragment.moofOffset, - (x) => x.moofOffset - ); - this.fragments.splice(insertionIndex + 1, 0, this.currentFragment); - for (const [, trackData] of this.currentFragment.trackData) { - const firstSample = trackData.samples[0]; - const lastSample = last(trackData.samples); - this.currentFragment.dataStart = Math.min( - this.currentFragment.dataStart, - firstSample.byteOffset - ); - this.currentFragment.dataEnd = Math.max( - this.currentFragment.dataEnd, - lastSample.byteOffset + lastSample.byteSize - ); - } - this.currentFragment = null; - } - ; - break; - case "traf": - { - assert(this.currentFragment); - this.readContiguousBoxes(boxInfo.contentSize); - if (this.currentTrack) { - const trackData = this.currentFragment.trackData.get(this.currentTrack.id); - if (trackData) { - const insertionIndex = binarySearchLessOrEqual( - this.currentTrack.fragments, - this.currentFragment.moofOffset, - (x) => x.moofOffset - ); - this.currentTrack.fragments.splice(insertionIndex + 1, 0, this.currentFragment); - const { currentFragmentState } = this.currentTrack; - assert(currentFragmentState); - if (currentFragmentState.startTimestamp !== null) { - offsetFragmentTrackDataByTimestamp(trackData, currentFragmentState.startTimestamp); - trackData.startTimestampIsFinal = true; - } - } - this.currentTrack.currentFragmentState = null; - this.currentTrack = null; - } - } - ; - break; - case "tfhd": - { - assert(this.currentFragment); - this.isobmffReader.pos += 1; - const flags = this.isobmffReader.readU24(); - const baseDataOffsetPresent = Boolean(flags & 1); - const sampleDescriptionIndexPresent = Boolean(flags & 2); - const defaultSampleDurationPresent = Boolean(flags & 8); - const defaultSampleSizePresent = Boolean(flags & 16); - const defaultSampleFlagsPresent = Boolean(flags & 32); - const durationIsEmpty = Boolean(flags & 65536); - const defaultBaseIsMoof = Boolean(flags & 131072); - const trackId = this.isobmffReader.readU32(); - const track = this.tracks.find((x) => x.id === trackId); - if (!track) { - break; - } - const defaults = this.fragmentTrackDefaults.find((x) => x.trackId === trackId); - this.currentTrack = track; - track.currentFragmentState = { - baseDataOffset: this.currentFragment.implicitBaseDataOffset, - sampleDescriptionIndex: defaults?.defaultSampleDescriptionIndex ?? null, - defaultSampleDuration: defaults?.defaultSampleDuration ?? null, - defaultSampleSize: defaults?.defaultSampleSize ?? null, - defaultSampleFlags: defaults?.defaultSampleFlags ?? null, - startTimestamp: null - }; - if (baseDataOffsetPresent) { - track.currentFragmentState.baseDataOffset = this.isobmffReader.readU64(); - } else if (defaultBaseIsMoof) { - track.currentFragmentState.baseDataOffset = this.currentFragment.moofOffset; - } - if (sampleDescriptionIndexPresent) { - track.currentFragmentState.sampleDescriptionIndex = this.isobmffReader.readU32(); - } - if (defaultSampleDurationPresent) { - track.currentFragmentState.defaultSampleDuration = this.isobmffReader.readU32(); - } - if (defaultSampleSizePresent) { - track.currentFragmentState.defaultSampleSize = this.isobmffReader.readU32(); - } - if (defaultSampleFlagsPresent) { - track.currentFragmentState.defaultSampleFlags = this.isobmffReader.readU32(); - } - if (durationIsEmpty) { - track.currentFragmentState.defaultSampleDuration = 0; - } - } - ; - break; - case "tfdt": - { - const track = this.currentTrack; - if (!track) { - break; - } - assert(track.currentFragmentState); - const version = this.isobmffReader.readU8(); - this.isobmffReader.pos += 3; - const baseMediaDecodeTime = version === 0 ? this.isobmffReader.readU32() : this.isobmffReader.readU64(); - track.currentFragmentState.startTimestamp = baseMediaDecodeTime; - } - ; - break; - case "trun": - { - const track = this.currentTrack; - if (!track) { - break; - } - assert(this.currentFragment); - assert(track.currentFragmentState); - if (this.currentFragment.trackData.has(track.id)) { - throw new Error("Can't have two trun boxes for the same track in one fragment."); - } - const version = this.isobmffReader.readU8(); - const flags = this.isobmffReader.readU24(); - const dataOffsetPresent = Boolean(flags & 1); - const firstSampleFlagsPresent = Boolean(flags & 4); - const sampleDurationPresent = Boolean(flags & 256); - const sampleSizePresent = Boolean(flags & 512); - const sampleFlagsPresent = Boolean(flags & 1024); - const sampleCompositionTimeOffsetsPresent = Boolean(flags & 2048); - const sampleCount = this.isobmffReader.readU32(); - let dataOffset = track.currentFragmentState.baseDataOffset; - if (dataOffsetPresent) { - dataOffset += this.isobmffReader.readI32(); - } - let firstSampleFlags = null; - if (firstSampleFlagsPresent) { - firstSampleFlags = this.isobmffReader.readU32(); - } - let currentOffset = dataOffset; - if (sampleCount === 0) { - this.currentFragment.implicitBaseDataOffset = currentOffset; - break; - } - let currentTimestamp = 0; - const trackData = { - startTimestamp: 0, - endTimestamp: 0, - samples: [], - presentationTimestamps: [], - startTimestampIsFinal: false - }; - this.currentFragment.trackData.set(track.id, trackData); - for (let i = 0; i < sampleCount; i++) { - let sampleDuration; - if (sampleDurationPresent) { - sampleDuration = this.isobmffReader.readU32(); - } else { - assert(track.currentFragmentState.defaultSampleDuration !== null); - sampleDuration = track.currentFragmentState.defaultSampleDuration; - } - let sampleSize; - if (sampleSizePresent) { - sampleSize = this.isobmffReader.readU32(); - } else { - assert(track.currentFragmentState.defaultSampleSize !== null); - sampleSize = track.currentFragmentState.defaultSampleSize; - } - let sampleFlags; - if (sampleFlagsPresent) { - sampleFlags = this.isobmffReader.readU32(); - } else { - assert(track.currentFragmentState.defaultSampleFlags !== null); - sampleFlags = track.currentFragmentState.defaultSampleFlags; - } - if (i === 0 && firstSampleFlags !== null) { - sampleFlags = firstSampleFlags; - } - let sampleCompositionTimeOffset = 0; - if (sampleCompositionTimeOffsetsPresent) { - if (version === 0) { - sampleCompositionTimeOffset = this.isobmffReader.readU32(); - } else { - sampleCompositionTimeOffset = this.isobmffReader.readI32(); - } - } - const isKeyFrame = !(sampleFlags & 65536); - trackData.samples.push({ - presentationTimestamp: currentTimestamp + sampleCompositionTimeOffset, - duration: sampleDuration, - byteOffset: currentOffset, - byteSize: sampleSize, - isKeyFrame - }); - currentOffset += sampleSize; - currentTimestamp += sampleDuration; - } - trackData.presentationTimestamps = trackData.samples.map((x, i) => ({ presentationTimestamp: x.presentationTimestamp, sampleIndex: i })).sort((a, b) => a.presentationTimestamp - b.presentationTimestamp); - const firstSample = trackData.samples[trackData.presentationTimestamps[0].sampleIndex]; - const lastSample = trackData.samples[last(trackData.presentationTimestamps).sampleIndex]; - trackData.startTimestamp = firstSample.presentationTimestamp; - trackData.endTimestamp = lastSample.presentationTimestamp + lastSample.duration; - this.currentFragment.implicitBaseDataOffset = currentOffset; - } - ; - break; - } - this.isobmffReader.pos = boxEndPos; - } - }; - var IsobmffTrackBacking = class { - constructor(internalTrack) { - this.internalTrack = internalTrack; - this.chunkToSampleIndex = /* @__PURE__ */ new WeakMap(); - this.chunkToFragmentLocation = /* @__PURE__ */ new WeakMap(); - } - getCodec() { - throw new Error("Not implemented on base class."); - } - async computeDuration() { - const lastChunk = await this.getChunk(Infinity, { metadataOnly: true }); - const timestamp = lastChunk?.timestamp; - return timestamp ? timestamp / 1e6 : 0; - } - async getFirstChunk(options) { - if (this.internalTrack.demuxer.isFragmented) { - return this.performFragmentedLookup( - () => { - const fragment = this.internalTrack.fragments[0]; - return { - fragmentIndex: fragment ? 0 : -1, - sampleIndex: fragment ? 0 : -1, - correctSampleFound: !!fragment - }; - }, - 0, - Infinity, - options - ); - } - return this.fetchChunkForSampleIndex(0, options); - } - roundToMicrosecond(timestamp) { - return (Math.floor(timestamp * 1e6) + 0.99999999) / 1e6; - } - async getChunk(timestamp, options) { - timestamp = this.roundToMicrosecond(timestamp); - const timestampInTimescale = timestamp * this.internalTrack.timescale; - if (this.internalTrack.demuxer.isFragmented) { - return this.performFragmentedLookup( - () => this.findSampleInFragmentsForTimestamp(timestampInTimescale), - timestampInTimescale, - timestampInTimescale, - options - ); - } else { - const sampleTable = this.internalTrack.demuxer.getSampleTableForTrack(this.internalTrack); - const sampleIndex = getSampleIndexForTimestamp(sampleTable, timestampInTimescale); - return this.fetchChunkForSampleIndex(sampleIndex, options); - } - } - async getNextChunk(chunk, options) { - if (this.internalTrack.demuxer.isFragmented) { - const locationInFragment = this.chunkToFragmentLocation.get(chunk); - if (locationInFragment === void 0) { - throw new Error("Chunk was not created from this track."); - } - const trackData = locationInFragment.fragment.trackData.get(this.internalTrack.id); - const sample = trackData.samples[locationInFragment.sampleIndex]; - const fragmentIndex = binarySearchExact( - this.internalTrack.fragments, - locationInFragment.fragment.moofOffset, - (x) => x.moofOffset - ); - assert(fragmentIndex !== -1); - return this.performFragmentedLookup( - () => { - if (locationInFragment.sampleIndex + 1 < trackData.samples.length) { - return { - fragmentIndex, - sampleIndex: locationInFragment.sampleIndex + 1, - correctSampleFound: true - }; - } else { - let currentFragment = locationInFragment.fragment; - while (currentFragment.nextFragment) { - currentFragment = currentFragment.nextFragment; - const trackData2 = currentFragment.trackData.get(this.internalTrack.id); - if (trackData2) { - const fragmentIndex2 = binarySearchExact( - this.internalTrack.fragments, - currentFragment.moofOffset, - (x) => x.moofOffset - ); - assert(fragmentIndex2 !== -1); - return { - fragmentIndex: fragmentIndex2, - sampleIndex: 0, - correctSampleFound: true - }; - } - } - return { - fragmentIndex, - sampleIndex: -1, - correctSampleFound: false - }; - } - }, - sample.presentationTimestamp, - Infinity, - options - ); - } - const sampleIndex = this.chunkToSampleIndex.get(chunk); - if (sampleIndex === void 0) { - throw new Error("Chunk was not created from this track."); - } - return this.fetchChunkForSampleIndex(sampleIndex + 1, options); - } - async getKeyChunk(timestamp, options) { - timestamp = this.roundToMicrosecond(timestamp); - const timestampInTimescale = timestamp * this.internalTrack.timescale; - if (this.internalTrack.demuxer.isFragmented) { - return this.performFragmentedLookup( - () => this.findKeySampleInFragmentsForTimestamp(timestampInTimescale), - timestampInTimescale, - timestampInTimescale, - options - ); - } - const sampleTable = this.internalTrack.demuxer.getSampleTableForTrack(this.internalTrack); - const sampleIndex = getSampleIndexForTimestamp(sampleTable, timestampInTimescale); - const keyFrameSampleIndex = sampleIndex === -1 ? -1 : getRelevantKeyframeIndexForSample(sampleTable, sampleIndex); - return this.fetchChunkForSampleIndex(keyFrameSampleIndex, options); - } - async getNextKeyChunk(chunk, options) { - if (this.internalTrack.demuxer.isFragmented) { - const locationInFragment = this.chunkToFragmentLocation.get(chunk); - if (locationInFragment === void 0) { - throw new Error("Chunk was not created from this track."); - } - const trackData = locationInFragment.fragment.trackData.get(this.internalTrack.id); - const sample = trackData.samples[locationInFragment.sampleIndex]; - const fragmentIndex = binarySearchExact( - this.internalTrack.fragments, - locationInFragment.fragment.moofOffset, - (x) => x.moofOffset - ); - assert(fragmentIndex !== -1); - return this.performFragmentedLookup( - () => { - const nextKeyFrameIndex = trackData.samples.findIndex( - (x, i) => x.isKeyFrame && i > locationInFragment.sampleIndex - ); - if (nextKeyFrameIndex !== -1) { - return { - fragmentIndex, - sampleIndex: nextKeyFrameIndex, - correctSampleFound: true - }; - } else { - let currentFragment = locationInFragment.fragment; - while (currentFragment.nextFragment) { - currentFragment = currentFragment.nextFragment; - const trackData2 = currentFragment.trackData.get(this.internalTrack.id); - if (trackData2) { - const fragmentIndex2 = binarySearchExact( - this.internalTrack.fragments, - currentFragment.moofOffset, - (x) => x.moofOffset - ); - assert(fragmentIndex2 !== -1); - const keyFrameIndex = trackData2.samples.findIndex((x) => x.isKeyFrame); - if (keyFrameIndex === -1) { - throw new Error("Not supported: Fragment does not contain key sample."); - } - return { - fragmentIndex: fragmentIndex2, - sampleIndex: keyFrameIndex, - correctSampleFound: true - }; - } - } - return { - fragmentIndex, - sampleIndex: -1, - correctSampleFound: false - }; - } - }, - sample.presentationTimestamp, - Infinity, - options - ); - } - const sampleIndex = this.chunkToSampleIndex.get(chunk); - if (sampleIndex === void 0) { - throw new Error("Chunk was not created from this track."); - } - const sampleTable = this.internalTrack.demuxer.getSampleTableForTrack(this.internalTrack); - const nextKeyFrameSampleIndex = getNextKeyframeIndexForSample(sampleTable, sampleIndex); - return this.fetchChunkForSampleIndex(nextKeyFrameSampleIndex, options); - } - async fetchChunkForSampleIndex(sampleIndex, options) { - if (sampleIndex === -1) { - return null; - } - const sampleTable = this.internalTrack.demuxer.getSampleTableForTrack(this.internalTrack); - const sampleInfo = getSampleInfo(sampleTable, sampleIndex); - if (!sampleInfo) { - return null; - } - let data; - if (options.metadataOnly) { - data = new Uint8Array(0); - } else { - await this.internalTrack.demuxer.chunkReader.reader.loadRange( - sampleInfo.chunkOffset, - sampleInfo.chunkOffset + sampleInfo.chunkSize - ); - data = this.internalTrack.demuxer.chunkReader.readRange( - sampleInfo.sampleOffset, - sampleInfo.sampleOffset + sampleInfo.sampleSize - ); - } - const timestamp = 1e6 * sampleInfo.presentationTimestamp / this.internalTrack.timescale; - const duration = 1e6 * sampleInfo.duration / this.internalTrack.timescale; - const chunk = this.createChunk(data, timestamp, duration, sampleInfo.isKeyFrame); - this.chunkToSampleIndex.set(chunk, sampleIndex); - return chunk; - } - async fetchChunkInFragment(fragment, sampleIndex, options) { - if (sampleIndex === -1) { - return null; - } - const trackData = fragment.trackData.get(this.internalTrack.id); - const sample = trackData.samples[sampleIndex]; - assert(sample); - let data; - if (options.metadataOnly) { - data = new Uint8Array(0); - } else { - await this.internalTrack.demuxer.chunkReader.reader.loadRange(fragment.dataStart, fragment.dataEnd); - data = this.internalTrack.demuxer.chunkReader.readRange( - sample.byteOffset, - sample.byteOffset + sample.byteSize - ); - } - const timestamp = 1e6 * sample.presentationTimestamp / this.internalTrack.timescale; - const duration = 1e6 * sample.duration / this.internalTrack.timescale; - const chunk = this.createChunk(data, timestamp, duration, sample.isKeyFrame); - this.chunkToFragmentLocation.set(chunk, { fragment, sampleIndex }); - return chunk; - } - findSampleInFragmentsForTimestamp(timestampInTimescale) { - const fragmentIndex = binarySearchLessOrEqual( - this.internalTrack.fragments, - timestampInTimescale, - (x) => x.trackData.get(this.internalTrack.id).startTimestamp - ); - let sampleIndex = -1; - let correctSampleFound = false; - if (fragmentIndex !== -1) { - const fragment = this.internalTrack.fragments[fragmentIndex]; - const trackData = fragment.trackData.get(this.internalTrack.id); - const index = binarySearchLessOrEqual( - trackData.presentationTimestamps, - timestampInTimescale, - (x) => x.presentationTimestamp - ); - assert(index !== -1); - sampleIndex = trackData.presentationTimestamps[index].sampleIndex; - correctSampleFound = timestampInTimescale < trackData.endTimestamp; - } - return { fragmentIndex, sampleIndex, correctSampleFound }; - } - findKeySampleInFragmentsForTimestamp(timestampInTimescale) { - const fragmentIndex = binarySearchLessOrEqual( - this.internalTrack.fragments, - timestampInTimescale, - (x) => x.trackData.get(this.internalTrack.id).startTimestamp - ); - let sampleIndex = -1; - let correctSampleFound = false; - if (fragmentIndex !== -1) { - const fragment = this.internalTrack.fragments[fragmentIndex]; - const trackData = fragment.trackData.get(this.internalTrack.id); - const index = findLastIndex(trackData.presentationTimestamps, (x) => { - const sample = trackData.samples[x.sampleIndex]; - return sample.isKeyFrame && x.presentationTimestamp <= timestampInTimescale; - }); - if (index === -1) { - throw new Error("Not supported: Fragment does not begin with a key sample."); - } - const entry = trackData.presentationTimestamps[index]; - sampleIndex = entry.sampleIndex; - correctSampleFound = timestampInTimescale < trackData.endTimestamp; - } - return { fragmentIndex, sampleIndex, correctSampleFound }; - } - /** Looks for a sample in the fragments while trying to load as few fragments as possible to retrieve it. */ - async performFragmentedLookup(getBestMatch, searchTimestamp, latestTimestamp, options) { - const demuxer = this.internalTrack.demuxer; - const release = await demuxer.fragmentLookupMutex.acquire(); - try { - const { fragmentIndex, sampleIndex, correctSampleFound } = getBestMatch(); - if (correctSampleFound) { - const fragment = this.internalTrack.fragments[fragmentIndex]; - return this.fetchChunkInFragment(fragment, sampleIndex, options); - } - const isobmffReader = demuxer.isobmffReader; - const sourceSize = await isobmffReader.reader.source._getSize(); - let prevFragment = null; - let bestFragmentIndex = fragmentIndex; - let bestSampleIndex = sampleIndex; - let lookupEntry = null; - if (this.internalTrack.fragmentLookupTable) { - const index = binarySearchLessOrEqual( - this.internalTrack.fragmentLookupTable, - searchTimestamp, - (x) => x.timestamp - ); - if (index !== -1) { - lookupEntry = this.internalTrack.fragmentLookupTable[index]; - } - } - if (fragmentIndex === -1) { - isobmffReader.pos = lookupEntry?.moofOffset ?? 0; - } else { - const fragment = this.internalTrack.fragments[fragmentIndex]; - if (!lookupEntry || fragment.moofOffset >= fragment.moofOffset) { - isobmffReader.pos = fragment.moofOffset + fragment.moofSize; - prevFragment = fragment; - } else { - isobmffReader.pos = lookupEntry.moofOffset; - } - } - while (isobmffReader.pos < sourceSize) { - if (prevFragment) { - const trackData = prevFragment.trackData.get(this.internalTrack.id); - if (trackData && trackData.startTimestamp > latestTimestamp) { - break; - } - if (prevFragment.nextFragment) { - isobmffReader.pos = prevFragment.nextFragment.moofOffset + prevFragment.nextFragment.moofSize; - prevFragment = prevFragment.nextFragment; - continue; - } - } - await isobmffReader.reader.loadRange(isobmffReader.pos, isobmffReader.pos + 16); - const startPos = isobmffReader.pos; - const boxInfo = isobmffReader.readBoxHeader(); - if (boxInfo.name === "moof") { - const index = binarySearchExact(demuxer.fragments, startPos, (x) => x.moofOffset); - if (index === -1) { - isobmffReader.pos = startPos; - const fragment = await demuxer.readFragment(); - if (prevFragment) prevFragment.nextFragment = fragment; - prevFragment = fragment; - const { fragmentIndex: fragmentIndex2, sampleIndex: sampleIndex2, correctSampleFound: correctSampleFound2 } = getBestMatch(); - if (correctSampleFound2) { - const fragment2 = this.internalTrack.fragments[fragmentIndex2]; - return this.fetchChunkInFragment(fragment2, sampleIndex2, options); - } - if (fragmentIndex2 !== -1) { - bestFragmentIndex = fragmentIndex2; - bestSampleIndex = sampleIndex2; - } - } else { - const fragment = demuxer.fragments[index]; - if (prevFragment) prevFragment.nextFragment = fragment; - prevFragment = fragment; - } - } - isobmffReader.pos = startPos + boxInfo.totalSize; - } - if (bestFragmentIndex !== -1) { - const fragment = this.internalTrack.fragments[bestFragmentIndex]; - return this.fetchChunkInFragment(fragment, bestSampleIndex, options); - } - return null; - } finally { - release(); - } - } - }; - var IsobmffVideoTrackBacking = class extends IsobmffTrackBacking { - constructor(internalTrack) { - super(internalTrack); - this.internalTrack = internalTrack; - } - async getCodec() { - return this.internalTrack.info.codec; - } - async getCodedWidth() { - return this.internalTrack.info.width; - } - async getCodedHeight() { - return this.internalTrack.info.height; - } - async getRotation() { - return this.internalTrack.rotation; - } - async getDecoderConfig() { - return { - codec: extractVideoCodecString(this.internalTrack.info.codec, this.internalTrack.info.codecDescription), - codedWidth: this.internalTrack.info.width, - codedHeight: this.internalTrack.info.height, - description: this.internalTrack.info.codecDescription ?? void 0, - colorSpace: this.internalTrack.info.colorSpace ?? void 0 - }; - } - createChunk(data, timestamp, duration, isKeyFrame) { - return new EncodedVideoChunk({ - data, - timestamp, - duration, - type: isKeyFrame ? "key" : "delta" - }); - } - }; - var IsobmffAudioTrackBacking = class extends IsobmffTrackBacking { - constructor(internalTrack) { - super(internalTrack); - this.internalTrack = internalTrack; - } - async getCodec() { - return this.internalTrack.info.codec; - } - async getNumberOfChannels() { - return this.internalTrack.info.numberOfChannels; - } - async getSampleRate() { - return this.internalTrack.info.sampleRate; - } - async getDecoderConfig() { - return { - codec: extractAudioCodecString(this.internalTrack.info.codec, this.internalTrack.info.codecDescription), - numberOfChannels: this.internalTrack.info.numberOfChannels, - sampleRate: this.internalTrack.info.sampleRate, - description: this.internalTrack.info.codecDescription ?? void 0 - }; - } - createChunk(data, timestamp, duration, isKeyFrame) { - return new EncodedAudioChunk({ - data, - timestamp, - duration, - type: isKeyFrame ? "key" : "delta" - }); - } - }; - var getSampleIndexForTimestamp = (sampleTable, timescaleUnits) => { - const index = binarySearchLessOrEqual( - sampleTable.presentationTimestamps, - timescaleUnits, - (x) => x.presentationTimestamp - ); - if (index === -1) { - return -1; - } - return sampleTable.presentationTimestamps[index].sampleIndex; - }; - var getSampleInfo = (sampleTable, sampleIndex) => { - const timingEntryIndex = binarySearchLessOrEqual(sampleTable.sampleTimingEntries, sampleIndex, (x) => x.startIndex); - const timingEntry = sampleTable.sampleTimingEntries[timingEntryIndex]; - if (!timingEntry || timingEntry.startIndex + timingEntry.count <= sampleIndex) { - return null; - } - const decodeTimestamp = timingEntry.startDecodeTimestamp + (sampleIndex - timingEntry.startIndex) * timingEntry.delta; - let presentationTimestamp = decodeTimestamp; - const offsetEntryIndex = binarySearchLessOrEqual( - sampleTable.sampleCompositionTimeOffsets, - sampleIndex, - (x) => x.startIndex - ); - const offsetEntry = sampleTable.sampleCompositionTimeOffsets[offsetEntryIndex]; - if (offsetEntry) { - presentationTimestamp += offsetEntry.offset; - } - const sampleSize = sampleTable.sampleSizes[Math.min(sampleIndex, sampleTable.sampleSizes.length - 1)]; - const chunkEntryIndex = binarySearchLessOrEqual(sampleTable.sampleToChunk, sampleIndex, (x) => x.startSampleIndex); - const chunkEntry = sampleTable.sampleToChunk[chunkEntryIndex]; - assert(chunkEntry); - const chunkIndex = chunkEntry.startChunkIndex + Math.floor((sampleIndex - chunkEntry.startSampleIndex) / chunkEntry.samplesPerChunk); - const chunkOffset = sampleTable.chunkOffsets[chunkIndex]; - let chunkSize = 0; - let sampleOffset = chunkOffset; - if (sampleTable.sampleSizes.length === 1) { - sampleOffset += sampleSize * (sampleIndex - chunkEntry.startSampleIndex); - chunkSize += sampleSize * chunkEntry.samplesPerChunk; - } else { - const startSampleIndex = chunkEntry.startSampleIndex + (chunkIndex - chunkEntry.startChunkIndex) * chunkEntry.samplesPerChunk; - for (let i = startSampleIndex; i < startSampleIndex + chunkEntry.samplesPerChunk; i++) { - const sampleSize2 = sampleTable.sampleSizes[i]; - if (i < sampleIndex) { - sampleOffset += sampleSize2; - } - chunkSize += sampleSize2; - } - } - return { - presentationTimestamp, - duration: timingEntry.delta, - sampleOffset, - sampleSize, - chunkOffset, - chunkSize, - isKeyFrame: sampleTable.keySampleIndices ? binarySearchExact(sampleTable.keySampleIndices, sampleIndex, (x) => x) !== -1 : true - }; - }; - var getRelevantKeyframeIndexForSample = (sampleTable, sampleIndex) => { - if (!sampleTable.keySampleIndices) { - return sampleIndex; - } - const index = binarySearchLessOrEqual(sampleTable.keySampleIndices, sampleIndex, (x) => x); - return sampleTable.keySampleIndices[index] ?? -1; - }; - var getNextKeyframeIndexForSample = (sampleTable, sampleIndex) => { - if (!sampleTable.keySampleIndices) { - return sampleIndex + 1; - } - const index = binarySearchLessOrEqual(sampleTable.keySampleIndices, sampleIndex, (x) => x); - return sampleTable.keySampleIndices[index + 1] ?? -1; - }; - var offsetFragmentTrackDataByTimestamp = (trackData, timestamp) => { - trackData.startTimestamp += timestamp; - trackData.endTimestamp += timestamp; - for (const sample of trackData.samples) { - sample.presentationTimestamp += timestamp; - } - for (const entry of trackData.presentationTimestamps) { - entry.presentationTimestamp += timestamp; - } - }; - - // src/input-format.ts - var InputFormat = class { - }; - var IsobmffInputFormat = class extends InputFormat { - /** @internal */ - async _canReadInput(input) { - const sourceSize = await input._mainReader.source._getSize(); - if (sourceSize < 8) { - return false; - } - const isobmffReader = new IsobmffReader(input._mainReader); - isobmffReader.pos = 4; - const fourCc = isobmffReader.readAscii(4); - return fourCc === "ftyp"; - } - /** @internal */ - _createDemuxer(input) { - return new IsobmffDemuxer(input); - } - }; - var MatroskaInputFormat = class extends InputFormat { - /** @internal */ - async _canReadInput() { - return false; - } - /** @internal */ - _createDemuxer() { - throw new Error("Not implemented"); - } - }; - var ISOBMFF = new IsobmffInputFormat(); - var MP4 = ISOBMFF; - var MOV = ISOBMFF; - var MATROSKA = new MatroskaInputFormat(); - var MKV = MATROSKA; - var WEBM = MATROSKA; - var ALL_FORMATS = [ISOBMFF, MKV]; - - // src/input.ts - var Input = class { - constructor(options) { - /** @internal */ - this._demuxerPromise = null; - /** @internal */ - this._format = null; - if (!options || typeof options !== "object") { - throw new TypeError("options must be an object."); - } - if (!Array.isArray(options.formats) || options.formats.some((x) => !(x instanceof InputFormat))) { - throw new TypeError("options.formats must be an array of InputFormat."); - } - if (!(options.source instanceof Source)) { - throw new TypeError("options.source must be a Source."); - } - this._formats = options.formats; - this._source = options.source; - this._mainReader = new Reader(options.source); - } - /** @internal */ - _getDemuxer() { - return this._demuxerPromise ??= (async () => { - await this._mainReader.loadRange(0, 4096); - for (const format of this._formats) { - const canRead = await format._canReadInput(this); - if (canRead) { - this._format = format; - return format._createDemuxer(this); - } - } - throw new Error("Input has an unrecognizable format."); - })(); - } - async getFormat() { - await this._getDemuxer(); - assert(this._format); - return this._format; - } - async computeDuration() { - const demuxer = await this._getDemuxer(); - return demuxer.computeDuration(); - } - async getTracks() { - const demuxer = await this._getDemuxer(); - return demuxer.getTracks(); - } - async getVideoTracks() { - const tracks = await this.getTracks(); - return tracks.filter((x) => x.isVideoTrack()); - } - async getPrimaryVideoTrack() { - const tracks = await this.getTracks(); - return tracks.find((x) => x.isVideoTrack()) ?? null; - } - async getAudioTracks() { - const tracks = await this.getTracks(); - return tracks.filter((x) => x.isAudioTrack()); - } - async getPrimaryAudioTrack() { - const tracks = await this.getTracks(); - return tracks.find((x) => x.isAudioTrack()) ?? null; - } - async getMimeType() { - const demuxer = await this._getDemuxer(); - return demuxer.getMimeType(); - } - }; - - // src/media-drain.ts - var validateChunkRetrievalOptions = (options) => { - if (!options || typeof options !== "object") { - throw new TypeError("options must be an object."); - } - if (options.metadataOnly !== void 0 && typeof options.metadataOnly !== "boolean") { - throw new TypeError("options.metadataOnly, when defined, must be a boolean."); - } - }; - var validateTimestamp = (timestamp) => { - if (typeof timestamp !== "number" || Number.isNaN(timestamp)) { - throw new TypeError("timestamp must be a number."); - } - }; - var BaseChunkDrain = class { - async *chunks(startChunk, endTimestamp = Infinity) { - const chunkQueue = []; - let { promise: queueNotEmpty, resolve: onQueueNotEmpty } = promiseWithResolvers(); - let { promise: queueDequeue, resolve: onQueueDequeue } = promiseWithResolvers(); - let ended = false; - let outOfBandError = null; - const timestamps = []; - const maxQueueSize = () => Math.max(2, timestamps.length); - (async () => { - let chunk = startChunk ?? await this.getFirstChunk(); - while (chunk && !ended) { - if (chunk.timestamp / 1e6 >= endTimestamp) { - break; - } - if (chunkQueue.length > maxQueueSize()) { - ({ promise: queueDequeue, resolve: onQueueDequeue } = promiseWithResolvers()); - await queueDequeue; - continue; - } - chunkQueue.push(chunk); - onQueueNotEmpty(); - ({ promise: queueNotEmpty, resolve: onQueueNotEmpty } = promiseWithResolvers()); - chunk = await this.getNextChunk(chunk); - } - ended = true; - onQueueNotEmpty(); - })().catch((error) => { - if (!outOfBandError) { - outOfBandError = error; - onQueueNotEmpty(); - } - }); - try { - while (true) { - if (outOfBandError) { - throw outOfBandError; - } else if (chunkQueue.length > 0) { - yield chunkQueue.shift(); - const now = performance.now(); - timestamps.push(now); - while (timestamps.length > 0 && now - timestamps[0] >= 1e3) { - timestamps.shift(); - } - onQueueDequeue(); - } else if (!ended) { - await queueNotEmpty; - } else { - break; - } - } - } finally { - ended = true; - onQueueDequeue(); - } - } - }; - var BaseMediaFrameDrain = class { - /** @internal */ - _duplicateFrame(frame) { - return structuredClone(frame); - } - async *mediaFramesAtTimestamps(timestamps) { - validateAnyIterable(timestamps); - const timestampIterator = toAsyncIterator(timestamps); - const timestampsOfInterest = []; - const MAX_QUEUE_SIZE = 8; - const frameQueue = []; - let { promise: queueNotEmpty, resolve: onQueueNotEmpty } = promiseWithResolvers(); - let { promise: queueDequeue, resolve: onQueueDequeue } = promiseWithResolvers(); - let decoderIsFlushed = false; - let ended = false; - let outOfBandError = null; - let lastUsedFrame = null; - const pushToQueue = (frame) => { - frameQueue.push(frame); - onQueueNotEmpty(); - ({ promise: queueNotEmpty, resolve: onQueueNotEmpty } = promiseWithResolvers()); - }; - const decoderError = new Error(); - const decoder = await this._createDecoder((frame) => { - onQueueDequeue(); - if (ended) { - frame.close(); - return; - } - let frameUsed = false; - while (timestampsOfInterest.length > 0 && timestampsOfInterest[0] === frame.timestamp) { - pushToQueue(this._duplicateFrame(frame)); - timestampsOfInterest.shift(); - frameUsed = true; - } - if (frameUsed) { - lastUsedFrame?.close(); - lastUsedFrame = frame; - } else { - frame.close(); - } - }, (error) => { - if (!outOfBandError) { - error.stack = decoderError.stack; - outOfBandError = error; - onQueueNotEmpty(); - } - }); - (async () => { - const chunkDrain = this._createChunkDrain(); - let lastKeyChunk = null; - let lastChunk = null; - for await (const timestamp of timestampIterator) { - validateTimestamp(timestamp); - while (frameQueue.length + decoder.decodeQueueSize > MAX_QUEUE_SIZE) { - ({ promise: queueDequeue, resolve: onQueueDequeue } = promiseWithResolvers()); - await queueDequeue; - } - if (ended) { - break; - } - const targetChunk = await chunkDrain.getChunk(timestamp); - if (!targetChunk) { - pushToQueue(null); - continue; - } - const keyChunk = await chunkDrain.getKeyChunk(timestamp); - if (!keyChunk) { - pushToQueue(null); - continue; - } - timestampsOfInterest.push(targetChunk.timestamp); - if (lastKeyChunk && keyChunk.timestamp === lastKeyChunk.timestamp && targetChunk.timestamp >= lastChunk.timestamp) { - assert(lastChunk); - if (targetChunk.timestamp === lastChunk.timestamp && timestampsOfInterest.length === 1) { - if (lastUsedFrame) { - pushToQueue(this._duplicateFrame(lastUsedFrame)); - } - timestampsOfInterest.shift(); - } - } else { - lastKeyChunk = keyChunk; - lastChunk = keyChunk; - decoder.decode(keyChunk); - } - while (lastChunk.timestamp !== targetChunk.timestamp) { - const nextChunk = await chunkDrain.getNextChunk(lastChunk); - assert(nextChunk); - lastChunk = nextChunk; - decoder.decode(nextChunk); - } - if (decoder.decodeQueueSize >= 10) { - await new Promise((resolve) => decoder.addEventListener("dequeue", resolve, { once: true })); - } - } - await decoder.flush(); - decoder.close(); - decoderIsFlushed = true; - onQueueNotEmpty(); - })().catch((error) => { - if (!outOfBandError) { - outOfBandError = error; - onQueueNotEmpty(); - } - }); - try { - while (true) { - if (outOfBandError) { - throw outOfBandError; - } else if (frameQueue.length > 0) { - const nextFrame = frameQueue.shift(); - assert(nextFrame !== void 0); - yield nextFrame; - onQueueDequeue(); - } else if (!decoderIsFlushed) { - await queueNotEmpty; - } else { - break; - } - } - } finally { - ended = true; - onQueueDequeue(); - for (const frame of frameQueue) { - frame?.close(); - } - lastUsedFrame?.close(); - } - } - async *mediaFramesInRange(startTimestamp = 0, endTimestamp = Infinity) { - validateTimestamp(startTimestamp); - validateTimestamp(endTimestamp); - const MAX_QUEUE_SIZE = 8; - const frameQueue = []; - let firstFrameQueued = false; - let lastFrame = null; - let { promise: queueNotEmpty, resolve: onQueueNotEmpty } = promiseWithResolvers(); - let { promise: queueDequeue, resolve: onQueueDequeue } = promiseWithResolvers(); - let decoderIsFlushed = false; - let ended = false; - let outOfBandError = null; - const decoderError = new Error(); - const decoder = await this._createDecoder((frame) => { - onQueueDequeue(); - const frameTimestamp = frame.timestamp / 1e6; - if (frameTimestamp >= endTimestamp) { - ended = true; - } - if (ended) { - frame.close(); - return; - } - if (lastFrame) { - if (frameTimestamp > startTimestamp) { - frameQueue.push(lastFrame); - firstFrameQueued = true; - } else { - lastFrame.close(); - } - } - if (frameTimestamp >= startTimestamp) { - frameQueue.push(frame); - firstFrameQueued = true; - } - lastFrame = firstFrameQueued ? null : frame; - if (frameQueue.length > 0) { - onQueueNotEmpty(); - ({ promise: queueNotEmpty, resolve: onQueueNotEmpty } = promiseWithResolvers()); - } - }, (error) => { - if (!outOfBandError) { - error.stack = decoderError.stack; - outOfBandError = error; - onQueueNotEmpty(); - } - }); - const chunkDrain = this._createChunkDrain(); - const keyChunk = await chunkDrain.getKeyChunk(startTimestamp) ?? await chunkDrain.getFirstChunk(); - if (!keyChunk) { - return; - } - (async () => { - let currentChunk = keyChunk; - let chunksEndTimestamp = Infinity; - if (endTimestamp < Infinity) { - const endFrame = await chunkDrain.getChunk(endTimestamp); - const endKeyFrame = !endFrame ? null : endFrame.type === "key" && endFrame.timestamp / 1e6 === endTimestamp ? endFrame : await chunkDrain.getNextKeyChunk(endFrame); - if (endKeyFrame) { - chunksEndTimestamp = endKeyFrame.timestamp / 1e6; - } - } - const chunks = chunkDrain.chunks(keyChunk, chunksEndTimestamp); - await chunks.next(); - while (currentChunk && !ended) { - if (frameQueue.length + decoder.decodeQueueSize > MAX_QUEUE_SIZE) { - ({ promise: queueDequeue, resolve: onQueueDequeue } = promiseWithResolvers()); - await queueDequeue; - continue; - } - decoder.decode(currentChunk); - const chunkResult = await chunks.next(); - if (chunkResult.done) { - break; - } - currentChunk = chunkResult.value; - } - await chunks.return(); - await decoder.flush(); - decoder.close(); - if (!firstFrameQueued && lastFrame) { - frameQueue.push(lastFrame); - } - decoderIsFlushed = true; - onQueueNotEmpty(); - })().catch((error) => { - if (!outOfBandError) { - outOfBandError = error; - onQueueNotEmpty(); - } - }); - try { - while (true) { - if (outOfBandError) { - throw outOfBandError; - } else if (frameQueue.length > 0) { - yield frameQueue.shift(); - onQueueDequeue(); - } else if (!decoderIsFlushed) { - await queueNotEmpty; - } else { - break; - } - } - } finally { - ended = true; - onQueueDequeue(); - for (const frame of frameQueue) { - frame.close(); - } - } - } - }; - var EncodedVideoChunkDrain = class extends BaseChunkDrain { - constructor(videoTrack) { - if (!(videoTrack instanceof InputVideoTrack)) { - throw new TypeError("videoTrack must be an InputVideoTrack."); - } - super(); - this._videoTrack = videoTrack; - } - getFirstChunk(options = {}) { - validateChunkRetrievalOptions(options); - return this._videoTrack._backing.getFirstChunk(options); - } - getChunk(timestamp, options = {}) { - validateTimestamp(timestamp); - validateChunkRetrievalOptions(options); - return this._videoTrack._backing.getChunk(timestamp, options); - } - getNextChunk(chunk, options = {}) { - if (!(chunk instanceof EncodedVideoChunk)) { - throw new TypeError("chunk must be an EncodedVideoChunk."); - } - validateChunkRetrievalOptions(options); - return this._videoTrack._backing.getNextChunk(chunk, options); - } - getKeyChunk(timestamp, options = {}) { - validateTimestamp(timestamp); - validateChunkRetrievalOptions(options); - return this._videoTrack._backing.getKeyChunk(timestamp, options); - } - getNextKeyChunk(chunk, options = {}) { - if (!(chunk instanceof EncodedVideoChunk)) { - throw new TypeError("chunk must be an EncodedVideoChunk."); - } - validateChunkRetrievalOptions(options); - return this._videoTrack._backing.getNextKeyChunk(chunk, options); - } - }; - var VideoFrameDrain = class extends BaseMediaFrameDrain { - constructor(videoTrack) { - if (!(videoTrack instanceof InputVideoTrack)) { - throw new TypeError("videoTrack must be an InputVideoTrack."); - } - super(); - /** @internal */ - this._decoderConfig = null; - this._videoTrack = videoTrack; - } - /** @internal */ - async _createDecoder(onFrame, onError) { - this._decoderConfig ??= await this._videoTrack.getDecoderConfig(); - const decoder = new VideoDecoder({ output: onFrame, error: onError }); - decoder.configure(this._decoderConfig); - return decoder; - } - /** @internal */ - _createChunkDrain() { - return new EncodedVideoChunkDrain(this._videoTrack); - } - async getFrame(timestamp) { - validateTimestamp(timestamp); - for await (const frame of this.mediaFramesAtTimestamps([timestamp])) { - return frame; - } - throw new Error("Internal error: Iterator returned nothing."); - } - frames(startTimestamp = 0, endTimestamp = Infinity) { - return this.mediaFramesInRange(startTimestamp, endTimestamp); - } - framesAtTimestamps(timestamps) { - return this.mediaFramesAtTimestamps(timestamps); - } - }; - var CanvasDrain = class { - constructor(videoTrack, dimensions) { - if (!(videoTrack instanceof InputVideoTrack)) { - throw new TypeError("videoTrack must be an InputVideoTrack."); - } - if (dimensions && typeof dimensions !== "object") { - throw new TypeError("dimensions, when defined, must be an object."); - } - if (dimensions && (!Number.isInteger(dimensions.width) || dimensions.width <= 0)) { - throw new TypeError("dimensions.width must be a positive integer."); - } - if (dimensions && (!Number.isInteger(dimensions.height) || dimensions.height <= 0)) { - throw new TypeError("dimensions.height must be a positive integer."); - } - this._videoTrack = videoTrack; - this._dimensions = dimensions; - this._videoFrameDrain = new VideoFrameDrain(videoTrack); - } - /** @internal */ - async _videoFrameToWrappedCanvas(frame) { - const width = this._dimensions?.width ?? await this._videoTrack.getDisplayWidth(); - const height = this._dimensions?.height ?? await this._videoTrack.getDisplayHeight(); - const rotation = await this._videoTrack.getRotation(); - const canvas = document.createElement("canvas"); - canvas.width = width; - canvas.height = height; - const context = canvas.getContext("2d", { alpha: false }); - assert(context); - context.translate(width / 2, height / 2); - context.rotate(rotation * Math.PI / 180); - context.translate(-width / 2, -height / 2); - const [imageWidth, imageHeight] = rotation % 180 === 0 ? [width, height] : [height, width]; - context.drawImage(frame, (width - imageWidth) / 2, (height - imageHeight) / 2, imageWidth, imageHeight); - const result = { - canvas, - timestamp: frame.timestamp / 1e6, - duration: (frame.duration ?? 0) / 1e6 - }; - frame.close(); - return result; - } - async getCanvas(timestamp) { - validateTimestamp(timestamp); - const frame = await this._videoFrameDrain.getFrame(timestamp); - return frame && this._videoFrameToWrappedCanvas(frame); - } - async *canvases(startTimestamp = 0, endTimestamp = Infinity) { - for await (const frame of this._videoFrameDrain.frames(startTimestamp, endTimestamp)) { - yield this._videoFrameToWrappedCanvas(frame); - } - } - async *canvasesAtTimestamps(timestamps) { - for await (const frame of this._videoFrameDrain.framesAtTimestamps(timestamps)) { - yield frame && this._videoFrameToWrappedCanvas(frame); - } - } - }; - var EncodedAudioChunkDrain = class extends BaseChunkDrain { - constructor(audioTrack) { - if (!(audioTrack instanceof InputAudioTrack)) { - throw new TypeError("audioTrack must be an InputAudioTrack."); - } - super(); - this._audioTrack = audioTrack; - } - getFirstChunk(options = {}) { - validateChunkRetrievalOptions(options); - return this._audioTrack._backing.getFirstChunk(options); - } - getChunk(timestamp, options = {}) { - validateTimestamp(timestamp); - validateChunkRetrievalOptions(options); - return this._audioTrack._backing.getChunk(timestamp, options); - } - getNextChunk(chunk, options = {}) { - if (!(chunk instanceof EncodedAudioChunk)) { - throw new TypeError("chunk must be an EncodedAudioChunk."); - } - validateChunkRetrievalOptions(options); - return this._audioTrack._backing.getNextChunk(chunk, options); - } - getKeyChunk(timestamp, options = {}) { - validateTimestamp(timestamp); - validateChunkRetrievalOptions(options); - return this._audioTrack._backing.getKeyChunk(timestamp, options); - } - getNextKeyChunk(chunk, options = {}) { - if (!(chunk instanceof EncodedAudioChunk)) { - throw new TypeError("chunk must be an EncodedAudioChunk."); - } - validateChunkRetrievalOptions(options); - return this._audioTrack._backing.getNextKeyChunk(chunk, options); - } - }; - var AudioDataDrain = class extends BaseMediaFrameDrain { - constructor(audioTrack) { - if (!(audioTrack instanceof InputAudioTrack)) { - throw new TypeError("audioTrack must be an InputAudioTrack."); - } - super(); - /** @internal */ - this._decoderConfig = null; - this._audioTrack = audioTrack; - } - /** @internal */ - async _createDecoder(onData, onError) { - this._decoderConfig ??= await this._audioTrack.getDecoderConfig(); - const decoder = new AudioDecoder({ output: onData, error: onError }); - decoder.configure(this._decoderConfig); - return decoder; - } - /** @internal */ - _createChunkDrain() { - return new EncodedAudioChunkDrain(this._audioTrack); - } - async getData(timestamp) { - validateTimestamp(timestamp); - for await (const data of this.mediaFramesAtTimestamps([timestamp])) { - return data; - } - throw new Error("Internal error: Iterator returned nothing."); - } - data(startTimestamp = 0, endTimestamp = Infinity) { - return this.mediaFramesInRange(startTimestamp, endTimestamp); - } - dataAtTimestamps(timestamps) { - return this.mediaFramesAtTimestamps(timestamps); - } - }; - var AudioBufferDrain = class { - constructor(audioTrack) { - if (!(audioTrack instanceof InputAudioTrack)) { - throw new TypeError("audioTrack must be an InputAudioTrack."); - } - this._audioDataDrain = new AudioDataDrain(audioTrack); - } - /** @internal */ - _audioDataToWrappedArrayBuffer(data) { - const audioBuffer = new AudioBuffer({ - numberOfChannels: data.numberOfChannels, - length: data.numberOfFrames, - sampleRate: data.sampleRate - }); - const dataBytes = new Float32Array(data.allocationSize({ planeIndex: 0, format: "f32-planar" }) / 4); - for (let i = 0; i < data.numberOfChannels; i++) { - data.copyTo(dataBytes, { planeIndex: i, format: "f32-planar" }); - audioBuffer.copyToChannel(dataBytes, i); - } - const sampleDuration = 1 / data.sampleRate; - const result = { - buffer: audioBuffer, - // Rounding the timestamp based on the sample duration removes audio playback artifacts - timestamp: Math.round(data.timestamp / 1e6 / sampleDuration) * sampleDuration - }; - data.close(); - return result; - } - async getBuffer(timestamp) { - validateTimestamp(timestamp); - const data = await this._audioDataDrain.getData(timestamp); - return data && this._audioDataToWrappedArrayBuffer(data); - } - async *buffers(startTimestamp = 0, endTimestamp = Infinity) { - for await (const data of this._audioDataDrain.data(startTimestamp, endTimestamp)) { - yield this._audioDataToWrappedArrayBuffer(data); - } - } - async *buffersAtTimestamps(timestamps) { - for await (const data of this._audioDataDrain.dataAtTimestamps(timestamps)) { - yield data && this._audioDataToWrappedArrayBuffer(data); - } - } - }; - return __toCommonJS(src_exports); -})(); -if (typeof module === "object" && typeof module.exports === "object") Object.assign(module.exports, Metamuxer) diff --git a/dist/metamuxer.min.js b/dist/metamuxer.min.js deleted file mode 100644 index 16f4b06..0000000 --- a/dist/metamuxer.min.js +++ /dev/null @@ -1,10 +0,0 @@ -"use strict";var Metamuxer=(()=>{var Ct=Object.defineProperty;var vr=Object.getOwnPropertyDescriptor;var _r=Object.getOwnPropertyNames;var Ar=Object.prototype.hasOwnProperty;var Ir=(i,t)=>{for(var e in t)Ct(i,e,{get:t[e],enumerable:!0})},Er=(i,t,e,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let a of _r(t))!Ar.call(i,a)&&a!==e&&Ct(i,a,{get:()=>t[a],enumerable:!(r=vr(t,a))||r.enumerable});return i};var Rr=i=>Er(Ct({},"__esModule",{value:!0}),i);var Wi={};Ir(Wi,{ALL_FORMATS:()=>yr,AUDIO_CODECS:()=>se,ArrayBufferTarget:()=>he,AudioBufferDrain:()=>Tt,AudioBufferSource:()=>st,AudioDataDrain:()=>Pe,AudioDataSource:()=>at,AudioSource:()=>Q,BaseChunkDrain:()=>ge,BaseMediaFrameDrain:()=>Te,BlobSource:()=>lt,BufferSource:()=>dt,CanvasDrain:()=>gt,CanvasSource:()=>rt,EncodedAudioChunkDrain:()=>De,EncodedAudioChunkSource:()=>nt,EncodedVideoChunkDrain:()=>Ve,EncodedVideoChunkSource:()=>et,ISOBMFF:()=>Fe,Input:()=>kt,InputAudioTrack:()=>B,InputFormat:()=>Z,InputTrack:()=>be,InputVideoTrack:()=>U,IsobmffInputFormat:()=>Re,MATROSKA:()=>bt,MKV:()=>Pt,MOV:()=>Cr,MP4:()=>xr,MatroskaInputFormat:()=>Oe,MediaSource:()=>ce,MediaStreamAudioTrackSource:()=>ot,MediaStreamVideoTrackSource:()=>it,MkvOutputFormat:()=>Ae,Mp4OutputFormat:()=>Je,Output:()=>ut,OutputFormat:()=>Y,SUBTITLE_CODECS:()=>Se,Source:()=>j,StreamTarget:()=>je,SubtitleSource:()=>ue,Target:()=>G,TextSubtitleSource:()=>ct,UrlSource:()=>mt,VIDEO_CODECS:()=>ae,VideoFrameDrain:()=>Me,VideoFrameSource:()=>tt,VideoSource:()=>L,WEBM:()=>wr,WebMOutputFormat:()=>oe});function l(i){if(!i)throw new Error("Assertion failed.")}var F=i=>i&&i[i.length-1],J=i=>i>=0&&i<2**32,V=(i,t,e)=>{let r=0;for(let a=t;a>o;r<<=1,r|=c}return r},zt=(i,t,e,r)=>{for(let a=t;a>e-a-1<i instanceof ArrayBuffer?new Uint8Array(i):new Uint8Array(i.buffer,i.byteOffset,i.byteLength),P=new TextEncoder,wt=i=>Object.fromEntries(Object.entries(i).map(([t,e])=>[e,t])),te={bt709:1,bt470bg:5,smpte170m:6},Ut=wt(te),re={bt709:1,smpte170m:6,"iec61966-2-1":13},Bt=wt(re),ie={rgb:0,bt709:1,bt470bg:5,smpte170m:6},Nt=wt(ie),Be=i=>!!i&&!!i.primaries&&!!i.transfer&&!!i.matrix&&i.fullRange!==void 0,yt=i=>i instanceof ArrayBuffer||typeof SharedArrayBuffer<"u"&&i instanceof SharedArrayBuffer||ArrayBuffer.isView(i)&&!(i instanceof DataView),q=class{constructor(){this.currentPromise=Promise.resolve()}async acquire(){let t,e=new Promise(a=>{t=a}),r=this.currentPromise;return this.currentPromise=e,await r,t}},X=i=>{let t=i*(Math.PI/180),e=Math.cos(t),r=Math.sin(t);return[e,r,0,-r,e,0,0,0,1]},St=X(0),Wt=i=>[...i].map(t=>t.toString(16).padStart(2,"0")).join(""),Ht=i=>(i=i>>1&1431655765|(i&1431655765)<<1,i=i>>2&858993459|(i&858993459)<<2,i=i>>4&252645135|(i&252645135)<<4,i=i>>8&16711935|(i&16711935)<<8,i=i>>16&65535|(i&65535)<<16,i>>>0),K=(i,t,e)=>{let r=0,a=i.length-1,n=-1;for(;r<=a;){let s=r+a>>1,o=e(i[s]);o===t?(n=s,a=s-1):o{let r=-1,a=0,n=i.length-1;for(;a<=n;){let s=a+(n-a+1)/2|0;e(i[s])<=t?(r=s,a=s+1):n=s-1}return r},M=()=>{let i,t;return{promise:new Promise((r,a)=>{i=r,t=a}),resolve:i,reject:t}},Lt=(i,t)=>{let e=i.indexOf(t);e!==-1&&i.splice(e,1)},Qt=(i,t)=>{for(let e=i.length-1;e>=0;e--)if(t(i[e]))return e;return-1},$t=async function*(i){Symbol.iterator in i?yield*i[Symbol.iterator]():yield*i[Symbol.asyncIterator]()},Kt=i=>{if(!(Symbol.iterator in i)&&!(Symbol.asyncIterator in i))throw new TypeError("Argument must be an iterable or async iterable.")};var xe=/(?:(.+?)\n)?((?:\d{2}:)?\d{2}:\d{2}.\d{3})\s+-->\s+((?:\d{2}:)?\d{2}:\d{2}.\d{3})/g,Or=/^WEBVTT(.|\n)*?\n{2}/,le=/<(?:(\d{2}):)?(\d{2}):(\d{2}).(\d{3})>/g,Ne=class{constructor(t){this.preambleText=null;this.preambleEmitted=!1;this.options=t}parse(t){t=t.replaceAll(`\r -`,` -`).replaceAll("\r",` -`),xe.lastIndex=0;let e;if(!this.preambleText){if(!Or.test(t))throw new Error("WebVTT preamble incorrect.");e=xe.exec(t);let r=t.slice(0,e?.index??t.length).trimEnd();if(!r)throw new Error("No WebVTT preamble provided.");this.preambleText=r,e&&(t=t.slice(e.index),xe.lastIndex=0)}for(;e=xe.exec(t);){let r=t.slice(0,e.index),a=e[1],n=e.index+e[0].length,s=t.indexOf(` -`,n)+1,o=t.slice(n,s).trim(),c=t.indexOf(` - -`,n);c===-1&&(c=t.length);let u=We(e[2]),f=We(e[3])-u,m=t.slice(s,c).trim();t=t.slice(c).trimStart(),xe.lastIndex=0;let g={timestamp:u/1e3,duration:f/1e3,text:m,identifier:a,settings:o,notes:r},b={};this.preambleEmitted||(b.config={description:this.preambleText},this.preambleEmitted=!0),this.options.output(g,b)}}},Fr=/(?:(\d{2}):)?(\d{2}):(\d{2}).(\d{3})/,We=i=>{let t=Fr.exec(i);if(!t)throw new Error("Expected match.");return 60*60*1e3*Number(t[1]||"0")+60*1e3*Number(t[2])+1e3*Number(t[3])+Number(t[4])},He=i=>{let t=Math.floor(i/36e5),e=Math.floor(i%(60*60*1e3)/(60*1e3)),r=Math.floor(i%(60*1e3)/1e3),a=i%1e3;return t.toString().padStart(2,"0")+":"+e.toString().padStart(2,"0")+":"+r.toString().padStart(2,"0")+"."+a.toString().padStart(3,"0")};var Ce=class{constructor(t){this.writer=t;this.helper=new Uint8Array(8);this.helperView=new DataView(this.helper.buffer);this.offsets=new WeakMap}writeU32(t){this.helperView.setUint32(0,t,!1),this.writer.write(this.helper.subarray(0,4))}writeU64(t){this.helperView.setUint32(0,Math.floor(t/2**32),!1),this.helperView.setUint32(4,t,!1),this.writer.write(this.helper.subarray(0,8))}writeAscii(t){for(let e=0;e[(i%256+256)%256],y=i=>(H.setUint16(0,i,!1),[w[0],w[1]]),Vr=i=>(H.setInt16(0,i,!1),[w[0],w[1]]),qt=i=>(H.setUint32(0,i,!1),[w[1],w[2],w[3]]),h=i=>(H.setUint32(0,i,!1),[w[0],w[1],w[2],w[3]]),Xt=i=>(H.setInt32(0,i,!1),[w[0],w[1],w[2],w[3]]),ne=i=>(H.setUint32(0,Math.floor(i/2**32),!1),H.setUint32(4,i,!1),[w[0],w[1],w[2],w[3],w[4],w[5],w[6],w[7]]),At=i=>(H.setInt16(0,2**8*i,!1),[w[0],w[1]]),W=i=>(H.setInt32(0,2**16*i,!1),[w[0],w[1],w[2],w[3]]),vt=i=>(H.setInt32(0,2**30*i,!1),[w[0],w[1],w[2],w[3]]),_t=(i,t)=>{let e=[],r=i;do{let a=r&127;r>>=7,e.length>0&&(a|=128),e.push(a),t!==void 0&&t--}while(r>0||t);return e.reverse()},D=(i,t=!1)=>{let e=Array(i.length).fill(null).map((r,a)=>i.charCodeAt(a));return t&&e.push(0),e},It=i=>{let t=null;for(let e of i)(!t||e.timestamp>t.timestamp)&&(t=e);return t},Gt=i=>[W(i[0]),W(i[1]),vt(i[2]),W(i[3]),W(i[4]),vt(i[5]),W(i[6]),W(i[7]),vt(i[8])],S=(i,t,e)=>({type:i,contents:t&&new Uint8Array(t.flat(10)),children:e}),v=(i,t,e,r,a)=>S(i,[A(t),qt(e),r??[]],a),Yt=i=>i.fragmented?S("ftyp",[D("iso5"),h(512),D("iso5"),D("iso6"),D("mp41")]):S("ftyp",[D("isom"),h(512),D("isom"),i.holdsAvc?D("avc1"):[],D("mp41")]),Qe=i=>({type:"mdat",largeSize:i}),Zt=i=>({type:"free",size:i}),we=(i,t,e=!1)=>S("moov",void 0,[Mr(t,i),...i.map(r=>Dr(r,t)),e?hi(i):null]),Mr=(i,t)=>{let e=z(Math.max(0,...t.filter(s=>s.samples.length>0).map(s=>{let o=It(s.samples);return o.timestamp+o.duration})),Le),r=Math.max(0,...t.map(s=>s.track.id))+1,a=!J(i)||!J(e),n=a?ne:h;return v("mvhd",+a,0,[n(i),n(i),h(Le),n(e),W(1),At(1),Array(10).fill(0),Gt(St),Array(24).fill(0),h(r)])},Dr=(i,t)=>S("trak",void 0,[Pr(i,t),zr(i,t)]),Pr=(i,t)=>{let e=It(i.samples),r=z(e?e.timestamp+e.duration:0,Le),a=!J(t)||!J(r),n=a?ne:h,s;if(i.type==="video"){let o=i.track.metadata.rotation;s=o===void 0||typeof o=="number"?X(o??0):o}else s=St;return v("tkhd",+a,3,[n(t),n(t),h(i.track.id),h(0),n(r),Array(8).fill(0),y(0),y(i.track.id),At(i.type==="audio"?1:0),y(0),Gt(s),W(i.type==="video"?i.info.width:0),W(i.type==="video"?i.info.height:0)])},zr=(i,t)=>S("mdia",void 0,[Ur(i,t),Wr(i),Hr(i)]),Ur=(i,t)=>{let e=It(i.samples),r=z(e?e.timestamp+e.duration:0,i.timescale),a=!J(t)||!J(r),n=a?ne:h;return v("mdhd",+a,0,[n(t),n(t),h(i.timescale),n(r),y(21956),y(0)])},Br={video:"vide",audio:"soun",subtitle:"text"},Nr={video:"VideoHandler",audio:"SoundHandler",subtitle:"TextHandler"},Wr=i=>v("hdlr",0,0,[D("mhlr"),D(Br[i.type]),h(0),h(0),h(0),D(Nr[i.type],!0)]),Hr=i=>S("minf",void 0,[Kr[i.type](),jr(),Gr(i)]),Lr=()=>v("vmhd",0,1,[y(0),y(0),y(0),y(0)]),Qr=()=>v("smhd",0,0,[y(0),y(0)]),$r=()=>v("nmhd",0,0),Kr={video:Lr,audio:Qr,subtitle:$r},jr=()=>S("dinf",void 0,[qr()]),qr=()=>v("dref",0,0,[h(1)],[Xr()]),Xr=()=>v("url ",0,1),Gr=i=>{let t=i.compositionTimeOffsetTable.length>1||i.compositionTimeOffsetTable.some(e=>e.sampleCompositionTimeOffset!==0);return S("stbl",void 0,[Yr(i),ci(i),ui(i),di(i),li(i),mi(i),t?fi(i):null])},Yr=i=>{let t;return i.type==="video"?t=Zr(yi[i.track.source._codec],i):i.type==="audio"?t=ii(vi[i.track.source._codec],i):i.type==="subtitle"&&(t=si(Ai[i.track.source._codec],i)),l(t),v("stsd",0,0,[h(1)],[t])},Zr=(i,t)=>S(i,[Array(6).fill(0),y(1),y(0),y(0),Array(12).fill(0),y(t.info.width),y(t.info.height),h(4718592),h(4718592),h(0),y(1),Array(32).fill(0),y(24),Vr(65535)],[Si[t.track.source._codec](t),Be(t.info.decoderConfig.colorSpace)?Jr(t):null]),Jr=i=>S("colr",[D("nclx"),y(te[i.info.decoderConfig.colorSpace.primaries]),y(re[i.info.decoderConfig.colorSpace.transfer]),y(ie[i.info.decoderConfig.colorSpace.matrix]),A((i.info.decoderConfig.colorSpace.fullRange?1:0)<<7)]),ei=i=>i.info.decoderConfig&&S("avcC",[...ee(i.info.decoderConfig.description)]),ti=i=>i.info.decoderConfig&&S("hvcC",[...ee(i.info.decoderConfig.description)]),jt=i=>{if(!i.info.decoderConfig)return null;let t=i.info.decoderConfig;l(t.colorSpace);let e=t.codec.split("."),r=Number(e[1]),a=Number(e[2]),o=(Number(e[3])<<4)+(0<<1)+Number(t.colorSpace.fullRange);return v("vpcC",1,0,[A(r),A(a),A(o),A(2),A(2),A(2),y(0)])},ri=()=>S("av1C",[129,0,0,0]),ii=(i,t)=>S(i,[Array(6).fill(0),y(1),y(0),y(0),h(0),y(t.info.numberOfChannels),y(16),y(0),y(0),W(t.info.sampleRate)],[_i[t.track.source._codec](t)]),ni=i=>{let e=[...ee(i.info.decoderConfig.description??new ArrayBuffer(0))];return e=[...A(64),...A(21),...qt(0),...h(0),...h(0),...A(5),..._t(e.length),...e],e=[...y(1),...A(0),...A(4),..._t(e.length),...e,...A(6),...A(1),...A(2)],e=[...A(3),..._t(e.length),...e],v("esds",0,0,e)},ai=i=>{let t=3840,e=0,r=i.info.decoderConfig?.description;if(r){l(r.byteLength>=18);let a=ArrayBuffer.isView(r)?new DataView(r.buffer,r.byteOffset,r.byteLength):new DataView(r);t=a.getUint16(10,!0),e=a.getInt16(14,!0)}return S("dOps",[A(0),A(i.info.numberOfChannels),y(t),h(i.info.sampleRate),At(e),A(0)])},si=(i,t)=>S(i,[Array(6).fill(0),y(1)],[Ii[t.track.source._codec](t)]),oi=i=>S("vttC",[...P.encode(i.info.config.description)]);var ci=i=>v("stts",0,0,[h(i.timeToSampleTable.length),i.timeToSampleTable.map(t=>[h(t.sampleCount),h(t.sampleDelta)])]),ui=i=>{if(i.samples.every(e=>e.type==="key"))return null;let t=[...i.samples.entries()].filter(([,e])=>e.type==="key");return v("stss",0,0,[h(t.length),t.map(([e])=>h(e+1))])},di=i=>v("stsc",0,0,[h(i.compactlyCodedChunkTable.length),i.compactlyCodedChunkTable.map(t=>[h(t.firstChunk),h(t.samplesPerChunk),h(1)])]),li=i=>v("stsz",0,0,[h(0),h(i.samples.length),i.samples.map(t=>h(t.size))]),mi=i=>i.finalizedChunks.length>0&&F(i.finalizedChunks).offset>=2**32?v("co64",0,0,[h(i.finalizedChunks.length),i.finalizedChunks.map(t=>ne(t.offset))]):v("stco",0,0,[h(i.finalizedChunks.length),i.finalizedChunks.map(t=>h(t.offset))]),fi=i=>v("ctts",0,0,[h(i.compositionTimeOffsetTable.length),i.compositionTimeOffsetTable.map(t=>[h(t.sampleCount),h(t.sampleCompositionTimeOffset)])]),hi=i=>S("mvex",void 0,i.map(pi)),pi=i=>v("trex",0,0,[h(i.track.id),h(1),h(0),h(0),h(0)]),Et=(i,t)=>S("moof",void 0,[bi(i),...t.map(ki)]),bi=i=>v("mfhd",0,0,[h(i)]),Jt=i=>{let t=0,e=0,r=0,a=0,n=i.type==="delta";return e|=+n,n?t|=1:t|=2,t<<24|e<<16|r<<8|a},ki=i=>S("traf",void 0,[gi(i),Ti(i),xi(i)]),gi=i=>{l(i.currentChunk);let t=0;t|=8,t|=16,t|=32,t|=131072;let e=i.currentChunk.samples[1]??i.currentChunk.samples[0],r={duration:e.timescaleUnitsToNextSample,size:e.size,flags:Jt(e)};return v("tfhd",0,t,[h(i.track.id),h(r.duration),h(r.size),h(r.flags)])},Ti=i=>(l(i.currentChunk),v("tfdt",1,0,[ne(z(i.currentChunk.startTimestamp,i.timescale))])),xi=i=>{l(i.currentChunk);let t=i.currentChunk.samples.map(p=>p.timescaleUnitsToNextSample),e=i.currentChunk.samples.map(p=>p.size),r=i.currentChunk.samples.map(Jt),a=i.currentChunk.samples.map(p=>z(p.timestamp-p.decodeTimestamp,i.timescale)),n=new Set(t),s=new Set(e),o=new Set(r),c=new Set(a),u=o.size===2&&r[0]!==r[1],d=n.size>1,f=s.size>1,m=!u&&o.size>1,g=c.size>1||[...c].some(p=>p!==0),b=0;return b|=1,b|=4*+u,b|=256*+d,b|=512*+f,b|=1024*+m,b|=2048*+g,v("trun",1,b,[h(i.currentChunk.samples.length),h(i.currentChunk.offset-i.currentChunk.moofOffset||0),u?h(r[0]):[],i.currentChunk.samples.map((p,k)=>[d?h(t[k]):[],f?h(e[k]):[],m?h(r[k]):[],g?Xt(a[k]):[]])])},er=i=>S("mfra",void 0,[...i.map(Ci),wi()]),Ci=(i,t)=>v("tfra",1,0,[h(i.track.id),h(63),h(i.finalizedChunks.length),i.finalizedChunks.map(r=>[ne(z(r.samples[0].timestamp,i.timescale)),ne(r.moofOffset),h(t+1),h(1),h(1)])]),wi=()=>v("mfro",0,0,[h(0)]),tr=()=>S("vtte"),rr=(i,t,e,r,a)=>S("vttc",void 0,[a!==null?S("vsid",[Xt(a)]):null,e!==null?S("iden",[...P.encode(e)]):null,t!==null?S("ctim",[...P.encode(He(t))]):null,r!==null?S("sttg",[...P.encode(r)]):null,S("payl",[...P.encode(i)])]),ir=i=>S("vtta",[...P.encode(i)]),yi={avc:"avc1",hevc:"hvc1",vp8:"vp08",vp9:"vp09",av1:"av01"},Si={avc:ei,hevc:ti,vp8:jt,vp9:jt,av1:ri},vi={aac:"mp4a",opus:"Opus"},_i={aac:ni,opus:ai},Ai={webvtt:"wvtt"},Ii={webvtt:oi};var me=class{constructor(t){this.mutex=new q;this.trackTimestampInfo=new WeakMap;this.output=t}beforeTrackAdd(t){}onTrackClose(t){}validateAndNormalizeTimestamp(t,e,r){let a=e/1e6,n=this.trackTimestampInfo.get(t);if(!n){if(!r)throw new Error("First frame must be a key frame.");if(this.timestampsMustStartAtZero&&a>0)throw new Error(`Timestamps must start at zero (got ${a}s).`);n={timestampOffset:a,maxTimestamp:t.source._offsetTimestamps?0:a,lastKeyFrameTimestamp:t.source._offsetTimestamps?0:a},this.trackTimestampInfo.set(t,n)}if(t.source._offsetTimestamps&&(a-=n.timestampOffset),a<0)throw new Error(`Timestamps must be non-negative (got ${a}s).`);if(aa.start-n.start);e.push({start:r[0].start,size:r[0].data.byteLength});for(let a=1;au.start<=r&&rRi){for(let u=0;u=e.written[s+1].start;)e.written[s].end=Math.max(e.written[s].end,e.written[s+1].end),e.written.splice(s+1,1)}createChunk(e){let a={start:Math.floor(e/this.chunkSize)*this.chunkSize,data:new Uint8Array(this.chunkSize),written:[],shouldFlush:!1};return this.chunks.push(a),this.chunks.sort((n,s)=>n.start-s.start),this.chunks.indexOf(a)}queueChunksForFlush(e=!1){l(this.writer);for(let r=0;r{if(i==="avc"){let n=Math.ceil(t/16)*Math.ceil(e/16),s=nr.find(f=>n<=f.maxMacroblocks&&r<=f.maxBitrate)??F(nr),o=s?s.level:0,c="64".padStart(2,"0"),u="00",d=o.toString(16).padStart(2,"0");return`avc1.${c}${u}${d}`}else if(i==="hevc"){let a="",s="6",o=t*e,c=ar.find(d=>o<=d.maxPictureSize&&r<=d.maxBitrate)??F(ar);return`hev1.${a}1.${s}.${c.tier}${c.level}.B0`}else{if(i==="vp8")return"vp8";if(i==="vp9"){let a="00",n=t*e,s=sr.find(c=>n<=c.maxPictureSize&&r<=c.maxBitrate)??F(sr);return`vp09.${a}.${s.level}.08`}else if(i==="av1"){let n=t*e,s=or.find(c=>n<=c.maxPictureSize&&r<=c.maxBitrate)??F(or);return`av01.0.${s.level.toString().padStart(2,"0")}${s.tier}.08`}}throw new TypeError(`Unhandled codec '${i}'.`)},ur=(i,t)=>{if(i==="avc"){if(!t||t.byteLength<4)throw new TypeError("AVC description must be at least 4 bytes long.");return`avc1.${Wt(t.subarray(1,4))}`}else if(i==="hevc"){if(!t)throw new TypeError("HEVC description must be provided.");let e=new DataView(t.buffer,t.byteOffset,t.byteLength),r="hev1.",a=t[1]>>6&3,n=t[1]&31;r+=["","A","B","C"][a]+n,r+=".";let s=Ht(e.getUint32(2));r+=s.toString(16),r+=".";let o=t[1]>>5&1,c=t[12];r+=o===0?"L":"H",r+=c,r+=".";let u=[];for(let d=0;d<6;d++){let f=t[d+13];u.push(f)}for(;u[u.length-1]===0;)u.pop();return r+=u.map(d=>d.toString(16)).join("."),r}throw new TypeError(`Unhandled codec '${i}'.`)},dr=(i,t,e)=>{if(i==="aac")return t>=2&&e<=24e3?"mp4a.40.29":e<=24e3?"mp4a.40.5":"mp4a.40.2";if(i==="opus")return"opus";if(i==="vorbis")return"vorbis";throw new TypeError(`Unhandled codec '${i}'.`)},lr=(i,t)=>{if(i==="aac")return`mp4a.40.${Rt(t).objectType}`;if(i==="opus")return"opus";if(i==="vorbis")return"vorbis";throw new TypeError(`Unhandled codec '${i}'.`)},Rt=i=>{if(!i||i.byteLength<2)throw new TypeError("AAC description must be at least 2 bytes long.");let t=0,e=V(i,t,t+5);t+=5,e===31&&(e=32+V(i,t,t+6),t+=6);let r=V(i,t,t+4);t+=4;let a=null;if(r===15)a=V(i,t,t+24),t+=24;else{let o=[96e3,88200,64e3,48e3,44100,32e3,24e3,22050,16e3,12e3,11025,8e3,7350];r=1&&n<=7&&(s={1:1,2:2,3:3,4:4,5:5,6:6,7:8}[n]),{objectType:e,frequencyIndex:r,sampleRate:a,channelConfiguration:n,numberOfChannels:s}},mr=i=>i==="avc"?{avc:{format:"avc"}}:i==="hevc"?{hevc:{format:"hevc"}}:{},fr=i=>i==="aac"?{aac:{format:"aac"}}:i==="opus"?{opus:{format:"opus"}}:{},qe=i=>{if(!i)throw new TypeError("Video chunk metadata must be provided.");if(typeof i!="object")throw new TypeError("Video chunk metadata must be an object.");if(!i.decoderConfig)throw new TypeError("Video chunk metadata must include a decoder configuration.");if(typeof i.decoderConfig!="object")throw new TypeError("Video chunk metadata decoder configuration must be an object.");if(typeof i.decoderConfig.codec!="string")throw new TypeError("Video chunk metadata decoder configuration must specify a codec string.");if(!Number.isInteger(i.decoderConfig.codedWidth)||i.decoderConfig.codedWidth<=0)throw new TypeError("Video chunk metadata decoder configuration must specify a valid codedWidth (positive integer).");if(!Number.isInteger(i.decoderConfig.codedHeight)||i.decoderConfig.codedHeight<=0)throw new TypeError("Video chunk metadata decoder configuration must specify a valid codedHeight (positive integer).");if(i.decoderConfig.description!==void 0&&!yt(i.decoderConfig.description))throw new TypeError("Video chunk metadata decoder configuration description, when defined, must be an ArrayBuffer or an ArrayBuffer view.");if(i.decoderConfig.colorSpace!==void 0){let{colorSpace:t}=i.decoderConfig;if(typeof t!="object")throw new TypeError("Video chunk metadata decoder configuration colorSpace, when provided, must be an object.");let e=Object.keys(te);if(t.primaries!=null&&!e.includes(t.primaries))throw new TypeError(`Video chunk metadata decoder configuration colorSpace primaries, when defined, must be one of ${e.join(", ")}.`);let r=Object.keys(re);if(t.transfer!=null&&!r.includes(t.transfer))throw new TypeError(`Video chunk metadata decoder configuration colorSpace transfer, when defined, must be one of ${r.join(", ")}.`);let a=Object.keys(ie);if(t.matrix!=null&&!a.includes(t.matrix))throw new TypeError(`Video chunk metadata decoder configuration colorSpace matrix, when defined, must be one of ${a.join(", ")}.`);if(t.fullRange!=null&&typeof t.fullRange!="boolean")throw new TypeError("Video chunk metadata decoder configuration colorSpace fullRange, when defined, must be a boolean.")}if((i.decoderConfig.codec.startsWith("avc1")||i.decoderConfig.codec.startsWith("avc3"))&&!i.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((i.decoderConfig.codec.startsWith("hev1")||i.decoderConfig.codec.startsWith("hvc1"))&&!i.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((i.decoderConfig.codec==="vp8"||i.decoderConfig.codec.startsWith("vp09"))&&i.decoderConfig.colorSpace===void 0)throw new TypeError("Video chunk metadata decoder configuration for VP8/VP9 must include a colorSpace.")},Xe=i=>{if(!i)throw new TypeError("Audio chunk metadata must be provided.");if(typeof i!="object")throw new TypeError("Audio chunk metadata must be an object.");if(!i.decoderConfig)throw new TypeError("Audio chunk metadata must include a decoder configuration.");if(typeof i.decoderConfig!="object")throw new TypeError("Audio chunk metadata decoder configuration must be an object.");if(typeof i.decoderConfig.codec!="string")throw new TypeError("Audio chunk metadata decoder configuration must specify a codec string.");if(!Number.isInteger(i.decoderConfig.sampleRate)||i.decoderConfig.sampleRate<=0)throw new TypeError("Audio chunk metadata decoder configuration must specify a valid sampleRate (positive integer).");if(!Number.isInteger(i.decoderConfig.numberOfChannels)||i.decoderConfig.numberOfChannels<=0)throw new TypeError("Audio chunk metadata decoder configuration must specify a valid numberOfChannels (positive integer).");if(i.decoderConfig.description!==void 0&&!yt(i.decoderConfig.description))throw new TypeError("Audio chunk metadata decoder configuration description, when defined, must be an ArrayBuffer or an ArrayBuffer view.");if(i.decoderConfig.codec.startsWith("mp4a")&&!i.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(i.decoderConfig.codec==="opus"&&i.decoderConfig.description&&i.decoderConfig.description.byteLength<18)throw new TypeError("Invalid decoder description provided for Opus; must be at least 18 bytes long.")},Ge=i=>{if(!i)throw new TypeError("Subtitle metadata must be provided.");if(typeof i!="object")throw new TypeError("Subtitle metadata must be an object.");if(!i.config)throw new TypeError("Subtitle metadata must include a config object.");if(typeof i.config!="object")throw new TypeError("Subtitle metadata config must be an object.");if(typeof i.config.description!="string")throw new TypeError("Subtitle metadata config description must be a string.")};var Le=1e3,Oi=2082844800,z=(i,t,e=!0)=>{let r=i*t;return e?Math.round(r):r},Ye=class extends me{constructor(e,r){super(e);this.timestampsMustStartAtZero=!0;this.auxTarget=new he;this.auxWriter=this.auxTarget._createWriter();this.auxBoxWriter=new Ce(this.auxWriter);this.ftypSize=null;this.mdat=null;this.trackDatas=[];this.creationTime=Math.floor(Date.now()/1e3)+Oi;this.finalizedChunks=[];this.nextFragmentNumber=1;this.writer=e._writer,this.boxWriter=new Ce(this.writer);let a=this.writer instanceof fe?"in-memory":!1;this.fastStart=r._options.fastStart??a,(this.fastStart==="in-memory"||this.fastStart==="fragmented")&&(this.writer.ensureMonotonicity=!0)}async start(){let e=await this.mutex.acquire(),r=this.output._tracks.some(a=>a.type==="video"&&a.source._codec==="avc");this.boxWriter.writeBox(Yt({holdsAvc:r,fragmented:this.fastStart==="fragmented"})),this.ftypSize=this.writer.getPos(),this.fastStart==="in-memory"?this.mdat=Qe(!1):this.fastStart==="fragmented"||(this.mdat=Qe(!0),this.boxWriter.writeBox(this.mdat)),await this.writer.flush(),e()}getVideoTrackData(e,r){let a=this.trackDatas.find(s=>s.track===e);if(a)return a;qe(r),l(r),l(r.decoderConfig),l(r.decoderConfig.codedWidth!==void 0),l(r.decoderConfig.codedHeight!==void 0);let n={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(n),this.trackDatas.sort((s,o)=>s.track.id-o.track.id),n}getAudioTrackData(e,r){let a=this.trackDatas.find(s=>s.track===e);if(a)return a;Xe(r),l(r),l(r.decoderConfig);let n={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(n),this.trackDatas.sort((s,o)=>s.track.id-o.track.id),n}getSubtitleTrackData(e,r){let a=this.trackDatas.find(s=>s.track===e);if(a)return a;Ge(r),l(r),l(r.config);let n={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(n),this.trackDatas.sort((s,o)=>s.track.id-o.track.id),this.validateAndNormalizeTimestamp(e,0,!0),n}async addEncodedVideoChunk(e,r,a){let n=await this.mutex.acquire();try{let s=this.getVideoTrackData(e,a),o=new Uint8Array(r.byteLength);r.copyTo(o);let c=this.validateAndNormalizeTimestamp(s.track,r.timestamp,r.type==="key"),u=this.createSampleForTrack(s,o,c,(r.duration??0)/1e6,r.type);await this.registerSample(s,u)}finally{n()}}async addEncodedAudioChunk(e,r,a){let n=await this.mutex.acquire();try{let s=this.getAudioTrackData(e,a),o=new Uint8Array(r.byteLength);r.copyTo(o);let c=r.type,u=this.validateAndNormalizeTimestamp(s.track,r.timestamp,c==="key"),d=this.createSampleForTrack(s,o,u,(r.duration??0)/1e6,c);await this.registerSample(s,d)}finally{n()}}async addSubtitleCue(e,r,a){let n=await this.mutex.acquire();try{let s=this.getSubtitleTrackData(e,a);this.validateAndNormalizeTimestamp(s.track,1e6*r.timestamp,!0),e.source._codec==="webvtt"&&(s.cueQueue.push(r),await this.processWebVTTCues(s,r.timestamp))}finally{n()}}async processWebVTTCues(e,r){for(;e.cueQueue.length>0;){let a=new Set([]);for(let d of e.cueQueue)l(d.timestamp<=r),l(e.lastCueEndTimestamp<=d.timestamp+d.duration),a.add(Math.max(d.timestamp,e.lastCueEndTimestamp)),a.add(d.timestamp+d.duration);let n=[...a].sort((d,f)=>d-f),s=n[0],o=n[1]??s;if(r=o)break;le.lastIndex=0;let m=le.test(f.text),g=f.timestamp+f.duration,b=e.cueToSourceId.get(f);if(b===void 0&&oa.timestamp).sort((a,n)=>a-n);for(let a=0;a{if(e===o)return r.type==="key";let c=o.sampleQueue[0];return c&&c.type==="key"});n>=1&&s&&(a=!0,await this.finalizeFragment())}else a=n>=.5}a&&(e.currentChunk&&await this.finalizeCurrentChunk(e),e.currentChunk={startTimestamp:r.timestamp,samples:[],offset:null,moofOffset:null}),l(e.currentChunk),e.currentChunk.samples.push(r),e.timestampProcessingQueue.push(r)}async finalizeCurrentChunk(e){if(l(this.fastStart!=="fragmented"),!!e.currentChunk){if(e.finalizedChunks.push(e.currentChunk),this.finalizedChunks.push(e.currentChunk),(e.compactlyCodedChunkTable.length===0||F(e.compactlyCodedChunkTable).samplesPerChunk!==e.currentChunk.samples.length)&&e.compactlyCodedChunkTable.push({firstChunk:e.finalizedChunks.length,samplesPerChunk:e.currentChunk.samples.length}),this.fastStart==="in-memory"){e.currentChunk.offset=0;return}e.currentChunk.offset=this.writer.getPos();for(let r of e.currentChunk.samples)l(r.data),this.writer.write(r.data),r.data=null;await this.writer.flush()}}async interleaveSamples(){l(this.fastStart==="fragmented");for(let e of this.output._tracks)if(!e.source._closed&&!this.trackDatas.some(r=>r.track===e))return;e:for(;;){let e=null,r=1/0;for(let n of this.trackDatas){if(n.sampleQueue.length===0&&!n.track.source._closed)break e;n.sampleQueue.length>0&&n.sampleQueue[0].timestampu.currentChunk),n=this.writer.getPos(),s=Et(r,a);this.boxWriter.writeBox(s);{let u=Qe(!1),d=0;for(let m of a)for(let g of m.currentChunk.samples)d+=g.size;let f=this.boxWriter.measureBox(u)+d;f>=2**32&&(u.largeSize=!0,f=this.boxWriter.measureBox(u)+d),u.size=f,this.boxWriter.writeBox(u)}for(let u of a){u.currentChunk.offset=this.writer.getPos(),u.currentChunk.moofOffset=n;for(let d of u.currentChunk.samples)this.writer.write(d.data),d.data=null}let o=this.writer.getPos();this.writer.seek(this.boxWriter.offsets.get(s));let c=Et(r,a);this.boxWriter.writeBox(c),this.writer.seek(o);for(let u of a)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 a=this.trackDatas.find(n=>n.track===e);a&&await this.processWebVTTCues(a,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 a of r.sampleQueue)await this.addSampleToTrack(r,a);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"){l(this.mdat);let r;for(let n=0;n<2;n++){let s=we(this.trackDatas,this.creationTime),o=this.boxWriter.measureBox(s);r=this.boxWriter.measureBox(this.mdat);let c=this.writer.getPos()+o+r;for(let u of this.finalizedChunks){u.offset=c;for(let{data:d}of u.samples)l(d),c+=d.byteLength,r+=d.byteLength}if(c<2**32)break;r>=2**32&&(this.mdat.largeSize=!0)}let a=we(this.trackDatas,this.creationTime);this.boxWriter.writeBox(a),this.mdat.size=r,this.boxWriter.writeBox(this.mdat);for(let n of this.finalizedChunks)for(let s of n.samples)l(s.data),this.writer.write(s.data),s.data=null}else if(this.fastStart==="fragmented"){let r=this.writer.getPos(),a=er(this.trackDatas);this.boxWriter.writeBox(a);let n=this.writer.getPos()-r;this.writer.seek(this.writer.getPos()-4),this.boxWriter.writeU32(n)}else{l(this.mdat),l(this.ftypSize!==null);let r=this.boxWriter.offsets.get(this.mdat);l(r!==void 0);let a=this.writer.getPos()-r;this.mdat.size=a,this.mdat.largeSize=a>=2**32,this.boxWriter.patchBox(this.mdat);let n=we(this.trackDatas,this.creationTime);if(typeof this.fastStart=="object"){this.writer.seek(this.ftypSize),this.boxWriter.writeBox(n);let s=r-this.writer.getPos();this.boxWriter.writeBox(Zt(s))}else this.boxWriter.writeBox(n)}e()}};var ve=class{constructor(t){this.value=t}},pe=class{constructor(t){this.value=t}},_e=class{constructor(t){this.value=t}};var Ot=i=>i<256?1:i<65536?2:i<1<<24?3:i<2**32?4:i<2**40?5:6,Ft=i=>i>=-64&&i<64?1:i>=-8192&&i<8192?2:i>=-(1<<20)&&i<1<<20?3:i>=-(1<<27)&&i<1<<27?4:i>=-(2**34)&&i<2**34?5:6,hr=i=>{if(i<127)return 1;if(i<16383)return 2;if(i<(1<<21)-1)return 3;if(i<(1<<28)-1)return 4;if(i<2**35-1)return 5;if(i<2**42-1)return 6;throw new Error("EBML VINT size not supported "+i)};var Vt=2**15,pr="https://github.com/Vanilagy/webm-muxer",br=6,kr=5,Fi={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"},Vi={video:1,audio:2,subtitle:17},Ze=class extends me{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=Ot(e)){let a=0;switch(r){case 6:this.helperView.setUint8(a++,e/2**40|0);case 5:this.helperView.setUint8(a++,e/2**32|0);case 4:this.helperView.setUint8(a++,e>>24);case 3:this.helperView.setUint8(a++,e>>16);case 2:this.helperView.setUint8(a++,e>>8);case 1:this.helperView.setUint8(a++,e);break;default:throw new Error("Bad UINT size "+r)}this.writer.write(this.helper.subarray(0,a))}writeSignedInt(e,r=Ft(e)){e<0&&(e+=2**(r*8)),this.writeUnsignedInt(e,r)}writeEBMLVarInt(e,r=hr(e)){let a=0;switch(r){case 1:this.helperView.setUint8(a++,128|e);break;case 2:this.helperView.setUint8(a++,64|e>>8),this.helperView.setUint8(a++,e);break;case 3:this.helperView.setUint8(a++,32|e>>16),this.helperView.setUint8(a++,e>>8),this.helperView.setUint8(a++,e);break;case 4:this.helperView.setUint8(a++,16|e>>24),this.helperView.setUint8(a++,e>>16),this.helperView.setUint8(a++,e>>8),this.helperView.setUint8(a++,e);break;case 5:this.helperView.setUint8(a++,8|e/2**32&7),this.helperView.setUint8(a++,e>>24),this.helperView.setUint8(a++,e>>16),this.helperView.setUint8(a++,e>>8),this.helperView.setUint8(a++,e);break;case 6:this.helperView.setUint8(a++,4|e/2**40&3),this.helperView.setUint8(a++,e/2**32|0),this.helperView.setUint8(a++,e>>24),this.helperView.setUint8(a++,e>>16),this.helperView.setUint8(a++,e>>8),this.helperView.setUint8(a++,e);break;default:throw new Error("Bad EBML VINT size "+r)}this.writer.write(this.helper.subarray(0,a))}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(),a=e.size===-1?1:e.size??4;e.size===-1?this.writeByte(255):this.writer.seek(this.writer.getPos()+a);let n=this.writer.getPos();if(this.dataOffsets.set(e,n),this.writeEBML(e.data),e.size!==-1){let s=this.writer.getPos()-n,o=this.writer.getPos();this.writer.seek(r),this.writeEBMLVarInt(s,a),this.writer.seek(o)}}else if(typeof e.data=="number"){let r=e.size??Ot(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 ve)this.writeEBMLVarInt(4),this.writeFloat32(e.data.value);else if(e.data instanceof pe)this.writeEBMLVarInt(8),this.writeFloat64(e.data.value);else if(e.data instanceof _e){let r=e.size??Ft(e.data.value);this.writeEBMLVarInt(r),this.writeSignedInt(e.data.value,r)}}}beforeTrackAdd(e){if(this.format instanceof oe)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 oe?"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]),a=new Uint8Array([22,84,174,107]),n={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:a},{id:21420,size:5,data:0}]}]};this.seekHead=n}createSegmentInfo(){let e={id:17545,data:new pe(0)};this.segmentDuration=e;let r={id:357149030,data:[{id:2807729,data:1e6},{id:19840,data:pr},{id:22337,data:pr},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:Vi[r.type]},{id:134,data:Fi[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:ee(e.info.decoderConfig.description)}:null,e.track.metadata.frameRate?{id:2352003,data:1e9/e.track.metadata.frameRate}:null],a=e.info.decoderConfig.colorSpace,n={id:224,data:[{id:176,data:e.info.width},{id:186,data:e.info.height},Be(a)?{id:21936,data:[{id:21937,data:ie[a.matrix]},{id:21946,data:re[a.transfer]},{id:21947,data:te[a.primaries]},{id:21945,data:a.fullRange?2:1}]}:null]};return r.push(n),r}audioSpecificTrackInfo(e){return[e.info.decoderConfig.description?{id:25506,data:ee(e.info.decoderConfig.description)}:null,{id:225,data:[{id:181,data:new ve(e.info.sampleRate)},{id:159,data:e.info.numberOfChannels}]}]}subtitleSpecificTrackInfo(e){return[{id:25506,data:P.encode(e.info.config.description)}]}createSegment(){let e={id:408125543,size:this.format._options.streamable?-1:br,data:[this.format._options.streamable?null:this.seekHead,this.segmentInfo,this.tracksElement]};this.segment=e,this.writeEBML(e)}createCues(){this.cues={id:475249515,data:[]}}get segmentDataOffset(){return l(this.segment),this.dataOffsets.get(this.segment)}getVideoTrackData(e,r){let a=this.trackDatas.find(s=>s.track===e);if(a)return a;qe(r),l(r),l(r.decoderConfig),l(r.decoderConfig.codedWidth!==void 0),l(r.decoderConfig.codedHeight!==void 0);let n={track:e,type:"video",info:{width:r.decoderConfig.codedWidth,height:r.decoderConfig.codedHeight,decoderConfig:r.decoderConfig},chunkQueue:[],lastWrittenMsTimestamp:null};return this.trackDatas.push(n),this.trackDatas.sort((s,o)=>s.track.id-o.track.id),n}getAudioTrackData(e,r){let a=this.trackDatas.find(s=>s.track===e);if(a)return a;Xe(r),l(r),l(r.decoderConfig);let n={track:e,type:"audio",info:{numberOfChannels:r.decoderConfig.numberOfChannels,sampleRate:r.decoderConfig.sampleRate,decoderConfig:r.decoderConfig},chunkQueue:[],lastWrittenMsTimestamp:null};return this.trackDatas.push(n),this.trackDatas.sort((s,o)=>s.track.id-o.track.id),n}getSubtitleTrackData(e,r){let a=this.trackDatas.find(s=>s.track===e);if(a)return a;Ge(r),l(r),l(r.config);let n={track:e,type:"subtitle",info:{config:r.config},chunkQueue:[],lastWrittenMsTimestamp:null};return this.trackDatas.push(n),this.trackDatas.sort((s,o)=>s.track.id-o.track.id),n}async addEncodedVideoChunk(e,r,a){let n=await this.mutex.acquire();try{let s=this.getVideoTrackData(e,a),o=new Uint8Array(r.byteLength);r.copyTo(o);let c=r.type==="key",u=this.validateAndNormalizeTimestamp(s.track,r.timestamp,c),d=this.createInternalChunk(o,u,(r.duration??0)/1e6,r.type);e.source._codec==="vp9"&&this.fixVP9ColorSpace(s,d),s.chunkQueue.push(d),await this.interleaveChunks()}finally{n()}}async addEncodedAudioChunk(e,r,a){let n=await this.mutex.acquire();try{let s=this.getAudioTrackData(e,a),o=new Uint8Array(r.byteLength);r.copyTo(o);let c=r.type,u=c==="key",d=this.validateAndNormalizeTimestamp(s.track,r.timestamp,u),f=this.createInternalChunk(o,d,(r.duration??0)/1e6,c);s.chunkQueue.push(f),await this.interleaveChunks()}finally{n()}}async addSubtitleCue(e,r,a){let n=await this.mutex.acquire();try{let s=this.getSubtitleTrackData(e,a),o=this.validateAndNormalizeTimestamp(s.track,1e6*r.timestamp,!0),c=r.text,u=Math.floor(o*1e3);le.lastIndex=0,c=c.replace(le,g=>{let p=We(g.slice(1,-1))-u;return`<${He(p)}>`});let d=P.encode(c),f=`${r.settings??""} -${r.identifier??""} -${r.notes??""}`,m=this.createInternalChunk(d,o,r.duration,"key",f.trim()?P.encode(f):null);s.chunkQueue.push(m),await this.interleaveChunks()}finally{n()}}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 n of this.trackDatas){if(n.chunkQueue.length===0&&!n.track.source._closed)break e;n.chunkQueue.length>0&&n.chunkQueue[0].timestamp=2&&a++;let u={rgb:7,bt709:2,bt470bg:1,smpte170m:3}[e.info.decoderConfig.colorSpace.matrix];zt(r.data,a+0,a+3,u)}createInternalChunk(e,r,a,n,s=null){return{data:e,type:n,timestamp:r,duration:a,additions:s}}writeBlock(e,r){this.segment||(this.createTracks(),this.createSegment());let a=Math.floor(1e3*r.timestamp),n=this.trackDatas.every(f=>{if(f.track.source._closed)return!0;if(e===f)return r.type==="key";let m=f.chunkQueue[0];return m&&m.type==="key"});(!this.currentCluster||n&&a-this.currentClusterMsTimestamp>=1e3)&&this.createNewCluster(a);let s=a-this.currentClusterMsTimestamp;if(s<0)return;if(s>=Vt)throw new Error(`Current Matroska cluster exceeded its maximum allowed length of ${Vt} milliseconds. In order to produce a correct WebM file, you must pass in a key frame at least every ${Vt} milliseconds.`);let c=new Uint8Array(4),u=new DataView(c.buffer);u.setUint8(0,128|e.track.id),u.setInt16(1,s,!1);let d=Math.floor(1e3*r.duration);if(d===0&&!r.additions){u.setUint8(3,+(r.type==="key")<<7);let f={id:163,data:[c,r.data]};this.writeEBML(f)}else{let f={id:160,data:[{id:161,data:[c,r.data]},r.type==="delta"?{id:251,data:new _e(e.lastWrittenMsTimestamp-a)}:null,r.additions?{id:30113,data:[{id:166,data:[{id:165,data:r.additions},{id:238,data:1}]}]}:null,d>0?{id:155,data:d}:null]};this.writeEBML(f)}this.duration=Math.max(this.duration,a+d),e.lastWrittenMsTimestamp=a,this.trackDatasInCurrentCluster.add(e)}createNewCluster(e){this.currentCluster&&!this.format._options.streamable&&this.finalizeCurrentCluster(),this.currentCluster={id:524531317,size:this.format._options.streamable?-1:kr,data:[{id:231,data:e}]},this.writeEBML(this.currentCluster),this.currentClusterMsTimestamp=e,this.trackDatasInCurrentCluster.clear()}finalizeCurrentCluster(){l(this.currentCluster);let e=this.writer.getPos()-this.dataOffsets.get(this.currentCluster),r=this.writer.getPos();this.writer.seek(this.offsets.get(this.currentCluster)+4),this.writeEBMLVarInt(e,kr),this.writer.seek(r);let a=this.offsets.get(this.currentCluster)-this.segmentDataOffset;l(this.cues),this.cues.data.push({id:187,data:[{id:179,data:this.currentClusterMsTimestamp},...[...this.trackDatasInCurrentCluster].map(n=>({id:183,data:[{id:247,data:n.track.id},{id:241,data:a}]}))]})}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(),l(this.cues),this.writeEBML(this.cues),!this.format._options.streamable){let r=this.writer.getPos(),a=this.writer.getPos()-this.segmentDataOffset;this.writer.seek(this.offsets.get(this.segment)+4),this.writeEBMLVarInt(a,br),this.segmentDuration.data=new pe(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 Y=class{},Je=class extends Y{constructor(t={}){if(!t||typeof t!="object")throw new TypeError("options must be an object.");if(t.fastStart!==void 0&&![!1,"in-memory","fragmented"].includes(t.fastStart))throw new TypeError('options.fastStart, when provided, must be false, "in-memory", or "fragmented".');super(),this._options=t}_createMuxer(t){return new Ye(t,this)}},Ae=class extends Y{constructor(t={}){if(!t||typeof t!="object")throw new TypeError("options must be an object.");if(t.streamable!==void 0&&typeof t.streamable!="boolean")throw new TypeError("options.streamable, when provided, must be a boolean.");super(),this._options=t}_createMuxer(t){return new Ze(t,this)}},oe=class extends Ae{};var ce=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)}},L=class extends ce{constructor(e){super();this._connectedTrack=null;if(!ae.includes(e))throw new TypeError(`Invalid video codec '${e}'. Must be one of: ${ae.join(", ")}.`);this._codec=e}},et=class extends L{constructor(t){super(t)}digest(t,e){if(!(t instanceof EncodedVideoChunk))throw new TypeError("chunk must be an EncodedVideoChunk.");return this._ensureValidDigest(),this._connectedTrack.output._muxer.addEncodedVideoChunk(this._connectedTrack,t,e)}},Mi=5,Di=i=>{if(!i||typeof i!="object")throw new TypeError("Codec config must be an object.");if(!ae.includes(i.codec))throw new TypeError(`Invalid video codec '${i.codec}'. Must be one of: ${ae.join(", ")}.`);if(!Number.isInteger(i.bitrate)||i.bitrate<=0)throw new TypeError("config.bitrate must be a positive integer.");if(i.latencyMode!==void 0&&!["quality","realtime"].includes(i.latencyMode))throw new TypeError("config.latencyMode, when provided, must be 'quality' or 'realtime'.");if(i.onEncodingError!==void 0&&typeof i.onEncodingError!="function")throw new TypeError("config.onEncodingError, when provided, must be a function.")},Ie=class{constructor(t,e){this.source=t;this.codecConfig=e;this.encoder=null;this.muxer=null;this.lastMultipleOfKeyFrameInterval=-1;this.lastWidth=null;this.lastHeight=null;Di(e)}async digest(t){if(this.source._ensureValidDigest(),this.lastWidth!==null&&this.lastHeight!==null){if(t.codedWidth!==this.lastWidth||t.codedHeight!==this.lastHeight)throw new Error(`Video frame size must remain constant. Expected ${this.lastWidth}x${this.lastHeight}, got ${t.codedWidth}x${t.codedHeight}.`)}else this.lastWidth=t.codedWidth,this.lastHeight=t.codedHeight;this.ensureEncoder(t),l(this.encoder);let e=Math.floor(t.timestamp/1e6/Mi);this.encoder.encode(t,{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(t){this.encoder||(this.encoder=new VideoEncoder({output:(e,r)=>void this.muxer.addEncodedVideoChunk(this.source._connectedTrack,e,r),error:this.codecConfig.onEncodingError??(e=>console.error("VideoEncoder error:",e))}),this.encoder.configure({codec:cr(this.codecConfig.codec,t.codedWidth,t.codedHeight,this.codecConfig.bitrate),width:t.codedWidth,height:t.codedHeight,bitrate:this.codecConfig.bitrate,framerate:this.source._connectedTrack?.metadata.frameRate,latencyMode:this.codecConfig.latencyMode,...mr(this.codecConfig.codec)}),l(this.source._connectedTrack),this.muxer=this.source._connectedTrack.output._muxer)}async flush(){this.encoder&&(await this.encoder.flush(),this.encoder.close())}},tt=class extends L{constructor(t){super(t.codec),this._encoder=new Ie(this,t)}digest(t){if(!(t instanceof VideoFrame))throw new TypeError("videoFrame must be a VideoFrame.");return this._encoder.digest(t)}_flush(){return this._encoder.flush()}},rt=class extends L{constructor(t,e){if(!(t instanceof HTMLCanvasElement))throw new TypeError("canvas must be an HTMLCanvasElement.");super(e.codec),this._encoder=new Ie(this,e),this._canvas=t}digest(t,e=0){if(!Number.isFinite(t)||t<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*t),duration:Math.round(1e6*e),alpha:"discard"}),a=this._encoder.digest(r);return r.close(),a}_flush(){return this._encoder.flush()}},it=class extends L{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 Ie(this,r),this._track=e}_start(){this._abortController=new AbortController;let e=new MediaStreamTrackProcessor({track:this._track}),r=new WritableStream({write:a=>{this._encoder.digest(a),a.close()}});e.readable.pipeTo(r,{signal:this._abortController.signal}).catch(a=>{a instanceof DOMException&&a.name==="AbortError"||console.error("Pipe error:",a)})}async _flush(){this._abortController&&(this._abortController.abort(),this._abortController=null),await this._encoder.flush()}},Q=class extends ce{constructor(e){super();this._connectedTrack=null;if(!se.includes(e))throw new TypeError(`Invalid audio codec '${e}'. Must be one of: ${se.join(", ")}.`);this._codec=e}},nt=class extends Q{constructor(t){super(t)}digest(t,e){if(!(t instanceof EncodedAudioChunk))throw new TypeError("chunk must be an EncodedAudioChunk.");return this._ensureValidDigest(),this._connectedTrack.output._muxer.addEncodedAudioChunk(this._connectedTrack,t,e)}},Pi=i=>{if(!i||typeof i!="object")throw new TypeError("Codec config must be an object.");if(!se.includes(i.codec))throw new TypeError(`Invalid audio codec '${i.codec}'. Must be one of: ${se.join(", ")}.`);if(!Number.isInteger(i.bitrate)||i.bitrate<=0)throw new TypeError("config.bitrate must be a positive integer.");if(i.onEncodingError!==void 0&&typeof i.onEncodingError!="function")throw new TypeError("config.onEncodingError, when provided, must be a function.")},Ee=class{constructor(t,e){this.source=t;this.codecConfig=e;this.encoder=null;this.muxer=null;this.lastNumberOfChannels=null;this.lastSampleRate=null;Pi(e)}async digest(t){if(this.source._ensureValidDigest(),this.lastNumberOfChannels!==null&&this.lastSampleRate!==null){if(t.numberOfChannels!==this.lastNumberOfChannels||t.sampleRate!==this.lastSampleRate)throw new Error(`Audio parameters must remain constant. Expected ${this.lastNumberOfChannels} channels at ${this.lastSampleRate} Hz, got ${t.numberOfChannels} channels at ${t.sampleRate} Hz.`)}else this.lastNumberOfChannels=t.numberOfChannels,this.lastSampleRate=t.sampleRate;this.ensureEncoder(t),l(this.encoder),this.encoder.encode(t),this.encoder.encodeQueueSize>=4&&await new Promise(e=>this.encoder.addEventListener("dequeue",e,{once:!0})),await this.muxer.mutex.currentPromise}ensureEncoder(t){this.encoder||(this.encoder=new AudioEncoder({output:(e,r)=>void this.muxer.addEncodedAudioChunk(this.source._connectedTrack,e,r),error:this.codecConfig.onEncodingError??(e=>console.error("AudioEncoder error:",e))}),this.encoder.configure({codec:dr(this.codecConfig.codec,t.numberOfChannels,t.sampleRate),numberOfChannels:t.numberOfChannels,sampleRate:t.sampleRate,bitrate:this.codecConfig.bitrate,...fr(this.codecConfig.codec)}),l(this.source._connectedTrack),this.muxer=this.source._connectedTrack.output._muxer)}async flush(){this.encoder&&(await this.encoder.flush(),this.encoder.close())}},at=class extends Q{constructor(t){super(t.codec),this._encoder=new Ee(this,t)}digest(t){if(!(t instanceof AudioData))throw new TypeError("audioData must be an AudioData.");return this._encoder.digest(t)}_flush(){return this._encoder.flush()}},st=class extends Q{constructor(e){super(e.codec);this._accumulatedFrameCount=0;this._encoder=new Ee(this,e)}digest(e){if(!(e instanceof AudioBuffer))throw new TypeError("audioBuffer must be an AudioBuffer.");let r=e.numberOfChannels,a=e.sampleRate,n=e.length,s=new Float32Array(r*n);for(let u=0;u{this._encoder.digest(a),a.close()}});e.readable.pipeTo(r,{signal:this._abortController.signal}).catch(a=>{a instanceof DOMException&&a.name==="AbortError"||console.error("Pipe error:",a)})}async _flush(){this._abortController&&(this._abortController.abort(),this._abortController=null),await this._encoder.flush()}},ue=class extends ce{constructor(e){super();this._connectedTrack=null;if(!Se.includes(e))throw new TypeError(`Invalid subtitle codec '${e}'. Must be one of: ${Se.join(", ")}.`);this._codec=e}},ct=class extends ue{constructor(t){super(t),this._parser=new Ne({codec:t,output:(e,r)=>this._connectedTrack?.output._muxer.addSubtitleCue(this._connectedTrack,e,r)})}digest(t){if(typeof t!="string")throw new TypeError("text must be a string.");return this._ensureValidDigest(),this._parser.parse(t),this._connectedTrack.output._muxer.mutex.currentPromise}};var ut=class{constructor(t){this._tracks=[];this._started=!1;this._finalizing=!1;this._mutex=new q;if(!t||typeof t!="object")throw new TypeError("options must be an object.");if(!(t.format instanceof Y))throw new TypeError("options.format must be an OutputFormat.");if(!(t.target instanceof G))throw new TypeError("options.target must be a Target.");if(t.target._output)throw new Error("Target is already used for another output.");t.target._output=this,this._writer=t.target._createWriter(),this._muxer=t.format._createMuxer(this)}addVideoTrack(t,e={}){if(!(t instanceof L))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",t,e)}addAudioTrack(t,e={}){if(!(t instanceof Q))throw new TypeError("source must be an AudioSource.");if(!e||typeof e!="object")throw new TypeError("metadata must be an object.");this._addTrack("audio",t,e)}addSubtitleTrack(t,e={}){if(!(t instanceof ue))throw new TypeError("source must be a SubtitleSource.");if(!e||typeof e!="object")throw new TypeError("metadata must be an object.");this._addTrack("subtitle",t,e)}_addTrack(t,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 a={id:this._tracks.length+1,output:this,type:t,source:e,metadata:r};this._muxer.beforeTrackAdd(a),this._tracks.push(a),e._connectedTrack=a}async start(){if(this._started)throw new Error("Output already started.");this._started=!0,this._writer.start();let t=await this._mutex.acquire();await this._muxer.start();for(let e of this._tracks)e.source._start();t()}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 t=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(),t()}};var j=class{constructor(){this._sizePromise=null}_getSize(){return this._sizePromise??=this._retrieveSize()}},dt=class extends j{constructor(t){if(!(t instanceof ArrayBuffer)&&!(t instanceof Uint8Array))throw new TypeError("buffer must be an ArrayBuffer or Uint8Array.");super(),this._bytes=t instanceof Uint8Array?t:new Uint8Array(t)}async _read(t,e){return this._bytes.subarray(t,e)}async _retrieveSize(){return this._bytes.byteLength}},lt=class extends j{constructor(t){if(!(t instanceof Blob))throw new TypeError("blob must be a Blob.");super(),this._blob=t}async _read(t,e){let a=await this._blob.slice(t,e).arrayBuffer();return new Uint8Array(a)}async _retrieveSize(){return this._blob.size}},mt=class extends j{constructor(e,r={}){if(typeof e!="string")throw new TypeError("url must be a string.");if(!r||typeof r!="object")throw new TypeError("options must be an object.");if(r.withCredentials!==void 0&&typeof r.withCredentials!="boolean")throw new TypeError("options.withCredentials, when specified, must be a boolean.");super();this._fullData=null;this._url=e,this._withCredentials=r.withCredentials??!1}_makeRequest(e){return new Promise((r,a)=>{let n=new XMLHttpRequest;n.open("GET",this._url,!0),n.responseType="arraybuffer",n.withCredentials=this._withCredentials,e&&n.setRequestHeader("Range",`bytes=${e.start}-${e.end-1}`),n.onload=()=>{if(n.status>=200&&n.status<300){let s=n.response;e||(this._fullData=s),r({response:s,statusCode:n.status})}else a(new Error(`Error fetching ${this._url}: ${n.status} ${n.statusText}`))},n.onerror=()=>{a(new Error("Network error occurred."))},n.ontimeout=()=>{a(new Error("Request timed out."))},n.send()})}async _read(e,r){if(this._fullData)return new Uint8Array(this._fullData,e,r-e);let{response:a,statusCode:n}=await this._makeRequest({start:e,end:r});return n===200?new Uint8Array(a).subarray(e,r):new Uint8Array(a)}async _retrieveSize(){if(this._fullData)return this._fullData.byteLength;let e=new XMLHttpRequest;e.open("HEAD",this._url,!0),e.withCredentials=this._withCredentials,await new Promise((a,n)=>{e.onload=()=>{e.status>=200&&e.status<300?a():n(new Error(`Error fetching ${this._url} (HEAD): ${e.status} ${e.statusText}`))},e.onerror=()=>{a()},e.send()});let r=e.getResponseHeader("Content-Length");if(!r){let{response:a}=await this._makeRequest();return a.byteLength}return parseInt(r,10)}};var ft=class{constructor(t){this.input=t}};var be=class{constructor(t){this._backing=t}isVideoTrack(){return this instanceof U}isAudioTrack(){return this instanceof B}computeDuration(){return this._backing.computeDuration()}},U=class extends be{constructor(t){super(t),this._backing=t}getCodec(){return this._backing.getCodec()}getCodedWidth(){return this._backing.getCodedWidth()}getCodedHeight(){return this._backing.getCodedHeight()}getRotation(){return this._backing.getRotation()}async getDisplayWidth(){return await this._backing.getRotation()%180===0?this._backing.getCodedWidth():this._backing.getCodedHeight()}async getDisplayHeight(){return await this._backing.getRotation()%180===0?this._backing.getCodedHeight():this._backing.getCodedWidth()}getDecoderConfig(){return this._backing.getDecoderConfig()}async getCodecMimeType(){return(await this.getDecoderConfig()).codec}},B=class extends be{constructor(t){super(t),this._backing=t}getCodec(){return this._backing.getCodec()}getNumberOfChannels(){return this._backing.getNumberOfChannels()}getSampleRate(){return this._backing.getSampleRate()}getDecoderConfig(){return this._backing.getDecoderConfig()}async getCodecMimeType(){return(await this.getDecoderConfig()).codec}};var ke=class{constructor(t,e=1/0){this.source=t;this.maxStorableBytes=e;this.loadedSegments=[];this.loadingSegments=[];this.sourceSizePromise=null;this.nextAge=0;this.totalStoredBytes=0}async loadRange(t,e){e=Math.min(e,await this.source._getSize());let r=this.loadingSegments.find(c=>c.start<=t&&c.end>=e);if(r){await r.promise;return}if(this.loadedSegments.some(c=>c.start<=t&&c.end>=e))return;let n=this.source._read(t,e),s={start:t,end:e,promise:n};this.loadingSegments.push(s);let o=await n;Lt(this.loadingSegments,s),this.insertIntoLoadedSegments(t,o)}insertIntoLoadedSegments(t,e){let r={start:t,end:t+e.byteLength,bytes:e,view:new DataView(e.buffer),age:this.nextAge++},a=E(this.loadedSegments,t,n=>n.start);(a===-1||this.loadedSegments[a].start=r.end)break;r.start<=s.start&&s.end<=r.end&&(this.loadedSegments.splice(n,1),n--)}for(;this.totalStoredBytes>this.maxStorableBytes&&this.loadedSegments.length>1;){let n=null,s=-1;for(let o=0;on.start),a=null;if(r!==-1)for(let n=r;nt)break;if(e<=s.end){a=s;break}}if(!a)throw new Error(`No segment loaded for range [${t}, ${e}).`);return a.age=this.nextAge++,{view:a.view,offset:a.bytes.byteOffset+t-a.start}}forgetRange(t,e){if(e<=t)return;let r=E(this.loadedSegments,t,n=>n.start);if(r===-1)return;let a=this.loadedSegments[r];a.start!==t||a.end!==e||(this.loadedSegments.splice(r,1),this.totalStoredBytes-=a.bytes.byteLength)}};var de=class{constructor(t){this.reader=t;this.pos=0}readRange(t,e){let{view:r,offset:a}=this.reader.getViewAndOffset(t,e);return new Uint8Array(r.buffer,a,e-t)}readU8(){let{view:t,offset:e}=this.reader.getViewAndOffset(this.pos,this.pos+1);return this.pos++,t.getUint8(e)}readU16(){let{view:t,offset:e}=this.reader.getViewAndOffset(this.pos,this.pos+2);return this.pos+=2,t.getUint16(e,!1)}readU24(){let{view:t,offset:e}=this.reader.getViewAndOffset(this.pos,this.pos+3);this.pos+=3;let r=t.getUint16(e,!1),a=t.getUint8(e+2);return r*256+a}readS32(){let{view:t,offset:e}=this.reader.getViewAndOffset(this.pos,this.pos+4);return this.pos+=4,t.getInt32(e,!1)}readU32(){let{view:t,offset:e}=this.reader.getViewAndOffset(this.pos,this.pos+4);return this.pos+=4,t.getUint32(e,!1)}readI32(){let{view:t,offset:e}=this.reader.getViewAndOffset(this.pos,this.pos+4);return this.pos+=4,t.getInt32(e,!1)}readU64(){let t=this.readU32(),e=this.readU32();return t*4294967296+e}readF64(){let{view:t,offset:e}=this.reader.getViewAndOffset(this.pos,this.pos+8);return this.pos+=8,t.getFloat64(e,!1)}readFixed_16_16(){return this.readS32()/65536}readFixed_2_30(){return this.readS32()/1073741824}readAscii(t){let{view:e,offset:r}=this.reader.getViewAndOffset(this.pos,this.pos+t);this.pos+=t;let a="";for(let n=0;na.computeDuration()));return Math.max(0,...r)}async getTracks(){return await this.readMetadata(),this.tracks.map(e=>e.inputTrack)}async getMimeType(){await this.readMetadata();let e="video/mp4";if(this.tracks.length>0){let r=await Promise.all(this.tracks.map(n=>n.inputTrack.getCodecMimeType())),a=[...new Set(r)];e+=`; codecs="${a.join(", ")}"`}return e}readMetadata(){return this.metadataPromise??=(async()=>{let e=await this.isobmffReader.reader.source._getSize();for(;this.isobmffReader.pos=0&&aa.presentationTimestamp-n.presentationTimestamp),e.sampleTable}async readFragment(){let e=this.isobmffReader.pos;await this.isobmffReader.reader.loadRange(this.isobmffReader.pos,this.isobmffReader.pos+16);let r=this.isobmffReader.readBoxHeader();l(r.name==="moof"),await this.isobmffReader.reader.loadRange(e,e+r.totalSize),this.isobmffReader.pos=e,this.traverseBox();let a=K(this.fragments,e,s=>s.moofOffset);l(a!==-1);let n=this.fragments[a];l(n.moofOffset===e),this.isobmffReader.reader.forgetRange(e,e+r.totalSize);for(let[s,o]of n.trackData){if(o.startTimestampIsFinal)continue;let c=this.tracks.find(m=>m.id===s);this.isobmffReader.pos=0;let u=null,d=null,f=E(c.fragments,e-1,m=>m.moofOffset);for(f!==-1&&(u=c.fragments[f],d=u,this.isobmffReader.pos=u.moofOffset+u.moofSize);this.isobmffReader.posp.moofOffset);if(b===-1){this.isobmffReader.pos=m;let p=await this.readFragment();u&&(u.nextFragment=p),u=p}else{let p=this.fragments[b];u&&(u.nextFragment=p),u=p}}this.isobmffReader.pos=m+g.totalSize}u&&u.trackData.has(s)&&(d=u)}if(d){let m=d.trackData.get(s);l(m.startTimestampIsFinal),Tr(o,m.endTimestamp)}o.startTimestampIsFinal=!0}return n}readContiguousBoxes(e){let r=this.isobmffReader.pos;for(;this.isobmffReader.pos-rf.every((m,g)=>m===u[g]));d===-1?n.rotation=0:n.rotation=90*d}break;case"mdhd":{let n=this.currentTrack;l(n);let s=this.isobmffReader.readU8();this.isobmffReader.pos+=3,s===0?(this.isobmffReader.pos+=8,n.timescale=this.isobmffReader.readU32(),n.durationInTimescale=this.isobmffReader.readU32()):s===1&&(this.isobmffReader.pos+=16,n.timescale=this.isobmffReader.readU32(),n.durationInTimescale=this.isobmffReader.readU64())}break;case"hdlr":{let n=this.currentTrack;l(n),this.isobmffReader.pos+=8;let s=this.isobmffReader.readAscii(4);s==="vide"?n.info={type:"video",width:-1,height:-1,codec:null,codecDescription:null,colorSpace:null}:s==="soun"&&(n.info={type:"audio",numberOfChannels:-1,sampleRate:-1,codec:null,codecDescription:null})}break;case"stbl":{let n=this.currentTrack;l(n),n.sampleTableOffset=e,this.readContiguousBoxes(r.contentSize)}break;case"stsd":{let n=this.currentTrack;if(l(n),n.info===null||n.sampleTable)break;let s=this.isobmffReader.readU8();this.isobmffReader.pos+=3;let o=this.isobmffReader.readU32();for(let c=0;c0){if(d===1)this.isobmffReader.pos+=4*4;else if(d===2){this.isobmffReader.pos+=4,m=this.isobmffReader.readF64(),f=this.isobmffReader.readU32(),this.isobmffReader.pos+=4;let g=this.isobmffReader.readU32(),b=this.isobmffReader.readU32(),p=this.isobmffReader.readU32(),k=this.isobmffReader.readU32()}}n.info.numberOfChannels=f,n.info.sampleRate=m,this.readContiguousBoxes(e+u.totalSize-this.isobmffReader.pos)}}}break;case"avcC":{let n=this.currentTrack;l(n&&n.info),n.info.codecDescription=this.isobmffReader.readRange(this.isobmffReader.pos,this.isobmffReader.pos+r.contentSize)}break;case"hvcC":{let n=this.currentTrack;l(n&&n.info),n.info.codecDescription=this.isobmffReader.readRange(this.isobmffReader.pos,this.isobmffReader.pos+r.contentSize)}break;case"colr":{let n=this.currentTrack;if(l(n&&n.info?.type==="video"),this.isobmffReader.readAscii(4)!=="nclx")break;let o=this.isobmffReader.readU16(),c=this.isobmffReader.readU16(),u=this.isobmffReader.readU16(),d=!!(this.isobmffReader.readU8()&128);n.info.colorSpace={primaries:Ut[o],transfer:Bt[c],matrix:Nt[u],fullRange:d}}break;case"wave":r.totalSize>8&&this.readContiguousBoxes(r.contentSize);break;case"esds":{let n=this.currentTrack;l(n&&n.info),this.isobmffReader.pos+=4;let s=this.isobmffReader.readU8();l(s===3),this.isobmffReader.readIsomVariableInteger(),this.isobmffReader.pos+=2;let o=this.isobmffReader.readU8(),c=(o&128)!==0,u=(o&64)!==0,d=(o&32)!==0;if(c&&(this.isobmffReader.pos+=2),u){let p=this.isobmffReader.readU8();this.isobmffReader.pos+=p}d&&(this.isobmffReader.pos+=2);let f=this.isobmffReader.readU8();l(f===4),this.isobmffReader.readIsomVariableInteger();let m=this.isobmffReader.readU8();l(m===64),this.isobmffReader.pos+=12;let g=this.isobmffReader.readU8();l(g===5);let b=this.isobmffReader.readIsomVariableInteger();n.info.codecDescription=this.isobmffReader.readRange(this.isobmffReader.pos,this.isobmffReader.pos+b)}break;case"stts":{let n=this.currentTrack;if(l(n),!n.sampleTable)break;this.isobmffReader.pos+=4;let s=this.isobmffReader.readU32(),o=0,c=0;for(let u=0;ux.id===s);if(!o)break;o.fragmentLookupTable=[];let c=this.isobmffReader.readU32(),u=(c&48)>>4,d=(c&12)>>2,f=c&3,m=this.isobmffReader,g=[m.readU8.bind(m),m.readU16.bind(m),m.readU24.bind(m),m.readU32.bind(m)],b=g[u],p=g[d],k=g[f],T=this.isobmffReader.readU32();for(let x=0;xs.moofOffset);this.fragments.splice(n+1,0,this.currentFragment);for(let[,s]of this.currentFragment.trackData){let o=s.samples[0],c=F(s.samples);this.currentFragment.dataStart=Math.min(this.currentFragment.dataStart,o.byteOffset),this.currentFragment.dataEnd=Math.max(this.currentFragment.dataEnd,c.byteOffset+c.byteSize)}this.currentFragment=null}break;case"traf":if(l(this.currentFragment),this.readContiguousBoxes(r.contentSize),this.currentTrack){let n=this.currentFragment.trackData.get(this.currentTrack.id);if(n){let s=E(this.currentTrack.fragments,this.currentFragment.moofOffset,c=>c.moofOffset);this.currentTrack.fragments.splice(s+1,0,this.currentFragment);let{currentFragmentState:o}=this.currentTrack;l(o),o.startTimestamp!==null&&(Tr(n,o.startTimestamp),n.startTimestampIsFinal=!0)}this.currentTrack.currentFragmentState=null,this.currentTrack=null}break;case"tfhd":{l(this.currentFragment),this.isobmffReader.pos+=1;let n=this.isobmffReader.readU24(),s=!!(n&1),o=!!(n&2),c=!!(n&8),u=!!(n&16),d=!!(n&32),f=!!(n&65536),m=!!(n&131072),g=this.isobmffReader.readU32(),b=this.tracks.find(k=>k.id===g);if(!b)break;let p=this.fragmentTrackDefaults.find(k=>k.trackId===g);this.currentTrack=b,b.currentFragmentState={baseDataOffset:this.currentFragment.implicitBaseDataOffset,sampleDescriptionIndex:p?.defaultSampleDescriptionIndex??null,defaultSampleDuration:p?.defaultSampleDuration??null,defaultSampleSize:p?.defaultSampleSize??null,defaultSampleFlags:p?.defaultSampleFlags??null,startTimestamp:null},s?b.currentFragmentState.baseDataOffset=this.isobmffReader.readU64():m&&(b.currentFragmentState.baseDataOffset=this.currentFragment.moofOffset),o&&(b.currentFragmentState.sampleDescriptionIndex=this.isobmffReader.readU32()),c&&(b.currentFragmentState.defaultSampleDuration=this.isobmffReader.readU32()),u&&(b.currentFragmentState.defaultSampleSize=this.isobmffReader.readU32()),d&&(b.currentFragmentState.defaultSampleFlags=this.isobmffReader.readU32()),f&&(b.currentFragmentState.defaultSampleDuration=0)}break;case"tfdt":{let n=this.currentTrack;if(!n)break;l(n.currentFragmentState);let s=this.isobmffReader.readU8();this.isobmffReader.pos+=3;let o=s===0?this.isobmffReader.readU32():this.isobmffReader.readU64();n.currentFragmentState.startTimestamp=o}break;case"trun":{let n=this.currentTrack;if(!n)break;if(l(this.currentFragment),l(n.currentFragmentState),this.currentFragment.trackData.has(n.id))throw new Error("Can't have two trun boxes for the same track in one fragment.");let s=this.isobmffReader.readU8(),o=this.isobmffReader.readU24(),c=!!(o&1),u=!!(o&4),d=!!(o&256),f=!!(o&512),m=!!(o&1024),g=!!(o&2048),b=this.isobmffReader.readU32(),p=n.currentFragmentState.baseDataOffset;c&&(p+=this.isobmffReader.readI32());let k=null;u&&(k=this.isobmffReader.readU32());let T=p;if(b===0){this.currentFragment.implicitBaseDataOffset=T;break}let x=0,C={startTimestamp:0,endTimestamp:0,samples:[],presentationTimestamps:[],startTimestampIsFinal:!1};this.currentFragment.trackData.set(n.id,C);for(let I=0;I({presentationTimestamp:I.presentationTimestamp,sampleIndex:R})).sort((I,R)=>I.presentationTimestamp-R.presentationTimestamp);let O=C.samples[C.presentationTimestamps[0].sampleIndex],_=C.samples[F(C.presentationTimestamps).sampleIndex];C.startTimestamp=O.presentationTimestamp,C.endTimestamp=_.presentationTimestamp+_.duration,this.currentFragment.implicitBaseDataOffset=T}break}this.isobmffReader.pos=a}},pt=class{constructor(t){this.internalTrack=t;this.chunkToSampleIndex=new WeakMap;this.chunkToFragmentLocation=new WeakMap}getCodec(){throw new Error("Not implemented on base class.")}async computeDuration(){let e=(await this.getChunk(1/0,{metadataOnly:!0}))?.timestamp;return e?e/1e6:0}async getFirstChunk(t){return this.internalTrack.demuxer.isFragmented?this.performFragmentedLookup(()=>{let e=this.internalTrack.fragments[0];return{fragmentIndex:e?0:-1,sampleIndex:e?0:-1,correctSampleFound:!!e}},0,1/0,t):this.fetchChunkForSampleIndex(0,t)}roundToMicrosecond(t){return(Math.floor(t*1e6)+.99999999)/1e6}async getChunk(t,e){t=this.roundToMicrosecond(t);let r=t*this.internalTrack.timescale;if(this.internalTrack.demuxer.isFragmented)return this.performFragmentedLookup(()=>this.findSampleInFragmentsForTimestamp(r),r,r,e);{let a=this.internalTrack.demuxer.getSampleTableForTrack(this.internalTrack),n=gr(a,r);return this.fetchChunkForSampleIndex(n,e)}}async getNextChunk(t,e){if(this.internalTrack.demuxer.isFragmented){let a=this.chunkToFragmentLocation.get(t);if(a===void 0)throw new Error("Chunk was not created from this track.");let n=a.fragment.trackData.get(this.internalTrack.id),s=n.samples[a.sampleIndex],o=K(this.internalTrack.fragments,a.fragment.moofOffset,c=>c.moofOffset);return l(o!==-1),this.performFragmentedLookup(()=>{if(a.sampleIndex+1f.moofOffset);return l(d!==-1),{fragmentIndex:d,sampleIndex:0,correctSampleFound:!0}}return{fragmentIndex:o,sampleIndex:-1,correctSampleFound:!1}}},s.presentationTimestamp,1/0,e)}let r=this.chunkToSampleIndex.get(t);if(r===void 0)throw new Error("Chunk was not created from this track.");return this.fetchChunkForSampleIndex(r+1,e)}async getKeyChunk(t,e){t=this.roundToMicrosecond(t);let r=t*this.internalTrack.timescale;if(this.internalTrack.demuxer.isFragmented)return this.performFragmentedLookup(()=>this.findKeySampleInFragmentsForTimestamp(r),r,r,e);let a=this.internalTrack.demuxer.getSampleTableForTrack(this.internalTrack),n=gr(a,r),s=n===-1?-1:Bi(a,n);return this.fetchChunkForSampleIndex(s,e)}async getNextKeyChunk(t,e){if(this.internalTrack.demuxer.isFragmented){let s=this.chunkToFragmentLocation.get(t);if(s===void 0)throw new Error("Chunk was not created from this track.");let o=s.fragment.trackData.get(this.internalTrack.id),c=o.samples[s.sampleIndex],u=K(this.internalTrack.fragments,s.fragment.moofOffset,d=>d.moofOffset);return l(u!==-1),this.performFragmentedLookup(()=>{let d=o.samples.findIndex((f,m)=>f.isKeyFrame&&m>s.sampleIndex);if(d!==-1)return{fragmentIndex:u,sampleIndex:d,correctSampleFound:!0};{let f=s.fragment;for(;f.nextFragment;){f=f.nextFragment;let m=f.trackData.get(this.internalTrack.id);if(m){let g=K(this.internalTrack.fragments,f.moofOffset,p=>p.moofOffset);l(g!==-1);let b=m.samples.findIndex(p=>p.isKeyFrame);if(b===-1)throw new Error("Not supported: Fragment does not contain key sample.");return{fragmentIndex:g,sampleIndex:b,correctSampleFound:!0}}}return{fragmentIndex:u,sampleIndex:-1,correctSampleFound:!1}}},c.presentationTimestamp,1/0,e)}let r=this.chunkToSampleIndex.get(t);if(r===void 0)throw new Error("Chunk was not created from this track.");let a=this.internalTrack.demuxer.getSampleTableForTrack(this.internalTrack),n=Ni(a,r);return this.fetchChunkForSampleIndex(n,e)}async fetchChunkForSampleIndex(t,e){if(t===-1)return null;let r=this.internalTrack.demuxer.getSampleTableForTrack(this.internalTrack),a=Ui(r,t);if(!a)return null;let n;e.metadataOnly?n=new Uint8Array(0):(await this.internalTrack.demuxer.chunkReader.reader.loadRange(a.chunkOffset,a.chunkOffset+a.chunkSize),n=this.internalTrack.demuxer.chunkReader.readRange(a.sampleOffset,a.sampleOffset+a.sampleSize));let s=1e6*a.presentationTimestamp/this.internalTrack.timescale,o=1e6*a.duration/this.internalTrack.timescale,c=this.createChunk(n,s,o,a.isKeyFrame);return this.chunkToSampleIndex.set(c,t),c}async fetchChunkInFragment(t,e,r){if(e===-1)return null;let n=t.trackData.get(this.internalTrack.id).samples[e];l(n);let s;r.metadataOnly?s=new Uint8Array(0):(await this.internalTrack.demuxer.chunkReader.reader.loadRange(t.dataStart,t.dataEnd),s=this.internalTrack.demuxer.chunkReader.readRange(n.byteOffset,n.byteOffset+n.byteSize));let o=1e6*n.presentationTimestamp/this.internalTrack.timescale,c=1e6*n.duration/this.internalTrack.timescale,u=this.createChunk(s,o,c,n.isKeyFrame);return this.chunkToFragmentLocation.set(u,{fragment:t,sampleIndex:e}),u}findSampleInFragmentsForTimestamp(t){let e=E(this.internalTrack.fragments,t,n=>n.trackData.get(this.internalTrack.id).startTimestamp),r=-1,a=!1;if(e!==-1){let s=this.internalTrack.fragments[e].trackData.get(this.internalTrack.id),o=E(s.presentationTimestamps,t,c=>c.presentationTimestamp);l(o!==-1),r=s.presentationTimestamps[o].sampleIndex,a=tn.trackData.get(this.internalTrack.id).startTimestamp),r=-1,a=!1;if(e!==-1){let s=this.internalTrack.fragments[e].trackData.get(this.internalTrack.id),o=Qt(s.presentationTimestamps,u=>s.samples[u.sampleIndex].isKeyFrame&&u.presentationTimestamp<=t);if(o===-1)throw new Error("Not supported: Fragment does not begin with a key sample.");r=s.presentationTimestamps[o].sampleIndex,a=tT.timestamp);k!==-1&&(p=this.internalTrack.fragmentLookupTable[k])}if(o===-1)d.pos=p?.moofOffset??0;else{let k=this.internalTrack.fragments[o];!p||k.moofOffset>=k.moofOffset?(d.pos=k.moofOffset+k.moofSize,m=k):d.pos=p.moofOffset}for(;d.posr)break;if(m.nextFragment){d.pos=m.nextFragment.moofOffset+m.nextFragment.moofSize,m=m.nextFragment;continue}}await d.reader.loadRange(d.pos,d.pos+16);let k=d.pos,T=d.readBoxHeader();if(T.name==="moof"){let x=K(n.fragments,k,C=>C.moofOffset);if(x===-1){d.pos=k;let C=await n.readFragment();m&&(m.nextFragment=C),m=C;let{fragmentIndex:O,sampleIndex:_,correctSampleFound:I}=t();if(I){let R=this.internalTrack.fragments[O];return this.fetchChunkInFragment(R,_,a)}O!==-1&&(g=O,b=_)}else{let C=n.fragments[x];m&&(m.nextFragment=C),m=C}}d.pos=k+T.totalSize}if(g!==-1){let k=this.internalTrack.fragments[g];return this.fetchChunkInFragment(k,b,a)}return null}finally{s()}}},Mt=class extends pt{constructor(t){super(t),this.internalTrack=t}async getCodec(){return this.internalTrack.info.codec}async getCodedWidth(){return this.internalTrack.info.width}async getCodedHeight(){return this.internalTrack.info.height}async getRotation(){return this.internalTrack.rotation}async getDecoderConfig(){return{codec:ur(this.internalTrack.info.codec,this.internalTrack.info.codecDescription),codedWidth:this.internalTrack.info.width,codedHeight:this.internalTrack.info.height,description:this.internalTrack.info.codecDescription??void 0,colorSpace:this.internalTrack.info.colorSpace??void 0}}createChunk(t,e,r,a){return new EncodedVideoChunk({data:t,timestamp:e,duration:r,type:a?"key":"delta"})}},Dt=class extends pt{constructor(t){super(t),this.internalTrack=t}async getCodec(){return this.internalTrack.info.codec}async getNumberOfChannels(){return this.internalTrack.info.numberOfChannels}async getSampleRate(){return this.internalTrack.info.sampleRate}async getDecoderConfig(){return{codec:lr(this.internalTrack.info.codec,this.internalTrack.info.codecDescription),numberOfChannels:this.internalTrack.info.numberOfChannels,sampleRate:this.internalTrack.info.sampleRate,description:this.internalTrack.info.codecDescription??void 0}}createChunk(t,e,r,a){return new EncodedAudioChunk({data:t,timestamp:e,duration:r,type:a?"key":"delta"})}},gr=(i,t)=>{let e=E(i.presentationTimestamps,t,r=>r.presentationTimestamp);return e===-1?-1:i.presentationTimestamps[e].sampleIndex},Ui=(i,t)=>{let e=E(i.sampleTimingEntries,t,p=>p.startIndex),r=i.sampleTimingEntries[e];if(!r||r.startIndex+r.count<=t)return null;let n=r.startDecodeTimestamp+(t-r.startIndex)*r.delta,s=E(i.sampleCompositionTimeOffsets,t,p=>p.startIndex),o=i.sampleCompositionTimeOffsets[s];o&&(n+=o.offset);let c=i.sampleSizes[Math.min(t,i.sampleSizes.length-1)],u=E(i.sampleToChunk,t,p=>p.startSampleIndex),d=i.sampleToChunk[u];l(d);let f=d.startChunkIndex+Math.floor((t-d.startSampleIndex)/d.samplesPerChunk),m=i.chunkOffsets[f],g=0,b=m;if(i.sampleSizes.length===1)b+=c*(t-d.startSampleIndex),g+=c*d.samplesPerChunk;else{let p=d.startSampleIndex+(f-d.startChunkIndex)*d.samplesPerChunk;for(let k=p;kp)!==-1:!0}},Bi=(i,t)=>{if(!i.keySampleIndices)return t;let e=E(i.keySampleIndices,t,r=>r);return i.keySampleIndices[e]??-1},Ni=(i,t)=>{if(!i.keySampleIndices)return t+1;let e=E(i.keySampleIndices,t,r=>r);return i.keySampleIndices[e+1]??-1},Tr=(i,t)=>{i.startTimestamp+=t,i.endTimestamp+=t;for(let e of i.samples)e.presentationTimestamp+=t;for(let e of i.presentationTimestamps)e.presentationTimestamp+=t};var Z=class{},Re=class extends Z{async _canReadInput(t){if(await t._mainReader.source._getSize()<8)return!1;let r=new de(t._mainReader);return r.pos=4,r.readAscii(4)==="ftyp"}_createDemuxer(t){return new ht(t)}},Oe=class extends Z{async _canReadInput(){return!1}_createDemuxer(){throw new Error("Not implemented")}},Fe=new Re,xr=Fe,Cr=Fe,bt=new Oe,Pt=bt,wr=bt,yr=[Fe,Pt];var kt=class{constructor(t){this._demuxerPromise=null;this._format=null;if(!t||typeof t!="object")throw new TypeError("options must be an object.");if(!Array.isArray(t.formats)||t.formats.some(e=>!(e instanceof Z)))throw new TypeError("options.formats must be an array of InputFormat.");if(!(t.source instanceof j))throw new TypeError("options.source must be a Source.");this._formats=t.formats,this._source=t.source,this._mainReader=new ke(t.source)}_getDemuxer(){return this._demuxerPromise??=(async()=>{await this._mainReader.loadRange(0,4096);for(let t of this._formats)if(await t._canReadInput(this))return this._format=t,t._createDemuxer(this);throw new Error("Input has an unrecognizable format.")})()}async getFormat(){return await this._getDemuxer(),l(this._format),this._format}async computeDuration(){return(await this._getDemuxer()).computeDuration()}async getTracks(){return(await this._getDemuxer()).getTracks()}async getVideoTracks(){return(await this.getTracks()).filter(e=>e.isVideoTrack())}async getPrimaryVideoTrack(){return(await this.getTracks()).find(e=>e.isVideoTrack())??null}async getAudioTracks(){return(await this.getTracks()).filter(e=>e.isAudioTrack())}async getPrimaryAudioTrack(){return(await this.getTracks()).find(e=>e.isAudioTrack())??null}async getMimeType(){return(await this._getDemuxer()).getMimeType()}};var $=i=>{if(!i||typeof i!="object")throw new TypeError("options must be an object.");if(i.metadataOnly!==void 0&&typeof i.metadataOnly!="boolean")throw new TypeError("options.metadataOnly, when defined, must be a boolean.")},N=i=>{if(typeof i!="number"||Number.isNaN(i))throw new TypeError("timestamp must be a number.")},ge=class{async*chunks(t,e=1/0){let r=[],{promise:a,resolve:n}=M(),{promise:s,resolve:o}=M(),c=!1,u=null,d=[],f=()=>Math.max(2,d.length);(async()=>{let m=t??await this.getFirstChunk();for(;m&&!c&&!(m.timestamp/1e6>=e);){if(r.length>f()){({promise:s,resolve:o}=M()),await s;continue}r.push(m),n(),{promise:a,resolve:n}=M(),m=await this.getNextChunk(m)}c=!0,n()})().catch(m=>{u||(u=m,n())});try{for(;;){if(u)throw u;if(r.length>0){yield r.shift();let m=performance.now();for(d.push(m);d.length>0&&m-d[0]>=1e3;)d.shift();o()}else if(!c)await a;else break}}finally{c=!0,o()}}},Te=class{_duplicateFrame(t){return structuredClone(t)}async*mediaFramesAtTimestamps(t){Kt(t);let e=$t(t),r=[],a=8,n=[],{promise:s,resolve:o}=M(),{promise:c,resolve:u}=M(),d=!1,f=!1,m=null,g=null,b=T=>{n.push(T),o(),{promise:s,resolve:o}=M()},p=new Error,k=await this._createDecoder(T=>{if(u(),f){T.close();return}let x=!1;for(;r.length>0&&r[0]===T.timestamp;)b(this._duplicateFrame(T)),r.shift(),x=!0;x?(g?.close(),g=T):T.close()},T=>{m||(T.stack=p.stack,m=T,o())});(async()=>{let T=this._createChunkDrain(),x=null,C=null;for await(let O of e){for(N(O);n.length+k.decodeQueueSize>a;)({promise:c,resolve:u}=M()),await c;if(f)break;let _=await T.getChunk(O);if(!_){b(null);continue}let I=await T.getKeyChunk(O);if(!I){b(null);continue}for(r.push(_.timestamp),x&&I.timestamp===x.timestamp&&_.timestamp>=C.timestamp?(l(C),_.timestamp===C.timestamp&&r.length===1&&(g&&b(this._duplicateFrame(g)),r.shift())):(x=I,C=I,k.decode(I));C.timestamp!==_.timestamp;){let R=await T.getNextChunk(C);l(R),C=R,k.decode(R)}k.decodeQueueSize>=10&&await new Promise(R=>k.addEventListener("dequeue",R,{once:!0}))}await k.flush(),k.close(),d=!0,o()})().catch(T=>{m||(m=T,o())});try{for(;;){if(m)throw m;if(n.length>0){let T=n.shift();l(T!==void 0),yield T,u()}else if(!d)await s;else break}}finally{f=!0,u();for(let T of n)T?.close();g?.close()}}async*mediaFramesInRange(t=0,e=1/0){N(t),N(e);let r=8,a=[],n=!1,s=null,{promise:o,resolve:c}=M(),{promise:u,resolve:d}=M(),f=!1,m=!1,g=null,b=new Error,p=await this._createDecoder(x=>{d();let C=x.timestamp/1e6;if(C>=e&&(m=!0),m){x.close();return}s&&(C>t?(a.push(s),n=!0):s.close()),C>=t&&(a.push(x),n=!0),s=n?null:x,a.length>0&&(c(),{promise:o,resolve:c}=M())},x=>{g||(x.stack=b.stack,g=x,c())}),k=this._createChunkDrain(),T=await k.getKeyChunk(t)??await k.getFirstChunk();if(T){(async()=>{let x=T,C=1/0;if(e<1/0){let _=await k.getChunk(e),I=_?_.type==="key"&&_.timestamp/1e6===e?_:await k.getNextKeyChunk(_):null;I&&(C=I.timestamp/1e6)}let O=k.chunks(T,C);for(await O.next();x&&!m;){if(a.length+p.decodeQueueSize>r){({promise:u,resolve:d}=M()),await u;continue}p.decode(x);let _=await O.next();if(_.done)break;x=_.value}await O.return(),await p.flush(),p.close(),!n&&s&&a.push(s),f=!0,c()})().catch(x=>{g||(g=x,c())});try{for(;;){if(g)throw g;if(a.length>0)yield a.shift(),d();else if(!f)await o;else break}}finally{m=!0,d();for(let x of a)x.close()}}}},Ve=class extends ge{constructor(t){if(!(t instanceof U))throw new TypeError("videoTrack must be an InputVideoTrack.");super(),this._videoTrack=t}getFirstChunk(t={}){return $(t),this._videoTrack._backing.getFirstChunk(t)}getChunk(t,e={}){return N(t),$(e),this._videoTrack._backing.getChunk(t,e)}getNextChunk(t,e={}){if(!(t instanceof EncodedVideoChunk))throw new TypeError("chunk must be an EncodedVideoChunk.");return $(e),this._videoTrack._backing.getNextChunk(t,e)}getKeyChunk(t,e={}){return N(t),$(e),this._videoTrack._backing.getKeyChunk(t,e)}getNextKeyChunk(t,e={}){if(!(t instanceof EncodedVideoChunk))throw new TypeError("chunk must be an EncodedVideoChunk.");return $(e),this._videoTrack._backing.getNextKeyChunk(t,e)}},Me=class extends Te{constructor(e){if(!(e instanceof U))throw new TypeError("videoTrack must be an InputVideoTrack.");super();this._decoderConfig=null;this._videoTrack=e}async _createDecoder(e,r){this._decoderConfig??=await this._videoTrack.getDecoderConfig();let a=new VideoDecoder({output:e,error:r});return a.configure(this._decoderConfig),a}_createChunkDrain(){return new Ve(this._videoTrack)}async getFrame(e){N(e);for await(let r of this.mediaFramesAtTimestamps([e]))return r;throw new Error("Internal error: Iterator returned nothing.")}frames(e=0,r=1/0){return this.mediaFramesInRange(e,r)}framesAtTimestamps(e){return this.mediaFramesAtTimestamps(e)}},gt=class{constructor(t,e){if(!(t instanceof U))throw new TypeError("videoTrack must be an InputVideoTrack.");if(e&&typeof e!="object")throw new TypeError("dimensions, when defined, must be an object.");if(e&&(!Number.isInteger(e.width)||e.width<=0))throw new TypeError("dimensions.width must be a positive integer.");if(e&&(!Number.isInteger(e.height)||e.height<=0))throw new TypeError("dimensions.height must be a positive integer.");this._videoTrack=t,this._dimensions=e,this._videoFrameDrain=new Me(t)}async _videoFrameToWrappedCanvas(t){let e=this._dimensions?.width??await this._videoTrack.getDisplayWidth(),r=this._dimensions?.height??await this._videoTrack.getDisplayHeight(),a=await this._videoTrack.getRotation(),n=document.createElement("canvas");n.width=e,n.height=r;let s=n.getContext("2d",{alpha:!1});l(s),s.translate(e/2,r/2),s.rotate(a*Math.PI/180),s.translate(-e/2,-r/2);let[o,c]=a%180===0?[e,r]:[r,e];s.drawImage(t,(e-o)/2,(r-c)/2,o,c);let u={canvas:n,timestamp:t.timestamp/1e6,duration:(t.duration??0)/1e6};return t.close(),u}async getCanvas(t){N(t);let e=await this._videoFrameDrain.getFrame(t);return e&&this._videoFrameToWrappedCanvas(e)}async*canvases(t=0,e=1/0){for await(let r of this._videoFrameDrain.frames(t,e))yield this._videoFrameToWrappedCanvas(r)}async*canvasesAtTimestamps(t){for await(let e of this._videoFrameDrain.framesAtTimestamps(t))yield e&&this._videoFrameToWrappedCanvas(e)}},De=class extends ge{constructor(t){if(!(t instanceof B))throw new TypeError("audioTrack must be an InputAudioTrack.");super(),this._audioTrack=t}getFirstChunk(t={}){return $(t),this._audioTrack._backing.getFirstChunk(t)}getChunk(t,e={}){return N(t),$(e),this._audioTrack._backing.getChunk(t,e)}getNextChunk(t,e={}){if(!(t instanceof EncodedAudioChunk))throw new TypeError("chunk must be an EncodedAudioChunk.");return $(e),this._audioTrack._backing.getNextChunk(t,e)}getKeyChunk(t,e={}){return N(t),$(e),this._audioTrack._backing.getKeyChunk(t,e)}getNextKeyChunk(t,e={}){if(!(t instanceof EncodedAudioChunk))throw new TypeError("chunk must be an EncodedAudioChunk.");return $(e),this._audioTrack._backing.getNextKeyChunk(t,e)}},Pe=class extends Te{constructor(e){if(!(e instanceof B))throw new TypeError("audioTrack must be an InputAudioTrack.");super();this._decoderConfig=null;this._audioTrack=e}async _createDecoder(e,r){this._decoderConfig??=await this._audioTrack.getDecoderConfig();let a=new AudioDecoder({output:e,error:r});return a.configure(this._decoderConfig),a}_createChunkDrain(){return new De(this._audioTrack)}async getData(e){N(e);for await(let r of this.mediaFramesAtTimestamps([e]))return r;throw new Error("Internal error: Iterator returned nothing.")}data(e=0,r=1/0){return this.mediaFramesInRange(e,r)}dataAtTimestamps(e){return this.mediaFramesAtTimestamps(e)}},Tt=class{constructor(t){if(!(t instanceof B))throw new TypeError("audioTrack must be an InputAudioTrack.");this._audioDataDrain=new Pe(t)}_audioDataToWrappedArrayBuffer(t){let e=new AudioBuffer({numberOfChannels:t.numberOfChannels,length:t.numberOfFrames,sampleRate:t.sampleRate}),r=new Float32Array(t.allocationSize({planeIndex:0,format:"f32-planar"})/4);for(let s=0;si&&i[i.length-1],G=i=>i>=0&&i<2**32,V=(i,t,e)=>{let r=0;for(let a=t;a>o;r<<=1,r|=c}return r},Dt=(i,t,e,r)=>{for(let a=t;a>e-a-1<i instanceof ArrayBuffer?new Uint8Array(i):new Uint8Array(i.buffer,i.byteOffset,i.byteLength),P=new TextEncoder,rt=i=>Object.fromEntries(Object.entries(i).map(([t,e])=>[e,t])),Z={bt709:1,bt470bg:5,smpte170m:6},Pt=rt(Z),J={bt709:1,smpte170m:6,"iec61966-2-1":13},zt=rt(J),ee={rgb:0,bt709:1,bt470bg:5,smpte170m:6},Ut=rt(ee),Re=i=>!!i&&!!i.primaries&&!!i.transfer&&!!i.matrix&&i.fullRange!==void 0,it=i=>i instanceof ArrayBuffer||typeof SharedArrayBuffer<"u"&&i instanceof SharedArrayBuffer||ArrayBuffer.isView(i)&&!(i instanceof DataView),j=class{constructor(){this.currentPromise=Promise.resolve()}async acquire(){let t,e=new Promise(a=>{t=a}),r=this.currentPromise;return this.currentPromise=e,await r,t}},q=i=>{let t=i*(Math.PI/180),e=Math.cos(t),r=Math.sin(t);return[e,r,0,-r,e,0,0,0,1]},nt=q(0),Bt=i=>[...i].map(t=>t.toString(16).padStart(2,"0")).join(""),Nt=i=>(i=i>>1&1431655765|(i&1431655765)<<1,i=i>>2&858993459|(i&858993459)<<2,i=i>>4&252645135|(i&252645135)<<4,i=i>>8&16711935|(i&16711935)<<8,i=i>>16&65535|(i&65535)<<16,i>>>0),Q=(i,t,e)=>{let r=0,a=i.length-1,n=-1;for(;r<=a;){let s=r+a>>1,o=e(i[s]);o===t?(n=s,a=s-1):o{let r=-1,a=0,n=i.length-1;for(;a<=n;){let s=a+(n-a+1)/2|0;e(i[s])<=t?(r=s,a=s+1):n=s-1}return r},M=()=>{let i,t;return{promise:new Promise((r,a)=>{i=r,t=a}),resolve:i,reject:t}},Wt=(i,t)=>{let e=i.indexOf(t);e!==-1&&i.splice(e,1)},Ht=(i,t)=>{for(let e=i.length-1;e>=0;e--)if(t(i[e]))return e;return-1},Lt=async function*(i){Symbol.iterator in i?yield*i[Symbol.iterator]():yield*i[Symbol.asyncIterator]()},Qt=i=>{if(!(Symbol.iterator in i)&&!(Symbol.asyncIterator in i))throw new TypeError("Argument must be an iterable or async iterable.")};var be=/(?:(.+?)\n)?((?:\d{2}:)?\d{2}:\d{2}.\d{3})\s+-->\s+((?:\d{2}:)?\d{2}:\d{2}.\d{3})/g,xr=/^WEBVTT(.|\n)*?\n{2}/,se=/<(?:(\d{2}):)?(\d{2}):(\d{2}).(\d{3})>/g,Oe=class{constructor(t){this.preambleText=null;this.preambleEmitted=!1;this.options=t}parse(t){t=t.replaceAll(`\r -`,` -`).replaceAll("\r",` -`),be.lastIndex=0;let e;if(!this.preambleText){if(!xr.test(t))throw new Error("WebVTT preamble incorrect.");e=be.exec(t);let r=t.slice(0,e?.index??t.length).trimEnd();if(!r)throw new Error("No WebVTT preamble provided.");this.preambleText=r,e&&(t=t.slice(e.index),be.lastIndex=0)}for(;e=be.exec(t);){let r=t.slice(0,e.index),a=e[1],n=e.index+e[0].length,s=t.indexOf(` -`,n)+1,o=t.slice(n,s).trim(),c=t.indexOf(` - -`,n);c===-1&&(c=t.length);let u=Fe(e[2]),f=Fe(e[3])-u,m=t.slice(s,c).trim();t=t.slice(c).trimStart(),be.lastIndex=0;let g={timestamp:u/1e3,duration:f/1e3,text:m,identifier:a,settings:o,notes:r},b={};this.preambleEmitted||(b.config={description:this.preambleText},this.preambleEmitted=!0),this.options.output(g,b)}}},Cr=/(?:(\d{2}):)?(\d{2}):(\d{2}).(\d{3})/,Fe=i=>{let t=Cr.exec(i);if(!t)throw new Error("Expected match.");return 60*60*1e3*Number(t[1]||"0")+60*1e3*Number(t[2])+1e3*Number(t[3])+Number(t[4])},Ve=i=>{let t=Math.floor(i/36e5),e=Math.floor(i%(60*60*1e3)/(60*1e3)),r=Math.floor(i%(60*1e3)/1e3),a=i%1e3;return t.toString().padStart(2,"0")+":"+e.toString().padStart(2,"0")+":"+r.toString().padStart(2,"0")+"."+a.toString().padStart(3,"0")};var ke=class{constructor(t){this.writer=t;this.helper=new Uint8Array(8);this.helperView=new DataView(this.helper.buffer);this.offsets=new WeakMap}writeU32(t){this.helperView.setUint32(0,t,!1),this.writer.write(this.helper.subarray(0,4))}writeU64(t){this.helperView.setUint32(0,Math.floor(t/2**32),!1),this.helperView.setUint32(4,t,!1),this.writer.write(this.helper.subarray(0,8))}writeAscii(t){for(let e=0;e[(i%256+256)%256],y=i=>(N.setUint16(0,i,!1),[w[0],w[1]]),wr=i=>(N.setInt16(0,i,!1),[w[0],w[1]]),Kt=i=>(N.setUint32(0,i,!1),[w[1],w[2],w[3]]),h=i=>(N.setUint32(0,i,!1),[w[0],w[1],w[2],w[3]]),jt=i=>(N.setInt32(0,i,!1),[w[0],w[1],w[2],w[3]]),te=i=>(N.setUint32(0,Math.floor(i/2**32),!1),N.setUint32(4,i,!1),[w[0],w[1],w[2],w[3],w[4],w[5],w[6],w[7]]),ot=i=>(N.setInt16(0,2**8*i,!1),[w[0],w[1]]),B=i=>(N.setInt32(0,2**16*i,!1),[w[0],w[1],w[2],w[3]]),at=i=>(N.setInt32(0,2**30*i,!1),[w[0],w[1],w[2],w[3]]),st=(i,t)=>{let e=[],r=i;do{let a=r&127;r>>=7,e.length>0&&(a|=128),e.push(a),t!==void 0&&t--}while(r>0||t);return e.reverse()},D=(i,t=!1)=>{let e=Array(i.length).fill(null).map((r,a)=>i.charCodeAt(a));return t&&e.push(0),e},ct=i=>{let t=null;for(let e of i)(!t||e.timestamp>t.timestamp)&&(t=e);return t},qt=i=>[B(i[0]),B(i[1]),at(i[2]),B(i[3]),B(i[4]),at(i[5]),B(i[6]),B(i[7]),at(i[8])],S=(i,t,e)=>({type:i,contents:t&&new Uint8Array(t.flat(10)),children:e}),v=(i,t,e,r,a)=>S(i,[A(t),Kt(e),r??[]],a),Xt=i=>i.fragmented?S("ftyp",[D("iso5"),h(512),D("iso5"),D("iso6"),D("mp41")]):S("ftyp",[D("isom"),h(512),D("isom"),i.holdsAvc?D("avc1"):[],D("mp41")]),De=i=>({type:"mdat",largeSize:i}),Gt=i=>({type:"free",size:i}),ge=(i,t,e=!1)=>S("moov",void 0,[yr(t,i),...i.map(r=>Sr(r,t)),e?ii(i):null]),yr=(i,t)=>{let e=z(Math.max(0,...t.filter(s=>s.samples.length>0).map(s=>{let o=ct(s.samples);return o.timestamp+o.duration})),Me),r=Math.max(0,...t.map(s=>s.track.id))+1,a=!G(i)||!G(e),n=a?te:h;return v("mvhd",+a,0,[n(i),n(i),h(Me),n(e),B(1),ot(1),Array(10).fill(0),qt(nt),Array(24).fill(0),h(r)])},Sr=(i,t)=>S("trak",void 0,[vr(i,t),_r(i,t)]),vr=(i,t)=>{let e=ct(i.samples),r=z(e?e.timestamp+e.duration:0,Me),a=!G(t)||!G(r),n=a?te:h,s;if(i.type==="video"){let o=i.track.metadata.rotation;s=o===void 0||typeof o=="number"?q(o??0):o}else s=nt;return v("tkhd",+a,3,[n(t),n(t),h(i.track.id),h(0),n(r),Array(8).fill(0),y(0),y(i.track.id),ot(i.type==="audio"?1:0),y(0),qt(s),B(i.type==="video"?i.info.width:0),B(i.type==="video"?i.info.height:0)])},_r=(i,t)=>S("mdia",void 0,[Ar(i,t),Rr(i),Or(i)]),Ar=(i,t)=>{let e=ct(i.samples),r=z(e?e.timestamp+e.duration:0,i.timescale),a=!G(t)||!G(r),n=a?te:h;return v("mdhd",+a,0,[n(t),n(t),h(i.timescale),n(r),y(21956),y(0)])},Ir={video:"vide",audio:"soun",subtitle:"text"},Er={video:"VideoHandler",audio:"SoundHandler",subtitle:"TextHandler"},Rr=i=>v("hdlr",0,0,[D("mhlr"),D(Ir[i.type]),h(0),h(0),h(0),D(Er[i.type],!0)]),Or=i=>S("minf",void 0,[Dr[i.type](),Pr(),Br(i)]),Fr=()=>v("vmhd",0,1,[y(0),y(0),y(0),y(0)]),Vr=()=>v("smhd",0,0,[y(0),y(0)]),Mr=()=>v("nmhd",0,0),Dr={video:Fr,audio:Vr,subtitle:Mr},Pr=()=>S("dinf",void 0,[zr()]),zr=()=>v("dref",0,0,[h(1)],[Ur()]),Ur=()=>v("url ",0,1),Br=i=>{let t=i.compositionTimeOffsetTable.length>1||i.compositionTimeOffsetTable.some(e=>e.sampleCompositionTimeOffset!==0);return S("stbl",void 0,[Nr(i),Yr(i),Zr(i),Jr(i),ei(i),ti(i),t?ri(i):null])},Nr=i=>{let t;return i.type==="video"?t=Wr(mi[i.track.source._codec],i):i.type==="audio"?t=Kr(hi[i.track.source._codec],i):i.type==="subtitle"&&(t=Xr(bi[i.track.source._codec],i)),l(t),v("stsd",0,0,[h(1)],[t])},Wr=(i,t)=>S(i,[Array(6).fill(0),y(1),y(0),y(0),Array(12).fill(0),y(t.info.width),y(t.info.height),h(4718592),h(4718592),h(0),y(1),Array(32).fill(0),y(24),wr(65535)],[fi[t.track.source._codec](t),Re(t.info.decoderConfig.colorSpace)?Hr(t):null]),Hr=i=>S("colr",[D("nclx"),y(Z[i.info.decoderConfig.colorSpace.primaries]),y(J[i.info.decoderConfig.colorSpace.transfer]),y(ee[i.info.decoderConfig.colorSpace.matrix]),A((i.info.decoderConfig.colorSpace.fullRange?1:0)<<7)]),Lr=i=>i.info.decoderConfig&&S("avcC",[...Y(i.info.decoderConfig.description)]),Qr=i=>i.info.decoderConfig&&S("hvcC",[...Y(i.info.decoderConfig.description)]),$t=i=>{if(!i.info.decoderConfig)return null;let t=i.info.decoderConfig;l(t.colorSpace);let e=t.codec.split("."),r=Number(e[1]),a=Number(e[2]),o=(Number(e[3])<<4)+(0<<1)+Number(t.colorSpace.fullRange);return v("vpcC",1,0,[A(r),A(a),A(o),A(2),A(2),A(2),y(0)])},$r=()=>S("av1C",[129,0,0,0]),Kr=(i,t)=>S(i,[Array(6).fill(0),y(1),y(0),y(0),h(0),y(t.info.numberOfChannels),y(16),y(0),y(0),B(t.info.sampleRate)],[pi[t.track.source._codec](t)]),jr=i=>{let e=[...Y(i.info.decoderConfig.description??new ArrayBuffer(0))];return e=[...A(64),...A(21),...Kt(0),...h(0),...h(0),...A(5),...st(e.length),...e],e=[...y(1),...A(0),...A(4),...st(e.length),...e,...A(6),...A(1),...A(2)],e=[...A(3),...st(e.length),...e],v("esds",0,0,e)},qr=i=>{let t=3840,e=0,r=i.info.decoderConfig?.description;if(r){l(r.byteLength>=18);let a=ArrayBuffer.isView(r)?new DataView(r.buffer,r.byteOffset,r.byteLength):new DataView(r);t=a.getUint16(10,!0),e=a.getInt16(14,!0)}return S("dOps",[A(0),A(i.info.numberOfChannels),y(t),h(i.info.sampleRate),ot(e),A(0)])},Xr=(i,t)=>S(i,[Array(6).fill(0),y(1)],[ki[t.track.source._codec](t)]),Gr=i=>S("vttC",[...P.encode(i.info.config.description)]);var Yr=i=>v("stts",0,0,[h(i.timeToSampleTable.length),i.timeToSampleTable.map(t=>[h(t.sampleCount),h(t.sampleDelta)])]),Zr=i=>{if(i.samples.every(e=>e.type==="key"))return null;let t=[...i.samples.entries()].filter(([,e])=>e.type==="key");return v("stss",0,0,[h(t.length),t.map(([e])=>h(e+1))])},Jr=i=>v("stsc",0,0,[h(i.compactlyCodedChunkTable.length),i.compactlyCodedChunkTable.map(t=>[h(t.firstChunk),h(t.samplesPerChunk),h(1)])]),ei=i=>v("stsz",0,0,[h(0),h(i.samples.length),i.samples.map(t=>h(t.size))]),ti=i=>i.finalizedChunks.length>0&&F(i.finalizedChunks).offset>=2**32?v("co64",0,0,[h(i.finalizedChunks.length),i.finalizedChunks.map(t=>te(t.offset))]):v("stco",0,0,[h(i.finalizedChunks.length),i.finalizedChunks.map(t=>h(t.offset))]),ri=i=>v("ctts",0,0,[h(i.compositionTimeOffsetTable.length),i.compositionTimeOffsetTable.map(t=>[h(t.sampleCount),h(t.sampleCompositionTimeOffset)])]),ii=i=>S("mvex",void 0,i.map(ni)),ni=i=>v("trex",0,0,[h(i.track.id),h(1),h(0),h(0),h(0)]),ut=(i,t)=>S("moof",void 0,[ai(i),...t.map(si)]),ai=i=>v("mfhd",0,0,[h(i)]),Yt=i=>{let t=0,e=0,r=0,a=0,n=i.type==="delta";return e|=+n,n?t|=1:t|=2,t<<24|e<<16|r<<8|a},si=i=>S("traf",void 0,[oi(i),ci(i),ui(i)]),oi=i=>{l(i.currentChunk);let t=0;t|=8,t|=16,t|=32,t|=131072;let e=i.currentChunk.samples[1]??i.currentChunk.samples[0],r={duration:e.timescaleUnitsToNextSample,size:e.size,flags:Yt(e)};return v("tfhd",0,t,[h(i.track.id),h(r.duration),h(r.size),h(r.flags)])},ci=i=>(l(i.currentChunk),v("tfdt",1,0,[te(z(i.currentChunk.startTimestamp,i.timescale))])),ui=i=>{l(i.currentChunk);let t=i.currentChunk.samples.map(p=>p.timescaleUnitsToNextSample),e=i.currentChunk.samples.map(p=>p.size),r=i.currentChunk.samples.map(Yt),a=i.currentChunk.samples.map(p=>z(p.timestamp-p.decodeTimestamp,i.timescale)),n=new Set(t),s=new Set(e),o=new Set(r),c=new Set(a),u=o.size===2&&r[0]!==r[1],d=n.size>1,f=s.size>1,m=!u&&o.size>1,g=c.size>1||[...c].some(p=>p!==0),b=0;return b|=1,b|=4*+u,b|=256*+d,b|=512*+f,b|=1024*+m,b|=2048*+g,v("trun",1,b,[h(i.currentChunk.samples.length),h(i.currentChunk.offset-i.currentChunk.moofOffset||0),u?h(r[0]):[],i.currentChunk.samples.map((p,k)=>[d?h(t[k]):[],f?h(e[k]):[],m?h(r[k]):[],g?jt(a[k]):[]])])},Zt=i=>S("mfra",void 0,[...i.map(di),li()]),di=(i,t)=>v("tfra",1,0,[h(i.track.id),h(63),h(i.finalizedChunks.length),i.finalizedChunks.map(r=>[te(z(r.samples[0].timestamp,i.timescale)),te(r.moofOffset),h(t+1),h(1),h(1)])]),li=()=>v("mfro",0,0,[h(0)]),Jt=()=>S("vtte"),er=(i,t,e,r,a)=>S("vttc",void 0,[a!==null?S("vsid",[jt(a)]):null,e!==null?S("iden",[...P.encode(e)]):null,t!==null?S("ctim",[...P.encode(Ve(t))]):null,r!==null?S("sttg",[...P.encode(r)]):null,S("payl",[...P.encode(i)])]),tr=i=>S("vtta",[...P.encode(i)]),mi={avc:"avc1",hevc:"hvc1",vp8:"vp08",vp9:"vp09",av1:"av01"},fi={avc:Lr,hevc:Qr,vp8:$t,vp9:$t,av1:$r},hi={aac:"mp4a",opus:"Opus"},pi={aac:jr,opus:qr},bi={webvtt:"wvtt"},ki={webvtt:Gr};var oe=class{constructor(t){this.mutex=new j;this.trackTimestampInfo=new WeakMap;this.output=t}beforeTrackAdd(t){}onTrackClose(t){}validateAndNormalizeTimestamp(t,e,r){let a=e/1e6,n=this.trackTimestampInfo.get(t);if(!n){if(!r)throw new Error("First frame must be a key frame.");if(this.timestampsMustStartAtZero&&a>0)throw new Error(`Timestamps must start at zero (got ${a}s).`);n={timestampOffset:a,maxTimestamp:t.source._offsetTimestamps?0:a,lastKeyFrameTimestamp:t.source._offsetTimestamps?0:a},this.trackTimestampInfo.set(t,n)}if(t.source._offsetTimestamps&&(a-=n.timestampOffset),a<0)throw new Error(`Timestamps must be non-negative (got ${a}s).`);if(aa.start-n.start);e.push({start:r[0].start,size:r[0].data.byteLength});for(let a=1;au.start<=r&&rTi){for(let u=0;u=e.written[s+1].start;)e.written[s].end=Math.max(e.written[s].end,e.written[s+1].end),e.written.splice(s+1,1)}createChunk(e){let a={start:Math.floor(e/this.chunkSize)*this.chunkSize,data:new Uint8Array(this.chunkSize),written:[],shouldFlush:!1};return this.chunks.push(a),this.chunks.sort((n,s)=>n.start-s.start),this.chunks.indexOf(a)}queueChunksForFlush(e=!1){l(this.writer);for(let r=0;r{if(i==="avc"){let n=Math.ceil(t/16)*Math.ceil(e/16),s=rr.find(f=>n<=f.maxMacroblocks&&r<=f.maxBitrate)??F(rr),o=s?s.level:0,c="64".padStart(2,"0"),u="00",d=o.toString(16).padStart(2,"0");return`avc1.${c}${u}${d}`}else if(i==="hevc"){let a="",s="6",o=t*e,c=ir.find(d=>o<=d.maxPictureSize&&r<=d.maxBitrate)??F(ir);return`hev1.${a}1.${s}.${c.tier}${c.level}.B0`}else{if(i==="vp8")return"vp8";if(i==="vp9"){let a="00",n=t*e,s=nr.find(c=>n<=c.maxPictureSize&&r<=c.maxBitrate)??F(nr);return`vp09.${a}.${s.level}.08`}else if(i==="av1"){let n=t*e,s=ar.find(c=>n<=c.maxPictureSize&&r<=c.maxBitrate)??F(ar);return`av01.0.${s.level.toString().padStart(2,"0")}${s.tier}.08`}}throw new TypeError(`Unhandled codec '${i}'.`)},or=(i,t)=>{if(i==="avc"){if(!t||t.byteLength<4)throw new TypeError("AVC description must be at least 4 bytes long.");return`avc1.${Bt(t.subarray(1,4))}`}else if(i==="hevc"){if(!t)throw new TypeError("HEVC description must be provided.");let e=new DataView(t.buffer,t.byteOffset,t.byteLength),r="hev1.",a=t[1]>>6&3,n=t[1]&31;r+=["","A","B","C"][a]+n,r+=".";let s=Nt(e.getUint32(2));r+=s.toString(16),r+=".";let o=t[1]>>5&1,c=t[12];r+=o===0?"L":"H",r+=c,r+=".";let u=[];for(let d=0;d<6;d++){let f=t[d+13];u.push(f)}for(;u[u.length-1]===0;)u.pop();return r+=u.map(d=>d.toString(16)).join("."),r}throw new TypeError(`Unhandled codec '${i}'.`)},cr=(i,t,e)=>{if(i==="aac")return t>=2&&e<=24e3?"mp4a.40.29":e<=24e3?"mp4a.40.5":"mp4a.40.2";if(i==="opus")return"opus";if(i==="vorbis")return"vorbis";throw new TypeError(`Unhandled codec '${i}'.`)},ur=(i,t)=>{if(i==="aac")return`mp4a.40.${lt(t).objectType}`;if(i==="opus")return"opus";if(i==="vorbis")return"vorbis";throw new TypeError(`Unhandled codec '${i}'.`)},lt=i=>{if(!i||i.byteLength<2)throw new TypeError("AAC description must be at least 2 bytes long.");let t=0,e=V(i,t,t+5);t+=5,e===31&&(e=32+V(i,t,t+6),t+=6);let r=V(i,t,t+4);t+=4;let a=null;if(r===15)a=V(i,t,t+24),t+=24;else{let o=[96e3,88200,64e3,48e3,44100,32e3,24e3,22050,16e3,12e3,11025,8e3,7350];r=1&&n<=7&&(s={1:1,2:2,3:3,4:4,5:5,6:6,7:8}[n]),{objectType:e,frequencyIndex:r,sampleRate:a,channelConfiguration:n,numberOfChannels:s}},dr=i=>i==="avc"?{avc:{format:"avc"}}:i==="hevc"?{hevc:{format:"hevc"}}:{},lr=i=>i==="aac"?{aac:{format:"aac"}}:i==="opus"?{opus:{format:"opus"}}:{},Be=i=>{if(!i)throw new TypeError("Video chunk metadata must be provided.");if(typeof i!="object")throw new TypeError("Video chunk metadata must be an object.");if(!i.decoderConfig)throw new TypeError("Video chunk metadata must include a decoder configuration.");if(typeof i.decoderConfig!="object")throw new TypeError("Video chunk metadata decoder configuration must be an object.");if(typeof i.decoderConfig.codec!="string")throw new TypeError("Video chunk metadata decoder configuration must specify a codec string.");if(!Number.isInteger(i.decoderConfig.codedWidth)||i.decoderConfig.codedWidth<=0)throw new TypeError("Video chunk metadata decoder configuration must specify a valid codedWidth (positive integer).");if(!Number.isInteger(i.decoderConfig.codedHeight)||i.decoderConfig.codedHeight<=0)throw new TypeError("Video chunk metadata decoder configuration must specify a valid codedHeight (positive integer).");if(i.decoderConfig.description!==void 0&&!it(i.decoderConfig.description))throw new TypeError("Video chunk metadata decoder configuration description, when defined, must be an ArrayBuffer or an ArrayBuffer view.");if(i.decoderConfig.colorSpace!==void 0){let{colorSpace:t}=i.decoderConfig;if(typeof t!="object")throw new TypeError("Video chunk metadata decoder configuration colorSpace, when provided, must be an object.");let e=Object.keys(Z);if(t.primaries!=null&&!e.includes(t.primaries))throw new TypeError(`Video chunk metadata decoder configuration colorSpace primaries, when defined, must be one of ${e.join(", ")}.`);let r=Object.keys(J);if(t.transfer!=null&&!r.includes(t.transfer))throw new TypeError(`Video chunk metadata decoder configuration colorSpace transfer, when defined, must be one of ${r.join(", ")}.`);let a=Object.keys(ee);if(t.matrix!=null&&!a.includes(t.matrix))throw new TypeError(`Video chunk metadata decoder configuration colorSpace matrix, when defined, must be one of ${a.join(", ")}.`);if(t.fullRange!=null&&typeof t.fullRange!="boolean")throw new TypeError("Video chunk metadata decoder configuration colorSpace fullRange, when defined, must be a boolean.")}if((i.decoderConfig.codec.startsWith("avc1")||i.decoderConfig.codec.startsWith("avc3"))&&!i.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((i.decoderConfig.codec.startsWith("hev1")||i.decoderConfig.codec.startsWith("hvc1"))&&!i.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((i.decoderConfig.codec==="vp8"||i.decoderConfig.codec.startsWith("vp09"))&&i.decoderConfig.colorSpace===void 0)throw new TypeError("Video chunk metadata decoder configuration for VP8/VP9 must include a colorSpace.")},Ne=i=>{if(!i)throw new TypeError("Audio chunk metadata must be provided.");if(typeof i!="object")throw new TypeError("Audio chunk metadata must be an object.");if(!i.decoderConfig)throw new TypeError("Audio chunk metadata must include a decoder configuration.");if(typeof i.decoderConfig!="object")throw new TypeError("Audio chunk metadata decoder configuration must be an object.");if(typeof i.decoderConfig.codec!="string")throw new TypeError("Audio chunk metadata decoder configuration must specify a codec string.");if(!Number.isInteger(i.decoderConfig.sampleRate)||i.decoderConfig.sampleRate<=0)throw new TypeError("Audio chunk metadata decoder configuration must specify a valid sampleRate (positive integer).");if(!Number.isInteger(i.decoderConfig.numberOfChannels)||i.decoderConfig.numberOfChannels<=0)throw new TypeError("Audio chunk metadata decoder configuration must specify a valid numberOfChannels (positive integer).");if(i.decoderConfig.description!==void 0&&!it(i.decoderConfig.description))throw new TypeError("Audio chunk metadata decoder configuration description, when defined, must be an ArrayBuffer or an ArrayBuffer view.");if(i.decoderConfig.codec.startsWith("mp4a")&&!i.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(i.decoderConfig.codec==="opus"&&i.decoderConfig.description&&i.decoderConfig.description.byteLength<18)throw new TypeError("Invalid decoder description provided for Opus; must be at least 18 bytes long.")},We=i=>{if(!i)throw new TypeError("Subtitle metadata must be provided.");if(typeof i!="object")throw new TypeError("Subtitle metadata must be an object.");if(!i.config)throw new TypeError("Subtitle metadata must include a config object.");if(typeof i.config!="object")throw new TypeError("Subtitle metadata config must be an object.");if(typeof i.config.description!="string")throw new TypeError("Subtitle metadata config description must be a string.")};var Me=1e3,xi=2082844800,z=(i,t,e=!0)=>{let r=i*t;return e?Math.round(r):r},He=class extends oe{constructor(e,r){super(e);this.timestampsMustStartAtZero=!0;this.auxTarget=new xe;this.auxWriter=this.auxTarget._createWriter();this.auxBoxWriter=new ke(this.auxWriter);this.ftypSize=null;this.mdat=null;this.trackDatas=[];this.creationTime=Math.floor(Date.now()/1e3)+xi;this.finalizedChunks=[];this.nextFragmentNumber=1;this.writer=e._writer,this.boxWriter=new ke(this.writer);let a=this.writer instanceof ce?"in-memory":!1;this.fastStart=r._options.fastStart??a,(this.fastStart==="in-memory"||this.fastStart==="fragmented")&&(this.writer.ensureMonotonicity=!0)}async start(){let e=await this.mutex.acquire(),r=this.output._tracks.some(a=>a.type==="video"&&a.source._codec==="avc");this.boxWriter.writeBox(Xt({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 a=this.trackDatas.find(s=>s.track===e);if(a)return a;Be(r),l(r),l(r.decoderConfig),l(r.decoderConfig.codedWidth!==void 0),l(r.decoderConfig.codedHeight!==void 0);let n={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(n),this.trackDatas.sort((s,o)=>s.track.id-o.track.id),n}getAudioTrackData(e,r){let a=this.trackDatas.find(s=>s.track===e);if(a)return a;Ne(r),l(r),l(r.decoderConfig);let n={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(n),this.trackDatas.sort((s,o)=>s.track.id-o.track.id),n}getSubtitleTrackData(e,r){let a=this.trackDatas.find(s=>s.track===e);if(a)return a;We(r),l(r),l(r.config);let n={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(n),this.trackDatas.sort((s,o)=>s.track.id-o.track.id),this.validateAndNormalizeTimestamp(e,0,!0),n}async addEncodedVideoChunk(e,r,a){let n=await this.mutex.acquire();try{let s=this.getVideoTrackData(e,a),o=new Uint8Array(r.byteLength);r.copyTo(o);let c=this.validateAndNormalizeTimestamp(s.track,r.timestamp,r.type==="key"),u=this.createSampleForTrack(s,o,c,(r.duration??0)/1e6,r.type);await this.registerSample(s,u)}finally{n()}}async addEncodedAudioChunk(e,r,a){let n=await this.mutex.acquire();try{let s=this.getAudioTrackData(e,a),o=new Uint8Array(r.byteLength);r.copyTo(o);let c=r.type,u=this.validateAndNormalizeTimestamp(s.track,r.timestamp,c==="key"),d=this.createSampleForTrack(s,o,u,(r.duration??0)/1e6,c);await this.registerSample(s,d)}finally{n()}}async addSubtitleCue(e,r,a){let n=await this.mutex.acquire();try{let s=this.getSubtitleTrackData(e,a);this.validateAndNormalizeTimestamp(s.track,1e6*r.timestamp,!0),e.source._codec==="webvtt"&&(s.cueQueue.push(r),await this.processWebVTTCues(s,r.timestamp))}finally{n()}}async processWebVTTCues(e,r){for(;e.cueQueue.length>0;){let a=new Set([]);for(let d of e.cueQueue)l(d.timestamp<=r),l(e.lastCueEndTimestamp<=d.timestamp+d.duration),a.add(Math.max(d.timestamp,e.lastCueEndTimestamp)),a.add(d.timestamp+d.duration);let n=[...a].sort((d,f)=>d-f),s=n[0],o=n[1]??s;if(r=o)break;se.lastIndex=0;let m=se.test(f.text),g=f.timestamp+f.duration,b=e.cueToSourceId.get(f);if(b===void 0&&oa.timestamp).sort((a,n)=>a-n);for(let a=0;a{if(e===o)return r.type==="key";let c=o.sampleQueue[0];return c&&c.type==="key"});n>=1&&s&&(a=!0,await this.finalizeFragment())}else a=n>=.5}a&&(e.currentChunk&&await this.finalizeCurrentChunk(e),e.currentChunk={startTimestamp:r.timestamp,samples:[],offset:null,moofOffset:null}),l(e.currentChunk),e.currentChunk.samples.push(r),e.timestampProcessingQueue.push(r)}async finalizeCurrentChunk(e){if(l(this.fastStart!=="fragmented"),!!e.currentChunk){if(e.finalizedChunks.push(e.currentChunk),this.finalizedChunks.push(e.currentChunk),(e.compactlyCodedChunkTable.length===0||F(e.compactlyCodedChunkTable).samplesPerChunk!==e.currentChunk.samples.length)&&e.compactlyCodedChunkTable.push({firstChunk:e.finalizedChunks.length,samplesPerChunk:e.currentChunk.samples.length}),this.fastStart==="in-memory"){e.currentChunk.offset=0;return}e.currentChunk.offset=this.writer.getPos();for(let r of e.currentChunk.samples)l(r.data),this.writer.write(r.data),r.data=null;await this.writer.flush()}}async interleaveSamples(){l(this.fastStart==="fragmented");for(let e of this.output._tracks)if(!e.source._closed&&!this.trackDatas.some(r=>r.track===e))return;e:for(;;){let e=null,r=1/0;for(let n of this.trackDatas){if(n.sampleQueue.length===0&&!n.track.source._closed)break e;n.sampleQueue.length>0&&n.sampleQueue[0].timestampu.currentChunk),n=this.writer.getPos(),s=ut(r,a);this.boxWriter.writeBox(s);{let u=De(!1),d=0;for(let m of a)for(let g of m.currentChunk.samples)d+=g.size;let f=this.boxWriter.measureBox(u)+d;f>=2**32&&(u.largeSize=!0,f=this.boxWriter.measureBox(u)+d),u.size=f,this.boxWriter.writeBox(u)}for(let u of a){u.currentChunk.offset=this.writer.getPos(),u.currentChunk.moofOffset=n;for(let d of u.currentChunk.samples)this.writer.write(d.data),d.data=null}let o=this.writer.getPos();this.writer.seek(this.boxWriter.offsets.get(s));let c=ut(r,a);this.boxWriter.writeBox(c),this.writer.seek(o);for(let u of a)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 a=this.trackDatas.find(n=>n.track===e);a&&await this.processWebVTTCues(a,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 a of r.sampleQueue)await this.addSampleToTrack(r,a);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"){l(this.mdat);let r;for(let n=0;n<2;n++){let s=ge(this.trackDatas,this.creationTime),o=this.boxWriter.measureBox(s);r=this.boxWriter.measureBox(this.mdat);let c=this.writer.getPos()+o+r;for(let u of this.finalizedChunks){u.offset=c;for(let{data:d}of u.samples)l(d),c+=d.byteLength,r+=d.byteLength}if(c<2**32)break;r>=2**32&&(this.mdat.largeSize=!0)}let a=ge(this.trackDatas,this.creationTime);this.boxWriter.writeBox(a),this.mdat.size=r,this.boxWriter.writeBox(this.mdat);for(let n of this.finalizedChunks)for(let s of n.samples)l(s.data),this.writer.write(s.data),s.data=null}else if(this.fastStart==="fragmented"){let r=this.writer.getPos(),a=Zt(this.trackDatas);this.boxWriter.writeBox(a);let n=this.writer.getPos()-r;this.writer.seek(this.writer.getPos()-4),this.boxWriter.writeU32(n)}else{l(this.mdat),l(this.ftypSize!==null);let r=this.boxWriter.offsets.get(this.mdat);l(r!==void 0);let a=this.writer.getPos()-r;this.mdat.size=a,this.mdat.largeSize=a>=2**32,this.boxWriter.patchBox(this.mdat);let n=ge(this.trackDatas,this.creationTime);if(typeof this.fastStart=="object"){this.writer.seek(this.ftypSize),this.boxWriter.writeBox(n);let s=r-this.writer.getPos();this.boxWriter.writeBox(Gt(s))}else this.boxWriter.writeBox(n)}e()}};var Ce=class{constructor(t){this.value=t}},le=class{constructor(t){this.value=t}},we=class{constructor(t){this.value=t}};var mt=i=>i<256?1:i<65536?2:i<1<<24?3:i<2**32?4:i<2**40?5:6,ft=i=>i>=-64&&i<64?1:i>=-8192&&i<8192?2:i>=-(1<<20)&&i<1<<20?3:i>=-(1<<27)&&i<1<<27?4:i>=-(2**34)&&i<2**34?5:6,mr=i=>{if(i<127)return 1;if(i<16383)return 2;if(i<(1<<21)-1)return 3;if(i<(1<<28)-1)return 4;if(i<2**35-1)return 5;if(i<2**42-1)return 6;throw new Error("EBML VINT size not supported "+i)};var ht=2**15,fr="https://github.com/Vanilagy/webm-muxer",hr=6,pr=5,Ci={avc:"V_MPEG4/ISO/AVC",hevc:"V_MPEGH/ISO/HEVC",vp8:"V_VP8",vp9:"V_VP9",av1:"V_AV1",aac:"A_AAC",opus:"A_OPUS",webvtt:"S_TEXT/WEBVTT"},wi={video:1,audio:2,subtitle:17},Le=class extends oe{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=mt(e)){let a=0;switch(r){case 6:this.helperView.setUint8(a++,e/2**40|0);case 5:this.helperView.setUint8(a++,e/2**32|0);case 4:this.helperView.setUint8(a++,e>>24);case 3:this.helperView.setUint8(a++,e>>16);case 2:this.helperView.setUint8(a++,e>>8);case 1:this.helperView.setUint8(a++,e);break;default:throw new Error("Bad UINT size "+r)}this.writer.write(this.helper.subarray(0,a))}writeSignedInt(e,r=ft(e)){e<0&&(e+=2**(r*8)),this.writeUnsignedInt(e,r)}writeEBMLVarInt(e,r=mr(e)){let a=0;switch(r){case 1:this.helperView.setUint8(a++,128|e);break;case 2:this.helperView.setUint8(a++,64|e>>8),this.helperView.setUint8(a++,e);break;case 3:this.helperView.setUint8(a++,32|e>>16),this.helperView.setUint8(a++,e>>8),this.helperView.setUint8(a++,e);break;case 4:this.helperView.setUint8(a++,16|e>>24),this.helperView.setUint8(a++,e>>16),this.helperView.setUint8(a++,e>>8),this.helperView.setUint8(a++,e);break;case 5:this.helperView.setUint8(a++,8|e/2**32&7),this.helperView.setUint8(a++,e>>24),this.helperView.setUint8(a++,e>>16),this.helperView.setUint8(a++,e>>8),this.helperView.setUint8(a++,e);break;case 6:this.helperView.setUint8(a++,4|e/2**40&3),this.helperView.setUint8(a++,e/2**32|0),this.helperView.setUint8(a++,e>>24),this.helperView.setUint8(a++,e>>16),this.helperView.setUint8(a++,e>>8),this.helperView.setUint8(a++,e);break;default:throw new Error("Bad EBML VINT size "+r)}this.writer.write(this.helper.subarray(0,a))}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(),a=e.size===-1?1:e.size??4;e.size===-1?this.writeByte(255):this.writer.seek(this.writer.getPos()+a);let n=this.writer.getPos();if(this.dataOffsets.set(e,n),this.writeEBML(e.data),e.size!==-1){let s=this.writer.getPos()-n,o=this.writer.getPos();this.writer.seek(r),this.writeEBMLVarInt(s,a),this.writer.seek(o)}}else if(typeof e.data=="number"){let r=e.size??mt(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 Ce)this.writeEBMLVarInt(4),this.writeFloat32(e.data.value);else if(e.data instanceof le)this.writeEBMLVarInt(8),this.writeFloat64(e.data.value);else if(e.data instanceof we){let r=e.size??ft(e.data.value);this.writeEBMLVarInt(r),this.writeSignedInt(e.data.value,r)}}}beforeTrackAdd(e){if(this.format instanceof me)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 me?"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]),a=new Uint8Array([22,84,174,107]),n={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:a},{id:21420,size:5,data:0}]}]};this.seekHead=n}createSegmentInfo(){let e={id:17545,data:new le(0)};this.segmentDuration=e;let r={id:357149030,data:[{id:2807729,data:1e6},{id:19840,data:fr},{id:22337,data:fr},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:wi[r.type]},{id:134,data:Ci[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:Y(e.info.decoderConfig.description)}:null,e.track.metadata.frameRate?{id:2352003,data:1e9/e.track.metadata.frameRate}:null],a=e.info.decoderConfig.colorSpace,n={id:224,data:[{id:176,data:e.info.width},{id:186,data:e.info.height},Re(a)?{id:21936,data:[{id:21937,data:ee[a.matrix]},{id:21946,data:J[a.transfer]},{id:21947,data:Z[a.primaries]},{id:21945,data:a.fullRange?2:1}]}:null]};return r.push(n),r}audioSpecificTrackInfo(e){return[e.info.decoderConfig.description?{id:25506,data:Y(e.info.decoderConfig.description)}:null,{id:225,data:[{id:181,data:new Ce(e.info.sampleRate)},{id:159,data:e.info.numberOfChannels}]}]}subtitleSpecificTrackInfo(e){return[{id:25506,data:P.encode(e.info.config.description)}]}createSegment(){let e={id:408125543,size:this.format._options.streamable?-1:hr,data:[this.format._options.streamable?null:this.seekHead,this.segmentInfo,this.tracksElement]};this.segment=e,this.writeEBML(e)}createCues(){this.cues={id:475249515,data:[]}}get segmentDataOffset(){return l(this.segment),this.dataOffsets.get(this.segment)}getVideoTrackData(e,r){let a=this.trackDatas.find(s=>s.track===e);if(a)return a;Be(r),l(r),l(r.decoderConfig),l(r.decoderConfig.codedWidth!==void 0),l(r.decoderConfig.codedHeight!==void 0);let n={track:e,type:"video",info:{width:r.decoderConfig.codedWidth,height:r.decoderConfig.codedHeight,decoderConfig:r.decoderConfig},chunkQueue:[],lastWrittenMsTimestamp:null};return this.trackDatas.push(n),this.trackDatas.sort((s,o)=>s.track.id-o.track.id),n}getAudioTrackData(e,r){let a=this.trackDatas.find(s=>s.track===e);if(a)return a;Ne(r),l(r),l(r.decoderConfig);let n={track:e,type:"audio",info:{numberOfChannels:r.decoderConfig.numberOfChannels,sampleRate:r.decoderConfig.sampleRate,decoderConfig:r.decoderConfig},chunkQueue:[],lastWrittenMsTimestamp:null};return this.trackDatas.push(n),this.trackDatas.sort((s,o)=>s.track.id-o.track.id),n}getSubtitleTrackData(e,r){let a=this.trackDatas.find(s=>s.track===e);if(a)return a;We(r),l(r),l(r.config);let n={track:e,type:"subtitle",info:{config:r.config},chunkQueue:[],lastWrittenMsTimestamp:null};return this.trackDatas.push(n),this.trackDatas.sort((s,o)=>s.track.id-o.track.id),n}async addEncodedVideoChunk(e,r,a){let n=await this.mutex.acquire();try{let s=this.getVideoTrackData(e,a),o=new Uint8Array(r.byteLength);r.copyTo(o);let c=r.type==="key",u=this.validateAndNormalizeTimestamp(s.track,r.timestamp,c),d=this.createInternalChunk(o,u,(r.duration??0)/1e6,r.type);e.source._codec==="vp9"&&this.fixVP9ColorSpace(s,d),s.chunkQueue.push(d),await this.interleaveChunks()}finally{n()}}async addEncodedAudioChunk(e,r,a){let n=await this.mutex.acquire();try{let s=this.getAudioTrackData(e,a),o=new Uint8Array(r.byteLength);r.copyTo(o);let c=r.type,u=c==="key",d=this.validateAndNormalizeTimestamp(s.track,r.timestamp,u),f=this.createInternalChunk(o,d,(r.duration??0)/1e6,c);s.chunkQueue.push(f),await this.interleaveChunks()}finally{n()}}async addSubtitleCue(e,r,a){let n=await this.mutex.acquire();try{let s=this.getSubtitleTrackData(e,a),o=this.validateAndNormalizeTimestamp(s.track,1e6*r.timestamp,!0),c=r.text,u=Math.floor(o*1e3);se.lastIndex=0,c=c.replace(se,g=>{let p=Fe(g.slice(1,-1))-u;return`<${Ve(p)}>`});let d=P.encode(c),f=`${r.settings??""} -${r.identifier??""} -${r.notes??""}`,m=this.createInternalChunk(d,o,r.duration,"key",f.trim()?P.encode(f):null);s.chunkQueue.push(m),await this.interleaveChunks()}finally{n()}}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 n of this.trackDatas){if(n.chunkQueue.length===0&&!n.track.source._closed)break e;n.chunkQueue.length>0&&n.chunkQueue[0].timestamp=2&&a++;let u={rgb:7,bt709:2,bt470bg:1,smpte170m:3}[e.info.decoderConfig.colorSpace.matrix];Dt(r.data,a+0,a+3,u)}createInternalChunk(e,r,a,n,s=null){return{data:e,type:n,timestamp:r,duration:a,additions:s}}writeBlock(e,r){this.segment||(this.createTracks(),this.createSegment());let a=Math.floor(1e3*r.timestamp),n=this.trackDatas.every(f=>{if(f.track.source._closed)return!0;if(e===f)return r.type==="key";let m=f.chunkQueue[0];return m&&m.type==="key"});(!this.currentCluster||n&&a-this.currentClusterMsTimestamp>=1e3)&&this.createNewCluster(a);let s=a-this.currentClusterMsTimestamp;if(s<0)return;if(s>=ht)throw new Error(`Current Matroska cluster exceeded its maximum allowed length of ${ht} milliseconds. In order to produce a correct WebM file, you must pass in a key frame at least every ${ht} milliseconds.`);let c=new Uint8Array(4),u=new DataView(c.buffer);u.setUint8(0,128|e.track.id),u.setInt16(1,s,!1);let d=Math.floor(1e3*r.duration);if(d===0&&!r.additions){u.setUint8(3,+(r.type==="key")<<7);let f={id:163,data:[c,r.data]};this.writeEBML(f)}else{let f={id:160,data:[{id:161,data:[c,r.data]},r.type==="delta"?{id:251,data:new we(e.lastWrittenMsTimestamp-a)}:null,r.additions?{id:30113,data:[{id:166,data:[{id:165,data:r.additions},{id:238,data:1}]}]}:null,d>0?{id:155,data:d}:null]};this.writeEBML(f)}this.duration=Math.max(this.duration,a+d),e.lastWrittenMsTimestamp=a,this.trackDatasInCurrentCluster.add(e)}createNewCluster(e){this.currentCluster&&!this.format._options.streamable&&this.finalizeCurrentCluster(),this.currentCluster={id:524531317,size:this.format._options.streamable?-1:pr,data:[{id:231,data:e}]},this.writeEBML(this.currentCluster),this.currentClusterMsTimestamp=e,this.trackDatasInCurrentCluster.clear()}finalizeCurrentCluster(){l(this.currentCluster);let e=this.writer.getPos()-this.dataOffsets.get(this.currentCluster),r=this.writer.getPos();this.writer.seek(this.offsets.get(this.currentCluster)+4),this.writeEBMLVarInt(e,pr),this.writer.seek(r);let a=this.offsets.get(this.currentCluster)-this.segmentDataOffset;l(this.cues),this.cues.data.push({id:187,data:[{id:179,data:this.currentClusterMsTimestamp},...[...this.trackDatasInCurrentCluster].map(n=>({id:183,data:[{id:247,data:n.track.id},{id:241,data:a}]}))]})}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(),l(this.cues),this.writeEBML(this.cues),!this.format._options.streamable){let r=this.writer.getPos(),a=this.writer.getPos()-this.segmentDataOffset;this.writer.seek(this.offsets.get(this.segment)+4),this.writeEBMLVarInt(a,hr),this.segmentDuration.data=new le(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 ie=class{},pt=class extends ie{constructor(t={}){if(!t||typeof t!="object")throw new TypeError("options must be an object.");if(t.fastStart!==void 0&&![!1,"in-memory","fragmented"].includes(t.fastStart))throw new TypeError('options.fastStart, when provided, must be false, "in-memory", or "fragmented".');super(),this._options=t}_createMuxer(t){return new He(t,this)}},Qe=class extends ie{constructor(t={}){if(!t||typeof t!="object")throw new TypeError("options must be an object.");if(t.streamable!==void 0&&typeof t.streamable!="boolean")throw new TypeError("options.streamable, when provided, must be a boolean.");super(),this._options=t}_createMuxer(t){return new Le(t,this)}},me=class extends Qe{};var fe=class{constructor(){this._connectedTrack=null;this._closed=!1;this._offsetTimestamps=!1}_ensureValidDigest(){if(!this._connectedTrack)throw new Error("Cannot call digest without connecting the source to an output track.");if(!this._connectedTrack.output._started)throw new Error("Cannot call digest before output has been started.");if(this._connectedTrack.output._finalizing)throw new Error("Cannot call digest after output has started finalizing.");if(this._closed)throw new Error("Cannot call digest after source has been closed.")}_start(){}async _flush(){}close(){if(this._closed)throw new Error("Source already closed.");if(!this._connectedTrack)throw new Error("Cannot call close without connecting the source to an output track.");if(!this._connectedTrack.output._started)throw new Error("Cannot call close before output has been started.");this._closed=!0,!this._connectedTrack.output._finalizing&&this._connectedTrack.output._muxer.onTrackClose(this._connectedTrack)}},$=class extends fe{constructor(e){super();this._connectedTrack=null;if(!ue.includes(e))throw new TypeError(`Invalid video codec '${e}'. Must be one of: ${ue.join(", ")}.`);this._codec=e}},bt=class extends ${constructor(t){super(t)}digest(t,e){if(!(t instanceof EncodedVideoChunk))throw new TypeError("chunk must be an EncodedVideoChunk.");return this._ensureValidDigest(),this._connectedTrack.output._muxer.addEncodedVideoChunk(this._connectedTrack,t,e)}},yi=5,Si=i=>{if(!i||typeof i!="object")throw new TypeError("Codec config must be an object.");if(!ue.includes(i.codec))throw new TypeError(`Invalid video codec '${i.codec}'. Must be one of: ${ue.join(", ")}.`);if(!Number.isInteger(i.bitrate)||i.bitrate<=0)throw new TypeError("config.bitrate must be a positive integer.");if(i.latencyMode!==void 0&&!["quality","realtime"].includes(i.latencyMode))throw new TypeError("config.latencyMode, when provided, must be 'quality' or 'realtime'.");if(i.onEncodingError!==void 0&&typeof i.onEncodingError!="function")throw new TypeError("config.onEncodingError, when provided, must be a function.")},ye=class{constructor(t,e){this.source=t;this.codecConfig=e;this.encoder=null;this.muxer=null;this.lastMultipleOfKeyFrameInterval=-1;this.lastWidth=null;this.lastHeight=null;Si(e)}async digest(t){if(this.source._ensureValidDigest(),this.lastWidth!==null&&this.lastHeight!==null){if(t.codedWidth!==this.lastWidth||t.codedHeight!==this.lastHeight)throw new Error(`Video frame size must remain constant. Expected ${this.lastWidth}x${this.lastHeight}, got ${t.codedWidth}x${t.codedHeight}.`)}else this.lastWidth=t.codedWidth,this.lastHeight=t.codedHeight;this.ensureEncoder(t),l(this.encoder);let e=Math.floor(t.timestamp/1e6/yi);this.encoder.encode(t,{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(t){this.encoder||(this.encoder=new VideoEncoder({output:(e,r)=>void this.muxer.addEncodedVideoChunk(this.source._connectedTrack,e,r),error:this.codecConfig.onEncodingError??(e=>console.error("VideoEncoder error:",e))}),this.encoder.configure({codec:sr(this.codecConfig.codec,t.codedWidth,t.codedHeight,this.codecConfig.bitrate),width:t.codedWidth,height:t.codedHeight,bitrate:this.codecConfig.bitrate,framerate:this.source._connectedTrack?.metadata.frameRate,latencyMode:this.codecConfig.latencyMode,...dr(this.codecConfig.codec)}),l(this.source._connectedTrack),this.muxer=this.source._connectedTrack.output._muxer)}async flush(){this.encoder&&(await this.encoder.flush(),this.encoder.close())}},kt=class extends ${constructor(t){super(t.codec),this._encoder=new ye(this,t)}digest(t){if(!(t instanceof VideoFrame))throw new TypeError("videoFrame must be a VideoFrame.");return this._encoder.digest(t)}_flush(){return this._encoder.flush()}},gt=class extends ${constructor(t,e){if(!(t instanceof HTMLCanvasElement))throw new TypeError("canvas must be an HTMLCanvasElement.");super(e.codec),this._encoder=new ye(this,e),this._canvas=t}digest(t,e=0){if(!Number.isFinite(t)||t<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*t),duration:Math.round(1e6*e),alpha:"discard"}),a=this._encoder.digest(r);return r.close(),a}_flush(){return this._encoder.flush()}},Tt=class extends ${constructor(e,r){if(!(e instanceof MediaStreamTrack)||e.kind!=="video")throw new TypeError("track must be a video MediaStreamTrack.");r={...r,latencyMode:"realtime"};super(r.codec);this._abortController=null;this._offsetTimestamps=!0;this._encoder=new ye(this,r),this._track=e}_start(){this._abortController=new AbortController;let e=new MediaStreamTrackProcessor({track:this._track}),r=new WritableStream({write:a=>{this._encoder.digest(a),a.close()}});e.readable.pipeTo(r,{signal:this._abortController.signal}).catch(a=>{a instanceof DOMException&&a.name==="AbortError"||console.error("Pipe error:",a)})}async _flush(){this._abortController&&(this._abortController.abort(),this._abortController=null),await this._encoder.flush()}},K=class extends fe{constructor(e){super();this._connectedTrack=null;if(!de.includes(e))throw new TypeError(`Invalid audio codec '${e}'. Must be one of: ${de.join(", ")}.`);this._codec=e}},xt=class extends K{constructor(t){super(t)}digest(t,e){if(!(t instanceof EncodedAudioChunk))throw new TypeError("chunk must be an EncodedAudioChunk.");return this._ensureValidDigest(),this._connectedTrack.output._muxer.addEncodedAudioChunk(this._connectedTrack,t,e)}},vi=i=>{if(!i||typeof i!="object")throw new TypeError("Codec config must be an object.");if(!de.includes(i.codec))throw new TypeError(`Invalid audio codec '${i.codec}'. Must be one of: ${de.join(", ")}.`);if(!Number.isInteger(i.bitrate)||i.bitrate<=0)throw new TypeError("config.bitrate must be a positive integer.");if(i.onEncodingError!==void 0&&typeof i.onEncodingError!="function")throw new TypeError("config.onEncodingError, when provided, must be a function.")},Se=class{constructor(t,e){this.source=t;this.codecConfig=e;this.encoder=null;this.muxer=null;this.lastNumberOfChannels=null;this.lastSampleRate=null;vi(e)}async digest(t){if(this.source._ensureValidDigest(),this.lastNumberOfChannels!==null&&this.lastSampleRate!==null){if(t.numberOfChannels!==this.lastNumberOfChannels||t.sampleRate!==this.lastSampleRate)throw new Error(`Audio parameters must remain constant. Expected ${this.lastNumberOfChannels} channels at ${this.lastSampleRate} Hz, got ${t.numberOfChannels} channels at ${t.sampleRate} Hz.`)}else this.lastNumberOfChannels=t.numberOfChannels,this.lastSampleRate=t.sampleRate;this.ensureEncoder(t),l(this.encoder),this.encoder.encode(t),this.encoder.encodeQueueSize>=4&&await new Promise(e=>this.encoder.addEventListener("dequeue",e,{once:!0})),await this.muxer.mutex.currentPromise}ensureEncoder(t){this.encoder||(this.encoder=new AudioEncoder({output:(e,r)=>void this.muxer.addEncodedAudioChunk(this.source._connectedTrack,e,r),error:this.codecConfig.onEncodingError??(e=>console.error("AudioEncoder error:",e))}),this.encoder.configure({codec:cr(this.codecConfig.codec,t.numberOfChannels,t.sampleRate),numberOfChannels:t.numberOfChannels,sampleRate:t.sampleRate,bitrate:this.codecConfig.bitrate,...lr(this.codecConfig.codec)}),l(this.source._connectedTrack),this.muxer=this.source._connectedTrack.output._muxer)}async flush(){this.encoder&&(await this.encoder.flush(),this.encoder.close())}},Ct=class extends K{constructor(t){super(t.codec),this._encoder=new Se(this,t)}digest(t){if(!(t instanceof AudioData))throw new TypeError("audioData must be an AudioData.");return this._encoder.digest(t)}_flush(){return this._encoder.flush()}},wt=class extends K{constructor(e){super(e.codec);this._accumulatedFrameCount=0;this._encoder=new Se(this,e)}digest(e){if(!(e instanceof AudioBuffer))throw new TypeError("audioBuffer must be an AudioBuffer.");let r=e.numberOfChannels,a=e.sampleRate,n=e.length,s=new Float32Array(r*n);for(let u=0;u{this._encoder.digest(a),a.close()}});e.readable.pipeTo(r,{signal:this._abortController.signal}).catch(a=>{a instanceof DOMException&&a.name==="AbortError"||console.error("Pipe error:",a)})}async _flush(){this._abortController&&(this._abortController.abort(),this._abortController=null),await this._encoder.flush()}},he=class extends fe{constructor(e){super();this._connectedTrack=null;if(!Ue.includes(e))throw new TypeError(`Invalid subtitle codec '${e}'. Must be one of: ${Ue.join(", ")}.`);this._codec=e}},St=class extends he{constructor(t){super(t),this._parser=new Oe({codec:t,output:(e,r)=>this._connectedTrack?.output._muxer.addSubtitleCue(this._connectedTrack,e,r)})}digest(t){if(typeof t!="string")throw new TypeError("text must be a string.");return this._ensureValidDigest(),this._parser.parse(t),this._connectedTrack.output._muxer.mutex.currentPromise}};var vt=class{constructor(t){this._tracks=[];this._started=!1;this._finalizing=!1;this._mutex=new j;if(!t||typeof t!="object")throw new TypeError("options must be an object.");if(!(t.format instanceof ie))throw new TypeError("options.format must be an OutputFormat.");if(!(t.target instanceof re))throw new TypeError("options.target must be a Target.");if(t.target._output)throw new Error("Target is already used for another output.");t.target._output=this,this._writer=t.target._createWriter(),this._muxer=t.format._createMuxer(this)}addVideoTrack(t,e={}){if(!(t instanceof $))throw new TypeError("source must be a VideoSource.");if(!e||typeof e!="object")throw new TypeError("metadata must be an object.");if(typeof e.rotation=="number"&&![0,90,180,270].includes(e.rotation))throw new TypeError(`Invalid video rotation: ${e.rotation}. Has to be 0, 90, 180 or 270.`);if(Array.isArray(e.rotation)&&(e.rotation.length!==9||e.rotation.some(r=>!Number.isFinite(r))))throw new TypeError(`Invalid video transformation matrix: ${e.rotation.join()}`);if(e.frameRate!==void 0&&(!Number.isInteger(e.frameRate)||e.frameRate<=0))throw new TypeError(`Invalid video frame rate: ${e.frameRate}. Must be a positive integer.`);this._addTrack("video",t,e)}addAudioTrack(t,e={}){if(!(t instanceof K))throw new TypeError("source must be an AudioSource.");if(!e||typeof e!="object")throw new TypeError("metadata must be an object.");this._addTrack("audio",t,e)}addSubtitleTrack(t,e={}){if(!(t instanceof he))throw new TypeError("source must be a SubtitleSource.");if(!e||typeof e!="object")throw new TypeError("metadata must be an object.");this._addTrack("subtitle",t,e)}_addTrack(t,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 a={id:this._tracks.length+1,output:this,type:t,source:e,metadata:r};this._muxer.beforeTrackAdd(a),this._tracks.push(a),e._connectedTrack=a}async start(){if(this._started)throw new Error("Output already started.");this._started=!0,this._writer.start();let t=await this._mutex.acquire();await this._muxer.start();for(let e of this._tracks)e.source._start();t()}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 t=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(),t()}};var X=class{constructor(){this._sizePromise=null}_getSize(){return this._sizePromise??=this._retrieveSize()}},_t=class extends X{constructor(t){if(!(t instanceof ArrayBuffer)&&!(t instanceof Uint8Array))throw new TypeError("buffer must be an ArrayBuffer or Uint8Array.");super(),this._bytes=t instanceof Uint8Array?t:new Uint8Array(t)}async _read(t,e){return this._bytes.subarray(t,e)}async _retrieveSize(){return this._bytes.byteLength}},At=class extends X{constructor(t){if(!(t instanceof Blob))throw new TypeError("blob must be a Blob.");super(),this._blob=t}async _read(t,e){let a=await this._blob.slice(t,e).arrayBuffer();return new Uint8Array(a)}async _retrieveSize(){return this._blob.size}},It=class extends X{constructor(e,r={}){if(typeof e!="string")throw new TypeError("url must be a string.");if(!r||typeof r!="object")throw new TypeError("options must be an object.");if(r.withCredentials!==void 0&&typeof r.withCredentials!="boolean")throw new TypeError("options.withCredentials, when specified, must be a boolean.");super();this._fullData=null;this._url=e,this._withCredentials=r.withCredentials??!1}_makeRequest(e){return new Promise((r,a)=>{let n=new XMLHttpRequest;n.open("GET",this._url,!0),n.responseType="arraybuffer",n.withCredentials=this._withCredentials,e&&n.setRequestHeader("Range",`bytes=${e.start}-${e.end-1}`),n.onload=()=>{if(n.status>=200&&n.status<300){let s=n.response;e||(this._fullData=s),r({response:s,statusCode:n.status})}else a(new Error(`Error fetching ${this._url}: ${n.status} ${n.statusText}`))},n.onerror=()=>{a(new Error("Network error occurred."))},n.ontimeout=()=>{a(new Error("Request timed out."))},n.send()})}async _read(e,r){if(this._fullData)return new Uint8Array(this._fullData,e,r-e);let{response:a,statusCode:n}=await this._makeRequest({start:e,end:r});return n===200?new Uint8Array(a).subarray(e,r):new Uint8Array(a)}async _retrieveSize(){if(this._fullData)return this._fullData.byteLength;let e=new XMLHttpRequest;e.open("HEAD",this._url,!0),e.withCredentials=this._withCredentials,await new Promise((a,n)=>{e.onload=()=>{e.status>=200&&e.status<300?a():n(new Error(`Error fetching ${this._url} (HEAD): ${e.status} ${e.statusText}`))},e.onerror=()=>{a()},e.send()});let r=e.getResponseHeader("Content-Length");if(!r){let{response:a}=await this._makeRequest();return a.byteLength}return parseInt(r,10)}};var $e=class{constructor(t){this.input=t}};var ve=class{constructor(t){this._backing=t}isVideoTrack(){return this instanceof W}isAudioTrack(){return this instanceof H}computeDuration(){return this._backing.computeDuration()}},W=class extends ve{constructor(t){super(t),this._backing=t}getCodec(){return this._backing.getCodec()}getCodedWidth(){return this._backing.getCodedWidth()}getCodedHeight(){return this._backing.getCodedHeight()}getRotation(){return this._backing.getRotation()}async getDisplayWidth(){return await this._backing.getRotation()%180===0?this._backing.getCodedWidth():this._backing.getCodedHeight()}async getDisplayHeight(){return await this._backing.getRotation()%180===0?this._backing.getCodedHeight():this._backing.getCodedWidth()}getDecoderConfig(){return this._backing.getDecoderConfig()}async getCodecMimeType(){return(await this.getDecoderConfig()).codec}},H=class extends ve{constructor(t){super(t),this._backing=t}getCodec(){return this._backing.getCodec()}getNumberOfChannels(){return this._backing.getNumberOfChannels()}getSampleRate(){return this._backing.getSampleRate()}getDecoderConfig(){return this._backing.getDecoderConfig()}async getCodecMimeType(){return(await this.getDecoderConfig()).codec}};var pe=class{constructor(t,e=1/0){this.source=t;this.maxStorableBytes=e;this.loadedSegments=[];this.loadingSegments=[];this.sourceSizePromise=null;this.nextAge=0;this.totalStoredBytes=0}async loadRange(t,e){e=Math.min(e,await this.source._getSize());let r=this.loadingSegments.find(c=>c.start<=t&&c.end>=e);if(r){await r.promise;return}if(this.loadedSegments.some(c=>c.start<=t&&c.end>=e))return;let n=this.source._read(t,e),s={start:t,end:e,promise:n};this.loadingSegments.push(s);let o=await n;Wt(this.loadingSegments,s),this.insertIntoLoadedSegments(t,o)}insertIntoLoadedSegments(t,e){let r={start:t,end:t+e.byteLength,bytes:e,view:new DataView(e.buffer),age:this.nextAge++},a=E(this.loadedSegments,t,n=>n.start);(a===-1||this.loadedSegments[a].start=r.end)break;r.start<=s.start&&s.end<=r.end&&(this.loadedSegments.splice(n,1),n--)}for(;this.totalStoredBytes>this.maxStorableBytes&&this.loadedSegments.length>1;){let n=null,s=-1;for(let o=0;on.start),a=null;if(r!==-1)for(let n=r;nt)break;if(e<=s.end){a=s;break}}if(!a)throw new Error(`No segment loaded for range [${t}, ${e}).`);return a.age=this.nextAge++,{view:a.view,offset:a.bytes.byteOffset+t-a.start}}forgetRange(t,e){if(e<=t)return;let r=E(this.loadedSegments,t,n=>n.start);if(r===-1)return;let a=this.loadedSegments[r];a.start!==t||a.end!==e||(this.loadedSegments.splice(r,1),this.totalStoredBytes-=a.bytes.byteLength)}};var ne=class{constructor(t){this.reader=t;this.pos=0}readRange(t,e){let{view:r,offset:a}=this.reader.getViewAndOffset(t,e);return new Uint8Array(r.buffer,a,e-t)}readU8(){let{view:t,offset:e}=this.reader.getViewAndOffset(this.pos,this.pos+1);return this.pos++,t.getUint8(e)}readU16(){let{view:t,offset:e}=this.reader.getViewAndOffset(this.pos,this.pos+2);return this.pos+=2,t.getUint16(e,!1)}readU24(){let{view:t,offset:e}=this.reader.getViewAndOffset(this.pos,this.pos+3);this.pos+=3;let r=t.getUint16(e,!1),a=t.getUint8(e+2);return r*256+a}readS32(){let{view:t,offset:e}=this.reader.getViewAndOffset(this.pos,this.pos+4);return this.pos+=4,t.getInt32(e,!1)}readU32(){let{view:t,offset:e}=this.reader.getViewAndOffset(this.pos,this.pos+4);return this.pos+=4,t.getUint32(e,!1)}readI32(){let{view:t,offset:e}=this.reader.getViewAndOffset(this.pos,this.pos+4);return this.pos+=4,t.getInt32(e,!1)}readU64(){let t=this.readU32(),e=this.readU32();return t*4294967296+e}readF64(){let{view:t,offset:e}=this.reader.getViewAndOffset(this.pos,this.pos+8);return this.pos+=8,t.getFloat64(e,!1)}readFixed_16_16(){return this.readS32()/65536}readFixed_2_30(){return this.readS32()/1073741824}readAscii(t){let{view:e,offset:r}=this.reader.getViewAndOffset(this.pos,this.pos+t);this.pos+=t;let a="";for(let n=0;na.computeDuration()));return Math.max(0,...r)}async getTracks(){return await this.readMetadata(),this.tracks.map(e=>e.inputTrack)}async getMimeType(){await this.readMetadata();let e="video/mp4";if(this.tracks.length>0){let r=await Promise.all(this.tracks.map(n=>n.inputTrack.getCodecMimeType())),a=[...new Set(r)];e+=`; codecs="${a.join(", ")}"`}return e}readMetadata(){return this.metadataPromise??=(async()=>{let e=await this.isobmffReader.reader.source._getSize();for(;this.isobmffReader.pos=0&&aa.presentationTimestamp-n.presentationTimestamp),e.sampleTable}async readFragment(){let e=this.isobmffReader.pos;await this.isobmffReader.reader.loadRange(this.isobmffReader.pos,this.isobmffReader.pos+16);let r=this.isobmffReader.readBoxHeader();l(r.name==="moof"),await this.isobmffReader.reader.loadRange(e,e+r.totalSize),this.isobmffReader.pos=e,this.traverseBox();let a=Q(this.fragments,e,s=>s.moofOffset);l(a!==-1);let n=this.fragments[a];l(n.moofOffset===e),this.isobmffReader.reader.forgetRange(e,e+r.totalSize);for(let[s,o]of n.trackData){if(o.startTimestampIsFinal)continue;let c=this.tracks.find(m=>m.id===s);this.isobmffReader.pos=0;let u=null,d=null,f=E(c.fragments,e-1,m=>m.moofOffset);for(f!==-1&&(u=c.fragments[f],d=u,this.isobmffReader.pos=u.moofOffset+u.moofSize);this.isobmffReader.posp.moofOffset);if(b===-1){this.isobmffReader.pos=m;let p=await this.readFragment();u&&(u.nextFragment=p),u=p}else{let p=this.fragments[b];u&&(u.nextFragment=p),u=p}}this.isobmffReader.pos=m+g.totalSize}u&&u.trackData.has(s)&&(d=u)}if(d){let m=d.trackData.get(s);l(m.startTimestampIsFinal),kr(o,m.endTimestamp)}o.startTimestampIsFinal=!0}return n}readContiguousBoxes(e){let r=this.isobmffReader.pos;for(;this.isobmffReader.pos-rf.every((m,g)=>m===u[g]));d===-1?n.rotation=0:n.rotation=90*d}break;case"mdhd":{let n=this.currentTrack;l(n);let s=this.isobmffReader.readU8();this.isobmffReader.pos+=3,s===0?(this.isobmffReader.pos+=8,n.timescale=this.isobmffReader.readU32(),n.durationInTimescale=this.isobmffReader.readU32()):s===1&&(this.isobmffReader.pos+=16,n.timescale=this.isobmffReader.readU32(),n.durationInTimescale=this.isobmffReader.readU64())}break;case"hdlr":{let n=this.currentTrack;l(n),this.isobmffReader.pos+=8;let s=this.isobmffReader.readAscii(4);s==="vide"?n.info={type:"video",width:-1,height:-1,codec:null,codecDescription:null,colorSpace:null}:s==="soun"&&(n.info={type:"audio",numberOfChannels:-1,sampleRate:-1,codec:null,codecDescription:null})}break;case"stbl":{let n=this.currentTrack;l(n),n.sampleTableOffset=e,this.readContiguousBoxes(r.contentSize)}break;case"stsd":{let n=this.currentTrack;if(l(n),n.info===null||n.sampleTable)break;let s=this.isobmffReader.readU8();this.isobmffReader.pos+=3;let o=this.isobmffReader.readU32();for(let c=0;c0){if(d===1)this.isobmffReader.pos+=4*4;else if(d===2){this.isobmffReader.pos+=4,m=this.isobmffReader.readF64(),f=this.isobmffReader.readU32(),this.isobmffReader.pos+=4;let g=this.isobmffReader.readU32(),b=this.isobmffReader.readU32(),p=this.isobmffReader.readU32(),k=this.isobmffReader.readU32()}}n.info.numberOfChannels=f,n.info.sampleRate=m,this.readContiguousBoxes(e+u.totalSize-this.isobmffReader.pos)}}}break;case"avcC":{let n=this.currentTrack;l(n&&n.info),n.info.codecDescription=this.isobmffReader.readRange(this.isobmffReader.pos,this.isobmffReader.pos+r.contentSize)}break;case"hvcC":{let n=this.currentTrack;l(n&&n.info),n.info.codecDescription=this.isobmffReader.readRange(this.isobmffReader.pos,this.isobmffReader.pos+r.contentSize)}break;case"colr":{let n=this.currentTrack;if(l(n&&n.info?.type==="video"),this.isobmffReader.readAscii(4)!=="nclx")break;let o=this.isobmffReader.readU16(),c=this.isobmffReader.readU16(),u=this.isobmffReader.readU16(),d=!!(this.isobmffReader.readU8()&128);n.info.colorSpace={primaries:Pt[o],transfer:zt[c],matrix:Ut[u],fullRange:d}}break;case"wave":r.totalSize>8&&this.readContiguousBoxes(r.contentSize);break;case"esds":{let n=this.currentTrack;l(n&&n.info),this.isobmffReader.pos+=4;let s=this.isobmffReader.readU8();l(s===3),this.isobmffReader.readIsomVariableInteger(),this.isobmffReader.pos+=2;let o=this.isobmffReader.readU8(),c=(o&128)!==0,u=(o&64)!==0,d=(o&32)!==0;if(c&&(this.isobmffReader.pos+=2),u){let p=this.isobmffReader.readU8();this.isobmffReader.pos+=p}d&&(this.isobmffReader.pos+=2);let f=this.isobmffReader.readU8();l(f===4),this.isobmffReader.readIsomVariableInteger();let m=this.isobmffReader.readU8();l(m===64),this.isobmffReader.pos+=12;let g=this.isobmffReader.readU8();l(g===5);let b=this.isobmffReader.readIsomVariableInteger();n.info.codecDescription=this.isobmffReader.readRange(this.isobmffReader.pos,this.isobmffReader.pos+b)}break;case"stts":{let n=this.currentTrack;if(l(n),!n.sampleTable)break;this.isobmffReader.pos+=4;let s=this.isobmffReader.readU32(),o=0,c=0;for(let u=0;ux.id===s);if(!o)break;o.fragmentLookupTable=[];let c=this.isobmffReader.readU32(),u=(c&48)>>4,d=(c&12)>>2,f=c&3,m=this.isobmffReader,g=[m.readU8.bind(m),m.readU16.bind(m),m.readU24.bind(m),m.readU32.bind(m)],b=g[u],p=g[d],k=g[f],T=this.isobmffReader.readU32();for(let x=0;xs.moofOffset);this.fragments.splice(n+1,0,this.currentFragment);for(let[,s]of this.currentFragment.trackData){let o=s.samples[0],c=F(s.samples);this.currentFragment.dataStart=Math.min(this.currentFragment.dataStart,o.byteOffset),this.currentFragment.dataEnd=Math.max(this.currentFragment.dataEnd,c.byteOffset+c.byteSize)}this.currentFragment=null}break;case"traf":if(l(this.currentFragment),this.readContiguousBoxes(r.contentSize),this.currentTrack){let n=this.currentFragment.trackData.get(this.currentTrack.id);if(n){let s=E(this.currentTrack.fragments,this.currentFragment.moofOffset,c=>c.moofOffset);this.currentTrack.fragments.splice(s+1,0,this.currentFragment);let{currentFragmentState:o}=this.currentTrack;l(o),o.startTimestamp!==null&&(kr(n,o.startTimestamp),n.startTimestampIsFinal=!0)}this.currentTrack.currentFragmentState=null,this.currentTrack=null}break;case"tfhd":{l(this.currentFragment),this.isobmffReader.pos+=1;let n=this.isobmffReader.readU24(),s=!!(n&1),o=!!(n&2),c=!!(n&8),u=!!(n&16),d=!!(n&32),f=!!(n&65536),m=!!(n&131072),g=this.isobmffReader.readU32(),b=this.tracks.find(k=>k.id===g);if(!b)break;let p=this.fragmentTrackDefaults.find(k=>k.trackId===g);this.currentTrack=b,b.currentFragmentState={baseDataOffset:this.currentFragment.implicitBaseDataOffset,sampleDescriptionIndex:p?.defaultSampleDescriptionIndex??null,defaultSampleDuration:p?.defaultSampleDuration??null,defaultSampleSize:p?.defaultSampleSize??null,defaultSampleFlags:p?.defaultSampleFlags??null,startTimestamp:null},s?b.currentFragmentState.baseDataOffset=this.isobmffReader.readU64():m&&(b.currentFragmentState.baseDataOffset=this.currentFragment.moofOffset),o&&(b.currentFragmentState.sampleDescriptionIndex=this.isobmffReader.readU32()),c&&(b.currentFragmentState.defaultSampleDuration=this.isobmffReader.readU32()),u&&(b.currentFragmentState.defaultSampleSize=this.isobmffReader.readU32()),d&&(b.currentFragmentState.defaultSampleFlags=this.isobmffReader.readU32()),f&&(b.currentFragmentState.defaultSampleDuration=0)}break;case"tfdt":{let n=this.currentTrack;if(!n)break;l(n.currentFragmentState);let s=this.isobmffReader.readU8();this.isobmffReader.pos+=3;let o=s===0?this.isobmffReader.readU32():this.isobmffReader.readU64();n.currentFragmentState.startTimestamp=o}break;case"trun":{let n=this.currentTrack;if(!n)break;if(l(this.currentFragment),l(n.currentFragmentState),this.currentFragment.trackData.has(n.id))throw new Error("Can't have two trun boxes for the same track in one fragment.");let s=this.isobmffReader.readU8(),o=this.isobmffReader.readU24(),c=!!(o&1),u=!!(o&4),d=!!(o&256),f=!!(o&512),m=!!(o&1024),g=!!(o&2048),b=this.isobmffReader.readU32(),p=n.currentFragmentState.baseDataOffset;c&&(p+=this.isobmffReader.readI32());let k=null;u&&(k=this.isobmffReader.readU32());let T=p;if(b===0){this.currentFragment.implicitBaseDataOffset=T;break}let x=0,C={startTimestamp:0,endTimestamp:0,samples:[],presentationTimestamps:[],startTimestampIsFinal:!1};this.currentFragment.trackData.set(n.id,C);for(let I=0;I({presentationTimestamp:I.presentationTimestamp,sampleIndex:R})).sort((I,R)=>I.presentationTimestamp-R.presentationTimestamp);let O=C.samples[C.presentationTimestamps[0].sampleIndex],_=C.samples[F(C.presentationTimestamps).sampleIndex];C.startTimestamp=O.presentationTimestamp,C.endTimestamp=_.presentationTimestamp+_.duration,this.currentFragment.implicitBaseDataOffset=T}break}this.isobmffReader.pos=a}},je=class{constructor(t){this.internalTrack=t;this.chunkToSampleIndex=new WeakMap;this.chunkToFragmentLocation=new WeakMap}getCodec(){throw new Error("Not implemented on base class.")}async computeDuration(){let e=(await this.getChunk(1/0,{metadataOnly:!0}))?.timestamp;return e?e/1e6:0}async getFirstChunk(t){return this.internalTrack.demuxer.isFragmented?this.performFragmentedLookup(()=>{let e=this.internalTrack.fragments[0];return{fragmentIndex:e?0:-1,sampleIndex:e?0:-1,correctSampleFound:!!e}},0,1/0,t):this.fetchChunkForSampleIndex(0,t)}roundToMicrosecond(t){return(Math.floor(t*1e6)+.99999999)/1e6}async getChunk(t,e){t=this.roundToMicrosecond(t);let r=t*this.internalTrack.timescale;if(this.internalTrack.demuxer.isFragmented)return this.performFragmentedLookup(()=>this.findSampleInFragmentsForTimestamp(r),r,r,e);{let a=this.internalTrack.demuxer.getSampleTableForTrack(this.internalTrack),n=br(a,r);return this.fetchChunkForSampleIndex(n,e)}}async getNextChunk(t,e){if(this.internalTrack.demuxer.isFragmented){let a=this.chunkToFragmentLocation.get(t);if(a===void 0)throw new Error("Chunk was not created from this track.");let n=a.fragment.trackData.get(this.internalTrack.id),s=n.samples[a.sampleIndex],o=Q(this.internalTrack.fragments,a.fragment.moofOffset,c=>c.moofOffset);return l(o!==-1),this.performFragmentedLookup(()=>{if(a.sampleIndex+1f.moofOffset);return l(d!==-1),{fragmentIndex:d,sampleIndex:0,correctSampleFound:!0}}return{fragmentIndex:o,sampleIndex:-1,correctSampleFound:!1}}},s.presentationTimestamp,1/0,e)}let r=this.chunkToSampleIndex.get(t);if(r===void 0)throw new Error("Chunk was not created from this track.");return this.fetchChunkForSampleIndex(r+1,e)}async getKeyChunk(t,e){t=this.roundToMicrosecond(t);let r=t*this.internalTrack.timescale;if(this.internalTrack.demuxer.isFragmented)return this.performFragmentedLookup(()=>this.findKeySampleInFragmentsForTimestamp(r),r,r,e);let a=this.internalTrack.demuxer.getSampleTableForTrack(this.internalTrack),n=br(a,r),s=n===-1?-1:Ii(a,n);return this.fetchChunkForSampleIndex(s,e)}async getNextKeyChunk(t,e){if(this.internalTrack.demuxer.isFragmented){let s=this.chunkToFragmentLocation.get(t);if(s===void 0)throw new Error("Chunk was not created from this track.");let o=s.fragment.trackData.get(this.internalTrack.id),c=o.samples[s.sampleIndex],u=Q(this.internalTrack.fragments,s.fragment.moofOffset,d=>d.moofOffset);return l(u!==-1),this.performFragmentedLookup(()=>{let d=o.samples.findIndex((f,m)=>f.isKeyFrame&&m>s.sampleIndex);if(d!==-1)return{fragmentIndex:u,sampleIndex:d,correctSampleFound:!0};{let f=s.fragment;for(;f.nextFragment;){f=f.nextFragment;let m=f.trackData.get(this.internalTrack.id);if(m){let g=Q(this.internalTrack.fragments,f.moofOffset,p=>p.moofOffset);l(g!==-1);let b=m.samples.findIndex(p=>p.isKeyFrame);if(b===-1)throw new Error("Not supported: Fragment does not contain key sample.");return{fragmentIndex:g,sampleIndex:b,correctSampleFound:!0}}}return{fragmentIndex:u,sampleIndex:-1,correctSampleFound:!1}}},c.presentationTimestamp,1/0,e)}let r=this.chunkToSampleIndex.get(t);if(r===void 0)throw new Error("Chunk was not created from this track.");let a=this.internalTrack.demuxer.getSampleTableForTrack(this.internalTrack),n=Ei(a,r);return this.fetchChunkForSampleIndex(n,e)}async fetchChunkForSampleIndex(t,e){if(t===-1)return null;let r=this.internalTrack.demuxer.getSampleTableForTrack(this.internalTrack),a=Ai(r,t);if(!a)return null;let n;e.metadataOnly?n=new Uint8Array(0):(await this.internalTrack.demuxer.chunkReader.reader.loadRange(a.chunkOffset,a.chunkOffset+a.chunkSize),n=this.internalTrack.demuxer.chunkReader.readRange(a.sampleOffset,a.sampleOffset+a.sampleSize));let s=1e6*a.presentationTimestamp/this.internalTrack.timescale,o=1e6*a.duration/this.internalTrack.timescale,c=this.createChunk(n,s,o,a.isKeyFrame);return this.chunkToSampleIndex.set(c,t),c}async fetchChunkInFragment(t,e,r){if(e===-1)return null;let n=t.trackData.get(this.internalTrack.id).samples[e];l(n);let s;r.metadataOnly?s=new Uint8Array(0):(await this.internalTrack.demuxer.chunkReader.reader.loadRange(t.dataStart,t.dataEnd),s=this.internalTrack.demuxer.chunkReader.readRange(n.byteOffset,n.byteOffset+n.byteSize));let o=1e6*n.presentationTimestamp/this.internalTrack.timescale,c=1e6*n.duration/this.internalTrack.timescale,u=this.createChunk(s,o,c,n.isKeyFrame);return this.chunkToFragmentLocation.set(u,{fragment:t,sampleIndex:e}),u}findSampleInFragmentsForTimestamp(t){let e=E(this.internalTrack.fragments,t,n=>n.trackData.get(this.internalTrack.id).startTimestamp),r=-1,a=!1;if(e!==-1){let s=this.internalTrack.fragments[e].trackData.get(this.internalTrack.id),o=E(s.presentationTimestamps,t,c=>c.presentationTimestamp);l(o!==-1),r=s.presentationTimestamps[o].sampleIndex,a=tn.trackData.get(this.internalTrack.id).startTimestamp),r=-1,a=!1;if(e!==-1){let s=this.internalTrack.fragments[e].trackData.get(this.internalTrack.id),o=Ht(s.presentationTimestamps,u=>s.samples[u.sampleIndex].isKeyFrame&&u.presentationTimestamp<=t);if(o===-1)throw new Error("Not supported: Fragment does not begin with a key sample.");r=s.presentationTimestamps[o].sampleIndex,a=tT.timestamp);k!==-1&&(p=this.internalTrack.fragmentLookupTable[k])}if(o===-1)d.pos=p?.moofOffset??0;else{let k=this.internalTrack.fragments[o];!p||k.moofOffset>=k.moofOffset?(d.pos=k.moofOffset+k.moofSize,m=k):d.pos=p.moofOffset}for(;d.posr)break;if(m.nextFragment){d.pos=m.nextFragment.moofOffset+m.nextFragment.moofSize,m=m.nextFragment;continue}}await d.reader.loadRange(d.pos,d.pos+16);let k=d.pos,T=d.readBoxHeader();if(T.name==="moof"){let x=Q(n.fragments,k,C=>C.moofOffset);if(x===-1){d.pos=k;let C=await n.readFragment();m&&(m.nextFragment=C),m=C;let{fragmentIndex:O,sampleIndex:_,correctSampleFound:I}=t();if(I){let R=this.internalTrack.fragments[O];return this.fetchChunkInFragment(R,_,a)}O!==-1&&(g=O,b=_)}else{let C=n.fragments[x];m&&(m.nextFragment=C),m=C}}d.pos=k+T.totalSize}if(g!==-1){let k=this.internalTrack.fragments[g];return this.fetchChunkInFragment(k,b,a)}return null}finally{s()}}},Et=class extends je{constructor(t){super(t),this.internalTrack=t}async getCodec(){return this.internalTrack.info.codec}async getCodedWidth(){return this.internalTrack.info.width}async getCodedHeight(){return this.internalTrack.info.height}async getRotation(){return this.internalTrack.rotation}async getDecoderConfig(){return{codec:or(this.internalTrack.info.codec,this.internalTrack.info.codecDescription),codedWidth:this.internalTrack.info.width,codedHeight:this.internalTrack.info.height,description:this.internalTrack.info.codecDescription??void 0,colorSpace:this.internalTrack.info.colorSpace??void 0}}createChunk(t,e,r,a){return new EncodedVideoChunk({data:t,timestamp:e,duration:r,type:a?"key":"delta"})}},Rt=class extends je{constructor(t){super(t),this.internalTrack=t}async getCodec(){return this.internalTrack.info.codec}async getNumberOfChannels(){return this.internalTrack.info.numberOfChannels}async getSampleRate(){return this.internalTrack.info.sampleRate}async getDecoderConfig(){return{codec:ur(this.internalTrack.info.codec,this.internalTrack.info.codecDescription),numberOfChannels:this.internalTrack.info.numberOfChannels,sampleRate:this.internalTrack.info.sampleRate,description:this.internalTrack.info.codecDescription??void 0}}createChunk(t,e,r,a){return new EncodedAudioChunk({data:t,timestamp:e,duration:r,type:a?"key":"delta"})}},br=(i,t)=>{let e=E(i.presentationTimestamps,t,r=>r.presentationTimestamp);return e===-1?-1:i.presentationTimestamps[e].sampleIndex},Ai=(i,t)=>{let e=E(i.sampleTimingEntries,t,p=>p.startIndex),r=i.sampleTimingEntries[e];if(!r||r.startIndex+r.count<=t)return null;let n=r.startDecodeTimestamp+(t-r.startIndex)*r.delta,s=E(i.sampleCompositionTimeOffsets,t,p=>p.startIndex),o=i.sampleCompositionTimeOffsets[s];o&&(n+=o.offset);let c=i.sampleSizes[Math.min(t,i.sampleSizes.length-1)],u=E(i.sampleToChunk,t,p=>p.startSampleIndex),d=i.sampleToChunk[u];l(d);let f=d.startChunkIndex+Math.floor((t-d.startSampleIndex)/d.samplesPerChunk),m=i.chunkOffsets[f],g=0,b=m;if(i.sampleSizes.length===1)b+=c*(t-d.startSampleIndex),g+=c*d.samplesPerChunk;else{let p=d.startSampleIndex+(f-d.startChunkIndex)*d.samplesPerChunk;for(let k=p;kp)!==-1:!0}},Ii=(i,t)=>{if(!i.keySampleIndices)return t;let e=E(i.keySampleIndices,t,r=>r);return i.keySampleIndices[e]??-1},Ei=(i,t)=>{if(!i.keySampleIndices)return t+1;let e=E(i.keySampleIndices,t,r=>r);return i.keySampleIndices[e+1]??-1},kr=(i,t)=>{i.startTimestamp+=t,i.endTimestamp+=t;for(let e of i.samples)e.presentationTimestamp+=t;for(let e of i.presentationTimestamps)e.presentationTimestamp+=t};var ae=class{},qe=class extends ae{async _canReadInput(t){if(await t._mainReader.source._getSize()<8)return!1;let r=new ne(t._mainReader);return r.pos=4,r.readAscii(4)==="ftyp"}_createDemuxer(t){return new Ke(t)}},Xe=class extends ae{async _canReadInput(){return!1}_createDemuxer(){throw new Error("Not implemented")}},Ge=new qe,Ri=Ge,Oi=Ge,Ot=new Xe,gr=Ot,Fi=Ot,Vi=[Ge,gr];var Ft=class{constructor(t){this._demuxerPromise=null;this._format=null;if(!t||typeof t!="object")throw new TypeError("options must be an object.");if(!Array.isArray(t.formats)||t.formats.some(e=>!(e instanceof ae)))throw new TypeError("options.formats must be an array of InputFormat.");if(!(t.source instanceof X))throw new TypeError("options.source must be a Source.");this._formats=t.formats,this._source=t.source,this._mainReader=new pe(t.source)}_getDemuxer(){return this._demuxerPromise??=(async()=>{await this._mainReader.loadRange(0,4096);for(let t of this._formats)if(await t._canReadInput(this))return this._format=t,t._createDemuxer(this);throw new Error("Input has an unrecognizable format.")})()}async getFormat(){return await this._getDemuxer(),l(this._format),this._format}async computeDuration(){return(await this._getDemuxer()).computeDuration()}async getTracks(){return(await this._getDemuxer()).getTracks()}async getVideoTracks(){return(await this.getTracks()).filter(e=>e.isVideoTrack())}async getPrimaryVideoTrack(){return(await this.getTracks()).find(e=>e.isVideoTrack())??null}async getAudioTracks(){return(await this.getTracks()).filter(e=>e.isAudioTrack())}async getPrimaryAudioTrack(){return(await this.getTracks()).find(e=>e.isAudioTrack())??null}async getMimeType(){return(await this._getDemuxer()).getMimeType()}};var L=i=>{if(!i||typeof i!="object")throw new TypeError("options must be an object.");if(i.metadataOnly!==void 0&&typeof i.metadataOnly!="boolean")throw new TypeError("options.metadataOnly, when defined, must be a boolean.")},U=i=>{if(typeof i!="number"||Number.isNaN(i))throw new TypeError("timestamp must be a number.")},_e=class{async*chunks(t,e=1/0){let r=[],{promise:a,resolve:n}=M(),{promise:s,resolve:o}=M(),c=!1,u=null,d=[],f=()=>Math.max(2,d.length);(async()=>{let m=t??await this.getFirstChunk();for(;m&&!c&&!(m.timestamp/1e6>=e);){if(r.length>f()){({promise:s,resolve:o}=M()),await s;continue}r.push(m),n(),{promise:a,resolve:n}=M(),m=await this.getNextChunk(m)}c=!0,n()})().catch(m=>{u||(u=m,n())});try{for(;;){if(u)throw u;if(r.length>0){yield r.shift();let m=performance.now();for(d.push(m);d.length>0&&m-d[0]>=1e3;)d.shift();o()}else if(!c)await a;else break}}finally{c=!0,o()}}},Ae=class{_duplicateFrame(t){return structuredClone(t)}async*mediaFramesAtTimestamps(t){Qt(t);let e=Lt(t),r=[],a=8,n=[],{promise:s,resolve:o}=M(),{promise:c,resolve:u}=M(),d=!1,f=!1,m=null,g=null,b=T=>{n.push(T),o(),{promise:s,resolve:o}=M()},p=new Error,k=await this._createDecoder(T=>{if(u(),f){T.close();return}let x=!1;for(;r.length>0&&r[0]===T.timestamp;)b(this._duplicateFrame(T)),r.shift(),x=!0;x?(g?.close(),g=T):T.close()},T=>{m||(T.stack=p.stack,m=T,o())});(async()=>{let T=this._createChunkDrain(),x=null,C=null;for await(let O of e){for(U(O);n.length+k.decodeQueueSize>a;)({promise:c,resolve:u}=M()),await c;if(f)break;let _=await T.getChunk(O);if(!_){b(null);continue}let I=await T.getKeyChunk(O);if(!I){b(null);continue}for(r.push(_.timestamp),x&&I.timestamp===x.timestamp&&_.timestamp>=C.timestamp?(l(C),_.timestamp===C.timestamp&&r.length===1&&(g&&b(this._duplicateFrame(g)),r.shift())):(x=I,C=I,k.decode(I));C.timestamp!==_.timestamp;){let R=await T.getNextChunk(C);l(R),C=R,k.decode(R)}k.decodeQueueSize>=10&&await new Promise(R=>k.addEventListener("dequeue",R,{once:!0}))}await k.flush(),k.close(),d=!0,o()})().catch(T=>{m||(m=T,o())});try{for(;;){if(m)throw m;if(n.length>0){let T=n.shift();l(T!==void 0),yield T,u()}else if(!d)await s;else break}}finally{f=!0,u();for(let T of n)T?.close();g?.close()}}async*mediaFramesInRange(t=0,e=1/0){U(t),U(e);let r=8,a=[],n=!1,s=null,{promise:o,resolve:c}=M(),{promise:u,resolve:d}=M(),f=!1,m=!1,g=null,b=new Error,p=await this._createDecoder(x=>{d();let C=x.timestamp/1e6;if(C>=e&&(m=!0),m){x.close();return}s&&(C>t?(a.push(s),n=!0):s.close()),C>=t&&(a.push(x),n=!0),s=n?null:x,a.length>0&&(c(),{promise:o,resolve:c}=M())},x=>{g||(x.stack=b.stack,g=x,c())}),k=this._createChunkDrain(),T=await k.getKeyChunk(t)??await k.getFirstChunk();if(T){(async()=>{let x=T,C=1/0;if(e<1/0){let _=await k.getChunk(e),I=_?_.type==="key"&&_.timestamp/1e6===e?_:await k.getNextKeyChunk(_):null;I&&(C=I.timestamp/1e6)}let O=k.chunks(T,C);for(await O.next();x&&!m;){if(a.length+p.decodeQueueSize>r){({promise:u,resolve:d}=M()),await u;continue}p.decode(x);let _=await O.next();if(_.done)break;x=_.value}await O.return(),await p.flush(),p.close(),!n&&s&&a.push(s),f=!0,c()})().catch(x=>{g||(g=x,c())});try{for(;;){if(g)throw g;if(a.length>0)yield a.shift(),d();else if(!f)await o;else break}}finally{m=!0,d();for(let x of a)x.close()}}}},Ye=class extends _e{constructor(t){if(!(t instanceof W))throw new TypeError("videoTrack must be an InputVideoTrack.");super(),this._videoTrack=t}getFirstChunk(t={}){return L(t),this._videoTrack._backing.getFirstChunk(t)}getChunk(t,e={}){return U(t),L(e),this._videoTrack._backing.getChunk(t,e)}getNextChunk(t,e={}){if(!(t instanceof EncodedVideoChunk))throw new TypeError("chunk must be an EncodedVideoChunk.");return L(e),this._videoTrack._backing.getNextChunk(t,e)}getKeyChunk(t,e={}){return U(t),L(e),this._videoTrack._backing.getKeyChunk(t,e)}getNextKeyChunk(t,e={}){if(!(t instanceof EncodedVideoChunk))throw new TypeError("chunk must be an EncodedVideoChunk.");return L(e),this._videoTrack._backing.getNextKeyChunk(t,e)}},Ze=class extends Ae{constructor(e){if(!(e instanceof W))throw new TypeError("videoTrack must be an InputVideoTrack.");super();this._decoderConfig=null;this._videoTrack=e}async _createDecoder(e,r){this._decoderConfig??=await this._videoTrack.getDecoderConfig();let a=new VideoDecoder({output:e,error:r});return a.configure(this._decoderConfig),a}_createChunkDrain(){return new Ye(this._videoTrack)}async getFrame(e){U(e);for await(let r of this.mediaFramesAtTimestamps([e]))return r;throw new Error("Internal error: Iterator returned nothing.")}frames(e=0,r=1/0){return this.mediaFramesInRange(e,r)}framesAtTimestamps(e){return this.mediaFramesAtTimestamps(e)}},Vt=class{constructor(t,e){if(!(t instanceof W))throw new TypeError("videoTrack must be an InputVideoTrack.");if(e&&typeof e!="object")throw new TypeError("dimensions, when defined, must be an object.");if(e&&(!Number.isInteger(e.width)||e.width<=0))throw new TypeError("dimensions.width must be a positive integer.");if(e&&(!Number.isInteger(e.height)||e.height<=0))throw new TypeError("dimensions.height must be a positive integer.");this._videoTrack=t,this._dimensions=e,this._videoFrameDrain=new Ze(t)}async _videoFrameToWrappedCanvas(t){let e=this._dimensions?.width??await this._videoTrack.getDisplayWidth(),r=this._dimensions?.height??await this._videoTrack.getDisplayHeight(),a=await this._videoTrack.getRotation(),n=document.createElement("canvas");n.width=e,n.height=r;let s=n.getContext("2d",{alpha:!1});l(s),s.translate(e/2,r/2),s.rotate(a*Math.PI/180),s.translate(-e/2,-r/2);let[o,c]=a%180===0?[e,r]:[r,e];s.drawImage(t,(e-o)/2,(r-c)/2,o,c);let u={canvas:n,timestamp:t.timestamp/1e6,duration:(t.duration??0)/1e6};return t.close(),u}async getCanvas(t){U(t);let e=await this._videoFrameDrain.getFrame(t);return e&&this._videoFrameToWrappedCanvas(e)}async*canvases(t=0,e=1/0){for await(let r of this._videoFrameDrain.frames(t,e))yield this._videoFrameToWrappedCanvas(r)}async*canvasesAtTimestamps(t){for await(let e of this._videoFrameDrain.framesAtTimestamps(t))yield e&&this._videoFrameToWrappedCanvas(e)}},Je=class extends _e{constructor(t){if(!(t instanceof H))throw new TypeError("audioTrack must be an InputAudioTrack.");super(),this._audioTrack=t}getFirstChunk(t={}){return L(t),this._audioTrack._backing.getFirstChunk(t)}getChunk(t,e={}){return U(t),L(e),this._audioTrack._backing.getChunk(t,e)}getNextChunk(t,e={}){if(!(t instanceof EncodedAudioChunk))throw new TypeError("chunk must be an EncodedAudioChunk.");return L(e),this._audioTrack._backing.getNextChunk(t,e)}getKeyChunk(t,e={}){return U(t),L(e),this._audioTrack._backing.getKeyChunk(t,e)}getNextKeyChunk(t,e={}){if(!(t instanceof EncodedAudioChunk))throw new TypeError("chunk must be an EncodedAudioChunk.");return L(e),this._audioTrack._backing.getNextKeyChunk(t,e)}},et=class extends Ae{constructor(e){if(!(e instanceof H))throw new TypeError("audioTrack must be an InputAudioTrack.");super();this._decoderConfig=null;this._audioTrack=e}async _createDecoder(e,r){this._decoderConfig??=await this._audioTrack.getDecoderConfig();let a=new AudioDecoder({output:e,error:r});return a.configure(this._decoderConfig),a}_createChunkDrain(){return new Je(this._audioTrack)}async getData(e){U(e);for await(let r of this.mediaFramesAtTimestamps([e]))return r;throw new Error("Internal error: Iterator returned nothing.")}data(e=0,r=1/0){return this.mediaFramesInRange(e,r)}dataAtTimestamps(e){return this.mediaFramesAtTimestamps(e)}},Mt=class{constructor(t){if(!(t instanceof H))throw new TypeError("audioTrack must be an InputAudioTrack.");this._audioDataDrain=new et(t)}_audioDataToWrappedArrayBuffer(t){let e=new AudioBuffer({numberOfChannels:t.numberOfChannels,length:t.numberOfFrames,sampleRate:t.sampleRate}),r=new Float32Array(t.allocationSize({planeIndex:0,format:"f32-planar"})/4);for(let s=0;s { - return arr && arr[arr.length - 1]; -}; -var isU32 = (value) => { - return value >= 0 && value < 2 ** 32; -}; -var readBits = (bytes2, start, end) => { - let result = 0; - for (let i = start; i < end; i++) { - 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; - } - return result; -}; -var writeBits = (bytes2, start, end, value) => { - for (let i = start; i < end; i++) { - const byteIndex = Math.floor(i / 8); - let byte = bytes2[byteIndex]; - const bitIndex = 7 - (i & 7); - byte &= ~(1 << bitIndex); - byte |= (value & 1 << end - i - 1) >> end - i - 1 << bitIndex; - bytes2[byteIndex] = byte; - } -}; -var toUint8Array = (source) => { - if (source instanceof ArrayBuffer) { - return new Uint8Array(source); - } else { - return new Uint8Array(source.buffer, source.byteOffset, source.byteLength); - } -}; -var textEncoder = new TextEncoder(); -var invertObject = (object) => { - return Object.fromEntries(Object.entries(object).map(([key, value]) => [value, key])); -}; -var COLOR_PRIMARIES_MAP = { - bt709: 1, - // ITU-R BT.709 - bt470bg: 5, - // ITU-R BT.470BG - smpte170m: 6 - // ITU-R BT.601 525 - SMPTE 170M -}; -var COLOR_PRIMARIES_MAP_INVERSE = invertObject(COLOR_PRIMARIES_MAP); -var TRANSFER_CHARACTERISTICS_MAP = { - "bt709": 1, - // ITU-R BT.709 - "smpte170m": 6, - // SMPTE 170M - "iec61966-2-1": 13 - // IEC 61966-2-1 -}; -var TRANSFER_CHARACTERISTICS_MAP_INVERSE = invertObject(TRANSFER_CHARACTERISTICS_MAP); -var MATRIX_COEFFICIENTS_MAP = { - rgb: 0, - // Identity - bt709: 1, - // ITU-R BT.709 - bt470bg: 5, - // ITU-R BT.470BG - smpte170m: 6 - // SMPTE 170M -}; -var MATRIX_COEFFICIENTS_MAP_INVERSE = invertObject(MATRIX_COEFFICIENTS_MAP); -var colorSpaceIsComplete = (colorSpace) => { - return !!colorSpace && !!colorSpace.primaries && !!colorSpace.transfer && !!colorSpace.matrix && colorSpace.fullRange !== void 0; -}; -var isAllowSharedBufferSource = (x) => { - return x instanceof ArrayBuffer || typeof SharedArrayBuffer !== "undefined" && x instanceof SharedArrayBuffer || ArrayBuffer.isView(x) && !(x instanceof DataView); -}; -var AsyncMutex = class { - constructor() { - this.currentPromise = Promise.resolve(); - } - async acquire() { - let resolver; - const nextPromise = new Promise((resolve) => { - resolver = resolve; - }); - const currentPromiseAlias = this.currentPromise; - this.currentPromise = nextPromise; - await currentPromiseAlias; - return resolver; - } -}; -var rotationMatrix = (rotationInDegrees) => { - const theta = rotationInDegrees * (Math.PI / 180); - const cosTheta = Math.cos(theta); - const sinTheta = Math.sin(theta); - return [ - cosTheta, - sinTheta, - 0, - -sinTheta, - cosTheta, - 0, - 0, - 0, - 1 - ]; -}; -var IDENTITY_MATRIX = rotationMatrix(0); -var bytesToHexString = (bytes2) => { - return [...bytes2].map((x) => x.toString(16).padStart(2, "0")).join(""); -}; -var reverseBitsU32 = (x) => { - x = x >> 1 & 1431655765 | (x & 1431655765) << 1; - x = x >> 2 & 858993459 | (x & 858993459) << 2; - x = x >> 4 & 252645135 | (x & 252645135) << 4; - x = x >> 8 & 16711935 | (x & 16711935) << 8; - x = x >> 16 & 65535 | (x & 65535) << 16; - return x >>> 0; -}; -var binarySearchExact = (arr, key, valueGetter) => { - let low = 0; - let high = arr.length - 1; - let res = -1; - while (low <= high) { - const mid = low + high >> 1; - const midVal = valueGetter(arr[mid]); - if (midVal === key) { - res = mid; - high = mid - 1; - } else if (midVal < key) { - low = mid + 1; - } else { - high = mid - 1; - } - } - return res; -}; -var binarySearchLessOrEqual = (arr, key, valueGetter) => { - let ans = -1; - let low = 0; - let high = arr.length - 1; - while (low <= high) { - const mid = low + (high - low + 1) / 2 | 0; - const midVal = valueGetter(arr[mid]); - if (midVal <= key) { - ans = mid; - low = mid + 1; - } else { - high = mid - 1; - } - } - return ans; -}; -var promiseWithResolvers = () => { - let resolve; - let reject; - const promise = new Promise((res, rej) => { - resolve = res; - reject = rej; - }); - return { promise, resolve, reject }; -}; -var removeItem = (arr, item) => { - const index = arr.indexOf(item); - if (index !== -1) { - arr.splice(index, 1); - } -}; -var findLastIndex = (arr, predicate) => { - for (let i = arr.length - 1; i >= 0; i--) { - if (predicate(arr[i])) { - return i; - } - } - return -1; -}; -var toAsyncIterator = async function* (source) { - if (Symbol.iterator in source) { - yield* source[Symbol.iterator](); - } else { - yield* source[Symbol.asyncIterator](); - } -}; -var validateAnyIterable = (iterable) => { - if (!(Symbol.iterator in iterable) && !(Symbol.asyncIterator in iterable)) { - throw new TypeError("Argument must be an iterable or async iterable."); - } -}; - -// src/subtitles.ts -var cueBlockHeaderRegex = /(?:(.+?)\n)?((?:\d{2}:)?\d{2}:\d{2}.\d{3})\s+-->\s+((?:\d{2}:)?\d{2}:\d{2}.\d{3})/g; -var preambleStartRegex = /^WEBVTT(.|\n)*?\n{2}/; -var inlineTimestampRegex = /<(?:(\d{2}):)?(\d{2}):(\d{2}).(\d{3})>/g; -var SubtitleParser = class { - constructor(options) { - this.preambleText = null; - this.preambleEmitted = false; - this.options = options; - } - parse(text) { - text = text.replaceAll("\r\n", "\n").replaceAll("\r", "\n"); - cueBlockHeaderRegex.lastIndex = 0; - let match; - if (!this.preambleText) { - if (!preambleStartRegex.test(text)) { - throw new Error("WebVTT preamble incorrect."); - } - match = cueBlockHeaderRegex.exec(text); - const preamble = text.slice(0, match?.index ?? text.length).trimEnd(); - if (!preamble) { - throw new Error("No WebVTT preamble provided."); - } - this.preambleText = preamble; - if (match) { - text = text.slice(match.index); - cueBlockHeaderRegex.lastIndex = 0; - } - } - 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; - 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; - const cue = { - timestamp: startTime / 1e3, - duration: duration / 1e3, - text: body, - identifier: cueIdentifier, - settings: cueSettings, - notes - }; - const meta = {}; - if (!this.preambleEmitted) { - meta.config = { - description: this.preambleText - }; - this.preambleEmitted = true; - } - this.options.output(cue, meta); - } - } -}; -var timestampRegex = /(?:(\d{2}):)?(\d{2}):(\d{2}).(\d{3})/; -var parseSubtitleTimestamp = (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) => { - 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"); -}; - -// src/isobmff/isobmff-boxes.ts -var IsobmffBoxWriter = class { - constructor(writer) { - this.writer = writer; - this.helper = new Uint8Array(8); - this.helperView = new DataView(this.helper.buffer); - /** - * Stores the position from the start of the file to where boxes elements have been written. This is used to - * rewrite/edit elements that were already added before, and to measure sizes of things. - */ - this.offsets = /* @__PURE__ */ new WeakMap(); - } - writeU32(value) { - this.helperView.setUint32(0, value, false); - this.writer.write(this.helper.subarray(0, 4)); - } - writeU64(value) { - this.helperView.setUint32(0, Math.floor(value / 2 ** 32), false); - this.helperView.setUint32(4, value, false); - this.writer.write(this.helper.subarray(0, 8)); - } - writeAscii(text) { - for (let i = 0; i < text.length; i++) { - this.helperView.setUint8(i % 8, text.charCodeAt(i)); - if (i % 8 === 7) this.writer.write(this.helper); - } - if (text.length % 8 !== 0) { - this.writer.write(this.helper.subarray(0, text.length % 8)); - } - } - writeBox(box2) { - this.offsets.set(box2, this.writer.getPos()); - if (box2.contents && !box2.children) { - this.writeBoxHeader(box2, box2.size ?? box2.contents.byteLength + 8); - this.writer.write(box2.contents); - } else { - const startPos = this.writer.getPos(); - this.writeBoxHeader(box2, 0); - if (box2.contents) this.writer.write(box2.contents); - if (box2.children) { - for (const child of box2.children) if (child) this.writeBox(child); - } - const endPos = this.writer.getPos(); - const size = box2.size ?? endPos - startPos; - this.writer.seek(startPos); - this.writeBoxHeader(box2, size); - this.writer.seek(endPos); - } - } - writeBoxHeader(box2, size) { - this.writeU32(box2.largeSize ? 1 : size); - this.writeAscii(box2.type); - if (box2.largeSize) this.writeU64(size); - } - measureBoxHeader(box2) { - return 8 + (box2.largeSize ? 8 : 0); - } - patchBox(box2) { - const boxOffset = this.offsets.get(box2); - assert(boxOffset !== void 0); - const endPos = this.writer.getPos(); - this.writer.seek(boxOffset); - this.writeBox(box2); - this.writer.seek(endPos); - } - measureBox(box2) { - if (box2.contents && !box2.children) { - 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 (const child of box2.children) if (child) result += this.measureBox(child); - } - return result; - } - } -}; -var bytes = new Uint8Array(8); -var view = new DataView(bytes.buffer); -var u8 = (value) => { - return [(value % 256 + 256) % 256]; -}; -var u16 = (value) => { - view.setUint16(0, value, false); - return [bytes[0], bytes[1]]; -}; -var i16 = (value) => { - view.setInt16(0, value, false); - return [bytes[0], bytes[1]]; -}; -var u24 = (value) => { - view.setUint32(0, value, false); - return [bytes[1], bytes[2], bytes[3]]; -}; -var u32 = (value) => { - view.setUint32(0, value, false); - return [bytes[0], bytes[1], bytes[2], bytes[3]]; -}; -var i32 = (value) => { - view.setInt32(0, value, false); - return [bytes[0], bytes[1], bytes[2], bytes[3]]; -}; -var u64 = (value) => { - 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]]; -}; -var fixed_8_8 = (value) => { - view.setInt16(0, 2 ** 8 * value, false); - return [bytes[0], bytes[1]]; -}; -var fixed_16_16 = (value) => { - view.setInt32(0, 2 ** 16 * value, false); - return [bytes[0], bytes[1], bytes[2], bytes[3]]; -}; -var fixed_2_30 = (value) => { - view.setInt32(0, 2 ** 30 * value, false); - return [bytes[0], bytes[1], bytes[2], bytes[3]]; -}; -var variableUnsignedInt = (value, byteLength) => { - const bytes2 = []; - let remaining = value; - do { - let byte = remaining & 127; - remaining >>= 7; - if (bytes2.length > 0) { - byte |= 128; - } - bytes2.push(byte); - if (byteLength !== void 0) { - byteLength--; - } - } while (remaining > 0 || byteLength); - return bytes2.reverse(); -}; -var ascii = (text, nullTerminated = false) => { - 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 (const sample of samples) { - if (!result || sample.timestamp > result.timestamp) { - result = sample; - } - } - return result; -}; -var matrixToBytes = (matrix) => { - 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]) - ]; -}; -var box = (type, contents, children) => ({ - type, - contents: contents && new Uint8Array(contents.flat(10)), - children -}); -var fullBox = (type, version, flags, contents, children) => box( - type, - [u8(version), u24(flags), contents ?? []], - children -); -var ftyp = (details) => { - const minorVersion = 512; - if (details.fragmented) return box("ftyp", [ - ascii("iso5"), - // Major brand - u32(minorVersion), - // Minor version - // Compatible brands - ascii("iso5"), - ascii("iso6"), - ascii("mp41") - ]); - return box("ftyp", [ - ascii("isom"), - // Major brand - u32(minorVersion), - // Minor version - // Compatible brands - ascii("isom"), - details.holdsAvc ? ascii("avc1") : [], - ascii("mp41") - ]); -}; -var mdat = (reserveLargeSize) => ({ type: "mdat", largeSize: reserveLargeSize }); -var free = (size) => ({ type: "free", size }); -var moov = (trackDatas, creationTime, fragmented = false) => box("moov", void 0, [ - mvhd(creationTime, trackDatas), - ...trackDatas.map((x) => trak(x, creationTime)), - fragmented ? mvex(trackDatas) : null -]); -var mvhd = (creationTime, trackDatas) => { - 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); - 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 - u32OrU64(creationTime), - // Modification time - u32(GLOBAL_TIMESCALE), - // Timescale - u32OrU64(duration), - // Duration - fixed_16_16(1), - // Preferred rate - fixed_8_8(1), - // Preferred volume - Array(10).fill(0), - // Reserved - matrixToBytes(IDENTITY_MATRIX), - // Matrix - Array(24).fill(0), - // Pre-defined - u32(nextTrackId) - // Next track ID - ]); -}; -var trak = (trackData, creationTime) => box("trak", void 0, [ - tkhd(trackData, creationTime), - mdia(trackData, creationTime) -]); -var tkhd = (trackData, creationTime) => { - const lastSample = lastPresentedSample(trackData.samples); - const durationInGlobalTimescale = intoTimescale( - lastSample ? lastSample.timestamp + lastSample.duration : 0, - GLOBAL_TIMESCALE - ); - const needsU64 = !isU32(creationTime) || !isU32(durationInGlobalTimescale); - const u32OrU64 = needsU64 ? u64 : u32; - let matrix; - if (trackData.type === "video") { - const rotation = trackData.track.metadata.rotation; - matrix = rotation === void 0 || typeof rotation === "number" ? rotationMatrix(rotation ?? 0) : rotation; - } else { - matrix = IDENTITY_MATRIX; - } - return fullBox("tkhd", +needsU64, 3, [ - u32OrU64(creationTime), - // Creation time - u32OrU64(creationTime), - // Modification time - u32(trackData.track.id), - // Track ID - u32(0), - // Reserved - u32OrU64(durationInGlobalTimescale), - // Duration - Array(8).fill(0), - // Reserved - u16(0), - // Layer - u16(trackData.track.id), - // Alternate group - fixed_8_8(trackData.type === "audio" ? 1 : 0), - // Volume - 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 - ]); -}; -var mdia = (trackData, creationTime) => box("mdia", void 0, [ - mdhd(trackData, creationTime), - hdlr(trackData), - minf(trackData) -]); -var mdhd = (trackData, creationTime) => { - const lastSample = lastPresentedSample(trackData.samples); - const localDuration = intoTimescale( - lastSample ? lastSample.timestamp + lastSample.duration : 0, - trackData.timescale - ); - const needsU64 = !isU32(creationTime) || !isU32(localDuration); - const u32OrU64 = needsU64 ? u64 : u32; - return fullBox("mdhd", +needsU64, 0, [ - u32OrU64(creationTime), - // Creation time - u32OrU64(creationTime), - // Modification time - u32(trackData.timescale), - // Timescale - u32OrU64(localDuration), - // Duration - u16(21956), - // Language ("und", undetermined) - u16(0) - // Quality - ]); -}; -var TRACK_TYPE_TO_COMPONENT_SUBTYPE = { - video: "vide", - audio: "soun", - subtitle: "text" -}; -var TRACK_TYPE_TO_HANDLER_NAME = { - video: "VideoHandler", - audio: "SoundHandler", - subtitle: "TextHandler" -}; -var hdlr = (trackData) => fullBox("hdlr", 0, 0, [ - ascii("mhlr"), - // Component type - ascii(TRACK_TYPE_TO_COMPONENT_SUBTYPE[trackData.type]), - // Component subtype - u32(0), - // Component manufacturer - u32(0), - // Component flags - u32(0), - // Component flags mask - ascii(TRACK_TYPE_TO_HANDLER_NAME[trackData.type], true) - // Component name -]); -var minf = (trackData) => box("minf", void 0, [ - TRACK_TYPE_TO_HEADER_BOX[trackData.type](), - dinf(), - stbl(trackData) -]); -var vmhd = () => fullBox("vmhd", 0, 1, [ - u16(0), - // Graphics mode - u16(0), - // Opcolor R - u16(0), - // Opcolor G - u16(0) - // Opcolor B -]); -var smhd = () => fullBox("smhd", 0, 0, [ - u16(0), - // Balance - u16(0) - // Reserved -]); -var nmhd = () => fullBox("nmhd", 0, 0); -var TRACK_TYPE_TO_HEADER_BOX = { - video: vmhd, - audio: smhd, - subtitle: nmhd -}; -var dinf = () => box("dinf", void 0, [ - dref() -]); -var dref = () => fullBox("dref", 0, 0, [ - u32(1) - // Entry count -], [ - url() -]); -var url = () => fullBox("url ", 0, 1); -var stbl = (trackData) => { - const needsCtts = trackData.compositionTimeOffsetTable.length > 1 || trackData.compositionTimeOffsetTable.some((x) => x.sampleCompositionTimeOffset !== 0); - return box("stbl", void 0, [ - stsd(trackData), - stts(trackData), - stss(trackData), - stsc(trackData), - stsz(trackData), - stco(trackData), - needsCtts ? ctts(trackData) : null - ]); -}; -var stsd = (trackData) => { - let sampleDescription; - if (trackData.type === "video") { - sampleDescription = videoSampleDescription( - VIDEO_CODEC_TO_BOX_NAME[trackData.track.source._codec], - trackData - ); - } else if (trackData.type === "audio") { - sampleDescription = soundSampleDescription( - AUDIO_CODEC_TO_BOX_NAME[trackData.track.source._codec], - trackData - ); - } else if (trackData.type === "subtitle") { - sampleDescription = subtitleSampleDescription( - SUBTITLE_CODEC_TO_BOX_NAME[trackData.track.source._codec], - trackData - ); - } - assert(sampleDescription); - return fullBox("stsd", 0, 0, [ - u32(1) - // Entry count - ], [ - sampleDescription - ]); -}; -var videoSampleDescription = (compressionType, trackData) => box(compressionType, [ - Array(6).fill(0), - // Reserved - u16(1), - // Data reference index - u16(0), - // Pre-defined - u16(0), - // Reserved - Array(12).fill(0), - // Pre-defined - u16(trackData.info.width), - // Width - u16(trackData.info.height), - // Height - u32(4718592), - // Horizontal resolution - u32(4718592), - // Vertical resolution - u32(0), - // Reserved - u16(1), - // Frame count - Array(32).fill(0), - // Compressor name - u16(24), - // Depth - i16(65535) - // Pre-defined -], [ - VIDEO_CODEC_TO_CONFIGURATION_BOX[trackData.track.source._codec](trackData), - colorSpaceIsComplete(trackData.info.decoderConfig.colorSpace) ? colr(trackData) : null -]); -var colr = (trackData) => box("colr", [ - ascii("nclx"), - // Colour type - 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 -]); -var avcC = (trackData) => trackData.info.decoderConfig && box("avcC", [ - // For AVC, description is an AVCDecoderConfigurationRecord, so nothing else to do here - ...toUint8Array(trackData.info.decoderConfig.description) -]); -var hvcC = (trackData) => trackData.info.decoderConfig && box("hvcC", [ - // For HEVC, description is an HEVCDecoderConfigurationRecord, so nothing else to do here - ...toUint8Array(trackData.info.decoderConfig.description) -]); -var vpcC = (trackData) => { - if (!trackData.info.decoderConfig) { - return null; - } - const decoderConfig = trackData.info.decoderConfig; - assert(decoderConfig.colorSpace); - 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 - u8(level), - // Level - u8(thirdByte), - // Bit depth, chroma subsampling, full range - u8(colourPrimaries), - // Colour primaries - u8(transferCharacteristics), - // Transfer characteristics - u8(matrixCoefficients), - // Matrix coefficients - u16(0) - // Codec initialization data size - ]); -}; -var av1C = () => { - const marker = 1; - const version = 1; - const firstByte = (marker << 7) + version; - return box("av1C", [ - firstByte, - 0, - 0, - 0 - ]); -}; -var soundSampleDescription = (compressionType, trackData) => box(compressionType, [ - Array(6).fill(0), - // Reserved - u16(1), - // Data reference index - u16(0), - // Version - u16(0), - // Revision level - u32(0), - // Vendor - u16(trackData.info.numberOfChannels), - // Number of channels - u16(16), - // Sample size (bits) - u16(0), - // Compression ID - u16(0), - // Packet size - fixed_16_16(trackData.info.sampleRate) - // Sample rate -], [ - AUDIO_CODEC_TO_CONFIGURATION_BOX[trackData.track.source._codec](trackData) -]); -var esds = (trackData) => { - const description = toUint8Array(trackData.info.decoderConfig.description ?? new ArrayBuffer(0)); - let bytes2 = [ - ...description - ]; - bytes2 = [ - ...u8(64), - // MPEG-4 Audio - ...u8(21), - // stream type(6bits)=5 audio, flags(2bits)=1 - ...u24(0), - // 24bit buffer size - ...u32(0), - // max bitrate - ...u32(0), - // avg bitrate - ...u8(5), - // TAG(5) = ASC ([2],[3]) embedded in above OD - ...variableUnsignedInt(bytes2.length), - ...bytes2 - ]; - bytes2 = [ - ...u16(1), - // ES_ID = 1 - ...u8(0), - // flags etc = 0 - ...u8(4), - // TAG(4) = ES Descriptor ([2]) embedded in above OD - ...variableUnsignedInt(bytes2.length), - ...bytes2, - ...u8(6), - // TAG(6) - ...u8(1), - // length - ...u8(2) - // data - ]; - bytes2 = [ - ...u8(3), - // TAG(3) = Object Descriptor ([2]) - ...variableUnsignedInt(bytes2.length), - ...bytes2 - ]; - return fullBox("esds", 0, 0, bytes2); -}; -var dOps = (trackData) => { - let preskip = 3840; - let gain = 0; - const description = trackData.info.decoderConfig?.description; - if (description) { - assert(description.byteLength >= 18); - const view2 = ArrayBuffer.isView(description) ? new DataView(description.buffer, description.byteOffset, description.byteLength) : new DataView(description); - preskip = view2.getUint16(10, true); - gain = view2.getInt16(14, true); - } - return box("dOps", [ - u8(0), - // Version - u8(trackData.info.numberOfChannels), - // OutputChannelCount - u16(preskip), - u32(trackData.info.sampleRate), - // InputSampleRate - fixed_8_8(gain), - // OutputGain - u8(0) - // ChannelMappingFamily - ]); -}; -var subtitleSampleDescription = (compressionType, trackData) => box(compressionType, [ - Array(6).fill(0), - // Reserved - u16(1) - // Data reference index -], [ - SUBTITLE_CODEC_TO_CONFIGURATION_BOX[trackData.track.source._codec](trackData) -]); -var vttC = (trackData) => box("vttC", [ - ...textEncoder.encode(trackData.info.config.description) -]); -var stts = (trackData) => { - return fullBox("stts", 0, 0, [ - 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 - ]) - ]); -}; -var stss = (trackData) => { - if (trackData.samples.every((x) => x.type === "key")) return null; - 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 - ]); -}; -var stsc = (trackData) => { - return fullBox("stsc", 0, 0, [ - u32(trackData.compactlyCodedChunkTable.length), - // Number of entries - trackData.compactlyCodedChunkTable.map((x) => [ - // Sample-to-chunk table - u32(x.firstChunk), - // First chunk - u32(x.samplesPerChunk), - // Samples per chunk - u32(1) - // Sample description index - ]) - ]); -}; -var stsz = (trackData) => 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 -]); -var stco = (trackData) => { - if (trackData.finalizedChunks.length > 0 && last(trackData.finalizedChunks).offset >= 2 ** 32) { - return fullBox("co64", 0, 0, [ - u32(trackData.finalizedChunks.length), - // Number of entries - 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 - ]); -}; -var ctts = (trackData) => { - return fullBox("ctts", 0, 0, [ - 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 - ]) - ]); -}; -var mvex = (trackDatas) => { - return box("mvex", void 0, trackDatas.map(trex)); -}; -var trex = (trackData) => { - return fullBox("trex", 0, 0, [ - u32(trackData.track.id), - // Track ID - u32(1), - // Default sample description index - u32(0), - // Default sample duration - u32(0), - // Default sample size - u32(0) - // Default sample flags - ]); -}; -var moof = (sequenceNumber, trackDatas) => { - return box("moof", void 0, [ - mfhd(sequenceNumber), - ...trackDatas.map(traf) - ]); -}; -var mfhd = (sequenceNumber) => { - return fullBox("mfhd", 0, 0, [ - u32(sequenceNumber) - // Sequence number - ]); -}; -var fragmentSampleFlags = (sample) => { - let byte1 = 0; - let byte2 = 0; - const byte3 = 0; - const byte4 = 0; - const sampleIsDifferenceSample = sample.type === "delta"; - byte2 |= +sampleIsDifferenceSample; - if (sampleIsDifferenceSample) { - byte1 |= 1; - } else { - byte1 |= 2; - } - return byte1 << 24 | byte2 << 16 | byte3 << 8 | byte4; -}; -var traf = (trackData) => { - return box("traf", void 0, [ - tfhd(trackData), - tfdt(trackData), - trun(trackData) - ]); -}; -var tfhd = (trackData) => { - assert(trackData.currentChunk); - let tfFlags = 0; - tfFlags |= 8; - tfFlags |= 16; - tfFlags |= 32; - tfFlags |= 131072; - const referenceSample = trackData.currentChunk.samples[1] ?? trackData.currentChunk.samples[0]; - const referenceSampleInfo = { - duration: referenceSample.timescaleUnitsToNextSample, - size: referenceSample.size, - 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 - ]); -}; -var tfdt = (trackData) => { - assert(trackData.currentChunk); - return fullBox("tfdt", 1, 0, [ - u64(intoTimescale(trackData.currentChunk.startTimestamp, trackData.timescale)) - // Base Media Decode Time - ]); -}; -var trun = (trackData) => { - assert(trackData.currentChunk); - 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; - flags |= 256 * +sampleDurationPresent; - flags |= 512 * +sampleSizePresent; - flags |= 1024 * +sampleFlagsPresent; - flags |= 2048 * +sampleCompositionTimeOffsetsPresent; - return fullBox("trun", 1, flags, [ - u32(trackData.currentChunk.samples.length), - // Sample count - u32(trackData.currentChunk.offset - trackData.currentChunk.moofOffset || 0), - // Data offset - firstSampleFlagsPresent ? u32(allSampleFlags[0]) : [], - trackData.currentChunk.samples.map((_, i) => [ - sampleDurationPresent ? u32(allSampleDurations[i]) : [], - // Sample duration - sampleSizePresent ? u32(allSampleSizes[i]) : [], - // Sample size - sampleFlagsPresent ? u32(allSampleFlags[i]) : [], - // Sample flags - // Sample composition time offsets - sampleCompositionTimeOffsetsPresent ? i32(allSampleCompositionTimeOffsets[i]) : [] - ]) - ]); -}; -var mfra = (trackDatas) => { - return box("mfra", void 0, [ - ...trackDatas.map(tfra), - mfro() - ]); -}; -var tfra = (trackData, trackIndex) => { - const version = 1; - return fullBox("tfra", version, 0, [ - u32(trackData.track.id), - // Track ID - u32(63), - // This specifies that traf number, trun number and sample number are 32-bit ints - u32(trackData.finalizedChunks.length), - // Number of entries - trackData.finalizedChunks.map((chunk) => [ - u64(intoTimescale(chunk.samples[0].timestamp, trackData.timescale)), - // Time (in presentation time) - u64(chunk.moofOffset), - // moof offset - u32(trackIndex + 1), - // traf number - u32(1), - // trun number - u32(1) - // Sample number - ]) - ]); -}; -var 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 - ]); -}; -var vtte = () => box("vtte"); -var vttc = (payload, timestamp, identifier, settings, sourceId) => box("vttc", void 0, [ - 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)]) -]); -var vtta = (notes) => box("vtta", [...textEncoder.encode(notes)]); -var VIDEO_CODEC_TO_BOX_NAME = { - 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 -}; -var AUDIO_CODEC_TO_BOX_NAME = { - aac: "mp4a", - opus: "Opus" -}; -var AUDIO_CODEC_TO_CONFIGURATION_BOX = { - aac: esds, - opus: dOps -}; -var SUBTITLE_CODEC_TO_BOX_NAME = { - webvtt: "wvtt" -}; -var SUBTITLE_CODEC_TO_CONFIGURATION_BOX = { - webvtt: vttC -}; - -// src/muxer.ts -var Muxer = class { - constructor(output) { - this.mutex = new AsyncMutex(); - 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) { - let timestampInSeconds = rawTimestampInUs / 1e6; - let timestampInfo = this.trackTimestampInfo.get(track); - if (!timestampInfo) { - if (!isKeyFrame) { - throw new Error("First frame must be a key frame."); - } - if (this.timestampsMustStartAtZero && timestampInSeconds > 0) { - throw new Error(`Timestamps must start at zero (got ${timestampInSeconds}s).`); - } - timestampInfo = { - timestampOffset: timestampInSeconds, - maxTimestamp: track.source._offsetTimestamps ? 0 : timestampInSeconds, - lastKeyFrameTimestamp: track.source._offsetTimestamps ? 0 : timestampInSeconds - }; - this.trackTimestampInfo.set(track, timestampInfo); - } - if (track.source._offsetTimestamps) { - timestampInSeconds -= timestampInfo.timestampOffset; - } - if (timestampInSeconds < 0) { - 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).` - ); - } - 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).` - ); - } - timestampInfo.lastKeyFrameTimestamp = timestampInSeconds; - } - timestampInfo.maxTimestamp = Math.max(timestampInfo.maxTimestamp, timestampInSeconds); - return timestampInSeconds; - } -}; - -// src/writer.ts -var Writer = class { - constructor() { - /** Setting this to true will cause the writer to ensure data is written in a strictly monotonic, streamable way. */ - this.ensureMonotonicity = false; - } - start() { - } -}; -var ArrayBufferTargetWriter = class extends Writer { - constructor(target) { - super(); - this.pos = 0; - this.buffer = new ArrayBuffer(2 ** 16); - this.bytes = new Uint8Array(this.buffer); - this.maxPos = 0; - this.target = target; - } - ensureSize(size) { - let newLength = this.buffer.byteLength; - while (newLength < size) newLength *= 2; - if (newLength === this.buffer.byteLength) return; - const newBuffer = new ArrayBuffer(newLength); - const newBytes = new Uint8Array(newBuffer); - newBytes.set(this.bytes, 0); - this.buffer = newBuffer; - this.bytes = newBytes; - } - write(data) { - this.ensureSize(this.pos + data.byteLength); - this.bytes.set(data, this.pos); - this.pos += data.byteLength; - this.maxPos = Math.max(this.maxPos, this.pos); - } - seek(newPos) { - this.pos = newPos; - } - getPos() { - return this.pos; - } - async flush() { - } - async finalize() { - this.ensureSize(this.pos); - this.target.buffer = this.buffer.slice(0, Math.max(this.maxPos, this.pos)); - } - getSlice(start, end) { - return this.bytes.slice(start, end); - } -}; -var StreamTargetWriter = class extends Writer { - constructor(target) { - super(); - this.pos = 0; - this.sections = []; - this.lastFlushEnd = 0; - this.writer = null; - this.target = target; - } - start() { - this.writer = this.target._writable.getWriter(); - } - write(data) { - this.sections.push({ - data: data.slice(), - start: this.pos - }); - this.pos += data.byteLength; - } - seek(newPos) { - this.pos = newPos; - } - getPos() { - return this.pos; - } - async flush() { - assert(this.writer); - if (this.sections.length === 0) return; - 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++) { - 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 - }); - } - } - for (const chunk of chunks) { - chunk.data = new Uint8Array(chunk.size); - 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); - } - } - if (this.ensureMonotonicity && chunk.start !== this.lastFlushEnd) { - throw new Error("Internal error: Monotonicity violation."); - } - if (this.writer.desiredSize !== null && this.writer.desiredSize <= 0) { - await this.writer.ready; - } - void this.writer.write({ - type: "write", - data: chunk.data, - position: chunk.start - }); - this.lastFlushEnd = chunk.start + chunk.data.byteLength; - } - this.sections.length = 0; - } - finalize() { - assert(this.writer); - return this.writer.close(); - } -}; -var DEFAULT_CHUNK_SIZE = 2 ** 24; -var MAX_CHUNKS_AT_ONCE = 2; -var ChunkedStreamTargetWriter = class extends Writer { - constructor(target) { - super(); - this.pos = 0; - /** - * The data is divided up into fixed-size chunks, whose contents are first filled in RAM and then flushed out. - * A chunk is flushed if all of its contents have been written. - */ - this.chunks = []; - this.lastFlushEnd = 0; - this.writer = null; - this.flushedChunkQueue = []; - this.target = target; - this.chunkSize = target._options?.chunkSize ?? DEFAULT_CHUNK_SIZE; - if (!Number.isInteger(this.chunkSize) || this.chunkSize < 2 ** 10) { - throw new Error("Invalid StreamTarget options: chunkSize must be an integer not smaller than 1024."); - } - } - start() { - this.writer = this.target._writable.getWriter(); - } - write(data) { - this.writeDataIntoChunks(data, this.pos); - this.queueChunksForFlush(); - this.pos += data.byteLength; - } - seek(newPos) { - this.pos = newPos; - } - getPos() { - return this.pos; - } - writeDataIntoChunks(data, position) { - let chunkIndex = this.chunks.findIndex((x) => x.start <= position && position < x.start + this.chunkSize); - if (chunkIndex === -1) chunkIndex = this.createChunk(position); - 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); - const section = { - start: relativePosition, - end: relativePosition + toWrite.byteLength - }; - this.insertSectionIntoChunk(chunk, section); - if (chunk.written[0].start === 0 && chunk.written[0].end === this.chunkSize) { - chunk.shouldFlush = true; - } - if (this.chunks.length > MAX_CHUNKS_AT_ONCE) { - for (let i = 0; i < this.chunks.length - 1; i++) { - this.chunks[i].shouldFlush = true; - } - this.queueChunksForFlush(); - } - if (toWrite.byteLength < data.byteLength) { - this.writeDataIntoChunks(data.subarray(toWrite.byteLength), position + toWrite.byteLength); - } - } - insertSectionIntoChunk(chunk, section) { - let low = 0; - let high = chunk.written.length - 1; - let index = -1; - while (low <= high) { - const mid = Math.floor(low + (high - low + 1) / 2); - if (chunk.written[mid].start <= section.start) { - low = mid + 1; - index = mid; - } else { - high = mid - 1; - } - } - chunk.written.splice(index + 1, 0, section); - if (index === -1 || chunk.written[index].end < section.start) index++; - while (index < chunk.written.length - 1 && chunk.written[index].end >= chunk.written[index + 1].start) { - chunk.written[index].end = Math.max(chunk.written[index].end, chunk.written[index + 1].end); - chunk.written.splice(index + 1, 1); - } - } - createChunk(includesPosition) { - const start = Math.floor(includesPosition / this.chunkSize) * this.chunkSize; - const chunk = { - start, - data: new Uint8Array(this.chunkSize), - written: [], - shouldFlush: false - }; - this.chunks.push(chunk); - this.chunks.sort((a, b) => a.start - b.start); - return this.chunks.indexOf(chunk); - } - queueChunksForFlush(force = false) { - assert(this.writer); - for (let i = 0; i < this.chunks.length; i++) { - const chunk = this.chunks[i]; - if (!chunk.shouldFlush && !force) continue; - for (const section of chunk.written) { - if (this.ensureMonotonicity && chunk.start + section.start !== this.lastFlushEnd) { - throw new Error("Internal error: Monotonicity violation."); - } - this.flushedChunkQueue.push({ - type: "write", - data: chunk.data.subarray(section.start, section.end), - position: chunk.start + section.start - }); - this.lastFlushEnd = chunk.start + section.end; - } - this.chunks.splice(i--, 1); - } - } - async flush() { - assert(this.writer); - if (this.flushedChunkQueue.length === 0) return; - for (const chunk of this.flushedChunkQueue) { - if (this.writer.desiredSize !== null && this.writer.desiredSize <= 0) { - await this.writer.ready; - } - void this.writer.write(chunk); - } - this.flushedChunkQueue.length = 0; - } - async finalize() { - assert(this.writer); - this.queueChunksForFlush(true); - await this.flush(); - return this.writer.close(); - } -}; - -// src/target.ts -var Target = class { - constructor() { - /** @internal */ - this._output = null; - } -}; -var ArrayBufferTarget = class extends Target { - constructor() { - super(...arguments); - this.buffer = null; - } - /** @internal */ - _createWriter() { - return new ArrayBufferTargetWriter(this); - } -}; -var StreamTarget = class extends Target { - constructor(writable, options = {}) { - super(); - if (!(writable instanceof WritableStream)) { - throw new TypeError("StreamTarget requires a WritableStream instance."); - } - if (options != null && typeof options !== "object") { - throw new TypeError("StreamTarget options, when provided, must be an object."); - } - if (options.chunked !== void 0 && typeof options.chunked !== "boolean") { - throw new TypeError("options.chunked, when provided, must be a boolean."); - } - if (options.chunkSize !== void 0 && (!Number.isInteger(options.chunkSize) || options.chunkSize <= 0)) { - throw new TypeError("options.chunkSize, when provided, must be a positive integer."); - } - this._writable = writable; - this._options = options; - } - /** @internal */ - _createWriter() { - return this._options.chunked ? new ChunkedStreamTargetWriter(this) : new StreamTargetWriter(this); - } -}; - -// src/codec.ts -var VIDEO_CODECS = ["avc", "hevc", "vp8", "vp9", "av1"]; -var AUDIO_CODECS = ["aac", "opus"]; -var SUBTITLE_CODECS = ["webvtt"]; -var AVC_LEVEL_TABLE = [ - { maxMacroblocks: 99, maxBitrate: 64e3, level: 10 }, - // Level 1 - { maxMacroblocks: 396, maxBitrate: 192e3, level: 11 }, - // Level 1.1 - { maxMacroblocks: 396, maxBitrate: 384e3, level: 12 }, - // Level 1.2 - { maxMacroblocks: 396, maxBitrate: 768e3, level: 13 }, - // Level 1.3 - { maxMacroblocks: 396, maxBitrate: 2e6, level: 20 }, - // Level 2 - { maxMacroblocks: 792, maxBitrate: 4e6, level: 21 }, - // Level 2.1 - { maxMacroblocks: 1620, maxBitrate: 4e6, level: 22 }, - // Level 2.2 - { maxMacroblocks: 1620, maxBitrate: 1e7, level: 30 }, - // Level 3 - { maxMacroblocks: 3600, maxBitrate: 14e6, level: 31 }, - // Level 3.1 - { maxMacroblocks: 5120, maxBitrate: 2e7, level: 32 }, - // Level 3.2 - { maxMacroblocks: 8192, maxBitrate: 2e7, level: 40 }, - // Level 4 - { maxMacroblocks: 8192, maxBitrate: 5e7, level: 41 }, - // Level 4.1 - { maxMacroblocks: 8704, maxBitrate: 5e7, level: 42 }, - // Level 4.2 - { maxMacroblocks: 22080, maxBitrate: 135e6, level: 50 }, - // Level 5 - { maxMacroblocks: 36864, maxBitrate: 24e7, level: 51 }, - // Level 5.1 - { maxMacroblocks: 36864, maxBitrate: 24e7, level: 52 }, - // Level 5.2 - { maxMacroblocks: 139264, maxBitrate: 24e7, level: 60 }, - // Level 6 - { maxMacroblocks: 139264, maxBitrate: 48e7, level: 61 }, - // Level 6.1 - { maxMacroblocks: 139264, maxBitrate: 8e8, level: 62 } - // Level 6.2 -]; -var HEVC_LEVEL_TABLE = [ - { maxPictureSize: 36864, maxBitrate: 128e3, tier: "L", level: 30 }, - // Level 1 (Low Tier) - { maxPictureSize: 122880, maxBitrate: 15e5, tier: "L", level: 60 }, - // Level 2 (Low Tier) - { maxPictureSize: 245760, maxBitrate: 3e6, tier: "L", level: 63 }, - // Level 2.1 (Low Tier) - { maxPictureSize: 552960, maxBitrate: 6e6, tier: "L", level: 90 }, - // Level 3 (Low Tier) - { maxPictureSize: 983040, maxBitrate: 1e7, tier: "L", level: 93 }, - // Level 3.1 (Low Tier) - { maxPictureSize: 2228224, maxBitrate: 12e6, tier: "L", level: 120 }, - // Level 4 (Low Tier) - { maxPictureSize: 2228224, maxBitrate: 3e7, tier: "H", level: 120 }, - // Level 4 (High Tier) - { maxPictureSize: 2228224, maxBitrate: 2e7, tier: "L", level: 123 }, - // Level 4.1 (Low Tier) - { maxPictureSize: 2228224, maxBitrate: 5e7, tier: "H", level: 123 }, - // Level 4.1 (High Tier) - { maxPictureSize: 8912896, maxBitrate: 25e6, tier: "L", level: 150 }, - // Level 5 (Low Tier) - { maxPictureSize: 8912896, maxBitrate: 1e8, tier: "H", level: 150 }, - // Level 5 (High Tier) - { maxPictureSize: 8912896, maxBitrate: 4e7, tier: "L", level: 153 }, - // Level 5.1 (Low Tier) - { maxPictureSize: 8912896, maxBitrate: 16e7, tier: "H", level: 153 }, - // Level 5.1 (High Tier) - { maxPictureSize: 8912896, maxBitrate: 6e7, tier: "L", level: 156 }, - // Level 5.2 (Low Tier) - { maxPictureSize: 8912896, maxBitrate: 24e7, tier: "H", level: 156 }, - // Level 5.2 (High Tier) - { maxPictureSize: 35651584, maxBitrate: 6e7, tier: "L", level: 180 }, - // Level 6 (Low Tier) - { maxPictureSize: 35651584, maxBitrate: 24e7, tier: "H", level: 180 }, - // Level 6 (High Tier) - { maxPictureSize: 35651584, maxBitrate: 12e7, tier: "L", level: 183 }, - // Level 6.1 (Low Tier) - { maxPictureSize: 35651584, maxBitrate: 48e7, tier: "H", level: 183 }, - // Level 6.1 (High Tier) - { maxPictureSize: 35651584, maxBitrate: 24e7, tier: "L", level: 186 }, - // Level 6.2 (Low Tier) - { maxPictureSize: 35651584, maxBitrate: 8e8, tier: "H", level: 186 } - // Level 6.2 (High Tier) -]; -var VP9_LEVEL_TABLE = [ - { maxPictureSize: 36864, maxBitrate: 2e5, level: 10 }, - // Level 1 - { maxPictureSize: 73728, maxBitrate: 8e5, level: 11 }, - // Level 1.1 - { maxPictureSize: 122880, maxBitrate: 18e5, level: 20 }, - // Level 2 - { maxPictureSize: 245760, maxBitrate: 36e5, level: 21 }, - // Level 2.1 - { maxPictureSize: 552960, maxBitrate: 72e5, level: 30 }, - // Level 3 - { maxPictureSize: 983040, maxBitrate: 12e6, level: 31 }, - // Level 3.1 - { maxPictureSize: 2228224, maxBitrate: 18e6, level: 40 }, - // Level 4 - { maxPictureSize: 2228224, maxBitrate: 3e7, level: 41 }, - // Level 4.1 - { maxPictureSize: 8912896, maxBitrate: 6e7, level: 50 }, - // Level 5 - { maxPictureSize: 8912896, maxBitrate: 12e7, level: 51 }, - // Level 5.1 - { maxPictureSize: 8912896, maxBitrate: 18e7, level: 52 }, - // Level 5.2 - { maxPictureSize: 35651584, maxBitrate: 18e7, level: 60 }, - // Level 6 - { maxPictureSize: 35651584, maxBitrate: 24e7, level: 61 }, - // Level 6.1 - { maxPictureSize: 35651584, maxBitrate: 48e7, level: 62 } - // Level 6.2 -]; -var AV1_LEVEL_TABLE = [ - { maxPictureSize: 147456, maxBitrate: 15e5, tier: "M", level: 0 }, - // Level 2.0 (Main Tier) - { maxPictureSize: 278784, maxBitrate: 3e6, tier: "M", level: 1 }, - // Level 2.1 (Main Tier) - { maxPictureSize: 665856, maxBitrate: 6e6, tier: "M", level: 4 }, - // Level 3.0 (Main Tier) - { maxPictureSize: 1065024, maxBitrate: 1e7, tier: "M", level: 5 }, - // Level 3.1 (Main Tier) - { maxPictureSize: 2359296, maxBitrate: 12e6, tier: "M", level: 8 }, - // Level 4.0 (Main Tier) - { maxPictureSize: 2359296, maxBitrate: 3e7, tier: "H", level: 8 }, - // Level 4.0 (High Tier) - { maxPictureSize: 2359296, maxBitrate: 2e7, tier: "M", level: 9 }, - // Level 4.1 (Main Tier) - { maxPictureSize: 2359296, maxBitrate: 5e7, tier: "H", level: 9 }, - // Level 4.1 (High Tier) - { maxPictureSize: 8912896, maxBitrate: 3e7, tier: "M", level: 12 }, - // Level 5.0 (Main Tier) - { maxPictureSize: 8912896, maxBitrate: 1e8, tier: "H", level: 12 }, - // Level 5.0 (High Tier) - { maxPictureSize: 8912896, maxBitrate: 4e7, tier: "M", level: 13 }, - // Level 5.1 (Main Tier) - { maxPictureSize: 8912896, maxBitrate: 16e7, tier: "H", level: 13 }, - // Level 5.1 (High Tier) - { maxPictureSize: 8912896, maxBitrate: 6e7, tier: "M", level: 14 }, - // Level 5.2 (Main Tier) - { maxPictureSize: 8912896, maxBitrate: 24e7, tier: "H", level: 14 }, - // Level 5.2 (High Tier) - { maxPictureSize: 35651584, maxBitrate: 6e7, tier: "M", level: 15 }, - // Level 5.3 (Main Tier) - { maxPictureSize: 35651584, maxBitrate: 24e7, tier: "H", level: 15 }, - // Level 5.3 (High Tier) - { maxPictureSize: 35651584, maxBitrate: 6e7, tier: "M", level: 16 }, - // Level 6.0 (Main Tier) - { maxPictureSize: 35651584, maxBitrate: 24e7, tier: "H", level: 16 }, - // Level 6.0 (High Tier) - { maxPictureSize: 35651584, maxBitrate: 1e8, tier: "M", level: 17 }, - // Level 6.1 (Main Tier) - { maxPictureSize: 35651584, maxBitrate: 48e7, tier: "H", level: 17 }, - // Level 6.1 (High Tier) - { maxPictureSize: 35651584, maxBitrate: 16e7, tier: "M", level: 18 }, - // Level 6.2 (Main Tier) - { maxPictureSize: 35651584, maxBitrate: 8e8, tier: "H", level: 18 }, - // Level 6.2 (High Tier) - { maxPictureSize: 35651584, maxBitrate: 16e7, tier: "M", level: 19 }, - // Level 6.3 (Main Tier) - { maxPictureSize: 35651584, maxBitrate: 8e8, tier: "H", level: 19 } - // Level 6.3 (High Tier) -]; -var buildVideoCodecString = (codec, width, height, bitrate) => { - if (codec === "avc") { - const profileIndication = 100; - const totalMacroblocks = Math.ceil(width / 16) * Math.ceil(height / 16); - 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"); - return `avc1.${hexProfileIndication}${hexProfileCompatibility}${hexLevelIndication}`; - } else if (codec === "hevc") { - 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"; - return `hev1.${profilePrefix}${profileIdc}.${compatibilityFlags}.${levelInfo.tier}${levelInfo.level}.${constraintFlags}`; - } else if (codec === "vp8") { - return "vp8"; - } else if (codec === "vp9") { - const profile = "00"; - const pictureSize = width * height; - const levelInfo = VP9_LEVEL_TABLE.find( - (level) => pictureSize <= level.maxPictureSize && bitrate <= level.maxBitrate - ) ?? last(VP9_LEVEL_TABLE); - const bitDepth = "08"; - return `vp09.${profile}.${levelInfo.level}.${bitDepth}`; - } else if (codec === "av1") { - const profile = 0; - const pictureSize = width * height; - const levelInfo = AV1_LEVEL_TABLE.find( - (level) => pictureSize <= level.maxPictureSize && bitrate <= level.maxBitrate - ) ?? last(AV1_LEVEL_TABLE); - const bitDepth = "08"; - return `av01.${profile}.${levelInfo.level.toString().padStart(2, "0")}${levelInfo.tier}.${bitDepth}`; - } - throw new TypeError(`Unhandled codec '${codec}'.`); -}; -var extractVideoCodecString = (codec, description) => { - if (codec === "avc") { - if (!description || description.byteLength < 4) { - throw new TypeError("AVC description must be at least 4 bytes long."); - } - return `avc1.${bytesToHexString(description.subarray(1, 4))}`; - } else if (codec === "hevc") { - if (!description) { - throw new TypeError("HEVC description must be provided."); - } - const view2 = new DataView(description.buffer, description.byteOffset, description.byteLength); - let codecString = "hev1."; - const generalProfileSpace = description[1] >> 6 & 3; - const generalProfileIdc = description[1] & 31; - codecString += ["", "A", "B", "C"][generalProfileSpace] + generalProfileIdc; - codecString += "."; - const compatibilityFlags = reverseBitsU32(view2.getUint32(2)); - codecString += compatibilityFlags.toString(16); - codecString += "."; - const generalTierFlag = description[1] >> 5 & 1; - const generalLevelIdc = description[12]; - codecString += generalTierFlag === 0 ? "L" : "H"; - codecString += generalLevelIdc; - codecString += "."; - const constraintFlags = []; - for (let i = 0; i < 6; i++) { - const byte = description[i + 13]; - constraintFlags.push(byte); - } - while (constraintFlags[constraintFlags.length - 1] === 0) { - constraintFlags.pop(); - } - codecString += constraintFlags.map((x) => x.toString(16)).join("."); - return codecString; - } - throw new TypeError(`Unhandled codec '${codec}'.`); -}; -var buildAudioCodecString = (codec, numberOfChannels, sampleRate) => { - if (codec === "aac") { - if (numberOfChannels >= 2 && sampleRate <= 24e3) { - return "mp4a.40.29"; - } - if (sampleRate <= 24e3) { - return "mp4a.40.5"; - } - return "mp4a.40.2"; - } else if (codec === "opus") { - return "opus"; - } else if (codec === "vorbis") { - return "vorbis"; - } - throw new TypeError(`Unhandled codec '${codec}'.`); -}; -var extractAudioCodecString = (codec, description) => { - if (codec === "aac") { - const audioSpecificConfig = parseAacAudioSpecificConfig(description); - return `mp4a.40.${audioSpecificConfig.objectType}`; - } else if (codec === "opus") { - return "opus"; - } else if (codec === "vorbis") { - return "vorbis"; - } - throw new TypeError(`Unhandled codec '${codec}'.`); -}; -var parseAacAudioSpecificConfig = (bytes2) => { - if (!bytes2 || bytes2.byteLength < 2) { - throw new TypeError("AAC description must be at least 2 bytes long."); - } - let bitOffset = 0; - let objectType = readBits(bytes2, bitOffset, bitOffset + 5); - bitOffset += 5; - if (objectType === 31) { - objectType = 32 + readBits(bytes2, bitOffset, bitOffset + 6); - bitOffset += 6; - } - const frequencyIndex = readBits(bytes2, bitOffset, bitOffset + 4); - bitOffset += 4; - let sampleRate = null; - if (frequencyIndex === 15) { - sampleRate = readBits(bytes2, bitOffset, bitOffset + 24); - bitOffset += 24; - } else { - const freqTable = [ - 96e3, - 88200, - 64e3, - 48e3, - 44100, - 32e3, - 24e3, - 22050, - 16e3, - 12e3, - 11025, - 8e3, - 7350 - ]; - if (frequencyIndex < freqTable.length) { - sampleRate = freqTable[frequencyIndex]; - } - } - const channelConfiguration = readBits(bytes2, bitOffset, bitOffset + 4); - bitOffset += 4; - let numberOfChannels = null; - if (channelConfiguration >= 1 && channelConfiguration <= 7) { - const channelMap = { - 1: 1, - 2: 2, - 3: 3, - 4: 4, - 5: 5, - 6: 6, - 7: 8 - }; - numberOfChannels = channelMap[channelConfiguration]; - } - return { - objectType, - frequencyIndex, - sampleRate, - channelConfiguration, - numberOfChannels - }; -}; -var getVideoEncoderConfigExtension = (codec) => { - if (codec === "avc") { - return { - avc: { - format: "avc" - // Ensure the format is not Annex B - } - }; - } else if (codec === "hevc") { - return { - hevc: { - format: "hevc" - // Ensure the format is not Annex B - } - }; - } - return {}; -}; -var getAudioEncoderConfigExtension = (codec) => { - if (codec === "aac") { - return { - aac: { - format: "aac" - // Ensure the format is not ADTS - } - }; - } else if (codec === "opus") { - return { - opus: { - format: "opus" - } - }; - } - return {}; -}; -var validateVideoChunkMetadata = (metadata) => { - if (!metadata) { - throw new TypeError("Video chunk metadata must be provided."); - } - if (typeof metadata !== "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."); - } - if (typeof metadata.decoderConfig !== "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."); - } - 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)." - ); - } - 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)." - ); - } - 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." - ); - } - } - if (metadata.decoderConfig.colorSpace !== void 0) { - const { colorSpace } = metadata.decoderConfig; - if (typeof colorSpace !== "object") { - throw new TypeError( - "Video chunk metadata decoder configuration colorSpace, when provided, must be an object." - ); - } - 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(", ")}.` - ); - } - 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(", ")}.` - ); - } - 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(", ")}.` - ); - } - if (colorSpace.fullRange != null && typeof colorSpace.fullRange !== "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("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 === void 0) { - throw new TypeError("Video chunk metadata decoder configuration for VP8/VP9 must include a colorSpace."); - } -}; -var validateAudioChunkMetadata = (metadata) => { - if (!metadata) { - throw new TypeError("Audio chunk metadata must be provided."); - } - if (typeof metadata !== "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."); - } - if (typeof metadata.decoderConfig !== "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."); - } - 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 (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." - ); - } - } - 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." - ); - } - 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."); - } -}; -var validateSubtitleMetadata = (metadata) => { - if (!metadata) { - throw new TypeError("Subtitle metadata must be provided."); - } - if (typeof metadata !== "object") { - throw new TypeError("Subtitle metadata must be an object."); - } - if (!metadata.config) { - 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."); - } - if (typeof metadata.config.description !== "string") { - throw new TypeError("Subtitle metadata config description must be a string."); - } -}; - -// src/isobmff/isobmff-muxer.ts -var GLOBAL_TIMESCALE = 1e3; -var TIMESTAMP_OFFSET = 2082844800; -var intoTimescale = (timeInSeconds, timescale, round = true) => { - const value = timeInSeconds * timescale; - return round ? Math.round(value) : value; -}; -var IsobmffMuxer = class extends Muxer { - constructor(output, format) { - super(output); - this.timestampsMustStartAtZero = true; - this.auxTarget = new ArrayBufferTarget(); - this.auxWriter = this.auxTarget._createWriter(); - this.auxBoxWriter = new IsobmffBoxWriter(this.auxWriter); - this.ftypSize = null; - this.mdat = null; - this.trackDatas = []; - this.creationTime = Math.floor(Date.now() / 1e3) + TIMESTAMP_OFFSET; - this.finalizedChunks = []; - this.nextFragmentNumber = 1; - this.writer = output._writer; - this.boxWriter = new IsobmffBoxWriter(this.writer); - const fastStartDefault = this.writer instanceof ArrayBufferTargetWriter ? "in-memory" : false; - this.fastStart = format._options.fastStart ?? fastStartDefault; - if (this.fastStart === "in-memory" || this.fastStart === "fragmented") { - this.writer.ensureMonotonicity = true; - } - } - async start() { - const release = await this.mutex.acquire(); - const holdsAvc = this.output._tracks.some((x) => x.type === "video" && x.source._codec === "avc"); - this.boxWriter.writeBox(ftyp({ - holdsAvc, - fragmented: this.fastStart === "fragmented" - })); - this.ftypSize = this.writer.getPos(); - if (this.fastStart === "in-memory") { - this.mdat = mdat(false); - } else if (this.fastStart === "fragmented") { - } else { - this.mdat = mdat(true); - this.boxWriter.writeBox(this.mdat); - } - await this.writer.flush(); - release(); - } - getVideoTrackData(track, meta) { - const existingTrackData = this.trackDatas.find((x) => x.track === track); - if (existingTrackData) { - return existingTrackData; - } - validateVideoChunkMetadata(meta); - assert(meta); - assert(meta.decoderConfig); - assert(meta.decoderConfig.codedWidth !== void 0); - assert(meta.decoderConfig.codedHeight !== void 0); - const newTrackData = { - track, - type: "video", - info: { - width: meta.decoderConfig.codedWidth, - height: meta.decoderConfig.codedHeight, - decoderConfig: meta.decoderConfig - }, - timescale: track.metadata.frameRate ?? 57600, - samples: [], - sampleQueue: [], - timestampProcessingQueue: [], - timeToSampleTable: [], - compositionTimeOffsetTable: [], - lastTimescaleUnits: null, - lastSample: null, - finalizedChunks: [], - currentChunk: null, - compactlyCodedChunkTable: [] - }; - this.trackDatas.push(newTrackData); - this.trackDatas.sort((a, b) => a.track.id - b.track.id); - return newTrackData; - } - getAudioTrackData(track, meta) { - const existingTrackData = this.trackDatas.find((x) => x.track === track); - if (existingTrackData) { - return existingTrackData; - } - validateAudioChunkMetadata(meta); - assert(meta); - assert(meta.decoderConfig); - const newTrackData = { - track, - type: "audio", - info: { - numberOfChannels: meta.decoderConfig.numberOfChannels, - sampleRate: meta.decoderConfig.sampleRate, - decoderConfig: meta.decoderConfig - }, - timescale: meta.decoderConfig.sampleRate, - samples: [], - sampleQueue: [], - timestampProcessingQueue: [], - timeToSampleTable: [], - compositionTimeOffsetTable: [], - lastTimescaleUnits: null, - lastSample: null, - finalizedChunks: [], - currentChunk: null, - compactlyCodedChunkTable: [] - }; - this.trackDatas.push(newTrackData); - this.trackDatas.sort((a, b) => a.track.id - b.track.id); - return newTrackData; - } - getSubtitleTrackData(track, meta) { - const existingTrackData = this.trackDatas.find((x) => x.track === track); - if (existingTrackData) { - return existingTrackData; - } - validateSubtitleMetadata(meta); - assert(meta); - assert(meta.config); - const newTrackData = { - track, - type: "subtitle", - info: { - config: meta.config - }, - timescale: 1e3, - // Reasonable - samples: [], - sampleQueue: [], - timestampProcessingQueue: [], - timeToSampleTable: [], - compositionTimeOffsetTable: [], - lastTimescaleUnits: null, - lastSample: null, - finalizedChunks: [], - currentChunk: null, - compactlyCodedChunkTable: [], - lastCueEndTimestamp: 0, - cueQueue: [], - nextSourceId: 0, - cueToSourceId: /* @__PURE__ */ new WeakMap() - }; - this.trackDatas.push(newTrackData); - this.trackDatas.sort((a, b) => a.track.id - b.track.id); - this.validateAndNormalizeTimestamp(track, 0, true); - return newTrackData; - } - async addEncodedVideoChunk(track, chunk, meta) { - const release = await this.mutex.acquire(); - try { - const trackData = this.getVideoTrackData(track, meta); - const data = new Uint8Array(chunk.byteLength); - chunk.copyTo(data); - 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(); - } - } - async addEncodedAudioChunk(track, chunk, meta) { - const release = await this.mutex.acquire(); - try { - const trackData = this.getAudioTrackData(track, meta); - const data = new Uint8Array(chunk.byteLength); - chunk.copyTo(data); - 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(); - } - } - async addSubtitleCue(track, cue, meta) { - const release = await this.mutex.acquire(); - try { - const trackData = this.getSubtitleTrackData(track, meta); - this.validateAndNormalizeTimestamp(trackData.track, 1e6 * cue.timestamp, true); - if (track.source._codec === "webvtt") { - trackData.cueQueue.push(cue); - await this.processWebVTTCues(trackData, cue.timestamp); - } else { - } - } finally { - release(); - } - } - async processWebVTTCues(trackData, until) { - while (trackData.cueQueue.length > 0) { - 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); - } - 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); - const box2 = vtte(); - this.auxBoxWriter.writeBox(box2); - 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++) { - const cue = trackData.cueQueue[i]; - if (cue.timestamp >= sampleEnd) { - break; - } - inlineTimestampRegex.lastIndex = 0; - 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) { - const box3 = vtta(cue.notes); - this.auxBoxWriter.writeBox(box3); - } - 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); - } - } - 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) { - const sample = { - timestamp, - decodeTimestamp: timestamp, - // This may be refined later - duration, - data, - size: data.byteLength, - type, - // Will be refined once the next sample comes in - timescaleUnitsToNextSample: intoTimescale(duration, trackData.timescale) - }; - return sample; - } - processTimestamps(trackData) { - if (trackData.timestampProcessingQueue.length === 0) { - return; - } - const sortedTimestamps = trackData.timestampProcessingQueue.map((x) => x.timestamp).sort((a, b) => a - b); - for (let i = 0; i < trackData.timestampProcessingQueue.length; i++) { - const sample = trackData.timestampProcessingQueue[i]; - sample.decodeTimestamp = sortedTimestamps[i]; - const sampleCompositionTimeOffset = intoTimescale(sample.timestamp - sample.decodeTimestamp, trackData.timescale); - const durationInTimescale = intoTimescale(sample.duration, trackData.timescale); - if (trackData.lastTimescaleUnits !== null) { - assert(trackData.lastSample); - 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); - if (lastTableEntry.sampleCount === 1) { - lastTableEntry.sampleDelta = delta; - const entryBefore = trackData.timeToSampleTable[trackData.timeToSampleTable.length - 2]; - if (entryBefore && entryBefore.sampleDelta === delta) { - entryBefore.sampleCount++; - trackData.timeToSampleTable.pop(); - lastTableEntry = entryBefore; - } - } else if (lastTableEntry.sampleDelta !== delta) { - lastTableEntry.sampleCount--; - trackData.timeToSampleTable.push(lastTableEntry = { - sampleCount: 1, - sampleDelta: delta - }); - } - if (lastTableEntry.sampleDelta === durationInTimescale) { - lastTableEntry.sampleCount++; - } else { - trackData.timeToSampleTable.push({ - sampleCount: 1, - sampleDelta: durationInTimescale - }); - } - const lastCompositionTimeOffsetTableEntry = last(trackData.compositionTimeOffsetTable); - assert(lastCompositionTimeOffsetTableEntry); - if (lastCompositionTimeOffsetTableEntry.sampleCompositionTimeOffset === sampleCompositionTimeOffset) { - lastCompositionTimeOffsetTableEntry.sampleCount++; - } else { - trackData.compositionTimeOffsetTable.push({ - sampleCount: 1, - sampleCompositionTimeOffset - }); - } - } - } else { - trackData.lastTimescaleUnits = 0; - if (this.fastStart !== "fragmented") { - trackData.timeToSampleTable.push({ - sampleCount: 1, - sampleDelta: durationInTimescale - }); - trackData.compositionTimeOffsetTable.push({ - sampleCount: 1, - sampleCompositionTimeOffset - }); - } - } - trackData.lastSample = sample; - } - trackData.timestampProcessingQueue.length = 0; - } - async registerSample(trackData, sample) { - if (this.fastStart === "fragmented") { - trackData.sampleQueue.push(sample); - await this.interleaveSamples(); - } else { - await this.addSampleToTrack(trackData, sample); - } - } - async addSampleToTrack(trackData, sample) { - if (sample.type === "key") { - this.processTimestamps(trackData); - } - if (this.fastStart !== "fragmented") { - trackData.samples.push(sample); - } - let beginNewChunk = false; - if (!trackData.currentChunk) { - beginNewChunk = true; - } else { - const currentChunkDuration = sample.timestamp - trackData.currentChunk.startTimestamp; - if (this.fastStart === "fragmented") { - const keyFrameQueuedEverywhere = this.trackDatas.every((otherTrackData) => { - if (trackData === otherTrackData) { - return sample.type === "key"; - } - const firstQueuedSample = otherTrackData.sampleQueue[0]; - return firstQueuedSample && firstQueuedSample.type === "key"; - }); - if (currentChunkDuration >= 1 && keyFrameQueuedEverywhere) { - beginNewChunk = true; - await this.finalizeFragment(); - } - } else { - beginNewChunk = currentChunkDuration >= 0.5; - } - } - if (beginNewChunk) { - if (trackData.currentChunk) { - await this.finalizeCurrentChunk(trackData); - } - trackData.currentChunk = { - startTimestamp: sample.timestamp, - samples: [], - offset: null, - moofOffset: null - }; - } - assert(trackData.currentChunk); - trackData.currentChunk.samples.push(sample); - trackData.timestampProcessingQueue.push(sample); - } - async finalizeCurrentChunk(trackData) { - assert(this.fastStart !== "fragmented"); - if (!trackData.currentChunk) return; - trackData.finalizedChunks.push(trackData.currentChunk); - this.finalizedChunks.push(trackData.currentChunk); - if (trackData.compactlyCodedChunkTable.length === 0 || last(trackData.compactlyCodedChunkTable).samplesPerChunk !== trackData.currentChunk.samples.length) { - trackData.compactlyCodedChunkTable.push({ - firstChunk: trackData.finalizedChunks.length, - // 1-indexed - samplesPerChunk: trackData.currentChunk.samples.length - }); - } - if (this.fastStart === "in-memory") { - trackData.currentChunk.offset = 0; - return; - } - trackData.currentChunk.offset = this.writer.getPos(); - for (const sample of trackData.currentChunk.samples) { - assert(sample.data); - this.writer.write(sample.data); - sample.data = null; - } - await this.writer.flush(); - } - async interleaveSamples() { - assert(this.fastStart === "fragmented"); - for (const track of this.output._tracks) { - if (!track.source._closed && !this.trackDatas.some((x) => x.track === track)) { - return; - } - } - outer: - while (true) { - let trackWithMinTimestamp = null; - let minTimestamp = Infinity; - for (const trackData of this.trackDatas) { - if (trackData.sampleQueue.length === 0 && !trackData.track.source._closed) { - break outer; - } - if (trackData.sampleQueue.length > 0 && trackData.sampleQueue[0].timestamp < minTimestamp) { - trackWithMinTimestamp = trackData; - minTimestamp = trackData.sampleQueue[0].timestamp; - } - } - if (!trackWithMinTimestamp) { - break; - } - const sample = trackWithMinTimestamp.sampleQueue.shift(); - await this.addSampleToTrack(trackWithMinTimestamp, sample); - } - } - async finalizeFragment(flushWriter = true) { - assert(this.fastStart === "fragmented"); - const fragmentNumber = this.nextFragmentNumber++; - if (fragmentNumber === 1) { - const movieBox = moov(this.trackDatas, this.creationTime, true); - this.boxWriter.writeBox(movieBox); - } - const tracksInFragment = this.trackDatas.filter((x) => x.currentChunk); - const moofOffset = this.writer.getPos(); - const moofBox = moof(fragmentNumber, tracksInFragment); - this.boxWriter.writeBox(moofBox); - { - const mdatBox = mdat(false); - let totalTrackSampleSize = 0; - for (const trackData of tracksInFragment) { - for (const sample of trackData.currentChunk.samples) { - totalTrackSampleSize += sample.size; - } - } - let mdatSize = this.boxWriter.measureBox(mdatBox) + totalTrackSampleSize; - if (mdatSize >= 2 ** 32) { - mdatBox.largeSize = true; - mdatSize = this.boxWriter.measureBox(mdatBox) + totalTrackSampleSize; - } - mdatBox.size = mdatSize; - this.boxWriter.writeBox(mdatBox); - } - for (const trackData of tracksInFragment) { - trackData.currentChunk.offset = this.writer.getPos(); - trackData.currentChunk.moofOffset = moofOffset; - for (const sample of trackData.currentChunk.samples) { - this.writer.write(sample.data); - sample.data = null; - } - } - const endPos = this.writer.getPos(); - this.writer.seek(this.boxWriter.offsets.get(moofBox)); - const newMoofBox = moof(fragmentNumber, tracksInFragment); - this.boxWriter.writeBox(newMoofBox); - this.writer.seek(endPos); - for (const trackData of tracksInFragment) { - trackData.finalizedChunks.push(trackData.currentChunk); - this.finalizedChunks.push(trackData.currentChunk); - trackData.currentChunk = null; - } - if (flushWriter) { - 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") { - const trackData = this.trackDatas.find((x) => x.track === track); - if (trackData) { - await this.processWebVTTCues(trackData, Infinity); - } - } - if (this.fastStart === "fragmented") { - await this.interleaveSamples(); - } - release(); - } - /** 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 (const trackData of this.trackDatas) { - if (trackData.type === "subtitle" && trackData.track.source._codec === "webvtt") { - await this.processWebVTTCues(trackData, Infinity); - } - } - if (this.fastStart === "fragmented") { - 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 (const trackData of this.trackDatas) { - this.processTimestamps(trackData); - await this.finalizeCurrentChunk(trackData); - } - } - if (this.fastStart === "in-memory") { - assert(this.mdat); - let mdatSize; - for (let i = 0; i < 2; i++) { - 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 (const chunk of this.finalizedChunks) { - chunk.offset = currentChunkPos; - 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; - } - const movieBox = moov(this.trackDatas, this.creationTime); - this.boxWriter.writeBox(movieBox); - this.mdat.size = mdatSize; - this.boxWriter.writeBox(this.mdat); - 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") { - const startPos = this.writer.getPos(); - const mfraBox = mfra(this.trackDatas); - this.boxWriter.writeBox(mfraBox); - 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); - const mdatPos = this.boxWriter.offsets.get(this.mdat); - assert(mdatPos !== void 0); - const mdatSize = this.writer.getPos() - mdatPos; - this.mdat.size = mdatSize; - this.mdat.largeSize = mdatSize >= 2 ** 32; - this.boxWriter.patchBox(this.mdat); - const movieBox = moov(this.trackDatas, this.creationTime); - if (typeof this.fastStart === "object") { - this.writer.seek(this.ftypSize); - this.boxWriter.writeBox(movieBox); - const remainingBytes = mdatPos - this.writer.getPos(); - this.boxWriter.writeBox(free(remainingBytes)); - } else { - this.boxWriter.writeBox(movieBox); - } - } - release(); - } -}; - -// src/matroska/ebml.ts -var EBMLFloat32 = class { - constructor(value) { - this.value = value; - } -}; -var EBMLFloat64 = class { - constructor(value) { - this.value = value; - } -}; -var EBMLSignedInt = class { - constructor(value) { - this.value = value; - } -}; -var measureUnsignedInt = (value) => { - if (value < 1 << 8) { - return 1; - } else if (value < 1 << 16) { - return 2; - } else if (value < 1 << 24) { - return 3; - } else if (value < 2 ** 32) { - return 4; - } else if (value < 2 ** 40) { - return 5; - } else { - return 6; - } -}; -var measureSignedInt = (value) => { - if (value >= -(1 << 6) && value < 1 << 6) { - return 1; - } else if (value >= -(1 << 13) && value < 1 << 13) { - return 2; - } else if (value >= -(1 << 20) && value < 1 << 20) { - return 3; - } else if (value >= -(1 << 27) && value < 1 << 27) { - return 4; - } else if (value >= -(2 ** 34) && value < 2 ** 34) { - return 5; - } else { - return 6; - } -}; -var measureEBMLVarInt = (value) => { - if (value < (1 << 7) - 1) { - return 1; - } else if (value < (1 << 14) - 1) { - return 2; - } else if (value < (1 << 21) - 1) { - return 3; - } else if (value < (1 << 28) - 1) { - return 4; - } else if (value < 2 ** 35 - 1) { - return 5; - } else if (value < 2 ** 42 - 1) { - return 6; - } else { - throw new Error("EBML VINT size not supported " + value); - } -}; - -// src/matroska/matroska-muxer.ts -var MAX_CHUNK_LENGTH_MS = 2 ** 15; -var APP_NAME = "https://github.com/Vanilagy/webm-muxer"; -var SEGMENT_SIZE_BYTES = 6; -var CLUSTER_SIZE_BYTES = 5; -var CODEC_STRING_MAP = { - 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" -}; -var TRACK_TYPE_MAP = { - video: 1, - audio: 2, - subtitle: 17 -}; -var MatroskaMuxer = class extends Muxer { - constructor(output, format) { - super(output); - this.timestampsMustStartAtZero = false; - this.helper = new Uint8Array(8); - this.helperView = new DataView(this.helper.buffer); - /** - * Stores the position from the start of the file to where EBML elements have been written. This is used to - * rewrite/edit elements that were already added before, and to measure sizes of things. - */ - this.offsets = /* @__PURE__ */ new WeakMap(); - /** Same as offsets, but stores position where the element's data starts (after ID and size fields). */ - this.dataOffsets = /* @__PURE__ */ new WeakMap(); - this.trackDatas = []; - this.segment = null; - this.segmentInfo = null; - this.seekHead = null; - this.tracksElement = null; - this.segmentDuration = null; - this.cues = null; - this.currentCluster = null; - this.currentClusterMsTimestamp = null; - this.trackDatasInCurrentCluster = /* @__PURE__ */ new Set(); - this.duration = 0; - this.writer = output._writer; - this.format = format; - if (this.format._options.streamable) { - this.writer.ensureMonotonicity = true; - } - } - writeByte(value) { - this.helperView.setUint8(0, value); - this.writer.write(this.helper.subarray(0, 1)); - } - writeFloat32(value) { - this.helperView.setFloat32(0, value, false); - this.writer.write(this.helper.subarray(0, 4)); - } - writeFloat64(value) { - this.helperView.setFloat64(0, value, false); - this.writer.write(this.helper); - } - writeUnsignedInt(value, width = measureUnsignedInt(value)) { - let pos = 0; - 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; - default: - throw new Error("Bad UINT size " + width); - } - this.writer.write(this.helper.subarray(0, pos)); - } - writeSignedInt(value, width = measureSignedInt(value)) { - if (value < 0) { - value += 2 ** (width * 8); - } - this.writeUnsignedInt(value, width); - } - writeEBMLVarInt(value, width = measureEBMLVarInt(value)) { - let pos = 0; - switch (width) { - case 1: - this.helperView.setUint8(pos++, 1 << 7 | value); - break; - case 2: - this.helperView.setUint8(pos++, 1 << 6 | value >> 8); - this.helperView.setUint8(pos++, value); - break; - case 3: - this.helperView.setUint8(pos++, 1 << 5 | value >> 16); - this.helperView.setUint8(pos++, value >> 8); - this.helperView.setUint8(pos++, value); - break; - case 4: - this.helperView.setUint8(pos++, 1 << 4 | value >> 24); - this.helperView.setUint8(pos++, value >> 16); - this.helperView.setUint8(pos++, value >> 8); - this.helperView.setUint8(pos++, value); - break; - case 5: - this.helperView.setUint8(pos++, 1 << 3 | value / 2 ** 32 & 7); - 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 & 3); - 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); - this.helperView.setUint8(pos++, value); - break; - default: - throw new Error("Bad EBML VINT size " + width); - } - this.writer.write(this.helper.subarray(0, pos)); - } - // Assumes the string is ASCII - writeString(str) { - this.writer.write(new Uint8Array(str.split("").map((x) => x.charCodeAt(0)))); - } - writeEBML(data) { - if (data === null) return; - if (data instanceof Uint8Array) { - this.writer.write(data); - } else if (Array.isArray(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)) { - 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); - } - const startPos = this.writer.getPos(); - this.dataOffsets.set(data, startPos); - this.writeEBML(data.data); - if (data.size !== -1) { - 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") { - const size = data.size ?? measureUnsignedInt(data.data); - this.writeEBMLVarInt(size); - this.writeUnsignedInt(data.data, size); - } else if (typeof data.data === "string") { - this.writeEBMLVarInt(data.data.length); - this.writeString(data.data); - } else if (data.data instanceof Uint8Array) { - this.writeEBMLVarInt(data.data.byteLength, data.size); - this.writer.write(data.data); - } else if (data.data instanceof EBMLFloat32) { - this.writeEBMLVarInt(4); - this.writeFloat32(data.data.value); - } else if (data.data instanceof EBMLFloat64) { - this.writeEBMLVarInt(8); - this.writeFloat64(data.data.value); - } else if (data.data instanceof EBMLSignedInt) { - const size = data.size ?? measureSignedInt(data.data.value); - this.writeEBMLVarInt(size); - this.writeSignedInt(data.data.value, size); - } - } - } - beforeTrackAdd(track) { - 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.` - ); - } - } 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.` - ); - } - } 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.` - ); - } - } else { - throw new Error( - "WebM only supports video, audio and subtitle tracks. Switching to MKV removes this restriction." - ); - } - } - async start() { - const release = await this.mutex.acquire(); - this.writeEBMLHeader(); - if (!this.format._options.streamable) { - this.createSeekHead(); - } - this.createSegmentInfo(); - this.createCues(); - await this.writer.flush(); - release(); - } - writeEBMLHeader() { - const ebmlHeader = { id: 440786851 /* EBML */, data: [ - { id: 17030 /* EBMLVersion */, data: 1 }, - { id: 17143 /* EBMLReadVersion */, data: 1 }, - { id: 17138 /* EBMLMaxIDLength */, data: 4 }, - { id: 17139 /* EBMLMaxSizeLength */, data: 8 }, - { id: 17026 /* DocType */, data: this.format instanceof WebMOutputFormat ? "webm" : "matroska" }, - { id: 17031 /* DocTypeVersion */, data: 2 }, - { id: 17029 /* DocTypeReadVersion */, data: 2 } - ] }; - this.writeEBML(ebmlHeader); - } - /** - * Creates a SeekHead element which is positioned near the start of the file and allows the media player to seek to - * relevant sections more easily. Since we don't know the positions of those sections yet, we'll set them later. - */ - createSeekHead() { - const kaxCues = new Uint8Array([28, 83, 187, 107]); - const kaxInfo = new Uint8Array([21, 73, 169, 102]); - const kaxTracks = new Uint8Array([22, 84, 174, 107]); - const seekHead = { id: 290298740 /* SeekHead */, data: [ - { id: 19899 /* Seek */, data: [ - { id: 21419 /* SeekID */, data: kaxCues }, - { id: 21420 /* SeekPosition */, size: 5, data: 0 } - ] }, - { id: 19899 /* Seek */, data: [ - { id: 21419 /* SeekID */, data: kaxInfo }, - { id: 21420 /* SeekPosition */, size: 5, data: 0 } - ] }, - { id: 19899 /* Seek */, data: [ - { id: 21419 /* SeekID */, data: kaxTracks }, - { id: 21420 /* SeekPosition */, size: 5, data: 0 } - ] } - ] }; - this.seekHead = seekHead; - } - createSegmentInfo() { - const segmentDuration = { id: 17545 /* Duration */, data: new EBMLFloat64(0) }; - this.segmentDuration = segmentDuration; - const segmentInfo = { id: 357149030 /* Info */, data: [ - { id: 2807729 /* TimestampScale */, data: 1e6 }, - { id: 19840 /* MuxingApp */, data: APP_NAME }, - { id: 22337 /* WritingApp */, data: APP_NAME }, - !this.format._options.streamable ? segmentDuration : null - ] }; - this.segmentInfo = segmentInfo; - } - createTracks() { - const tracksElement = { id: 374648427 /* Tracks */, data: [] }; - this.tracksElement = tracksElement; - 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" ? 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() { - const segment = { - id: 408125543 /* Segment */, - size: this.format._options.streamable ? -1 : SEGMENT_SIZE_BYTES, - data: [ - !this.format._options.streamable ? this.seekHead : null, - this.segmentInfo, - this.tracksElement - ] - }; - this.segment = segment; - this.writeEBML(segment); - } - createCues() { - this.cues = { id: 475249515 /* Cues */, data: [] }; - } - get segmentDataOffset() { - assert(this.segment); - return this.dataOffsets.get(this.segment); - } - getVideoTrackData(track, meta) { - const existingTrackData = this.trackDatas.find((x) => x.track === track); - if (existingTrackData) { - return existingTrackData; - } - validateVideoChunkMetadata(meta); - assert(meta); - assert(meta.decoderConfig); - assert(meta.decoderConfig.codedWidth !== void 0); - assert(meta.decoderConfig.codedHeight !== void 0); - const newTrackData = { - track, - type: "video", - info: { - width: meta.decoderConfig.codedWidth, - height: meta.decoderConfig.codedHeight, - decoderConfig: meta.decoderConfig - }, - chunkQueue: [], - lastWrittenMsTimestamp: null - }; - this.trackDatas.push(newTrackData); - this.trackDatas.sort((a, b) => a.track.id - b.track.id); - return newTrackData; - } - getAudioTrackData(track, meta) { - const existingTrackData = this.trackDatas.find((x) => x.track === track); - if (existingTrackData) { - return existingTrackData; - } - validateAudioChunkMetadata(meta); - assert(meta); - assert(meta.decoderConfig); - const newTrackData = { - track, - type: "audio", - info: { - numberOfChannels: meta.decoderConfig.numberOfChannels, - sampleRate: meta.decoderConfig.sampleRate, - decoderConfig: meta.decoderConfig - }, - chunkQueue: [], - lastWrittenMsTimestamp: null - }; - this.trackDatas.push(newTrackData); - this.trackDatas.sort((a, b) => a.track.id - b.track.id); - return newTrackData; - } - getSubtitleTrackData(track, meta) { - const existingTrackData = this.trackDatas.find((x) => x.track === track); - if (existingTrackData) { - return existingTrackData; - } - validateSubtitleMetadata(meta); - assert(meta); - assert(meta.config); - const newTrackData = { - track, - type: "subtitle", - info: { - config: meta.config - }, - chunkQueue: [], - lastWrittenMsTimestamp: null - }; - this.trackDatas.push(newTrackData); - this.trackDatas.sort((a, b) => a.track.id - b.track.id); - return newTrackData; - } - async addEncodedVideoChunk(track, chunk, meta) { - const release = await this.mutex.acquire(); - try { - const trackData = this.getVideoTrackData(track, meta); - const data = new Uint8Array(chunk.byteLength); - chunk.copyTo(data); - 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(); - } finally { - release(); - } - } - async addEncodedAudioChunk(track, chunk, meta) { - const release = await this.mutex.acquire(); - try { - const trackData = this.getAudioTrackData(track, meta); - const data = new Uint8Array(chunk.byteLength); - chunk.copyTo(data); - 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 { - release(); - } - } - async addSubtitleCue(track, cue, meta) { - const release = await this.mutex.acquire(); - try { - const trackData = this.getSubtitleTrackData(track, meta); - const timestamp = this.validateAndNormalizeTimestamp(trackData.track, 1e6 * cue.timestamp, true); - let bodyText = cue.text; - const timestampMs = Math.floor(timestamp * 1e3); - inlineTimestampRegex.lastIndex = 0; - bodyText = bodyText.replace(inlineTimestampRegex, (match) => { - 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 ?? ""}`; - const subtitleChunk = this.createInternalChunk( - body, - timestamp, - cue.duration, - "key", - additions.trim() ? textEncoder.encode(additions) : null - ); - trackData.chunkQueue.push(subtitleChunk); - await this.interleaveChunks(); - } finally { - release(); - } - } - async interleaveChunks() { - for (const track of this.output._tracks) { - if (!track.source._closed && !this.trackDatas.some((x) => x.track === track)) { - return; - } - } - outer: - while (true) { - let trackWithMinTimestamp = null; - let minTimestamp = Infinity; - for (const trackData of this.trackDatas) { - if (trackData.chunkQueue.length === 0 && !trackData.track.source._closed) { - break outer; - } - if (trackData.chunkQueue.length > 0 && trackData.chunkQueue[0].timestamp < minTimestamp) { - trackWithMinTimestamp = trackData; - minTimestamp = trackData.chunkQueue[0].timestamp; - } - } - if (!trackWithMinTimestamp) { - break; - } - const chunk = trackWithMinTimestamp.chunkQueue.shift(); - this.writeBlock(trackWithMinTimestamp, chunk); - } - await this.writer.flush(); - } - /** Due to [a bug in Chromium](https://bugs.chromium.org/p/chromium/issues/detail?id=1377842), VP9 streams often - * lack color space information. This method patches in that information. */ - // http://downloads.webmproject.org/docs/vp9/vp9-bitstream_superframe-and-uncompressed-header_v1.0.pdf - fixVP9ColorSpace(trackData, chunk) { - if (chunk.type !== "key") return; - if (!trackData.info.decoderConfig.colorSpace || !trackData.info.decoderConfig.colorSpace.matrix) return; - let i = 0; - if (readBits(chunk.data, 0, 2) !== 2) return; - 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++; - const showExistingFrame = readBits(chunk.data, i + 0, i + 1); - i++; - if (showExistingFrame) return; - const frameType = readBits(chunk.data, i + 0, i + 1); - i++; - if (frameType !== 0) return; - i += 2; - const syncCode = readBits(chunk.data, i + 0, i + 24); - i += 24; - if (syncCode !== 4817730) return; - if (profile >= 2) i++; - 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) { - const internalChunk = { - data, - type, - timestamp, - duration, - additions - }; - return internalChunk; - } - /** Writes a block containing media data to the file. */ - writeBlock(trackData, chunk) { - if (!this.segment) { - this.createTracks(); - this.createSegment(); - } - const msTimestamp = Math.floor(1e3 * chunk.timestamp); - const keyFrameQueuedEverywhere = this.trackDatas.every((otherTrackData) => { - if (otherTrackData.track.source._closed) { - return true; - } - if (trackData === otherTrackData) { - return chunk.type === "key"; - } - const firstQueuedSample = otherTrackData.chunkQueue[0]; - return firstQueuedSample && firstQueuedSample.type === "key"; - }); - if (!this.currentCluster || keyFrameQueuedEverywhere && msTimestamp - this.currentClusterMsTimestamp >= 1e3) { - this.createNewCluster(msTimestamp); - } - const relativeTimestamp = msTimestamp - this.currentClusterMsTimestamp; - if (relativeTimestamp < 0) { - return; - } - 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.` - ); - } - const prelude = new Uint8Array(4); - const view2 = new DataView(prelude.buffer); - view2.setUint8(0, 128 | trackData.track.id); - view2.setInt16(1, relativeTimestamp, false); - const msDuration = Math.floor(1e3 * chunk.duration); - if (msDuration === 0 && !chunk.additions) { - view2.setUint8(3, Number(chunk.type === "key") << 7); - const simpleBlock = { id: 163 /* SimpleBlock */, data: [ - prelude, - chunk.data - ] }; - this.writeEBML(simpleBlock); - } else { - 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.additions ? { id: 30113 /* BlockAdditions */, data: [ - { id: 166 /* BlockMore */, data: [ - { id: 165 /* BlockAdditional */, data: chunk.additions }, - { id: 238 /* BlockAddID */, data: 1 } - ] } - ] } : null, - msDuration > 0 ? { id: 155 /* BlockDuration */, data: msDuration } : null - ] }; - this.writeEBML(blockGroup); - } - this.duration = Math.max(this.duration, msTimestamp + msDuration); - trackData.lastWrittenMsTimestamp = msTimestamp; - this.trackDatasInCurrentCluster.add(trackData); - } - /** Creates a new Cluster element to contain media chunks. */ - createNewCluster(msTimestamp) { - if (this.currentCluster && !this.format._options.streamable) { - this.finalizeCurrentCluster(); - } - this.currentCluster = { - id: 524531317 /* Cluster */, - size: this.format._options.streamable ? -1 : CLUSTER_SIZE_BYTES, - data: [ - { id: 231 /* Timestamp */, data: msTimestamp } - ] - }; - this.writeEBML(this.currentCluster); - this.currentClusterMsTimestamp = msTimestamp; - this.trackDatasInCurrentCluster.clear(); - } - finalizeCurrentCluster() { - assert(this.currentCluster); - 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); - 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 }, - // We only write out cues for tracks that have at least one chunk in this cluster - ...[...this.trackDatasInCurrentCluster].map((trackData) => { - return { id: 183 /* CueTrackPositions */, data: [ - { id: 247 /* CueTrack */, data: trackData.track.id }, - { id: 241 /* CueClusterPosition */, data: clusterOffsetFromSegment } - ] }; - }) - ] }); - } - // eslint-disable-next-line @typescript-eslint/no-misused-promises - async onTrackClose() { - const release = await this.mutex.acquire(); - await this.interleaveChunks(); - release(); - } - /** Finalizes the file, making it ready for use. Must be called after all media chunks have been added. */ - async finalize() { - const release = await this.mutex.acquire(); - if (!this.segment) { - this.createTracks(); - this.createSegment(); - } - for (const trackData of this.trackDatas) { - while (trackData.chunkQueue.length > 0) { - this.writeBlock(trackData, trackData.chunkQueue.shift()); - } - } - if (!this.format._options.streamable && this.currentCluster) { - this.finalizeCurrentCluster(); - } - assert(this.cues); - this.writeEBML(this.cues); - if (!this.format._options.streamable) { - 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); - 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(endPos); - } - release(); - } -}; - -// src/output-format.ts -var OutputFormat = class { -}; -var Mp4OutputFormat = class extends OutputFormat { - constructor(options = {}) { - if (!options || typeof options !== "object") { - throw new TypeError("options must be an object."); - } - if (options.fastStart !== void 0 && ![false, "in-memory", "fragmented"].includes(options.fastStart)) { - throw new TypeError('options.fastStart, when provided, must be false, "in-memory", or "fragmented".'); - } - super(); - this._options = options; - } - /** @internal */ - _createMuxer(output) { - return new IsobmffMuxer(output, this); - } -}; -var MkvOutputFormat2 = class extends OutputFormat { - constructor(options = {}) { - if (!options || typeof options !== "object") { - throw new TypeError("options must be an object."); - } - if (options.streamable !== void 0 && typeof options.streamable !== "boolean") { - throw new TypeError("options.streamable, when provided, must be a boolean."); - } - super(); - this._options = options; - } - /** @internal */ - _createMuxer(output) { - return new MatroskaMuxer(output, this); - } -}; -var WebMOutputFormat = class extends MkvOutputFormat2 { -}; - -// src/media-source.ts -var MediaSource = class { - constructor() { - /** @internal */ - this._connectedTrack = null; - /** @internal */ - this._closed = false; - /** @internal */ - this._offsetTimestamps = false; - } - /** @internal */ - _ensureValidDigest() { - if (!this._connectedTrack) { - throw new Error("Cannot call digest without connecting the source to an output track."); - } - if (!this._connectedTrack.output._started) { - throw new Error("Cannot call digest before output has been started."); - } - if (this._connectedTrack.output._finalizing) { - throw new Error("Cannot call digest after output has started finalizing."); - } - if (this._closed) { - throw new Error("Cannot call digest after source has been closed."); - } - } - /** @internal */ - _start() { - } - /** @internal */ - async _flush() { - } - close() { - if (this._closed) { - throw new Error("Source already closed."); - } - if (!this._connectedTrack) { - throw new Error("Cannot call close without connecting the source to an output track."); - } - if (!this._connectedTrack.output._started) { - throw new Error("Cannot call close before output has been started."); - } - this._closed = true; - if (this._connectedTrack.output._finalizing) { - return; - } - this._connectedTrack.output._muxer.onTrackClose(this._connectedTrack); - } -}; -var VideoSource = class extends MediaSource { - constructor(codec) { - super(); - /** @internal */ - this._connectedTrack = null; - if (!VIDEO_CODECS.includes(codec)) { - throw new TypeError(`Invalid video codec '${codec}'. Must be one of: ${VIDEO_CODECS.join(", ")}.`); - } - this._codec = codec; - } -}; -var EncodedVideoChunkSource = class extends VideoSource { - constructor(codec) { - super(codec); - } - digest(chunk, meta) { - if (!(chunk instanceof EncodedVideoChunk)) { - throw new TypeError("chunk must be an EncodedVideoChunk."); - } - this._ensureValidDigest(); - return this._connectedTrack.output._muxer.addEncodedVideoChunk(this._connectedTrack, chunk, meta); - } -}; -var KEY_FRAME_INTERVAL = 5; -var validateVideoCodecConfig = (config) => { - if (!config || typeof config !== "object") { - throw new TypeError("Codec config must be an object."); - } - if (!VIDEO_CODECS.includes(config.codec)) { - throw new TypeError(`Invalid video codec '${config.codec}'. Must be one of: ${VIDEO_CODECS.join(", ")}.`); - } - if (!Number.isInteger(config.bitrate) || config.bitrate <= 0) { - throw new TypeError("config.bitrate must be a positive integer."); - } - if (config.latencyMode !== void 0 && !["quality", "realtime"].includes(config.latencyMode)) { - throw new TypeError("config.latencyMode, when provided, must be 'quality' or 'realtime'."); - } - if (config.onEncodingError !== void 0 && typeof config.onEncodingError !== "function") { - throw new TypeError("config.onEncodingError, when provided, must be a function."); - } -}; -var VideoEncoderWrapper = class { - constructor(source, codecConfig) { - this.source = source; - this.codecConfig = codecConfig; - this.encoder = null; - this.muxer = null; - this.lastMultipleOfKeyFrameInterval = -1; - this.lastWidth = null; - this.lastHeight = null; - validateVideoCodecConfig(codecConfig); - } - async digest(videoFrame) { - 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}.` - ); - } - } else { - this.lastWidth = videoFrame.codedWidth; - this.lastHeight = videoFrame.codedHeight; - } - this.ensureEncoder(videoFrame); - assert(this.encoder); - const multipleOfKeyFrameInterval = Math.floor(videoFrame.timestamp / 1e6 / KEY_FRAME_INTERVAL); - this.encoder.encode(videoFrame, { - keyFrame: multipleOfKeyFrameInterval !== this.lastMultipleOfKeyFrameInterval - }); - this.lastMultipleOfKeyFrameInterval = multipleOfKeyFrameInterval; - if (this.encoder.encodeQueueSize >= 4) { - await new Promise((resolve) => this.encoder.addEventListener("dequeue", resolve, { once: true })); - } - await this.muxer.mutex.currentPromise; - } - ensureEncoder(videoFrame) { - if (this.encoder) { - return; - } - this.encoder = new VideoEncoder({ - output: (chunk, meta) => void this.muxer.addEncodedVideoChunk(this.source._connectedTrack, chunk, meta), - error: this.codecConfig.onEncodingError ?? ((error) => console.error("VideoEncoder error:", error)) - }); - this.encoder.configure({ - codec: buildVideoCodecString( - this.codecConfig.codec, - videoFrame.codedWidth, - videoFrame.codedHeight, - this.codecConfig.bitrate - ), - width: videoFrame.codedWidth, - height: videoFrame.codedHeight, - bitrate: this.codecConfig.bitrate, - framerate: this.source._connectedTrack?.metadata.frameRate, - latencyMode: this.codecConfig.latencyMode, - ...getVideoEncoderConfigExtension(this.codecConfig.codec) - }); - assert(this.source._connectedTrack); - this.muxer = this.source._connectedTrack.output._muxer; - } - async flush() { - if (this.encoder) { - await this.encoder.flush(); - this.encoder.close(); - } - } -}; -var VideoFrameSource = class extends VideoSource { - constructor(codecConfig) { - super(codecConfig.codec); - this._encoder = new VideoEncoderWrapper(this, codecConfig); - } - digest(videoFrame) { - if (!(videoFrame instanceof VideoFrame)) { - throw new TypeError("videoFrame must be a VideoFrame."); - } - return this._encoder.digest(videoFrame); - } - /** @internal */ - _flush() { - return this._encoder.flush(); - } -}; -var CanvasSource = class extends VideoSource { - constructor(canvas, codecConfig) { - if (!(canvas instanceof HTMLCanvasElement)) { - throw new TypeError("canvas must be an HTMLCanvasElement."); - } - super(codecConfig.codec); - this._encoder = new VideoEncoderWrapper(this, codecConfig); - this._canvas = canvas; - } - digest(timestamp, duration = 0) { - if (!Number.isFinite(timestamp) || timestamp < 0) { - throw new TypeError("timestamp must be a non-negative number."); - } - if (!Number.isFinite(duration) || duration < 0) { - throw new TypeError("duration must be a non-negative number."); - } - const frame = new VideoFrame(this._canvas, { - timestamp: Math.round(1e6 * timestamp), - duration: Math.round(1e6 * duration), - alpha: "discard" - }); - const promise = this._encoder.digest(frame); - frame.close(); - return promise; - } - /** @internal */ - _flush() { - return this._encoder.flush(); - } -}; -var MediaStreamVideoTrackSource = class extends VideoSource { - constructor(track, codecConfig) { - if (!(track instanceof MediaStreamTrack) || track.kind !== "video") { - throw new TypeError("track must be a video MediaStreamTrack."); - } - codecConfig = { - ...codecConfig, - latencyMode: "realtime" - }; - super(codecConfig.codec); - /** @internal */ - this._abortController = null; - /** @internal */ - this._offsetTimestamps = true; - this._encoder = new VideoEncoderWrapper(this, codecConfig); - this._track = track; - } - /** @internal */ - _start() { - this._abortController = new AbortController(); - const processor = new MediaStreamTrackProcessor({ track: this._track }); - const consumer = new WritableStream({ - write: (videoFrame) => { - void this._encoder.digest(videoFrame); - videoFrame.close(); - } - }); - processor.readable.pipeTo(consumer, { - signal: this._abortController.signal - }).catch((err) => { - if (err instanceof DOMException && err.name === "AbortError") return; - console.error("Pipe error:", err); - }); - } - /** @internal */ - async _flush() { - if (this._abortController) { - this._abortController.abort(); - this._abortController = null; - } - await this._encoder.flush(); - } -}; -var AudioSource = class extends MediaSource { - constructor(codec) { - super(); - /** @internal */ - this._connectedTrack = null; - if (!AUDIO_CODECS.includes(codec)) { - throw new TypeError(`Invalid audio codec '${codec}'. Must be one of: ${AUDIO_CODECS.join(", ")}.`); - } - this._codec = codec; - } -}; -var EncodedAudioChunkSource = class extends AudioSource { - constructor(codec) { - super(codec); - } - digest(chunk, meta) { - if (!(chunk instanceof EncodedAudioChunk)) { - throw new TypeError("chunk must be an EncodedAudioChunk."); - } - this._ensureValidDigest(); - return this._connectedTrack.output._muxer.addEncodedAudioChunk(this._connectedTrack, chunk, meta); - } -}; -var validateAudioCodecConfig = (config) => { - if (!config || typeof config !== "object") { - throw new TypeError("Codec config must be an object."); - } - if (!AUDIO_CODECS.includes(config.codec)) { - throw new TypeError(`Invalid audio codec '${config.codec}'. Must be one of: ${AUDIO_CODECS.join(", ")}.`); - } - if (!Number.isInteger(config.bitrate) || config.bitrate <= 0) { - throw new TypeError("config.bitrate must be a positive integer."); - } - if (config.onEncodingError !== void 0 && typeof config.onEncodingError !== "function") { - throw new TypeError("config.onEncodingError, when provided, must be a function."); - } -}; -var AudioEncoderWrapper = class { - constructor(source, codecConfig) { - this.source = source; - this.codecConfig = codecConfig; - this.encoder = null; - this.muxer = null; - this.lastNumberOfChannels = null; - this.lastSampleRate = null; - validateAudioCodecConfig(codecConfig); - } - async digest(audioData) { - 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.` - ); - } - } else { - this.lastNumberOfChannels = audioData.numberOfChannels; - this.lastSampleRate = audioData.sampleRate; - } - this.ensureEncoder(audioData); - assert(this.encoder); - this.encoder.encode(audioData); - if (this.encoder.encodeQueueSize >= 4) { - await new Promise((resolve) => this.encoder.addEventListener("dequeue", resolve, { once: true })); - } - await this.muxer.mutex.currentPromise; - } - ensureEncoder(audioData) { - if (this.encoder) { - return; - } - this.encoder = new AudioEncoder({ - output: (chunk, meta) => void this.muxer.addEncodedAudioChunk(this.source._connectedTrack, chunk, meta), - error: this.codecConfig.onEncodingError ?? ((error) => console.error("AudioEncoder error:", error)) - }); - this.encoder.configure({ - codec: buildAudioCodecString(this.codecConfig.codec, audioData.numberOfChannels, audioData.sampleRate), - numberOfChannels: audioData.numberOfChannels, - sampleRate: audioData.sampleRate, - bitrate: this.codecConfig.bitrate, - ...getAudioEncoderConfigExtension(this.codecConfig.codec) - }); - assert(this.source._connectedTrack); - this.muxer = this.source._connectedTrack.output._muxer; - } - async flush() { - if (this.encoder) { - await this.encoder.flush(); - this.encoder.close(); - } - } -}; -var AudioDataSource = class extends AudioSource { - constructor(codecConfig) { - super(codecConfig.codec); - this._encoder = new AudioEncoderWrapper(this, codecConfig); - } - digest(audioData) { - if (!(audioData instanceof AudioData)) { - throw new TypeError("audioData must be an AudioData."); - } - return this._encoder.digest(audioData); - } - /** @internal */ - _flush() { - return this._encoder.flush(); - } -}; -var AudioBufferSource = class extends AudioSource { - constructor(codecConfig) { - super(codecConfig.codec); - /** @internal */ - this._accumulatedFrameCount = 0; - this._encoder = new AudioEncoderWrapper(this, codecConfig); - } - digest(audioBuffer) { - if (!(audioBuffer instanceof AudioBuffer)) { - throw new TypeError("audioBuffer must be an AudioBuffer."); - } - const numberOfChannels = audioBuffer.numberOfChannels; - const sampleRate = audioBuffer.sampleRate; - const numberOfFrames = audioBuffer.length; - const data = new Float32Array(numberOfChannels * numberOfFrames); - for (let channel = 0; channel < numberOfChannels; channel++) { - const channelData = audioBuffer.getChannelData(channel); - data.set(channelData, channel * numberOfFrames); - } - const audioData = new AudioData({ - format: "f32-planar", - sampleRate, - numberOfFrames, - numberOfChannels, - timestamp: Math.round(1e6 * this._accumulatedFrameCount / sampleRate), - data - }); - const promise = this._encoder.digest(audioData); - audioData.close(); - this._accumulatedFrameCount += numberOfFrames; - return promise; - } - /** @internal */ - _flush() { - return this._encoder.flush(); - } -}; -var MediaStreamAudioTrackSource = class extends AudioSource { - constructor(track, codecConfig) { - if (!(track instanceof MediaStreamTrack) || track.kind !== "audio") { - throw new TypeError("track must be an audio MediaStreamTrack."); - } - super(codecConfig.codec); - /** @internal */ - this._abortController = null; - /** @internal */ - this._offsetTimestamps = true; - this._encoder = new AudioEncoderWrapper(this, codecConfig); - this._track = track; - } - /** @internal */ - _start() { - this._abortController = new AbortController(); - const processor = new MediaStreamTrackProcessor({ track: this._track }); - const consumer = new WritableStream({ - write: (audioData) => { - void this._encoder.digest(audioData); - audioData.close(); - } - }); - processor.readable.pipeTo(consumer, { - signal: this._abortController.signal - }).catch((err) => { - if (err instanceof DOMException && err.name === "AbortError") return; - console.error("Pipe error:", err); - }); - } - /** @internal */ - async _flush() { - if (this._abortController) { - this._abortController.abort(); - this._abortController = null; - } - await this._encoder.flush(); - } -}; -var SubtitleSource = class extends MediaSource { - constructor(codec) { - super(); - /** @internal */ - this._connectedTrack = null; - if (!SUBTITLE_CODECS.includes(codec)) { - throw new TypeError(`Invalid subtitle codec '${codec}'. Must be one of: ${SUBTITLE_CODECS.join(", ")}.`); - } - this._codec = codec; - } -}; -var TextSubtitleSource = class extends SubtitleSource { - constructor(codec) { - super(codec); - this._parser = new SubtitleParser({ - codec, - output: (cue, metadata) => this._connectedTrack?.output._muxer.addSubtitleCue(this._connectedTrack, cue, metadata) - }); - } - digest(text) { - if (typeof text !== "string") { - throw new TypeError("text must be a string."); - } - this._ensureValidDigest(); - this._parser.parse(text); - return this._connectedTrack.output._muxer.mutex.currentPromise; - } -}; - -// src/output.ts -var Output = class { - constructor(options) { - /** @internal */ - this._tracks = []; - /** @internal */ - this._started = false; - /** @internal */ - this._finalizing = false; - /** @internal */ - this._mutex = new AsyncMutex(); - if (!options || typeof options !== "object") { - throw new TypeError("options must be an object."); - } - if (!(options.format instanceof OutputFormat)) { - throw new TypeError("options.format must be an OutputFormat."); - } - if (!(options.target instanceof Target)) { - throw new TypeError("options.target must be a Target."); - } - if (options.target._output) { - throw new Error("Target is already used for another output."); - } - options.target._output = this; - this._writer = options.target._createWriter(); - this._muxer = options.format._createMuxer(this); - } - addVideoTrack(source, metadata = {}) { - if (!(source instanceof VideoSource)) { - throw new TypeError("source must be a VideoSource."); - } - if (!metadata || typeof metadata !== "object") { - throw new TypeError("metadata must be an object."); - } - 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)))) { - throw new TypeError(`Invalid video transformation matrix: ${metadata.rotation.join()}`); - } - if (metadata.frameRate !== void 0 && (!Number.isInteger(metadata.frameRate) || metadata.frameRate <= 0)) { - throw new TypeError( - `Invalid video frame rate: ${metadata.frameRate}. Must be a positive integer.` - ); - } - this._addTrack("video", source, metadata); - } - addAudioTrack(source, metadata = {}) { - if (!(source instanceof AudioSource)) { - throw new TypeError("source must be an AudioSource."); - } - if (!metadata || typeof metadata !== "object") { - throw new TypeError("metadata must be an object."); - } - this._addTrack("audio", source, metadata); - } - addSubtitleTrack(source, metadata = {}) { - if (!(source instanceof SubtitleSource)) { - throw new TypeError("source must be a SubtitleSource."); - } - if (!metadata || typeof metadata !== "object") { - throw new TypeError("metadata must be an object."); - } - this._addTrack("subtitle", source, metadata); - } - /** @internal */ - _addTrack(type, source, metadata) { - if (this._started) { - throw new Error("Cannot add track after output has started."); - } - if (source._connectedTrack) { - throw new Error("Source is already used for a track."); - } - const track = { - id: this._tracks.length + 1, - output: this, - type, - source, - metadata - }; - this._muxer.beforeTrackAdd(track); - this._tracks.push(track); - source._connectedTrack = track; - } - async start() { - if (this._started) { - throw new Error("Output already started."); - } - this._started = true; - this._writer.start(); - const release = await this._mutex.acquire(); - await this._muxer.start(); - for (const track of this._tracks) { - track.source._start(); - } - release(); - } - async finalize() { - if (!this._started) { - throw new Error("Cannot finalize before starting."); - } - if (this._finalizing) { - throw new Error("Cannot call finalize twice."); - } - this._finalizing = true; - const release = await this._mutex.acquire(); - const promises = this._tracks.map((x) => x.source._flush()); - await Promise.all(promises); - await this._muxer.finalize(); - await this._writer.flush(); - await this._writer.finalize(); - release(); - } -}; - -// src/source.ts -var Source = class { - constructor() { - /** @internal */ - this._sizePromise = null; - } - /** @internal */ - _getSize() { - return this._sizePromise ??= this._retrieveSize(); - } -}; -var BufferSource = class extends Source { - constructor(buffer) { - if (!(buffer instanceof ArrayBuffer) && !(buffer instanceof Uint8Array)) { - throw new TypeError("buffer must be an ArrayBuffer or Uint8Array."); - } - super(); - this._bytes = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer); - } - /** @internal */ - async _read(start, end) { - return this._bytes.subarray(start, end); - } - /** @internal */ - async _retrieveSize() { - return this._bytes.byteLength; - } -}; -var BlobSource = class extends Source { - constructor(blob) { - if (!(blob instanceof Blob)) { - throw new TypeError("blob must be a Blob."); - } - super(); - this._blob = blob; - } - /** @internal */ - async _read(start, end) { - const slice = this._blob.slice(start, end); - const buffer = await slice.arrayBuffer(); - return new Uint8Array(buffer); - } - /** @internal */ - async _retrieveSize() { - return this._blob.size; - } -}; -var UrlSource = class extends Source { - constructor(url2, options = {}) { - if (typeof url2 !== "string") { - throw new TypeError("url must be a string."); - } - if (!options || typeof options !== "object") { - throw new TypeError("options must be an object."); - } - if (options.withCredentials !== void 0 && typeof options.withCredentials !== "boolean") { - throw new TypeError("options.withCredentials, when specified, must be a boolean."); - } - super(); - /** @internal */ - this._fullData = null; - this._url = url2; - this._withCredentials = options.withCredentials ?? false; - } - /** @internal */ - _makeRequest(range) { - return new Promise((resolve, reject) => { - const xhr = new XMLHttpRequest(); - xhr.open("GET", this._url, true); - xhr.responseType = "arraybuffer"; - xhr.withCredentials = this._withCredentials; - if (range) { - xhr.setRequestHeader("Range", `bytes=${range.start}-${range.end - 1}`); - } - xhr.onload = () => { - if (xhr.status >= 200 && xhr.status < 300) { - const buffer = xhr.response; - if (!range) { - this._fullData = buffer; - } - resolve({ - response: buffer, - statusCode: xhr.status - }); - } else { - reject(new Error(`Error fetching ${this._url}: ${xhr.status} ${xhr.statusText}`)); - } - }; - xhr.onerror = () => { - reject(new Error("Network error occurred.")); - }; - xhr.ontimeout = () => { - reject(new Error("Request timed out.")); - }; - xhr.send(); - }); - } - /** @internal */ - async _read(start, end) { - if (this._fullData) { - return new Uint8Array(this._fullData, start, end - start); - } - const { response, statusCode } = await this._makeRequest({ start, end }); - if (statusCode === 200) { - const fullData = new Uint8Array(response); - return fullData.subarray(start, end); - } - return new Uint8Array(response); - } - /** @internal */ - async _retrieveSize() { - if (this._fullData) { - return this._fullData.byteLength; - } - const xhr = new XMLHttpRequest(); - xhr.open("HEAD", this._url, true); - xhr.withCredentials = this._withCredentials; - await new Promise((resolve, reject) => { - xhr.onload = () => { - if (xhr.status >= 200 && xhr.status < 300) { - resolve(); - } else { - reject(new Error(`Error fetching ${this._url} (HEAD): ${xhr.status} ${xhr.statusText}`)); - } - }; - xhr.onerror = () => { - resolve(); - }; - xhr.send(); - }); - const contentLength = xhr.getResponseHeader("Content-Length"); - if (!contentLength) { - const { response } = await this._makeRequest(); - return response.byteLength; - } - return parseInt(contentLength, 10); - } -}; - -// src/demuxer.ts -var Demuxer = class { - constructor(input) { - this.input = input; - } -}; - -// src/input-track.ts -var InputTrack = class { - /** @internal */ - constructor(backing) { - this._backing = backing; - } - isVideoTrack() { - return this instanceof InputVideoTrack; - } - isAudioTrack() { - return this instanceof InputAudioTrack; - } - computeDuration() { - return this._backing.computeDuration(); - } -}; -var InputVideoTrack = class extends InputTrack { - /** @internal */ - constructor(backing) { - super(backing); - this._backing = backing; - } - getCodec() { - return this._backing.getCodec(); - } - getCodedWidth() { - return this._backing.getCodedWidth(); - } - getCodedHeight() { - return this._backing.getCodedHeight(); - } - getRotation() { - return this._backing.getRotation(); - } - async getDisplayWidth() { - const rotation = await this._backing.getRotation(); - return rotation % 180 === 0 ? this._backing.getCodedWidth() : this._backing.getCodedHeight(); - } - async getDisplayHeight() { - const rotation = await this._backing.getRotation(); - return rotation % 180 === 0 ? this._backing.getCodedHeight() : this._backing.getCodedWidth(); - } - getDecoderConfig() { - return this._backing.getDecoderConfig(); - } - async getCodecMimeType() { - const decoderConfig = await this.getDecoderConfig(); - return decoderConfig.codec; - } -}; -var InputAudioTrack = class extends InputTrack { - /** @internal */ - constructor(backing) { - super(backing); - this._backing = backing; - } - getCodec() { - return this._backing.getCodec(); - } - getNumberOfChannels() { - return this._backing.getNumberOfChannels(); - } - getSampleRate() { - return this._backing.getSampleRate(); - } - getDecoderConfig() { - return this._backing.getDecoderConfig(); - } - async getCodecMimeType() { - const decoderConfig = await this.getDecoderConfig(); - return decoderConfig.codec; - } -}; - -// src/reader.ts -var Reader = class { - constructor(source, maxStorableBytes = Infinity) { - this.source = source; - this.maxStorableBytes = maxStorableBytes; - this.loadedSegments = []; - this.loadingSegments = []; - this.sourceSizePromise = null; - this.nextAge = 0; - this.totalStoredBytes = 0; - } - async loadRange(start, end) { - end = Math.min(end, await this.source._getSize()); - const matchingLoadingSegment = this.loadingSegments.find((x) => x.start <= start && x.end >= end); - if (matchingLoadingSegment) { - await matchingLoadingSegment.promise; - return; - } - const encasingSegmentExists = this.loadedSegments.some((x) => x.start <= start && x.end >= end); - if (encasingSegmentExists) { - return; - } - const bytesPromise = this.source._read(start, end); - const loadingSegment = { start, end, promise: bytesPromise }; - this.loadingSegments.push(loadingSegment); - const bytes2 = await bytesPromise; - removeItem(this.loadingSegments, loadingSegment); - this.insertIntoLoadedSegments(start, bytes2); - } - insertIntoLoadedSegments(start, bytes2) { - const segment = { - start, - end: start + bytes2.byteLength, - bytes: bytes2, - view: new DataView(bytes2.buffer), - age: this.nextAge++ - }; - let index = binarySearchLessOrEqual(this.loadedSegments, start, (x) => x.start); - if (index === -1 || this.loadedSegments[index].start < segment.start) { - index++; - } - this.loadedSegments.splice(index, 0, segment); - this.totalStoredBytes += bytes2.byteLength; - for (let i = index + 1; i < this.loadedSegments.length; i++) { - const otherSegment = this.loadedSegments[i]; - if (otherSegment.start >= segment.end) { - break; - } - if (segment.start <= otherSegment.start && otherSegment.end <= segment.end) { - this.loadedSegments.splice(i, 1); - i--; - } - } - while (this.totalStoredBytes > this.maxStorableBytes && this.loadedSegments.length > 1) { - let oldestSegment = null; - let oldestSegmentIndex = -1; - for (let i = 0; i < this.loadedSegments.length; i++) { - const candidate = this.loadedSegments[i]; - if (!oldestSegment || candidate.age < oldestSegment.age) { - oldestSegment = candidate; - oldestSegmentIndex = i; - } - } - assert(oldestSegment); - this.totalStoredBytes -= oldestSegment.bytes.byteLength; - this.loadedSegments.splice(oldestSegmentIndex, 1); - } - } - getViewAndOffset(start, end) { - const startIndex = binarySearchLessOrEqual(this.loadedSegments, start, (x) => x.start); - let segment = null; - if (startIndex !== -1) { - for (let i = startIndex; i < this.loadedSegments.length; i++) { - const candidate = this.loadedSegments[i]; - if (candidate.start > start) { - break; - } - if (end <= candidate.end) { - segment = candidate; - break; - } - } - } - if (!segment) { - throw new Error(`No segment loaded for range [${start}, ${end}).`); - } - segment.age = this.nextAge++; - return { - view: segment.view, - offset: segment.bytes.byteOffset + start - segment.start - }; - } - forgetRange(start, end) { - if (end <= start) { - return; - } - const startIndex = binarySearchLessOrEqual(this.loadedSegments, start, (x) => x.start); - if (startIndex === -1) { - return; - } - const segment = this.loadedSegments[startIndex]; - if (segment.start !== start || segment.end !== end) { - return; - } - this.loadedSegments.splice(startIndex, 1); - this.totalStoredBytes -= segment.bytes.byteLength; - } -}; - -// src/isobmff/isobmff-reader.ts -var IsobmffReader = class { - constructor(reader) { - this.reader = reader; - this.pos = 0; - } - readRange(start, end) { - const { view: view2, offset } = this.reader.getViewAndOffset(start, end); - return new Uint8Array(view2.buffer, offset, end - start); - } - readU8() { - const { view: view2, offset } = this.reader.getViewAndOffset(this.pos, this.pos + 1); - this.pos++; - return view2.getUint8(offset); - } - readU16() { - const { view: view2, offset } = this.reader.getViewAndOffset(this.pos, this.pos + 2); - this.pos += 2; - return view2.getUint16(offset, false); - } - readU24() { - const { view: view2, offset } = this.reader.getViewAndOffset(this.pos, this.pos + 3); - this.pos += 3; - const high = view2.getUint16(offset, false); - const low = view2.getUint8(offset + 2); - return high * 256 + low; - } - readS32() { - const { view: view2, offset } = this.reader.getViewAndOffset(this.pos, this.pos + 4); - this.pos += 4; - return view2.getInt32(offset, false); - } - readU32() { - const { view: view2, offset } = this.reader.getViewAndOffset(this.pos, this.pos + 4); - this.pos += 4; - return view2.getUint32(offset, false); - } - readI32() { - const { view: view2, offset } = this.reader.getViewAndOffset(this.pos, this.pos + 4); - this.pos += 4; - return view2.getInt32(offset, false); - } - readU64() { - const high = this.readU32(); - const low = this.readU32(); - return high * 4294967296 + low; - } - readF64() { - const { view: view2, offset } = this.reader.getViewAndOffset(this.pos, this.pos + 8); - this.pos += 8; - return view2.getFloat64(offset, false); - } - readFixed_16_16() { - return this.readS32() / 65536; - } - readFixed_2_30() { - return this.readS32() / 1073741824; - } - readAscii(length) { - const { view: view2, offset } = this.reader.getViewAndOffset(this.pos, this.pos + length); - this.pos += length; - let str = ""; - for (let i = 0; i < length; i++) { - str += String.fromCharCode(view2.getUint8(offset + i)); - } - return str; - } - readIsomVariableInteger() { - let result = 0; - for (let i = 0; i < 4; i++) { - result <<= 7; - const nextByte = this.readU8(); - result |= nextByte & 127; - if ((nextByte & 128) === 0) { - break; - } - } - return result; - } - readBoxHeader() { - let totalSize = this.readU32(); - const name = this.readAscii(4); - let headerSize = 8; - const hasLargeSize = totalSize === 1; - if (hasLargeSize) { - totalSize = this.readU64(); - headerSize = 16; - } - return { name, totalSize, headerSize, contentSize: totalSize - headerSize }; - } -}; - -// src/isobmff/isobmff-demuxer.ts -var knownMatrixes = [rotationMatrix(0), rotationMatrix(90), rotationMatrix(180), rotationMatrix(270)]; -var IsobmffDemuxer = class extends Demuxer { - constructor(input) { - super(input); - this.currentTrack = null; - this.tracks = []; - this.metadataPromise = null; - this.movieTimescale = -1; - this.movieDurationInTimescale = -1; - this.isFragmented = false; - this.fragmentTrackDefaults = []; - this.fragments = []; - this.currentFragment = null; - this.fragmentLookupMutex = new AsyncMutex(); - this.isobmffReader = new IsobmffReader(input._mainReader); - this.chunkReader = new IsobmffReader(new Reader(input._source, 64 * 2 ** 20)); - } - async computeDuration() { - const tracks = await this.getTracks(); - const trackDurations = await Promise.all(tracks.map((x) => x.computeDuration())); - return Math.max(0, ...trackDurations); - } - async getTracks() { - await this.readMetadata(); - return this.tracks.map((track) => track.inputTrack); - } - async getMimeType() { - await this.readMetadata(); - let string = "video/mp4"; - if (this.tracks.length > 0) { - const codecMimeTypes = await Promise.all(this.tracks.map((x) => x.inputTrack.getCodecMimeType())); - const uniqueCodecMimeTypes = [...new Set(codecMimeTypes)]; - string += `; codecs="${uniqueCodecMimeTypes.join(", ")}"`; - } - return string; - } - readMetadata() { - return this.metadataPromise ??= (async () => { - const sourceSize = await this.isobmffReader.reader.source._getSize(); - while (this.isobmffReader.pos < sourceSize) { - await this.isobmffReader.reader.loadRange(this.isobmffReader.pos, this.isobmffReader.pos + 16); - const startPos = this.isobmffReader.pos; - const boxInfo = this.isobmffReader.readBoxHeader(); - if (boxInfo.name === "moov") { - await this.isobmffReader.reader.loadRange( - this.isobmffReader.pos, - this.isobmffReader.pos + boxInfo.contentSize - ); - this.readContiguousBoxes(boxInfo.contentSize); - break; - } - this.isobmffReader.pos = startPos + boxInfo.totalSize; - } - if (this.isFragmented) { - await this.isobmffReader.reader.loadRange(sourceSize - 4, sourceSize); - this.isobmffReader.pos = sourceSize - 4; - const lastWord = this.isobmffReader.readU32(); - const potentialMfraPos = sourceSize - lastWord; - if (potentialMfraPos >= 0 && potentialMfraPos < sourceSize) { - await this.isobmffReader.reader.loadRange(potentialMfraPos, sourceSize); - this.isobmffReader.pos = potentialMfraPos; - const boxInfo = this.isobmffReader.readBoxHeader(); - if (boxInfo.name === "mfra") { - this.readContiguousBoxes(boxInfo.contentSize); - } - } - } - })(); - } - getSampleTableForTrack(internalTrack) { - if (internalTrack.sampleTable) { - return internalTrack.sampleTable; - } - const sampleTable = { - sampleTimingEntries: [], - sampleCompositionTimeOffsets: [], - sampleSizes: [], - keySampleIndices: null, - chunkOffsets: [], - sampleToChunk: [], - presentationTimestamps: [] - }; - internalTrack.sampleTable = sampleTable; - this.isobmffReader.pos = internalTrack.sampleTableOffset; - this.currentTrack = internalTrack; - this.traverseBox(); - this.currentTrack = null; - for (const entry of sampleTable.sampleTimingEntries) { - for (let i = 0; i < entry.count; i++) { - sampleTable.presentationTimestamps.push({ - presentationTimestamp: entry.startDecodeTimestamp + i * entry.delta, - sampleIndex: entry.startIndex + i - }); - } - } - for (const entry of sampleTable.sampleCompositionTimeOffsets) { - for (let i = 0; i < entry.count; i++) { - const sampleIndex = entry.startIndex + i; - const sample = sampleTable.presentationTimestamps[sampleIndex]; - if (!sample) { - continue; - } - sample.presentationTimestamp += entry.offset; - } - } - sampleTable.presentationTimestamps.sort((a, b) => a.presentationTimestamp - b.presentationTimestamp); - return internalTrack.sampleTable; - } - async readFragment() { - const startPos = this.isobmffReader.pos; - await this.isobmffReader.reader.loadRange(this.isobmffReader.pos, this.isobmffReader.pos + 16); - const moofBoxInfo = this.isobmffReader.readBoxHeader(); - assert(moofBoxInfo.name === "moof"); - await this.isobmffReader.reader.loadRange(startPos, startPos + moofBoxInfo.totalSize); - this.isobmffReader.pos = startPos; - this.traverseBox(); - const index = binarySearchExact(this.fragments, startPos, (x) => x.moofOffset); - assert(index !== -1); - const fragment = this.fragments[index]; - assert(fragment.moofOffset === startPos); - this.isobmffReader.reader.forgetRange(startPos, startPos + moofBoxInfo.totalSize); - for (const [trackId, trackData] of fragment.trackData) { - if (trackData.startTimestampIsFinal) { - continue; - } - const internalTrack = this.tracks.find((x) => x.id === trackId); - this.isobmffReader.pos = 0; - let currentFragment = null; - let lastFragment = null; - const index2 = binarySearchLessOrEqual( - internalTrack.fragments, - startPos - 1, - (x) => x.moofOffset - ); - if (index2 !== -1) { - currentFragment = internalTrack.fragments[index2]; - lastFragment = currentFragment; - this.isobmffReader.pos = currentFragment.moofOffset + currentFragment.moofSize; - } - while (this.isobmffReader.pos < startPos) { - if (currentFragment?.nextFragment) { - currentFragment = currentFragment.nextFragment; - this.isobmffReader.pos = currentFragment.moofOffset + currentFragment.moofSize; - } else { - await this.isobmffReader.reader.loadRange(this.isobmffReader.pos, this.isobmffReader.pos + 16); - const startPos2 = this.isobmffReader.pos; - const boxInfo = this.isobmffReader.readBoxHeader(); - if (boxInfo.name === "moof") { - const index3 = binarySearchExact(this.fragments, startPos2, (x) => x.moofOffset); - if (index3 === -1) { - this.isobmffReader.pos = startPos2; - const fragment2 = await this.readFragment(); - if (currentFragment) currentFragment.nextFragment = fragment2; - currentFragment = fragment2; - } else { - const fragment2 = this.fragments[index3]; - if (currentFragment) currentFragment.nextFragment = fragment2; - currentFragment = fragment2; - } - } - this.isobmffReader.pos = startPos2 + boxInfo.totalSize; - } - if (currentFragment && currentFragment.trackData.has(trackId)) { - lastFragment = currentFragment; - } - } - if (lastFragment) { - const otherTrackData = lastFragment.trackData.get(trackId); - assert(otherTrackData.startTimestampIsFinal); - offsetFragmentTrackDataByTimestamp(trackData, otherTrackData.endTimestamp); - } - trackData.startTimestampIsFinal = true; - } - return fragment; - } - readContiguousBoxes(totalSize) { - const startIndex = this.isobmffReader.pos; - while (this.isobmffReader.pos - startIndex < totalSize) { - this.traverseBox(); - } - } - traverseBox() { - const startPos = this.isobmffReader.pos; - const boxInfo = this.isobmffReader.readBoxHeader(); - const boxEndPos = startPos + boxInfo.totalSize; - switch (boxInfo.name) { - case "mdia": - case "minf": - case "dinf": - case "mfra": - { - this.readContiguousBoxes(boxInfo.contentSize); - } - ; - break; - case "mvhd": - { - const version = this.isobmffReader.readU8(); - this.isobmffReader.pos += 3; - if (version === 1) { - this.isobmffReader.pos += 8 + 8; - this.movieTimescale = this.isobmffReader.readU32(); - this.movieDurationInTimescale = this.isobmffReader.readU64(); - } else { - this.isobmffReader.pos += 4 + 4; - this.movieTimescale = this.isobmffReader.readU32(); - this.movieDurationInTimescale = this.isobmffReader.readU32(); - } - } - ; - break; - case "trak": - { - const track = { - id: -1, - demuxer: this, - inputTrack: null, - info: null, - timescale: -1, - durationInTimescale: -1, - rotation: 0, - sampleTableOffset: -1, - sampleTable: null, - fragmentLookupTable: null, - currentFragmentState: null, - fragments: [] - }; - this.currentTrack = track; - this.readContiguousBoxes(boxInfo.contentSize); - if (track.id !== -1 && track.timescale !== -1 && track.info !== null) { - if (track.info.type === "video" && track.info.codec !== null) { - const videoTrack = track; - track.inputTrack = new InputVideoTrack(new IsobmffVideoTrackBacking(videoTrack)); - this.tracks.push(track); - } else if (track.info.type === "audio" && track.info.codec !== null) { - const audioTrack = track; - track.inputTrack = new InputAudioTrack(new IsobmffAudioTrackBacking(audioTrack)); - this.tracks.push(track); - if (track.info.codec === "aac") { - const audioSpecificConfig = parseAacAudioSpecificConfig(track.info.codecDescription); - if (audioSpecificConfig.numberOfChannels !== null) { - track.info.numberOfChannels = audioSpecificConfig.numberOfChannels; - } - if (audioSpecificConfig.sampleRate !== null) { - track.info.sampleRate = audioSpecificConfig.sampleRate; - } - } - } - } - this.currentTrack = null; - } - ; - break; - case "tkhd": - { - const track = this.currentTrack; - assert(track); - const version = this.isobmffReader.readU8(); - const flags = this.isobmffReader.readU24(); - const trackEnabled = (flags & 1) !== 0; - if (!trackEnabled) { - break; - } - if (version === 0) { - this.isobmffReader.pos += 8; - track.id = this.isobmffReader.readU32(); - this.isobmffReader.pos += 8; - } else if (version === 1) { - this.isobmffReader.pos += 16; - track.id = this.isobmffReader.readU32(); - this.isobmffReader.pos += 12; - } else { - throw new Error(`Incorrect track header version ${version}.`); - } - this.isobmffReader.pos += 2 * 4 + 2 + 2 + 2 + 2; - const rotationMatrix2 = []; - rotationMatrix2.push(this.isobmffReader.readFixed_16_16(), this.isobmffReader.readFixed_16_16()); - this.isobmffReader.pos += 4; - rotationMatrix2.push(this.isobmffReader.readFixed_16_16(), this.isobmffReader.readFixed_16_16()); - const matrixIndex = knownMatrixes.findIndex((x) => x.every((y, i) => y === rotationMatrix2[i])); - if (matrixIndex === -1) { - track.rotation = 0; - } else { - track.rotation = 90 * matrixIndex; - } - } - ; - break; - case "mdhd": - { - const track = this.currentTrack; - assert(track); - const version = this.isobmffReader.readU8(); - this.isobmffReader.pos += 3; - if (version === 0) { - this.isobmffReader.pos += 8; - track.timescale = this.isobmffReader.readU32(); - track.durationInTimescale = this.isobmffReader.readU32(); - } else if (version === 1) { - this.isobmffReader.pos += 16; - track.timescale = this.isobmffReader.readU32(); - track.durationInTimescale = this.isobmffReader.readU64(); - } - } - ; - break; - case "hdlr": - { - const track = this.currentTrack; - assert(track); - this.isobmffReader.pos += 8; - const handlerType = this.isobmffReader.readAscii(4); - if (handlerType === "vide") { - track.info = { - type: "video", - width: -1, - height: -1, - codec: null, - codecDescription: null, - colorSpace: null - }; - } else if (handlerType === "soun") { - track.info = { - type: "audio", - numberOfChannels: -1, - sampleRate: -1, - codec: null, - codecDescription: null - }; - } - } - ; - break; - case "stbl": - { - const track = this.currentTrack; - assert(track); - track.sampleTableOffset = startPos; - this.readContiguousBoxes(boxInfo.contentSize); - } - ; - break; - case "stsd": - { - const track = this.currentTrack; - assert(track); - if (track.info === null || track.sampleTable) { - break; - } - const stsdVersion = this.isobmffReader.readU8(); - this.isobmffReader.pos += 3; - const entries = this.isobmffReader.readU32(); - for (let i = 0; i < entries; i++) { - const sampleBoxInfo = this.isobmffReader.readBoxHeader(); - if (track.info.type === "video") { - if (sampleBoxInfo.name === "avc1") { - track.info.codec = "avc"; - } else if (sampleBoxInfo.name === "hvc1" || sampleBoxInfo.name === "hev1") { - track.info.codec = "hevc"; - } else { - console.warn(`Unsupported video sample entry type ${sampleBoxInfo.name}.`); - break; - } - this.isobmffReader.pos += 6 * 1 + 2 + 2 + 2 + 3 * 4; - track.info.width = this.isobmffReader.readU16(); - track.info.height = this.isobmffReader.readU16(); - this.isobmffReader.pos += 4 + 4 + 4 + 2 + 32 + 2 + 2; - this.readContiguousBoxes(startPos + sampleBoxInfo.totalSize - this.isobmffReader.pos); - } else { - if (sampleBoxInfo.name === "mp4a") { - track.info.codec = "aac"; - } else if (sampleBoxInfo.name.toLowerCase() === "opus") { - track.info.codec = "opus"; - } else { - console.warn(`Unsupported audio sample entry type ${sampleBoxInfo.name}.`); - break; - } - this.isobmffReader.pos += 6 * 1 + 2; - const version = this.isobmffReader.readU16(); - this.isobmffReader.pos += 3 * 2; - let channelCount = this.isobmffReader.readU16(); - this.isobmffReader.pos += 2 + 2 + 2; - let sampleRate = this.isobmffReader.readU32() / 65536; - if (stsdVersion === 0 && version > 0) { - if (version === 1) { - this.isobmffReader.pos += 4 * 4; - } else if (version === 2) { - this.isobmffReader.pos += 4; - sampleRate = this.isobmffReader.readF64(); - channelCount = this.isobmffReader.readU32(); - this.isobmffReader.pos += 4; - const sampleSize = this.isobmffReader.readU32(); - const flags = this.isobmffReader.readU32(); - const bytesPerFrame = this.isobmffReader.readU32(); - const samplesPerFrame = this.isobmffReader.readU32(); - } - } - track.info.numberOfChannels = channelCount; - track.info.sampleRate = sampleRate; - this.readContiguousBoxes(startPos + sampleBoxInfo.totalSize - this.isobmffReader.pos); - } - } - } - ; - break; - case "avcC": - { - const track = this.currentTrack; - assert(track && track.info); - track.info.codecDescription = this.isobmffReader.readRange( - this.isobmffReader.pos, - this.isobmffReader.pos + boxInfo.contentSize - ); - } - ; - break; - case "hvcC": - { - const track = this.currentTrack; - assert(track && track.info); - track.info.codecDescription = this.isobmffReader.readRange( - this.isobmffReader.pos, - this.isobmffReader.pos + boxInfo.contentSize - ); - } - ; - break; - case "colr": - { - const track = this.currentTrack; - assert(track && track.info?.type === "video"); - const colourType = this.isobmffReader.readAscii(4); - if (colourType !== "nclx") { - break; - } - const colourPrimaries = this.isobmffReader.readU16(); - const transferCharacteristics = this.isobmffReader.readU16(); - const matrixCoefficients = this.isobmffReader.readU16(); - const fullRangeFlag = Boolean(this.isobmffReader.readU8() & 128); - track.info.colorSpace = { - primaries: COLOR_PRIMARIES_MAP_INVERSE[colourPrimaries], - transfer: TRANSFER_CHARACTERISTICS_MAP_INVERSE[transferCharacteristics], - matrix: MATRIX_COEFFICIENTS_MAP_INVERSE[matrixCoefficients], - fullRange: fullRangeFlag - }; - } - ; - break; - case "wave": - { - if (boxInfo.totalSize > 8) { - this.readContiguousBoxes(boxInfo.contentSize); - } - } - ; - break; - case "esds": - { - const track = this.currentTrack; - assert(track && track.info); - this.isobmffReader.pos += 4; - const tag = this.isobmffReader.readU8(); - assert(tag === 3); - this.isobmffReader.readIsomVariableInteger(); - this.isobmffReader.pos += 2; - const mixed = this.isobmffReader.readU8(); - const streamDependenceFlag = (mixed & 128) !== 0; - const urlFlag = (mixed & 64) !== 0; - const ocrStreamFlag = (mixed & 32) !== 0; - if (streamDependenceFlag) { - this.isobmffReader.pos += 2; - } - if (urlFlag) { - const urlLength = this.isobmffReader.readU8(); - this.isobmffReader.pos += urlLength; - } - if (ocrStreamFlag) { - this.isobmffReader.pos += 2; - } - const decoderConfigTag = this.isobmffReader.readU8(); - assert(decoderConfigTag === 4); - this.isobmffReader.readIsomVariableInteger(); - const objectTypeIndication = this.isobmffReader.readU8(); - assert(objectTypeIndication === 64); - this.isobmffReader.pos += 1 + 3 + 4 + 4; - const decoderSpecificInfoTag = this.isobmffReader.readU8(); - assert(decoderSpecificInfoTag === 5); - const decoderSpecificInfoLength = this.isobmffReader.readIsomVariableInteger(); - track.info.codecDescription = this.isobmffReader.readRange( - this.isobmffReader.pos, - this.isobmffReader.pos + decoderSpecificInfoLength - ); - } - ; - break; - case "stts": - { - const track = this.currentTrack; - assert(track); - if (!track.sampleTable) { - break; - } - this.isobmffReader.pos += 4; - const entryCount = this.isobmffReader.readU32(); - let currentIndex = 0; - let currentTimestamp = 0; - for (let i = 0; i < entryCount; i++) { - const sampleCount = this.isobmffReader.readU32(); - const sampleDelta = this.isobmffReader.readU32(); - track.sampleTable.sampleTimingEntries.push({ - startIndex: currentIndex, - startDecodeTimestamp: currentTimestamp, - count: sampleCount, - delta: sampleDelta - }); - currentIndex += sampleCount; - currentTimestamp += sampleCount * sampleDelta; - } - } - ; - break; - case "ctts": - { - const track = this.currentTrack; - assert(track); - if (!track.sampleTable) { - break; - } - this.isobmffReader.pos += 1 + 3; - const entryCount = this.isobmffReader.readU32(); - let sampleIndex = 0; - for (let i = 0; i < entryCount; i++) { - const sampleCount = this.isobmffReader.readU32(); - const sampleOffset = this.isobmffReader.readI32(); - track.sampleTable.sampleCompositionTimeOffsets.push({ - startIndex: sampleIndex, - count: sampleCount, - offset: sampleOffset - }); - sampleIndex += sampleCount; - } - } - ; - break; - case "stsz": - { - const track = this.currentTrack; - assert(track); - if (!track.sampleTable) { - break; - } - this.isobmffReader.pos += 4; - const sampleSize = this.isobmffReader.readU32(); - const sampleCount = this.isobmffReader.readU32(); - if (sampleSize === 0) { - for (let i = 0; i < sampleCount; i++) { - const sampleSize2 = this.isobmffReader.readU32(); - track.sampleTable.sampleSizes.push(sampleSize2); - } - } else { - track.sampleTable.sampleSizes.push(sampleSize); - } - } - ; - break; - case "stz2": - { - throw new Error("Unsupported."); - } - ; - case "stss": - { - const track = this.currentTrack; - assert(track); - if (!track.sampleTable) { - break; - } - this.isobmffReader.pos += 4; - track.sampleTable.keySampleIndices = []; - const entryCount = this.isobmffReader.readU32(); - for (let i = 0; i < entryCount; i++) { - const sampleIndex = this.isobmffReader.readU32() - 1; - track.sampleTable.keySampleIndices.push(sampleIndex); - } - } - ; - break; - case "stsc": - { - const track = this.currentTrack; - assert(track); - if (!track.sampleTable) { - break; - } - this.isobmffReader.pos += 4; - const entryCount = this.isobmffReader.readU32(); - for (let i = 0; i < entryCount; i++) { - const startChunkIndex = this.isobmffReader.readU32() - 1; - const samplesPerChunk = this.isobmffReader.readU32(); - const sampleDescriptionIndex = this.isobmffReader.readU32(); - track.sampleTable.sampleToChunk.push({ - startSampleIndex: -1, - startChunkIndex, - samplesPerChunk, - sampleDescriptionIndex - }); - } - let startSampleIndex = 0; - for (let i = 0; i < track.sampleTable.sampleToChunk.length; i++) { - track.sampleTable.sampleToChunk[i].startSampleIndex = startSampleIndex; - if (i < track.sampleTable.sampleToChunk.length - 1) { - const nextChunk = track.sampleTable.sampleToChunk[i + 1]; - const chunkCount = nextChunk.startChunkIndex - track.sampleTable.sampleToChunk[i].startChunkIndex; - startSampleIndex += chunkCount * track.sampleTable.sampleToChunk[i].samplesPerChunk; - } - } - } - ; - break; - case "stco": - { - const track = this.currentTrack; - assert(track); - if (!track.sampleTable) { - break; - } - this.isobmffReader.pos += 4; - const entryCount = this.isobmffReader.readU32(); - for (let i = 0; i < entryCount; i++) { - const chunkOffset = this.isobmffReader.readU32(); - track.sampleTable.chunkOffsets.push(chunkOffset); - } - } - ; - break; - case "co64": - { - const track = this.currentTrack; - assert(track); - if (!track.sampleTable) { - break; - } - this.isobmffReader.pos += 4; - const entryCount = this.isobmffReader.readU32(); - for (let i = 0; i < entryCount; i++) { - const chunkOffset = this.isobmffReader.readU64(); - track.sampleTable.chunkOffsets.push(chunkOffset); - } - } - ; - break; - case "mvex": - { - this.isFragmented = true; - this.readContiguousBoxes(boxInfo.contentSize); - } - ; - break; - case "mehd": - { - const version = this.isobmffReader.readU8(); - this.isobmffReader.pos += 3; - const fragmentDuration = version === 1 ? this.isobmffReader.readU64() : this.isobmffReader.readU32(); - this.movieDurationInTimescale = fragmentDuration; - } - ; - break; - case "trex": - { - this.isobmffReader.pos += 4; - const trackId = this.isobmffReader.readU32(); - const defaultSampleDescriptionIndex = this.isobmffReader.readU32(); - const defaultSampleDuration = this.isobmffReader.readU32(); - const defaultSampleSize = this.isobmffReader.readU32(); - const defaultSampleFlags = this.isobmffReader.readU32(); - this.fragmentTrackDefaults.push({ - trackId, - defaultSampleDescriptionIndex, - defaultSampleDuration, - defaultSampleSize, - defaultSampleFlags - }); - } - ; - break; - case "tfra": - { - const version = this.isobmffReader.readU8(); - this.isobmffReader.pos += 3; - const trackId = this.isobmffReader.readU32(); - const track = this.tracks.find((x2) => x2.id === trackId); - if (!track) { - break; - } - track.fragmentLookupTable = []; - const word = this.isobmffReader.readU32(); - const lengthSizeOfTrafNum = (word & 48) >> 4; - const lengthSizeOfTrunNum = (word & 12) >> 2; - const lengthSizeOfSampleNum = word & 3; - const x = this.isobmffReader; - const functions = [x.readU8.bind(x), x.readU16.bind(x), x.readU24.bind(x), x.readU32.bind(x)]; - const readTrafNum = functions[lengthSizeOfTrafNum]; - const readTrunNum = functions[lengthSizeOfTrunNum]; - const readSampleNum = functions[lengthSizeOfSampleNum]; - const numberOfEntries = this.isobmffReader.readU32(); - for (let i = 0; i < numberOfEntries; i++) { - const time = version === 1 ? this.isobmffReader.readU64() : this.isobmffReader.readU32(); - const moofOffset = version === 1 ? this.isobmffReader.readU64() : this.isobmffReader.readU32(); - const trafNumber = readTrafNum(); - const trunNumber = readTrunNum(); - const sampleNumber = readSampleNum(); - track.fragmentLookupTable.push({ - timestamp: time, - moofOffset - }); - } - } - ; - break; - case "moof": - { - this.currentFragment = { - moofOffset: startPos, - moofSize: boxInfo.totalSize, - implicitBaseDataOffset: startPos, - trackData: /* @__PURE__ */ new Map(), - dataStart: Infinity, - dataEnd: 0, - nextFragment: null - }; - this.readContiguousBoxes(boxInfo.contentSize); - const insertionIndex = binarySearchLessOrEqual( - this.fragments, - this.currentFragment.moofOffset, - (x) => x.moofOffset - ); - this.fragments.splice(insertionIndex + 1, 0, this.currentFragment); - for (const [, trackData] of this.currentFragment.trackData) { - const firstSample = trackData.samples[0]; - const lastSample = last(trackData.samples); - this.currentFragment.dataStart = Math.min( - this.currentFragment.dataStart, - firstSample.byteOffset - ); - this.currentFragment.dataEnd = Math.max( - this.currentFragment.dataEnd, - lastSample.byteOffset + lastSample.byteSize - ); - } - this.currentFragment = null; - } - ; - break; - case "traf": - { - assert(this.currentFragment); - this.readContiguousBoxes(boxInfo.contentSize); - if (this.currentTrack) { - const trackData = this.currentFragment.trackData.get(this.currentTrack.id); - if (trackData) { - const insertionIndex = binarySearchLessOrEqual( - this.currentTrack.fragments, - this.currentFragment.moofOffset, - (x) => x.moofOffset - ); - this.currentTrack.fragments.splice(insertionIndex + 1, 0, this.currentFragment); - const { currentFragmentState } = this.currentTrack; - assert(currentFragmentState); - if (currentFragmentState.startTimestamp !== null) { - offsetFragmentTrackDataByTimestamp(trackData, currentFragmentState.startTimestamp); - trackData.startTimestampIsFinal = true; - } - } - this.currentTrack.currentFragmentState = null; - this.currentTrack = null; - } - } - ; - break; - case "tfhd": - { - assert(this.currentFragment); - this.isobmffReader.pos += 1; - const flags = this.isobmffReader.readU24(); - const baseDataOffsetPresent = Boolean(flags & 1); - const sampleDescriptionIndexPresent = Boolean(flags & 2); - const defaultSampleDurationPresent = Boolean(flags & 8); - const defaultSampleSizePresent = Boolean(flags & 16); - const defaultSampleFlagsPresent = Boolean(flags & 32); - const durationIsEmpty = Boolean(flags & 65536); - const defaultBaseIsMoof = Boolean(flags & 131072); - const trackId = this.isobmffReader.readU32(); - const track = this.tracks.find((x) => x.id === trackId); - if (!track) { - break; - } - const defaults = this.fragmentTrackDefaults.find((x) => x.trackId === trackId); - this.currentTrack = track; - track.currentFragmentState = { - baseDataOffset: this.currentFragment.implicitBaseDataOffset, - sampleDescriptionIndex: defaults?.defaultSampleDescriptionIndex ?? null, - defaultSampleDuration: defaults?.defaultSampleDuration ?? null, - defaultSampleSize: defaults?.defaultSampleSize ?? null, - defaultSampleFlags: defaults?.defaultSampleFlags ?? null, - startTimestamp: null - }; - if (baseDataOffsetPresent) { - track.currentFragmentState.baseDataOffset = this.isobmffReader.readU64(); - } else if (defaultBaseIsMoof) { - track.currentFragmentState.baseDataOffset = this.currentFragment.moofOffset; - } - if (sampleDescriptionIndexPresent) { - track.currentFragmentState.sampleDescriptionIndex = this.isobmffReader.readU32(); - } - if (defaultSampleDurationPresent) { - track.currentFragmentState.defaultSampleDuration = this.isobmffReader.readU32(); - } - if (defaultSampleSizePresent) { - track.currentFragmentState.defaultSampleSize = this.isobmffReader.readU32(); - } - if (defaultSampleFlagsPresent) { - track.currentFragmentState.defaultSampleFlags = this.isobmffReader.readU32(); - } - if (durationIsEmpty) { - track.currentFragmentState.defaultSampleDuration = 0; - } - } - ; - break; - case "tfdt": - { - const track = this.currentTrack; - if (!track) { - break; - } - assert(track.currentFragmentState); - const version = this.isobmffReader.readU8(); - this.isobmffReader.pos += 3; - const baseMediaDecodeTime = version === 0 ? this.isobmffReader.readU32() : this.isobmffReader.readU64(); - track.currentFragmentState.startTimestamp = baseMediaDecodeTime; - } - ; - break; - case "trun": - { - const track = this.currentTrack; - if (!track) { - break; - } - assert(this.currentFragment); - assert(track.currentFragmentState); - if (this.currentFragment.trackData.has(track.id)) { - throw new Error("Can't have two trun boxes for the same track in one fragment."); - } - const version = this.isobmffReader.readU8(); - const flags = this.isobmffReader.readU24(); - const dataOffsetPresent = Boolean(flags & 1); - const firstSampleFlagsPresent = Boolean(flags & 4); - const sampleDurationPresent = Boolean(flags & 256); - const sampleSizePresent = Boolean(flags & 512); - const sampleFlagsPresent = Boolean(flags & 1024); - const sampleCompositionTimeOffsetsPresent = Boolean(flags & 2048); - const sampleCount = this.isobmffReader.readU32(); - let dataOffset = track.currentFragmentState.baseDataOffset; - if (dataOffsetPresent) { - dataOffset += this.isobmffReader.readI32(); - } - let firstSampleFlags = null; - if (firstSampleFlagsPresent) { - firstSampleFlags = this.isobmffReader.readU32(); - } - let currentOffset = dataOffset; - if (sampleCount === 0) { - this.currentFragment.implicitBaseDataOffset = currentOffset; - break; - } - let currentTimestamp = 0; - const trackData = { - startTimestamp: 0, - endTimestamp: 0, - samples: [], - presentationTimestamps: [], - startTimestampIsFinal: false - }; - this.currentFragment.trackData.set(track.id, trackData); - for (let i = 0; i < sampleCount; i++) { - let sampleDuration; - if (sampleDurationPresent) { - sampleDuration = this.isobmffReader.readU32(); - } else { - assert(track.currentFragmentState.defaultSampleDuration !== null); - sampleDuration = track.currentFragmentState.defaultSampleDuration; - } - let sampleSize; - if (sampleSizePresent) { - sampleSize = this.isobmffReader.readU32(); - } else { - assert(track.currentFragmentState.defaultSampleSize !== null); - sampleSize = track.currentFragmentState.defaultSampleSize; - } - let sampleFlags; - if (sampleFlagsPresent) { - sampleFlags = this.isobmffReader.readU32(); - } else { - assert(track.currentFragmentState.defaultSampleFlags !== null); - sampleFlags = track.currentFragmentState.defaultSampleFlags; - } - if (i === 0 && firstSampleFlags !== null) { - sampleFlags = firstSampleFlags; - } - let sampleCompositionTimeOffset = 0; - if (sampleCompositionTimeOffsetsPresent) { - if (version === 0) { - sampleCompositionTimeOffset = this.isobmffReader.readU32(); - } else { - sampleCompositionTimeOffset = this.isobmffReader.readI32(); - } - } - const isKeyFrame = !(sampleFlags & 65536); - trackData.samples.push({ - presentationTimestamp: currentTimestamp + sampleCompositionTimeOffset, - duration: sampleDuration, - byteOffset: currentOffset, - byteSize: sampleSize, - isKeyFrame - }); - currentOffset += sampleSize; - currentTimestamp += sampleDuration; - } - trackData.presentationTimestamps = trackData.samples.map((x, i) => ({ presentationTimestamp: x.presentationTimestamp, sampleIndex: i })).sort((a, b) => a.presentationTimestamp - b.presentationTimestamp); - const firstSample = trackData.samples[trackData.presentationTimestamps[0].sampleIndex]; - const lastSample = trackData.samples[last(trackData.presentationTimestamps).sampleIndex]; - trackData.startTimestamp = firstSample.presentationTimestamp; - trackData.endTimestamp = lastSample.presentationTimestamp + lastSample.duration; - this.currentFragment.implicitBaseDataOffset = currentOffset; - } - ; - break; - } - this.isobmffReader.pos = boxEndPos; - } -}; -var IsobmffTrackBacking = class { - constructor(internalTrack) { - this.internalTrack = internalTrack; - this.chunkToSampleIndex = /* @__PURE__ */ new WeakMap(); - this.chunkToFragmentLocation = /* @__PURE__ */ new WeakMap(); - } - getCodec() { - throw new Error("Not implemented on base class."); - } - async computeDuration() { - const lastChunk = await this.getChunk(Infinity, { metadataOnly: true }); - const timestamp = lastChunk?.timestamp; - return timestamp ? timestamp / 1e6 : 0; - } - async getFirstChunk(options) { - if (this.internalTrack.demuxer.isFragmented) { - return this.performFragmentedLookup( - () => { - const fragment = this.internalTrack.fragments[0]; - return { - fragmentIndex: fragment ? 0 : -1, - sampleIndex: fragment ? 0 : -1, - correctSampleFound: !!fragment - }; - }, - 0, - Infinity, - options - ); - } - return this.fetchChunkForSampleIndex(0, options); - } - roundToMicrosecond(timestamp) { - return (Math.floor(timestamp * 1e6) + 0.99999999) / 1e6; - } - async getChunk(timestamp, options) { - timestamp = this.roundToMicrosecond(timestamp); - const timestampInTimescale = timestamp * this.internalTrack.timescale; - if (this.internalTrack.demuxer.isFragmented) { - return this.performFragmentedLookup( - () => this.findSampleInFragmentsForTimestamp(timestampInTimescale), - timestampInTimescale, - timestampInTimescale, - options - ); - } else { - const sampleTable = this.internalTrack.demuxer.getSampleTableForTrack(this.internalTrack); - const sampleIndex = getSampleIndexForTimestamp(sampleTable, timestampInTimescale); - return this.fetchChunkForSampleIndex(sampleIndex, options); - } - } - async getNextChunk(chunk, options) { - if (this.internalTrack.demuxer.isFragmented) { - const locationInFragment = this.chunkToFragmentLocation.get(chunk); - if (locationInFragment === void 0) { - throw new Error("Chunk was not created from this track."); - } - const trackData = locationInFragment.fragment.trackData.get(this.internalTrack.id); - const sample = trackData.samples[locationInFragment.sampleIndex]; - const fragmentIndex = binarySearchExact( - this.internalTrack.fragments, - locationInFragment.fragment.moofOffset, - (x) => x.moofOffset - ); - assert(fragmentIndex !== -1); - return this.performFragmentedLookup( - () => { - if (locationInFragment.sampleIndex + 1 < trackData.samples.length) { - return { - fragmentIndex, - sampleIndex: locationInFragment.sampleIndex + 1, - correctSampleFound: true - }; - } else { - let currentFragment = locationInFragment.fragment; - while (currentFragment.nextFragment) { - currentFragment = currentFragment.nextFragment; - const trackData2 = currentFragment.trackData.get(this.internalTrack.id); - if (trackData2) { - const fragmentIndex2 = binarySearchExact( - this.internalTrack.fragments, - currentFragment.moofOffset, - (x) => x.moofOffset - ); - assert(fragmentIndex2 !== -1); - return { - fragmentIndex: fragmentIndex2, - sampleIndex: 0, - correctSampleFound: true - }; - } - } - return { - fragmentIndex, - sampleIndex: -1, - correctSampleFound: false - }; - } - }, - sample.presentationTimestamp, - Infinity, - options - ); - } - const sampleIndex = this.chunkToSampleIndex.get(chunk); - if (sampleIndex === void 0) { - throw new Error("Chunk was not created from this track."); - } - return this.fetchChunkForSampleIndex(sampleIndex + 1, options); - } - async getKeyChunk(timestamp, options) { - timestamp = this.roundToMicrosecond(timestamp); - const timestampInTimescale = timestamp * this.internalTrack.timescale; - if (this.internalTrack.demuxer.isFragmented) { - return this.performFragmentedLookup( - () => this.findKeySampleInFragmentsForTimestamp(timestampInTimescale), - timestampInTimescale, - timestampInTimescale, - options - ); - } - const sampleTable = this.internalTrack.demuxer.getSampleTableForTrack(this.internalTrack); - const sampleIndex = getSampleIndexForTimestamp(sampleTable, timestampInTimescale); - const keyFrameSampleIndex = sampleIndex === -1 ? -1 : getRelevantKeyframeIndexForSample(sampleTable, sampleIndex); - return this.fetchChunkForSampleIndex(keyFrameSampleIndex, options); - } - async getNextKeyChunk(chunk, options) { - if (this.internalTrack.demuxer.isFragmented) { - const locationInFragment = this.chunkToFragmentLocation.get(chunk); - if (locationInFragment === void 0) { - throw new Error("Chunk was not created from this track."); - } - const trackData = locationInFragment.fragment.trackData.get(this.internalTrack.id); - const sample = trackData.samples[locationInFragment.sampleIndex]; - const fragmentIndex = binarySearchExact( - this.internalTrack.fragments, - locationInFragment.fragment.moofOffset, - (x) => x.moofOffset - ); - assert(fragmentIndex !== -1); - return this.performFragmentedLookup( - () => { - const nextKeyFrameIndex = trackData.samples.findIndex( - (x, i) => x.isKeyFrame && i > locationInFragment.sampleIndex - ); - if (nextKeyFrameIndex !== -1) { - return { - fragmentIndex, - sampleIndex: nextKeyFrameIndex, - correctSampleFound: true - }; - } else { - let currentFragment = locationInFragment.fragment; - while (currentFragment.nextFragment) { - currentFragment = currentFragment.nextFragment; - const trackData2 = currentFragment.trackData.get(this.internalTrack.id); - if (trackData2) { - const fragmentIndex2 = binarySearchExact( - this.internalTrack.fragments, - currentFragment.moofOffset, - (x) => x.moofOffset - ); - assert(fragmentIndex2 !== -1); - const keyFrameIndex = trackData2.samples.findIndex((x) => x.isKeyFrame); - if (keyFrameIndex === -1) { - throw new Error("Not supported: Fragment does not contain key sample."); - } - return { - fragmentIndex: fragmentIndex2, - sampleIndex: keyFrameIndex, - correctSampleFound: true - }; - } - } - return { - fragmentIndex, - sampleIndex: -1, - correctSampleFound: false - }; - } - }, - sample.presentationTimestamp, - Infinity, - options - ); - } - const sampleIndex = this.chunkToSampleIndex.get(chunk); - if (sampleIndex === void 0) { - throw new Error("Chunk was not created from this track."); - } - const sampleTable = this.internalTrack.demuxer.getSampleTableForTrack(this.internalTrack); - const nextKeyFrameSampleIndex = getNextKeyframeIndexForSample(sampleTable, sampleIndex); - return this.fetchChunkForSampleIndex(nextKeyFrameSampleIndex, options); - } - async fetchChunkForSampleIndex(sampleIndex, options) { - if (sampleIndex === -1) { - return null; - } - const sampleTable = this.internalTrack.demuxer.getSampleTableForTrack(this.internalTrack); - const sampleInfo = getSampleInfo(sampleTable, sampleIndex); - if (!sampleInfo) { - return null; - } - let data; - if (options.metadataOnly) { - data = new Uint8Array(0); - } else { - await this.internalTrack.demuxer.chunkReader.reader.loadRange( - sampleInfo.chunkOffset, - sampleInfo.chunkOffset + sampleInfo.chunkSize - ); - data = this.internalTrack.demuxer.chunkReader.readRange( - sampleInfo.sampleOffset, - sampleInfo.sampleOffset + sampleInfo.sampleSize - ); - } - const timestamp = 1e6 * sampleInfo.presentationTimestamp / this.internalTrack.timescale; - const duration = 1e6 * sampleInfo.duration / this.internalTrack.timescale; - const chunk = this.createChunk(data, timestamp, duration, sampleInfo.isKeyFrame); - this.chunkToSampleIndex.set(chunk, sampleIndex); - return chunk; - } - async fetchChunkInFragment(fragment, sampleIndex, options) { - if (sampleIndex === -1) { - return null; - } - const trackData = fragment.trackData.get(this.internalTrack.id); - const sample = trackData.samples[sampleIndex]; - assert(sample); - let data; - if (options.metadataOnly) { - data = new Uint8Array(0); - } else { - await this.internalTrack.demuxer.chunkReader.reader.loadRange(fragment.dataStart, fragment.dataEnd); - data = this.internalTrack.demuxer.chunkReader.readRange( - sample.byteOffset, - sample.byteOffset + sample.byteSize - ); - } - const timestamp = 1e6 * sample.presentationTimestamp / this.internalTrack.timescale; - const duration = 1e6 * sample.duration / this.internalTrack.timescale; - const chunk = this.createChunk(data, timestamp, duration, sample.isKeyFrame); - this.chunkToFragmentLocation.set(chunk, { fragment, sampleIndex }); - return chunk; - } - findSampleInFragmentsForTimestamp(timestampInTimescale) { - const fragmentIndex = binarySearchLessOrEqual( - this.internalTrack.fragments, - timestampInTimescale, - (x) => x.trackData.get(this.internalTrack.id).startTimestamp - ); - let sampleIndex = -1; - let correctSampleFound = false; - if (fragmentIndex !== -1) { - const fragment = this.internalTrack.fragments[fragmentIndex]; - const trackData = fragment.trackData.get(this.internalTrack.id); - const index = binarySearchLessOrEqual( - trackData.presentationTimestamps, - timestampInTimescale, - (x) => x.presentationTimestamp - ); - assert(index !== -1); - sampleIndex = trackData.presentationTimestamps[index].sampleIndex; - correctSampleFound = timestampInTimescale < trackData.endTimestamp; - } - return { fragmentIndex, sampleIndex, correctSampleFound }; - } - findKeySampleInFragmentsForTimestamp(timestampInTimescale) { - const fragmentIndex = binarySearchLessOrEqual( - this.internalTrack.fragments, - timestampInTimescale, - (x) => x.trackData.get(this.internalTrack.id).startTimestamp - ); - let sampleIndex = -1; - let correctSampleFound = false; - if (fragmentIndex !== -1) { - const fragment = this.internalTrack.fragments[fragmentIndex]; - const trackData = fragment.trackData.get(this.internalTrack.id); - const index = findLastIndex(trackData.presentationTimestamps, (x) => { - const sample = trackData.samples[x.sampleIndex]; - return sample.isKeyFrame && x.presentationTimestamp <= timestampInTimescale; - }); - if (index === -1) { - throw new Error("Not supported: Fragment does not begin with a key sample."); - } - const entry = trackData.presentationTimestamps[index]; - sampleIndex = entry.sampleIndex; - correctSampleFound = timestampInTimescale < trackData.endTimestamp; - } - return { fragmentIndex, sampleIndex, correctSampleFound }; - } - /** Looks for a sample in the fragments while trying to load as few fragments as possible to retrieve it. */ - async performFragmentedLookup(getBestMatch, searchTimestamp, latestTimestamp, options) { - const demuxer = this.internalTrack.demuxer; - const release = await demuxer.fragmentLookupMutex.acquire(); - try { - const { fragmentIndex, sampleIndex, correctSampleFound } = getBestMatch(); - if (correctSampleFound) { - const fragment = this.internalTrack.fragments[fragmentIndex]; - return this.fetchChunkInFragment(fragment, sampleIndex, options); - } - const isobmffReader = demuxer.isobmffReader; - const sourceSize = await isobmffReader.reader.source._getSize(); - let prevFragment = null; - let bestFragmentIndex = fragmentIndex; - let bestSampleIndex = sampleIndex; - let lookupEntry = null; - if (this.internalTrack.fragmentLookupTable) { - const index = binarySearchLessOrEqual( - this.internalTrack.fragmentLookupTable, - searchTimestamp, - (x) => x.timestamp - ); - if (index !== -1) { - lookupEntry = this.internalTrack.fragmentLookupTable[index]; - } - } - if (fragmentIndex === -1) { - isobmffReader.pos = lookupEntry?.moofOffset ?? 0; - } else { - const fragment = this.internalTrack.fragments[fragmentIndex]; - if (!lookupEntry || fragment.moofOffset >= fragment.moofOffset) { - isobmffReader.pos = fragment.moofOffset + fragment.moofSize; - prevFragment = fragment; - } else { - isobmffReader.pos = lookupEntry.moofOffset; - } - } - while (isobmffReader.pos < sourceSize) { - if (prevFragment) { - const trackData = prevFragment.trackData.get(this.internalTrack.id); - if (trackData && trackData.startTimestamp > latestTimestamp) { - break; - } - if (prevFragment.nextFragment) { - isobmffReader.pos = prevFragment.nextFragment.moofOffset + prevFragment.nextFragment.moofSize; - prevFragment = prevFragment.nextFragment; - continue; - } - } - await isobmffReader.reader.loadRange(isobmffReader.pos, isobmffReader.pos + 16); - const startPos = isobmffReader.pos; - const boxInfo = isobmffReader.readBoxHeader(); - if (boxInfo.name === "moof") { - const index = binarySearchExact(demuxer.fragments, startPos, (x) => x.moofOffset); - if (index === -1) { - isobmffReader.pos = startPos; - const fragment = await demuxer.readFragment(); - if (prevFragment) prevFragment.nextFragment = fragment; - prevFragment = fragment; - const { fragmentIndex: fragmentIndex2, sampleIndex: sampleIndex2, correctSampleFound: correctSampleFound2 } = getBestMatch(); - if (correctSampleFound2) { - const fragment2 = this.internalTrack.fragments[fragmentIndex2]; - return this.fetchChunkInFragment(fragment2, sampleIndex2, options); - } - if (fragmentIndex2 !== -1) { - bestFragmentIndex = fragmentIndex2; - bestSampleIndex = sampleIndex2; - } - } else { - const fragment = demuxer.fragments[index]; - if (prevFragment) prevFragment.nextFragment = fragment; - prevFragment = fragment; - } - } - isobmffReader.pos = startPos + boxInfo.totalSize; - } - if (bestFragmentIndex !== -1) { - const fragment = this.internalTrack.fragments[bestFragmentIndex]; - return this.fetchChunkInFragment(fragment, bestSampleIndex, options); - } - return null; - } finally { - release(); - } - } -}; -var IsobmffVideoTrackBacking = class extends IsobmffTrackBacking { - constructor(internalTrack) { - super(internalTrack); - this.internalTrack = internalTrack; - } - async getCodec() { - return this.internalTrack.info.codec; - } - async getCodedWidth() { - return this.internalTrack.info.width; - } - async getCodedHeight() { - return this.internalTrack.info.height; - } - async getRotation() { - return this.internalTrack.rotation; - } - async getDecoderConfig() { - return { - codec: extractVideoCodecString(this.internalTrack.info.codec, this.internalTrack.info.codecDescription), - codedWidth: this.internalTrack.info.width, - codedHeight: this.internalTrack.info.height, - description: this.internalTrack.info.codecDescription ?? void 0, - colorSpace: this.internalTrack.info.colorSpace ?? void 0 - }; - } - createChunk(data, timestamp, duration, isKeyFrame) { - return new EncodedVideoChunk({ - data, - timestamp, - duration, - type: isKeyFrame ? "key" : "delta" - }); - } -}; -var IsobmffAudioTrackBacking = class extends IsobmffTrackBacking { - constructor(internalTrack) { - super(internalTrack); - this.internalTrack = internalTrack; - } - async getCodec() { - return this.internalTrack.info.codec; - } - async getNumberOfChannels() { - return this.internalTrack.info.numberOfChannels; - } - async getSampleRate() { - return this.internalTrack.info.sampleRate; - } - async getDecoderConfig() { - return { - codec: extractAudioCodecString(this.internalTrack.info.codec, this.internalTrack.info.codecDescription), - numberOfChannels: this.internalTrack.info.numberOfChannels, - sampleRate: this.internalTrack.info.sampleRate, - description: this.internalTrack.info.codecDescription ?? void 0 - }; - } - createChunk(data, timestamp, duration, isKeyFrame) { - return new EncodedAudioChunk({ - data, - timestamp, - duration, - type: isKeyFrame ? "key" : "delta" - }); - } -}; -var getSampleIndexForTimestamp = (sampleTable, timescaleUnits) => { - const index = binarySearchLessOrEqual( - sampleTable.presentationTimestamps, - timescaleUnits, - (x) => x.presentationTimestamp - ); - if (index === -1) { - return -1; - } - return sampleTable.presentationTimestamps[index].sampleIndex; -}; -var getSampleInfo = (sampleTable, sampleIndex) => { - const timingEntryIndex = binarySearchLessOrEqual(sampleTable.sampleTimingEntries, sampleIndex, (x) => x.startIndex); - const timingEntry = sampleTable.sampleTimingEntries[timingEntryIndex]; - if (!timingEntry || timingEntry.startIndex + timingEntry.count <= sampleIndex) { - return null; - } - const decodeTimestamp = timingEntry.startDecodeTimestamp + (sampleIndex - timingEntry.startIndex) * timingEntry.delta; - let presentationTimestamp = decodeTimestamp; - const offsetEntryIndex = binarySearchLessOrEqual( - sampleTable.sampleCompositionTimeOffsets, - sampleIndex, - (x) => x.startIndex - ); - const offsetEntry = sampleTable.sampleCompositionTimeOffsets[offsetEntryIndex]; - if (offsetEntry) { - presentationTimestamp += offsetEntry.offset; - } - const sampleSize = sampleTable.sampleSizes[Math.min(sampleIndex, sampleTable.sampleSizes.length - 1)]; - const chunkEntryIndex = binarySearchLessOrEqual(sampleTable.sampleToChunk, sampleIndex, (x) => x.startSampleIndex); - const chunkEntry = sampleTable.sampleToChunk[chunkEntryIndex]; - assert(chunkEntry); - const chunkIndex = chunkEntry.startChunkIndex + Math.floor((sampleIndex - chunkEntry.startSampleIndex) / chunkEntry.samplesPerChunk); - const chunkOffset = sampleTable.chunkOffsets[chunkIndex]; - let chunkSize = 0; - let sampleOffset = chunkOffset; - if (sampleTable.sampleSizes.length === 1) { - sampleOffset += sampleSize * (sampleIndex - chunkEntry.startSampleIndex); - chunkSize += sampleSize * chunkEntry.samplesPerChunk; - } else { - const startSampleIndex = chunkEntry.startSampleIndex + (chunkIndex - chunkEntry.startChunkIndex) * chunkEntry.samplesPerChunk; - for (let i = startSampleIndex; i < startSampleIndex + chunkEntry.samplesPerChunk; i++) { - const sampleSize2 = sampleTable.sampleSizes[i]; - if (i < sampleIndex) { - sampleOffset += sampleSize2; - } - chunkSize += sampleSize2; - } - } - return { - presentationTimestamp, - duration: timingEntry.delta, - sampleOffset, - sampleSize, - chunkOffset, - chunkSize, - isKeyFrame: sampleTable.keySampleIndices ? binarySearchExact(sampleTable.keySampleIndices, sampleIndex, (x) => x) !== -1 : true - }; -}; -var getRelevantKeyframeIndexForSample = (sampleTable, sampleIndex) => { - if (!sampleTable.keySampleIndices) { - return sampleIndex; - } - const index = binarySearchLessOrEqual(sampleTable.keySampleIndices, sampleIndex, (x) => x); - return sampleTable.keySampleIndices[index] ?? -1; -}; -var getNextKeyframeIndexForSample = (sampleTable, sampleIndex) => { - if (!sampleTable.keySampleIndices) { - return sampleIndex + 1; - } - const index = binarySearchLessOrEqual(sampleTable.keySampleIndices, sampleIndex, (x) => x); - return sampleTable.keySampleIndices[index + 1] ?? -1; -}; -var offsetFragmentTrackDataByTimestamp = (trackData, timestamp) => { - trackData.startTimestamp += timestamp; - trackData.endTimestamp += timestamp; - for (const sample of trackData.samples) { - sample.presentationTimestamp += timestamp; - } - for (const entry of trackData.presentationTimestamps) { - entry.presentationTimestamp += timestamp; - } -}; - -// src/input-format.ts -var InputFormat = class { -}; -var IsobmffInputFormat = class extends InputFormat { - /** @internal */ - async _canReadInput(input) { - const sourceSize = await input._mainReader.source._getSize(); - if (sourceSize < 8) { - return false; - } - const isobmffReader = new IsobmffReader(input._mainReader); - isobmffReader.pos = 4; - const fourCc = isobmffReader.readAscii(4); - return fourCc === "ftyp"; - } - /** @internal */ - _createDemuxer(input) { - return new IsobmffDemuxer(input); - } -}; -var MatroskaInputFormat = class extends InputFormat { - /** @internal */ - async _canReadInput() { - return false; - } - /** @internal */ - _createDemuxer() { - throw new Error("Not implemented"); - } -}; -var ISOBMFF = new IsobmffInputFormat(); -var MP4 = ISOBMFF; -var MOV = ISOBMFF; -var MATROSKA = new MatroskaInputFormat(); -var MKV = MATROSKA; -var WEBM = MATROSKA; -var ALL_FORMATS = [ISOBMFF, MKV]; - -// src/input.ts -var Input = class { - constructor(options) { - /** @internal */ - this._demuxerPromise = null; - /** @internal */ - this._format = null; - if (!options || typeof options !== "object") { - throw new TypeError("options must be an object."); - } - if (!Array.isArray(options.formats) || options.formats.some((x) => !(x instanceof InputFormat))) { - throw new TypeError("options.formats must be an array of InputFormat."); - } - if (!(options.source instanceof Source)) { - throw new TypeError("options.source must be a Source."); - } - this._formats = options.formats; - this._source = options.source; - this._mainReader = new Reader(options.source); - } - /** @internal */ - _getDemuxer() { - return this._demuxerPromise ??= (async () => { - await this._mainReader.loadRange(0, 4096); - for (const format of this._formats) { - const canRead = await format._canReadInput(this); - if (canRead) { - this._format = format; - return format._createDemuxer(this); - } - } - throw new Error("Input has an unrecognizable format."); - })(); - } - async getFormat() { - await this._getDemuxer(); - assert(this._format); - return this._format; - } - async computeDuration() { - const demuxer = await this._getDemuxer(); - return demuxer.computeDuration(); - } - async getTracks() { - const demuxer = await this._getDemuxer(); - return demuxer.getTracks(); - } - async getVideoTracks() { - const tracks = await this.getTracks(); - return tracks.filter((x) => x.isVideoTrack()); - } - async getPrimaryVideoTrack() { - const tracks = await this.getTracks(); - return tracks.find((x) => x.isVideoTrack()) ?? null; - } - async getAudioTracks() { - const tracks = await this.getTracks(); - return tracks.filter((x) => x.isAudioTrack()); - } - async getPrimaryAudioTrack() { - const tracks = await this.getTracks(); - return tracks.find((x) => x.isAudioTrack()) ?? null; - } - async getMimeType() { - const demuxer = await this._getDemuxer(); - return demuxer.getMimeType(); - } -}; - -// src/media-drain.ts -var validateChunkRetrievalOptions = (options) => { - if (!options || typeof options !== "object") { - throw new TypeError("options must be an object."); - } - if (options.metadataOnly !== void 0 && typeof options.metadataOnly !== "boolean") { - throw new TypeError("options.metadataOnly, when defined, must be a boolean."); - } -}; -var validateTimestamp = (timestamp) => { - if (typeof timestamp !== "number" || Number.isNaN(timestamp)) { - throw new TypeError("timestamp must be a number."); - } -}; -var BaseChunkDrain = class { - async *chunks(startChunk, endTimestamp = Infinity) { - const chunkQueue = []; - let { promise: queueNotEmpty, resolve: onQueueNotEmpty } = promiseWithResolvers(); - let { promise: queueDequeue, resolve: onQueueDequeue } = promiseWithResolvers(); - let ended = false; - let outOfBandError = null; - const timestamps = []; - const maxQueueSize = () => Math.max(2, timestamps.length); - (async () => { - let chunk = startChunk ?? await this.getFirstChunk(); - while (chunk && !ended) { - if (chunk.timestamp / 1e6 >= endTimestamp) { - break; - } - if (chunkQueue.length > maxQueueSize()) { - ({ promise: queueDequeue, resolve: onQueueDequeue } = promiseWithResolvers()); - await queueDequeue; - continue; - } - chunkQueue.push(chunk); - onQueueNotEmpty(); - ({ promise: queueNotEmpty, resolve: onQueueNotEmpty } = promiseWithResolvers()); - chunk = await this.getNextChunk(chunk); - } - ended = true; - onQueueNotEmpty(); - })().catch((error) => { - if (!outOfBandError) { - outOfBandError = error; - onQueueNotEmpty(); - } - }); - try { - while (true) { - if (outOfBandError) { - throw outOfBandError; - } else if (chunkQueue.length > 0) { - yield chunkQueue.shift(); - const now = performance.now(); - timestamps.push(now); - while (timestamps.length > 0 && now - timestamps[0] >= 1e3) { - timestamps.shift(); - } - onQueueDequeue(); - } else if (!ended) { - await queueNotEmpty; - } else { - break; - } - } - } finally { - ended = true; - onQueueDequeue(); - } - } -}; -var BaseMediaFrameDrain = class { - /** @internal */ - _duplicateFrame(frame) { - return structuredClone(frame); - } - async *mediaFramesAtTimestamps(timestamps) { - validateAnyIterable(timestamps); - const timestampIterator = toAsyncIterator(timestamps); - const timestampsOfInterest = []; - const MAX_QUEUE_SIZE = 8; - const frameQueue = []; - let { promise: queueNotEmpty, resolve: onQueueNotEmpty } = promiseWithResolvers(); - let { promise: queueDequeue, resolve: onQueueDequeue } = promiseWithResolvers(); - let decoderIsFlushed = false; - let ended = false; - let outOfBandError = null; - let lastUsedFrame = null; - const pushToQueue = (frame) => { - frameQueue.push(frame); - onQueueNotEmpty(); - ({ promise: queueNotEmpty, resolve: onQueueNotEmpty } = promiseWithResolvers()); - }; - const decoderError = new Error(); - const decoder = await this._createDecoder((frame) => { - onQueueDequeue(); - if (ended) { - frame.close(); - return; - } - let frameUsed = false; - while (timestampsOfInterest.length > 0 && timestampsOfInterest[0] === frame.timestamp) { - pushToQueue(this._duplicateFrame(frame)); - timestampsOfInterest.shift(); - frameUsed = true; - } - if (frameUsed) { - lastUsedFrame?.close(); - lastUsedFrame = frame; - } else { - frame.close(); - } - }, (error) => { - if (!outOfBandError) { - error.stack = decoderError.stack; - outOfBandError = error; - onQueueNotEmpty(); - } - }); - (async () => { - const chunkDrain = this._createChunkDrain(); - let lastKeyChunk = null; - let lastChunk = null; - for await (const timestamp of timestampIterator) { - validateTimestamp(timestamp); - while (frameQueue.length + decoder.decodeQueueSize > MAX_QUEUE_SIZE) { - ({ promise: queueDequeue, resolve: onQueueDequeue } = promiseWithResolvers()); - await queueDequeue; - } - if (ended) { - break; - } - const targetChunk = await chunkDrain.getChunk(timestamp); - if (!targetChunk) { - pushToQueue(null); - continue; - } - const keyChunk = await chunkDrain.getKeyChunk(timestamp); - if (!keyChunk) { - pushToQueue(null); - continue; - } - timestampsOfInterest.push(targetChunk.timestamp); - if (lastKeyChunk && keyChunk.timestamp === lastKeyChunk.timestamp && targetChunk.timestamp >= lastChunk.timestamp) { - assert(lastChunk); - if (targetChunk.timestamp === lastChunk.timestamp && timestampsOfInterest.length === 1) { - if (lastUsedFrame) { - pushToQueue(this._duplicateFrame(lastUsedFrame)); - } - timestampsOfInterest.shift(); - } - } else { - lastKeyChunk = keyChunk; - lastChunk = keyChunk; - decoder.decode(keyChunk); - } - while (lastChunk.timestamp !== targetChunk.timestamp) { - const nextChunk = await chunkDrain.getNextChunk(lastChunk); - assert(nextChunk); - lastChunk = nextChunk; - decoder.decode(nextChunk); - } - if (decoder.decodeQueueSize >= 10) { - await new Promise((resolve) => decoder.addEventListener("dequeue", resolve, { once: true })); - } - } - await decoder.flush(); - decoder.close(); - decoderIsFlushed = true; - onQueueNotEmpty(); - })().catch((error) => { - if (!outOfBandError) { - outOfBandError = error; - onQueueNotEmpty(); - } - }); - try { - while (true) { - if (outOfBandError) { - throw outOfBandError; - } else if (frameQueue.length > 0) { - const nextFrame = frameQueue.shift(); - assert(nextFrame !== void 0); - yield nextFrame; - onQueueDequeue(); - } else if (!decoderIsFlushed) { - await queueNotEmpty; - } else { - break; - } - } - } finally { - ended = true; - onQueueDequeue(); - for (const frame of frameQueue) { - frame?.close(); - } - lastUsedFrame?.close(); - } - } - async *mediaFramesInRange(startTimestamp = 0, endTimestamp = Infinity) { - validateTimestamp(startTimestamp); - validateTimestamp(endTimestamp); - const MAX_QUEUE_SIZE = 8; - const frameQueue = []; - let firstFrameQueued = false; - let lastFrame = null; - let { promise: queueNotEmpty, resolve: onQueueNotEmpty } = promiseWithResolvers(); - let { promise: queueDequeue, resolve: onQueueDequeue } = promiseWithResolvers(); - let decoderIsFlushed = false; - let ended = false; - let outOfBandError = null; - const decoderError = new Error(); - const decoder = await this._createDecoder((frame) => { - onQueueDequeue(); - const frameTimestamp = frame.timestamp / 1e6; - if (frameTimestamp >= endTimestamp) { - ended = true; - } - if (ended) { - frame.close(); - return; - } - if (lastFrame) { - if (frameTimestamp > startTimestamp) { - frameQueue.push(lastFrame); - firstFrameQueued = true; - } else { - lastFrame.close(); - } - } - if (frameTimestamp >= startTimestamp) { - frameQueue.push(frame); - firstFrameQueued = true; - } - lastFrame = firstFrameQueued ? null : frame; - if (frameQueue.length > 0) { - onQueueNotEmpty(); - ({ promise: queueNotEmpty, resolve: onQueueNotEmpty } = promiseWithResolvers()); - } - }, (error) => { - if (!outOfBandError) { - error.stack = decoderError.stack; - outOfBandError = error; - onQueueNotEmpty(); - } - }); - const chunkDrain = this._createChunkDrain(); - const keyChunk = await chunkDrain.getKeyChunk(startTimestamp) ?? await chunkDrain.getFirstChunk(); - if (!keyChunk) { - return; - } - (async () => { - let currentChunk = keyChunk; - let chunksEndTimestamp = Infinity; - if (endTimestamp < Infinity) { - const endFrame = await chunkDrain.getChunk(endTimestamp); - const endKeyFrame = !endFrame ? null : endFrame.type === "key" && endFrame.timestamp / 1e6 === endTimestamp ? endFrame : await chunkDrain.getNextKeyChunk(endFrame); - if (endKeyFrame) { - chunksEndTimestamp = endKeyFrame.timestamp / 1e6; - } - } - const chunks = chunkDrain.chunks(keyChunk, chunksEndTimestamp); - await chunks.next(); - while (currentChunk && !ended) { - if (frameQueue.length + decoder.decodeQueueSize > MAX_QUEUE_SIZE) { - ({ promise: queueDequeue, resolve: onQueueDequeue } = promiseWithResolvers()); - await queueDequeue; - continue; - } - decoder.decode(currentChunk); - const chunkResult = await chunks.next(); - if (chunkResult.done) { - break; - } - currentChunk = chunkResult.value; - } - await chunks.return(); - await decoder.flush(); - decoder.close(); - if (!firstFrameQueued && lastFrame) { - frameQueue.push(lastFrame); - } - decoderIsFlushed = true; - onQueueNotEmpty(); - })().catch((error) => { - if (!outOfBandError) { - outOfBandError = error; - onQueueNotEmpty(); - } - }); - try { - while (true) { - if (outOfBandError) { - throw outOfBandError; - } else if (frameQueue.length > 0) { - yield frameQueue.shift(); - onQueueDequeue(); - } else if (!decoderIsFlushed) { - await queueNotEmpty; - } else { - break; - } - } - } finally { - ended = true; - onQueueDequeue(); - for (const frame of frameQueue) { - frame.close(); - } - } - } -}; -var EncodedVideoChunkDrain = class extends BaseChunkDrain { - constructor(videoTrack) { - if (!(videoTrack instanceof InputVideoTrack)) { - throw new TypeError("videoTrack must be an InputVideoTrack."); - } - super(); - this._videoTrack = videoTrack; - } - getFirstChunk(options = {}) { - validateChunkRetrievalOptions(options); - return this._videoTrack._backing.getFirstChunk(options); - } - getChunk(timestamp, options = {}) { - validateTimestamp(timestamp); - validateChunkRetrievalOptions(options); - return this._videoTrack._backing.getChunk(timestamp, options); - } - getNextChunk(chunk, options = {}) { - if (!(chunk instanceof EncodedVideoChunk)) { - throw new TypeError("chunk must be an EncodedVideoChunk."); - } - validateChunkRetrievalOptions(options); - return this._videoTrack._backing.getNextChunk(chunk, options); - } - getKeyChunk(timestamp, options = {}) { - validateTimestamp(timestamp); - validateChunkRetrievalOptions(options); - return this._videoTrack._backing.getKeyChunk(timestamp, options); - } - getNextKeyChunk(chunk, options = {}) { - if (!(chunk instanceof EncodedVideoChunk)) { - throw new TypeError("chunk must be an EncodedVideoChunk."); - } - validateChunkRetrievalOptions(options); - return this._videoTrack._backing.getNextKeyChunk(chunk, options); - } -}; -var VideoFrameDrain = class extends BaseMediaFrameDrain { - constructor(videoTrack) { - if (!(videoTrack instanceof InputVideoTrack)) { - throw new TypeError("videoTrack must be an InputVideoTrack."); - } - super(); - /** @internal */ - this._decoderConfig = null; - this._videoTrack = videoTrack; - } - /** @internal */ - async _createDecoder(onFrame, onError) { - this._decoderConfig ??= await this._videoTrack.getDecoderConfig(); - const decoder = new VideoDecoder({ output: onFrame, error: onError }); - decoder.configure(this._decoderConfig); - return decoder; - } - /** @internal */ - _createChunkDrain() { - return new EncodedVideoChunkDrain(this._videoTrack); - } - async getFrame(timestamp) { - validateTimestamp(timestamp); - for await (const frame of this.mediaFramesAtTimestamps([timestamp])) { - return frame; - } - throw new Error("Internal error: Iterator returned nothing."); - } - frames(startTimestamp = 0, endTimestamp = Infinity) { - return this.mediaFramesInRange(startTimestamp, endTimestamp); - } - framesAtTimestamps(timestamps) { - return this.mediaFramesAtTimestamps(timestamps); - } -}; -var CanvasDrain = class { - constructor(videoTrack, dimensions) { - if (!(videoTrack instanceof InputVideoTrack)) { - throw new TypeError("videoTrack must be an InputVideoTrack."); - } - if (dimensions && typeof dimensions !== "object") { - throw new TypeError("dimensions, when defined, must be an object."); - } - if (dimensions && (!Number.isInteger(dimensions.width) || dimensions.width <= 0)) { - throw new TypeError("dimensions.width must be a positive integer."); - } - if (dimensions && (!Number.isInteger(dimensions.height) || dimensions.height <= 0)) { - throw new TypeError("dimensions.height must be a positive integer."); - } - this._videoTrack = videoTrack; - this._dimensions = dimensions; - this._videoFrameDrain = new VideoFrameDrain(videoTrack); - } - /** @internal */ - async _videoFrameToWrappedCanvas(frame) { - const width = this._dimensions?.width ?? await this._videoTrack.getDisplayWidth(); - const height = this._dimensions?.height ?? await this._videoTrack.getDisplayHeight(); - const rotation = await this._videoTrack.getRotation(); - const canvas = document.createElement("canvas"); - canvas.width = width; - canvas.height = height; - const context = canvas.getContext("2d", { alpha: false }); - assert(context); - context.translate(width / 2, height / 2); - context.rotate(rotation * Math.PI / 180); - context.translate(-width / 2, -height / 2); - const [imageWidth, imageHeight] = rotation % 180 === 0 ? [width, height] : [height, width]; - context.drawImage(frame, (width - imageWidth) / 2, (height - imageHeight) / 2, imageWidth, imageHeight); - const result = { - canvas, - timestamp: frame.timestamp / 1e6, - duration: (frame.duration ?? 0) / 1e6 - }; - frame.close(); - return result; - } - async getCanvas(timestamp) { - validateTimestamp(timestamp); - const frame = await this._videoFrameDrain.getFrame(timestamp); - return frame && this._videoFrameToWrappedCanvas(frame); - } - async *canvases(startTimestamp = 0, endTimestamp = Infinity) { - for await (const frame of this._videoFrameDrain.frames(startTimestamp, endTimestamp)) { - yield this._videoFrameToWrappedCanvas(frame); - } - } - async *canvasesAtTimestamps(timestamps) { - for await (const frame of this._videoFrameDrain.framesAtTimestamps(timestamps)) { - yield frame && this._videoFrameToWrappedCanvas(frame); - } - } -}; -var EncodedAudioChunkDrain = class extends BaseChunkDrain { - constructor(audioTrack) { - if (!(audioTrack instanceof InputAudioTrack)) { - throw new TypeError("audioTrack must be an InputAudioTrack."); - } - super(); - this._audioTrack = audioTrack; - } - getFirstChunk(options = {}) { - validateChunkRetrievalOptions(options); - return this._audioTrack._backing.getFirstChunk(options); - } - getChunk(timestamp, options = {}) { - validateTimestamp(timestamp); - validateChunkRetrievalOptions(options); - return this._audioTrack._backing.getChunk(timestamp, options); - } - getNextChunk(chunk, options = {}) { - if (!(chunk instanceof EncodedAudioChunk)) { - throw new TypeError("chunk must be an EncodedAudioChunk."); - } - validateChunkRetrievalOptions(options); - return this._audioTrack._backing.getNextChunk(chunk, options); - } - getKeyChunk(timestamp, options = {}) { - validateTimestamp(timestamp); - validateChunkRetrievalOptions(options); - return this._audioTrack._backing.getKeyChunk(timestamp, options); - } - getNextKeyChunk(chunk, options = {}) { - if (!(chunk instanceof EncodedAudioChunk)) { - throw new TypeError("chunk must be an EncodedAudioChunk."); - } - validateChunkRetrievalOptions(options); - return this._audioTrack._backing.getNextKeyChunk(chunk, options); - } -}; -var AudioDataDrain = class extends BaseMediaFrameDrain { - constructor(audioTrack) { - if (!(audioTrack instanceof InputAudioTrack)) { - throw new TypeError("audioTrack must be an InputAudioTrack."); - } - super(); - /** @internal */ - this._decoderConfig = null; - this._audioTrack = audioTrack; - } - /** @internal */ - async _createDecoder(onData, onError) { - this._decoderConfig ??= await this._audioTrack.getDecoderConfig(); - const decoder = new AudioDecoder({ output: onData, error: onError }); - decoder.configure(this._decoderConfig); - return decoder; - } - /** @internal */ - _createChunkDrain() { - return new EncodedAudioChunkDrain(this._audioTrack); - } - async getData(timestamp) { - validateTimestamp(timestamp); - for await (const data of this.mediaFramesAtTimestamps([timestamp])) { - return data; - } - throw new Error("Internal error: Iterator returned nothing."); - } - data(startTimestamp = 0, endTimestamp = Infinity) { - return this.mediaFramesInRange(startTimestamp, endTimestamp); - } - dataAtTimestamps(timestamps) { - return this.mediaFramesAtTimestamps(timestamps); - } -}; -var AudioBufferDrain = class { - constructor(audioTrack) { - if (!(audioTrack instanceof InputAudioTrack)) { - throw new TypeError("audioTrack must be an InputAudioTrack."); - } - this._audioDataDrain = new AudioDataDrain(audioTrack); - } - /** @internal */ - _audioDataToWrappedArrayBuffer(data) { - const audioBuffer = new AudioBuffer({ - numberOfChannels: data.numberOfChannels, - length: data.numberOfFrames, - sampleRate: data.sampleRate - }); - const dataBytes = new Float32Array(data.allocationSize({ planeIndex: 0, format: "f32-planar" }) / 4); - for (let i = 0; i < data.numberOfChannels; i++) { - data.copyTo(dataBytes, { planeIndex: i, format: "f32-planar" }); - audioBuffer.copyToChannel(dataBytes, i); - } - const sampleDuration = 1 / data.sampleRate; - const result = { - buffer: audioBuffer, - // Rounding the timestamp based on the sample duration removes audio playback artifacts - timestamp: Math.round(data.timestamp / 1e6 / sampleDuration) * sampleDuration - }; - data.close(); - return result; - } - async getBuffer(timestamp) { - validateTimestamp(timestamp); - const data = await this._audioDataDrain.getData(timestamp); - return data && this._audioDataToWrappedArrayBuffer(data); - } - async *buffers(startTimestamp = 0, endTimestamp = Infinity) { - for await (const data of this._audioDataDrain.data(startTimestamp, endTimestamp)) { - yield this._audioDataToWrappedArrayBuffer(data); - } - } - async *buffersAtTimestamps(timestamps) { - for await (const data of this._audioDataDrain.dataAtTimestamps(timestamps)) { - yield data && this._audioDataToWrappedArrayBuffer(data); - } - } -}; -export { - ALL_FORMATS, - AUDIO_CODECS, - ArrayBufferTarget, - AudioBufferDrain, - AudioBufferSource, - AudioDataDrain, - AudioDataSource, - AudioSource, - BaseChunkDrain, - BaseMediaFrameDrain, - BlobSource, - BufferSource, - CanvasDrain, - CanvasSource, - EncodedAudioChunkDrain, - EncodedAudioChunkSource, - EncodedVideoChunkDrain, - EncodedVideoChunkSource, - ISOBMFF, - Input, - InputAudioTrack, - InputFormat, - InputTrack, - InputVideoTrack, - IsobmffInputFormat, - MATROSKA, - MKV, - MOV, - MP4, - MatroskaInputFormat, - MediaSource, - MediaStreamAudioTrackSource, - MediaStreamVideoTrackSource, - MkvOutputFormat2 as MkvOutputFormat, - Mp4OutputFormat, - Output, - OutputFormat, - SUBTITLE_CODECS, - Source, - StreamTarget, - SubtitleSource, - Target, - TextSubtitleSource, - UrlSource, - VIDEO_CODECS, - VideoFrameDrain, - VideoFrameSource, - VideoSource, - WEBM, - WebMOutputFormat -};