"use strict"; var EncoderWorkerGlobal = (() => { var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __commonJS = (cb, mod) => function __require() { return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; }; 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 __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); // node_modules/webm-muxer/build/webm-muxer.js var require_webm_muxer = __commonJS({ "node_modules/webm-muxer/build/webm-muxer.js"(exports, module) { "use strict"; var WebMMuxer2 = (() => { var __defProp3 = Object.defineProperty; var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor; var __getOwnPropNames2 = Object.getOwnPropertyNames; var __hasOwnProp3 = Object.prototype.hasOwnProperty; var __pow2 = Math.pow; var __export = (target, all) => { for (var name in all) __defProp3(target, name, { get: all[name], enumerable: true }); }; var __copyProps2 = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames2(from)) if (!__hasOwnProp3.call(to, key) && key !== except) __defProp3(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc2(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps2(__defProp3({}, "__esModule", { value: true }), mod); var __accessCheck2 = (obj, member, msg) => { if (!member.has(obj)) throw TypeError("Cannot " + msg); }; var __privateGet2 = (obj, member, getter) => { __accessCheck2(obj, member, "read from private field"); return getter ? getter.call(obj) : member.get(obj); }; var __privateAdd2 = (obj, member, value) => { if (member.has(obj)) throw TypeError("Cannot add the same private member more than once"); member instanceof WeakSet ? member.add(obj) : member.set(obj, value); }; var __privateSet2 = (obj, member, value, setter) => { __accessCheck2(obj, member, "write to private field"); setter ? setter.call(obj, value) : member.set(obj, value); return value; }; var __privateMethod2 = (obj, member, method) => { __accessCheck2(obj, member, "access private method"); return method; }; var main_exports = {}; __export(main_exports, { default: () => main_default }); var EBMLFloat32 = class { constructor(value) { this.value = value; } }; var EBMLFloat64 = class { constructor(value) { this.value = value; } }; var WriteTarget = class { constructor() { this.pos = 0; this.helper = new Uint8Array(8); this.helperView = new DataView(this.helper.buffer); this.offsets = /* @__PURE__ */ new WeakMap(); this.dataOffsets = /* @__PURE__ */ new WeakMap(); } writeFloat32(value) { this.helperView.setFloat32(0, value, false); this.write(this.helper.subarray(0, 4)); } writeFloat64(value) { this.helperView.setFloat64(0, value, false); this.write(this.helper); } writeUnsignedInt(value, width = measureUnsignedInt(value)) { let pos = 0; switch (width) { case 6: this.helperView.setUint8(pos++, value / __pow2(2, 40) | 0); case 5: this.helperView.setUint8(pos++, value / __pow2(2, 32) | 0); case 4: this.helperView.setUint8(pos++, value >> 24); case 3: this.helperView.setUint8(pos++, value >> 16); case 2: this.helperView.setUint8(pos++, value >> 8); case 1: this.helperView.setUint8(pos++, value); break; default: throw new Error("Bad UINT size " + width); } this.write(this.helper.subarray(0, pos)); } 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 / __pow2(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 / __pow2(2, 40) & 3); this.helperView.setUint8(pos++, value / __pow2(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.write(this.helper.subarray(0, pos)); } writeString(str) { this.write(new Uint8Array(str.split("").map((x) => x.charCodeAt(0)))); } writeEBML(data) { var _a, _b; if (data instanceof Uint8Array) { this.write(data); } else if (Array.isArray(data)) { for (let elem of data) { this.writeEBML(elem); } } else { this.offsets.set(data, this.pos); this.writeUnsignedInt(data.id); if (Array.isArray(data.data)) { let sizePos = this.pos; let sizeSize = (_a = data.size) != null ? _a : 4; this.seek(this.pos + sizeSize); let startPos = this.pos; this.dataOffsets.set(data, startPos); this.writeEBML(data.data); let size = this.pos - startPos; let endPos = this.pos; this.seek(sizePos); this.writeEBMLVarInt(size, sizeSize); this.seek(endPos); } else if (typeof data.data === "number") { let size = (_b = data.size) != null ? _b : 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.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); } } } }; 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 < __pow2(2, 32)) { return 4; } else if (value < __pow2(2, 40)) { 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 < __pow2(2, 35) - 1) { return 5; } else if (value < __pow2(2, 42) - 1) { return 6; } else { throw new Error("EBML VINT size not supported " + value); } }; var ArrayBufferWriteTarget = class extends WriteTarget { constructor() { super(); this.buffer = new ArrayBuffer(__pow2(2, 16)); this.bytes = new Uint8Array(this.buffer); } ensureSize(size) { let newLength = this.buffer.byteLength; while (newLength < size) newLength *= 2; if (newLength === this.buffer.byteLength) return; let newBuffer = new ArrayBuffer(newLength); let newBytes = new Uint8Array(newBuffer); 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; } seek(newPos) { this.pos = newPos; } finalize() { this.ensureSize(this.pos); return this.buffer.slice(0, this.pos); } }; var FILE_CHUNK_SIZE = __pow2(2, 24); var MAX_CHUNKS_AT_ONCE2 = 2; var FileSystemWritableFileStreamWriteTarget = class extends WriteTarget { constructor(stream) { super(); this.chunks = []; this.stream = stream; } write(data) { this.writeDataIntoChunks(data, this.pos); this.flushChunks(); this.pos += data.byteLength; } writeDataIntoChunks(data, position) { let chunkIndex = this.chunks.findIndex((x) => x.start <= position && position < x.start + FILE_CHUNK_SIZE); if (chunkIndex === -1) chunkIndex = this.createChunk(position); let chunk = this.chunks[chunkIndex]; let relativePosition = position - chunk.start; let toWrite = data.subarray(0, Math.min(FILE_CHUNK_SIZE - relativePosition, data.byteLength)); chunk.data.set(toWrite, relativePosition); let section = { start: relativePosition, end: relativePosition + toWrite.byteLength }; insertSectionIntoFileChunk(chunk, section); if (chunk.written[0].start === 0 && chunk.written[0].end === FILE_CHUNK_SIZE) { chunk.shouldFlush = true; } if (this.chunks.length > MAX_CHUNKS_AT_ONCE2) { for (let i = 0; i < this.chunks.length - 1; i++) { this.chunks[i].shouldFlush = true; } this.flushChunks(); } if (toWrite.byteLength < data.byteLength) { this.writeDataIntoChunks(data.subarray(toWrite.byteLength), position + toWrite.byteLength); } } createChunk(includesPosition) { let start = Math.floor(includesPosition / FILE_CHUNK_SIZE) * FILE_CHUNK_SIZE; let chunk = { start, data: new Uint8Array(FILE_CHUNK_SIZE), written: [], shouldFlush: false }; this.chunks.push(chunk); this.chunks.sort((a, b) => a.start - b.start); return this.chunks.indexOf(chunk); } flushChunks(force = false) { for (let i = 0; i < this.chunks.length; i++) { let chunk = this.chunks[i]; if (!chunk.shouldFlush && !force) continue; for (let section of chunk.written) { this.stream.write({ type: "write", data: chunk.data.subarray(section.start, section.end), position: chunk.start + section.start }); } this.chunks.splice(i--, 1); } } seek(newPos) { this.pos = newPos; } finalize() { this.flushChunks(true); } }; var insertSectionIntoFileChunk = (chunk, section) => { let low = 0; let high = chunk.written.length - 1; let index = -1; while (low <= high) { let 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); } }; var VIDEO_TRACK_NUMBER = 1; var AUDIO_TRACK_NUMBER = 2; var VIDEO_TRACK_TYPE = 1; var AUDIO_TRACK_TYPE = 2; var MAX_CHUNK_LENGTH_MS = __pow2(2, 15); var CODEC_PRIVATE_MAX_SIZE = __pow2(2, 12); var APP_NAME = "https://github.com/Vanilagy/webm-muxer"; var SEGMENT_SIZE_BYTES = 6; var CLUSTER_SIZE_BYTES = 5; var _target4, _options2, _segment, _segmentInfo, _seekHead, _tracksElement, _segmentDuration, _colourElement, _videoCodecPrivate, _audioCodecPrivate, _cues, _currentCluster, _currentClusterTimestamp, _duration, _videoChunkQueue, _audioChunkQueue, _lastVideoTimestamp, _lastAudioTimestamp, _colorSpace, _finalized2, _validateOptions2, validateOptions_fn2, _createFileHeader, createFileHeader_fn, _writeEBMLHeader, writeEBMLHeader_fn, _createSeekHead, createSeekHead_fn, _createSegmentInfo, createSegmentInfo_fn, _createTracks, createTracks_fn, _createSegment, createSegment_fn, _createCues, createCues_fn, _segmentDataOffset, segmentDataOffset_get, _writeVideoDecoderConfig, writeVideoDecoderConfig_fn, _fixVP9ColorSpace, fixVP9ColorSpace_fn, _createInternalChunk, createInternalChunk_fn, _writeSimpleBlock, writeSimpleBlock_fn, _writeCodecPrivate, writeCodecPrivate_fn, _createNewCluster, createNewCluster_fn, _finalizeCurrentCluster, finalizeCurrentCluster_fn, _ensureNotFinalized2, ensureNotFinalized_fn2; var WebMMuxer3 = class { constructor(options) { __privateAdd2(this, _validateOptions2); __privateAdd2(this, _createFileHeader); __privateAdd2(this, _writeEBMLHeader); __privateAdd2(this, _createSeekHead); __privateAdd2(this, _createSegmentInfo); __privateAdd2(this, _createTracks); __privateAdd2(this, _createSegment); __privateAdd2(this, _createCues); __privateAdd2(this, _segmentDataOffset); __privateAdd2(this, _writeVideoDecoderConfig); __privateAdd2(this, _fixVP9ColorSpace); __privateAdd2(this, _createInternalChunk); __privateAdd2(this, _writeSimpleBlock); __privateAdd2(this, _writeCodecPrivate); __privateAdd2(this, _createNewCluster); __privateAdd2(this, _finalizeCurrentCluster); __privateAdd2(this, _ensureNotFinalized2); __privateAdd2(this, _target4, void 0); __privateAdd2(this, _options2, void 0); __privateAdd2(this, _segment, void 0); __privateAdd2(this, _segmentInfo, void 0); __privateAdd2(this, _seekHead, void 0); __privateAdd2(this, _tracksElement, void 0); __privateAdd2(this, _segmentDuration, void 0); __privateAdd2(this, _colourElement, void 0); __privateAdd2(this, _videoCodecPrivate, void 0); __privateAdd2(this, _audioCodecPrivate, void 0); __privateAdd2(this, _cues, void 0); __privateAdd2(this, _currentCluster, void 0); __privateAdd2(this, _currentClusterTimestamp, void 0); __privateAdd2(this, _duration, 0); __privateAdd2(this, _videoChunkQueue, []); __privateAdd2(this, _audioChunkQueue, []); __privateAdd2(this, _lastVideoTimestamp, 0); __privateAdd2(this, _lastAudioTimestamp, 0); __privateAdd2(this, _colorSpace, void 0); __privateAdd2(this, _finalized2, false); __privateMethod2(this, _validateOptions2, validateOptions_fn2).call(this, options); __privateSet2(this, _options2, options); if (options.target === "buffer") { __privateSet2(this, _target4, new ArrayBufferWriteTarget()); } else { __privateSet2(this, _target4, new FileSystemWritableFileStreamWriteTarget(options.target)); } __privateMethod2(this, _createFileHeader, createFileHeader_fn).call(this); } addVideoChunk(chunk, meta, timestamp) { let data = new Uint8Array(chunk.byteLength); chunk.copyTo(data); this.addVideoChunkRaw(data, chunk.type, timestamp != null ? timestamp : chunk.timestamp, meta); } addVideoChunkRaw(data, type, timestamp, meta) { __privateMethod2(this, _ensureNotFinalized2, ensureNotFinalized_fn2).call(this); if (!__privateGet2(this, _options2).video) throw new Error("No video track declared."); if (meta) __privateMethod2(this, _writeVideoDecoderConfig, writeVideoDecoderConfig_fn).call(this, meta); let internalChunk = __privateMethod2(this, _createInternalChunk, createInternalChunk_fn).call(this, data, type, timestamp, VIDEO_TRACK_NUMBER); if (__privateGet2(this, _options2).video.codec === "V_VP9") __privateMethod2(this, _fixVP9ColorSpace, fixVP9ColorSpace_fn).call(this, internalChunk); __privateSet2(this, _lastVideoTimestamp, internalChunk.timestamp); while (__privateGet2(this, _audioChunkQueue).length > 0 && __privateGet2(this, _audioChunkQueue)[0].timestamp <= internalChunk.timestamp) { let audioChunk = __privateGet2(this, _audioChunkQueue).shift(); __privateMethod2(this, _writeSimpleBlock, writeSimpleBlock_fn).call(this, audioChunk); } if (!__privateGet2(this, _options2).audio || internalChunk.timestamp <= __privateGet2(this, _lastAudioTimestamp)) { __privateMethod2(this, _writeSimpleBlock, writeSimpleBlock_fn).call(this, internalChunk); } else { __privateGet2(this, _videoChunkQueue).push(internalChunk); } } addAudioChunk(chunk, meta, timestamp) { let data = new Uint8Array(chunk.byteLength); chunk.copyTo(data); this.addAudioChunkRaw(data, chunk.type, timestamp != null ? timestamp : chunk.timestamp, meta); } addAudioChunkRaw(data, type, timestamp, meta) { __privateMethod2(this, _ensureNotFinalized2, ensureNotFinalized_fn2).call(this); if (!__privateGet2(this, _options2).audio) throw new Error("No audio track declared."); let internalChunk = __privateMethod2(this, _createInternalChunk, createInternalChunk_fn).call(this, data, type, timestamp, AUDIO_TRACK_NUMBER); __privateSet2(this, _lastAudioTimestamp, internalChunk.timestamp); while (__privateGet2(this, _videoChunkQueue).length > 0 && __privateGet2(this, _videoChunkQueue)[0].timestamp <= internalChunk.timestamp) { let videoChunk = __privateGet2(this, _videoChunkQueue).shift(); __privateMethod2(this, _writeSimpleBlock, writeSimpleBlock_fn).call(this, videoChunk); } if (!__privateGet2(this, _options2).video || internalChunk.timestamp <= __privateGet2(this, _lastVideoTimestamp)) { __privateMethod2(this, _writeSimpleBlock, writeSimpleBlock_fn).call(this, internalChunk); } else { __privateGet2(this, _audioChunkQueue).push(internalChunk); } if (meta == null ? void 0 : meta.decoderConfig) { __privateMethod2(this, _writeCodecPrivate, writeCodecPrivate_fn).call(this, __privateGet2(this, _audioCodecPrivate), meta.decoderConfig.description); } } finalize() { while (__privateGet2(this, _videoChunkQueue).length > 0) __privateMethod2(this, _writeSimpleBlock, writeSimpleBlock_fn).call(this, __privateGet2(this, _videoChunkQueue).shift()); while (__privateGet2(this, _audioChunkQueue).length > 0) __privateMethod2(this, _writeSimpleBlock, writeSimpleBlock_fn).call(this, __privateGet2(this, _audioChunkQueue).shift()); __privateMethod2(this, _finalizeCurrentCluster, finalizeCurrentCluster_fn).call(this); __privateGet2(this, _target4).writeEBML(__privateGet2(this, _cues)); let endPos = __privateGet2(this, _target4).pos; let segmentSize = __privateGet2(this, _target4).pos - __privateGet2(this, _segmentDataOffset, segmentDataOffset_get); __privateGet2(this, _target4).seek(__privateGet2(this, _target4).offsets.get(__privateGet2(this, _segment)) + 4); __privateGet2(this, _target4).writeEBMLVarInt(segmentSize, SEGMENT_SIZE_BYTES); __privateGet2(this, _segmentDuration).data = new EBMLFloat64(__privateGet2(this, _duration)); __privateGet2(this, _target4).seek(__privateGet2(this, _target4).offsets.get(__privateGet2(this, _segmentDuration))); __privateGet2(this, _target4).writeEBML(__privateGet2(this, _segmentDuration)); __privateGet2(this, _seekHead).data[0].data[1].data = __privateGet2(this, _target4).offsets.get(__privateGet2(this, _cues)) - __privateGet2(this, _segmentDataOffset, segmentDataOffset_get); __privateGet2(this, _seekHead).data[1].data[1].data = __privateGet2(this, _target4).offsets.get(__privateGet2(this, _segmentInfo)) - __privateGet2(this, _segmentDataOffset, segmentDataOffset_get); __privateGet2(this, _seekHead).data[2].data[1].data = __privateGet2(this, _target4).offsets.get(__privateGet2(this, _tracksElement)) - __privateGet2(this, _segmentDataOffset, segmentDataOffset_get); __privateGet2(this, _target4).seek(__privateGet2(this, _target4).offsets.get(__privateGet2(this, _seekHead))); __privateGet2(this, _target4).writeEBML(__privateGet2(this, _seekHead)); __privateGet2(this, _target4).seek(endPos); __privateSet2(this, _finalized2, true); if (__privateGet2(this, _target4) instanceof ArrayBufferWriteTarget) { return __privateGet2(this, _target4).finalize(); } else if (__privateGet2(this, _target4) instanceof FileSystemWritableFileStreamWriteTarget) { __privateGet2(this, _target4).finalize(); } return null; } }; _target4 = /* @__PURE__ */ new WeakMap(); _options2 = /* @__PURE__ */ new WeakMap(); _segment = /* @__PURE__ */ new WeakMap(); _segmentInfo = /* @__PURE__ */ new WeakMap(); _seekHead = /* @__PURE__ */ new WeakMap(); _tracksElement = /* @__PURE__ */ new WeakMap(); _segmentDuration = /* @__PURE__ */ new WeakMap(); _colourElement = /* @__PURE__ */ new WeakMap(); _videoCodecPrivate = /* @__PURE__ */ new WeakMap(); _audioCodecPrivate = /* @__PURE__ */ new WeakMap(); _cues = /* @__PURE__ */ new WeakMap(); _currentCluster = /* @__PURE__ */ new WeakMap(); _currentClusterTimestamp = /* @__PURE__ */ new WeakMap(); _duration = /* @__PURE__ */ new WeakMap(); _videoChunkQueue = /* @__PURE__ */ new WeakMap(); _audioChunkQueue = /* @__PURE__ */ new WeakMap(); _lastVideoTimestamp = /* @__PURE__ */ new WeakMap(); _lastAudioTimestamp = /* @__PURE__ */ new WeakMap(); _colorSpace = /* @__PURE__ */ new WeakMap(); _finalized2 = /* @__PURE__ */ new WeakMap(); _validateOptions2 = /* @__PURE__ */ new WeakSet(); validateOptions_fn2 = function(options) { if (options.type && options.type !== "webm" && options.type !== "matroska") { throw new Error(`Invalid type: ${options.type}`); } }; _createFileHeader = /* @__PURE__ */ new WeakSet(); createFileHeader_fn = function() { __privateMethod2(this, _writeEBMLHeader, writeEBMLHeader_fn).call(this); __privateMethod2(this, _createSeekHead, createSeekHead_fn).call(this); __privateMethod2(this, _createSegmentInfo, createSegmentInfo_fn).call(this); __privateMethod2(this, _createTracks, createTracks_fn).call(this); __privateMethod2(this, _createSegment, createSegment_fn).call(this); __privateMethod2(this, _createCues, createCues_fn).call(this); }; _writeEBMLHeader = /* @__PURE__ */ new WeakSet(); writeEBMLHeader_fn = function() { var _a; let ebmlHeader = { id: 440786851, data: [ { id: 17030, data: 1 }, { id: 17143, data: 1 }, { id: 17138, data: 4 }, { id: 17139, data: 8 }, { id: 17026, data: (_a = __privateGet2(this, _options2).type) != null ? _a : "webm" }, { id: 17031, data: 2 }, { id: 17029, data: 2 } ] }; __privateGet2(this, _target4).writeEBML(ebmlHeader); }; _createSeekHead = /* @__PURE__ */ new WeakSet(); createSeekHead_fn = function() { const kaxCues = new Uint8Array([28, 83, 187, 107]); const kaxInfo = new Uint8Array([21, 73, 169, 102]); const kaxTracks = new Uint8Array([22, 84, 174, 107]); let seekHead = { id: 290298740, data: [ { id: 19899, data: [ { id: 21419, data: kaxCues }, { id: 21420, size: 5, data: 0 } ] }, { id: 19899, data: [ { id: 21419, data: kaxInfo }, { id: 21420, size: 5, data: 0 } ] }, { id: 19899, data: [ { id: 21419, data: kaxTracks }, { id: 21420, size: 5, data: 0 } ] } ] }; __privateSet2(this, _seekHead, seekHead); }; _createSegmentInfo = /* @__PURE__ */ new WeakSet(); createSegmentInfo_fn = function() { let segmentDuration = { id: 17545, data: new EBMLFloat64(0) }; __privateSet2(this, _segmentDuration, segmentDuration); let segmentInfo = { id: 357149030, data: [ { id: 2807729, data: 1e6 }, { id: 19840, data: APP_NAME }, { id: 22337, data: APP_NAME }, segmentDuration ] }; __privateSet2(this, _segmentInfo, segmentInfo); }; _createTracks = /* @__PURE__ */ new WeakSet(); createTracks_fn = function() { let tracksElement = { id: 374648427, data: [] }; __privateSet2(this, _tracksElement, tracksElement); if (__privateGet2(this, _options2).video) { __privateSet2(this, _videoCodecPrivate, { id: 236, size: 4, data: new Uint8Array(CODEC_PRIVATE_MAX_SIZE) }); let colourElement = { id: 21936, data: [ { id: 21937, data: 2 }, { id: 21946, data: 2 }, { id: 21947, data: 2 }, { id: 21945, data: 0 } ] }; __privateSet2(this, _colourElement, colourElement); tracksElement.data.push({ id: 174, data: [ { id: 215, data: VIDEO_TRACK_NUMBER }, { id: 29637, data: VIDEO_TRACK_NUMBER }, { id: 131, data: VIDEO_TRACK_TYPE }, { id: 134, data: __privateGet2(this, _options2).video.codec }, __privateGet2(this, _videoCodecPrivate), __privateGet2(this, _options2).video.frameRate ? { id: 2352003, data: 1e9 / __privateGet2(this, _options2).video.frameRate } : null, { id: 224, data: [ { id: 176, data: __privateGet2(this, _options2).video.width }, { id: 186, data: __privateGet2(this, _options2).video.height }, colourElement ] } ].filter(Boolean) }); } if (__privateGet2(this, _options2).audio) { __privateSet2(this, _audioCodecPrivate, { id: 236, size: 4, data: new Uint8Array(CODEC_PRIVATE_MAX_SIZE) }); tracksElement.data.push({ id: 174, data: [ { id: 215, data: AUDIO_TRACK_NUMBER }, { id: 29637, data: AUDIO_TRACK_NUMBER }, { id: 131, data: AUDIO_TRACK_TYPE }, { id: 134, data: __privateGet2(this, _options2).audio.codec }, __privateGet2(this, _audioCodecPrivate), { id: 225, data: [ { id: 181, data: new EBMLFloat32(__privateGet2(this, _options2).audio.sampleRate) }, { id: 159, data: __privateGet2(this, _options2).audio.numberOfChannels }, __privateGet2(this, _options2).audio.bitDepth ? { id: 25188, data: __privateGet2(this, _options2).audio.bitDepth } : null ].filter(Boolean) } ] }); } }; _createSegment = /* @__PURE__ */ new WeakSet(); createSegment_fn = function() { let segment = { id: 408125543, size: SEGMENT_SIZE_BYTES, data: [ __privateGet2(this, _seekHead), __privateGet2(this, _segmentInfo), __privateGet2(this, _tracksElement) ] }; __privateSet2(this, _segment, segment); __privateGet2(this, _target4).writeEBML(segment); }; _createCues = /* @__PURE__ */ new WeakSet(); createCues_fn = function() { __privateSet2(this, _cues, { id: 475249515, data: [] }); }; _segmentDataOffset = /* @__PURE__ */ new WeakSet(); segmentDataOffset_get = function() { return __privateGet2(this, _target4).dataOffsets.get(__privateGet2(this, _segment)); }; _writeVideoDecoderConfig = /* @__PURE__ */ new WeakSet(); writeVideoDecoderConfig_fn = function(meta) { if (meta.decoderConfig) { if (meta.decoderConfig.colorSpace) { let colorSpace = meta.decoderConfig.colorSpace; __privateSet2(this, _colorSpace, colorSpace); __privateGet2(this, _colourElement).data = [ { id: 21937, data: { "rgb": 1, "bt709": 1, "bt470bg": 5, "smpte170m": 6 }[colorSpace.matrix] }, { id: 21946, data: { "bt709": 1, "smpte170m": 6, "iec61966-2-1": 13 }[colorSpace.transfer] }, { id: 21947, data: { "bt709": 1, "bt470bg": 5, "smpte170m": 6 }[colorSpace.primaries] }, { id: 21945, data: [1, 2][Number(colorSpace.fullRange)] } ]; let endPos = __privateGet2(this, _target4).pos; __privateGet2(this, _target4).seek(__privateGet2(this, _target4).offsets.get(__privateGet2(this, _colourElement))); __privateGet2(this, _target4).writeEBML(__privateGet2(this, _colourElement)); __privateGet2(this, _target4).seek(endPos); } if (meta.decoderConfig.description) { __privateMethod2(this, _writeCodecPrivate, writeCodecPrivate_fn).call(this, __privateGet2(this, _videoCodecPrivate), meta.decoderConfig.description); } } }; _fixVP9ColorSpace = /* @__PURE__ */ new WeakSet(); fixVP9ColorSpace_fn = function(chunk) { if (chunk.type !== "key") return; if (!__privateGet2(this, _colorSpace)) return; let i = 0; if (readBits(chunk.data, 0, 2) !== 2) return; i += 2; let profile = (readBits(chunk.data, i + 1, i + 2) << 1) + readBits(chunk.data, i + 0, i + 1); i += 2; if (profile === 3) i++; let showExistingFrame = readBits(chunk.data, i + 0, i + 1); i++; if (showExistingFrame) return; let frameType = readBits(chunk.data, i + 0, i + 1); i++; if (frameType !== 0) return; i += 2; let syncCode = readBits(chunk.data, i + 0, i + 24); i += 24; if (syncCode !== 4817730) return; if (profile >= 2) i++; let colorSpaceID = { "rgb": 7, "bt709": 2, "bt470bg": 1, "smpte170m": 3 }[__privateGet2(this, _colorSpace).matrix]; writeBits(chunk.data, i + 0, i + 3, colorSpaceID); }; _createInternalChunk = /* @__PURE__ */ new WeakSet(); createInternalChunk_fn = function(data, type, timestamp, trackNumber) { let internalChunk = { data, type, timestamp, trackNumber }; return internalChunk; }; _writeSimpleBlock = /* @__PURE__ */ new WeakSet(); writeSimpleBlock_fn = function(chunk) { let msTime = Math.floor(chunk.timestamp / 1e3); let clusterIsTooLong = chunk.type !== "key" && msTime - __privateGet2(this, _currentClusterTimestamp) >= 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 video key frame at least every ${MAX_CHUNK_LENGTH_MS} milliseconds.` ); } let shouldCreateNewClusterFromKeyFrame = (chunk.trackNumber === VIDEO_TRACK_NUMBER || !__privateGet2(this, _options2).video) && chunk.type === "key" && msTime - __privateGet2(this, _currentClusterTimestamp) >= 1e3; if (!__privateGet2(this, _currentCluster) || shouldCreateNewClusterFromKeyFrame) { __privateMethod2(this, _createNewCluster, createNewCluster_fn).call(this, msTime); } let prelude = new Uint8Array(4); let view2 = new DataView(prelude.buffer); view2.setUint8(0, 128 | chunk.trackNumber); view2.setUint16(1, msTime - __privateGet2(this, _currentClusterTimestamp), false); view2.setUint8(3, Number(chunk.type === "key") << 7); let simpleBlock = { id: 163, data: [ prelude, chunk.data ] }; __privateGet2(this, _target4).writeEBML(simpleBlock); __privateSet2(this, _duration, Math.max(__privateGet2(this, _duration), msTime)); }; _writeCodecPrivate = /* @__PURE__ */ new WeakSet(); writeCodecPrivate_fn = function(element, data) { let endPos = __privateGet2(this, _target4).pos; __privateGet2(this, _target4).seek(__privateGet2(this, _target4).offsets.get(element)); element = [ { id: 25506, size: 4, data: new Uint8Array(data) }, { id: 236, size: 4, data: new Uint8Array(CODEC_PRIVATE_MAX_SIZE - 2 - 4 - data.byteLength) } ]; __privateGet2(this, _target4).writeEBML(element); __privateGet2(this, _target4).seek(endPos); }; _createNewCluster = /* @__PURE__ */ new WeakSet(); createNewCluster_fn = function(timestamp) { if (__privateGet2(this, _currentCluster)) { __privateMethod2(this, _finalizeCurrentCluster, finalizeCurrentCluster_fn).call(this); } __privateSet2(this, _currentCluster, { id: 524531317, size: CLUSTER_SIZE_BYTES, data: [ { id: 231, data: timestamp } ] }); __privateGet2(this, _target4).writeEBML(__privateGet2(this, _currentCluster)); __privateSet2(this, _currentClusterTimestamp, timestamp); let clusterOffsetFromSegment = __privateGet2(this, _target4).offsets.get(__privateGet2(this, _currentCluster)) - __privateGet2(this, _segmentDataOffset, segmentDataOffset_get); __privateGet2(this, _cues).data.push({ id: 187, data: [ { id: 179, data: timestamp }, { id: 183, data: [ { id: 247, data: VIDEO_TRACK_NUMBER }, { id: 241, data: clusterOffsetFromSegment } ] } ] }); }; _finalizeCurrentCluster = /* @__PURE__ */ new WeakSet(); finalizeCurrentCluster_fn = function() { let clusterSize = __privateGet2(this, _target4).pos - __privateGet2(this, _target4).dataOffsets.get(__privateGet2(this, _currentCluster)); let endPos = __privateGet2(this, _target4).pos; __privateGet2(this, _target4).seek(__privateGet2(this, _target4).offsets.get(__privateGet2(this, _currentCluster)) + 4); __privateGet2(this, _target4).writeEBMLVarInt(clusterSize, CLUSTER_SIZE_BYTES); __privateGet2(this, _target4).seek(endPos); }; _ensureNotFinalized2 = /* @__PURE__ */ new WeakSet(); ensureNotFinalized_fn2 = function() { if (__privateGet2(this, _finalized2)) { throw new Error("Cannot add new video or audio chunks after the file has been finalized."); } }; var main_default = WebMMuxer3; var readBits = (bytes2, start, end) => { let result = 0; for (let i = start; i < end; i++) { let byteIndex = Math.floor(i / 8); let byte = bytes2[byteIndex]; let bitIndex = 7 - (i & 7); let bit = (byte & 1 << bitIndex) >> bitIndex; result <<= 1; result |= bit; } return result; }; var writeBits = (bytes2, start, end, value) => { for (let i = start; i < end; i++) { let byteIndex = Math.floor(i / 8); let byte = bytes2[byteIndex]; let bitIndex = 7 - (i & 7); byte &= ~(1 << bitIndex); byte |= (value & 1 << end - i - 1) >> end - i - 1 << bitIndex; bytes2[byteIndex] = byte; } }; return __toCommonJS(main_exports); })(); WebMMuxer2 = WebMMuxer2.default; if (typeof module === "object" && typeof module.exports === "object") module.exports = WebMMuxer2; } }); // node_modules/mp4-muxer/build/mp4-muxer.mjs var __defProp2 = Object.defineProperty; var __getOwnPropSymbols = Object.getOwnPropertySymbols; var __hasOwnProp2 = Object.prototype.hasOwnProperty; var __propIsEnum = Object.prototype.propertyIsEnumerable; var __pow = Math.pow; var __defNormalProp = (obj, key, value) => key in obj ? __defProp2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __spreadValues = (a, b) => { for (var prop in b || (b = {})) if (__hasOwnProp2.call(b, prop)) __defNormalProp(a, prop, b[prop]); if (__getOwnPropSymbols) for (var prop of __getOwnPropSymbols(b)) { if (__propIsEnum.call(b, prop)) __defNormalProp(a, prop, b[prop]); } return a; }; var __accessCheck = (obj, member, msg) => { if (!member.has(obj)) throw TypeError("Cannot " + msg); }; var __privateGet = (obj, member, getter) => { __accessCheck(obj, member, "read from private field"); return getter ? getter.call(obj) : member.get(obj); }; var __privateAdd = (obj, member, value) => { if (member.has(obj)) throw TypeError("Cannot add the same private member more than once"); member instanceof WeakSet ? member.add(obj) : member.set(obj, value); }; var __privateSet = (obj, member, value, setter) => { __accessCheck(obj, member, "write to private field"); setter ? setter.call(obj, value) : member.set(obj, value); return value; }; var __privateMethod = (obj, member, method) => { __accessCheck(obj, member, "access private method"); return method; }; 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 u64 = (value) => { view.setUint32(0, Math.floor(value / __pow(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, __pow(2, 8) * value, false); return [bytes[0], bytes[1]]; }; var fixed_16_16 = (value) => { view.setInt32(0, __pow(2, 16) * value, false); return [bytes[0], bytes[1], bytes[2], bytes[3]]; }; var fixed_2_30 = (value) => { view.setInt32(0, __pow(2, 30) * value, false); return [bytes[0], bytes[1], bytes[2], bytes[3]]; }; var ascii = (text, nullTerminated = false) => { let bytes2 = Array(text.length).fill(null).map((_, i) => text.charCodeAt(i)); if (nullTerminated) bytes2.push(0); return bytes2; }; var last = (arr) => { return arr && arr[arr.length - 1]; }; var intoTimescale = (timeInSeconds, timescale, round = true) => { let value = timeInSeconds * timescale; return round ? Math.round(value) : value; }; var rotationMatrix = (rotationInDegrees) => { let theta = rotationInDegrees * (Math.PI / 180); let cosTheta = Math.cos(theta); let sinTheta = Math.sin(theta); return [ cosTheta, sinTheta, 0, -sinTheta, cosTheta, 0, 0, 0, 1 ]; }; var IDENTITY_MATRIX = rotationMatrix(0); var matrixToBytes = (matrix) => { return [ fixed_16_16(matrix[0]), 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 != null ? contents : []], children ); var ftyp = (holdsHevc) => { if (holdsHevc) return box("ftyp", [ ascii("isom"), // Major brand u32(0), // Minor version ascii("iso4"), // Compatible brand 1 ascii("hvc1") // Compatible brand 2 ]); return box("ftyp", [ ascii("isom"), // Major brand u32(0), // Minor version ascii("isom"), // Compatible brand 1 ascii("avc1"), // Compatible brand 2 ascii("mp41") // Compatible brand 3 ]); }; var mdat = () => ({ type: "mdat", largeSize: true }); var moov = (tracks, creationTime) => box("moov", null, [ mvhd(creationTime, tracks), ...tracks.map((x) => trak(x, creationTime)) ]); var mvhd = (creationTime, tracks) => { let duration = intoTimescale(Math.max( 0, ...tracks.filter((x) => x.samples.length > 0).map((x) => last(x.samples).timestamp + last(x.samples).duration) ), GLOBAL_TIMESCALE); let nextTrackId = Math.max(...tracks.map((x) => x.id)) + 1; return fullBox("mvhd", 0, 0, [ u32(creationTime), // Creation time u32(creationTime), // Modification time u32(GLOBAL_TIMESCALE), // Timescale u32(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 = (track, creationTime) => box("trak", null, [ tkhd(track, creationTime), mdia(track, creationTime) ]); var tkhd = (track, creationTime) => { let lastSample = last(track.samples); let durationInGlobalTimescale = intoTimescale( lastSample ? lastSample.timestamp + lastSample.duration : 0, GLOBAL_TIMESCALE ); return fullBox("tkhd", 0, 3, [ u32(creationTime), // Creation time u32(creationTime), // Modification time u32(track.id), // Track ID u32(0), // Reserved u32(durationInGlobalTimescale), // Duration Array(8).fill(0), // Reserved u16(0), // Layer u16(0), // Alternate group fixed_8_8(track.info.type === "audio" ? 1 : 0), // Volume u16(0), // Reserved matrixToBytes(rotationMatrix(track.info.type === "video" ? track.info.rotation : 0)), // Matrix fixed_16_16(track.info.type === "video" ? track.info.width : 0), // Track width fixed_16_16(track.info.type === "video" ? track.info.height : 0) // Track height ]); }; var mdia = (track, creationTime) => box("mdia", null, [ mdhd(track, creationTime), hdlr(track.info.type === "video" ? "vide" : "soun"), minf(track) ]); var mdhd = (track, creationTime) => { let lastSample = last(track.samples); let localDuration = intoTimescale( lastSample ? lastSample.timestamp + lastSample.duration : 0, track.timescale ); return fullBox("mdhd", 0, 0, [ u32(creationTime), // Creation time u32(creationTime), // Modification time u32(track.timescale), // Timescale u32(localDuration), // Duration u16(21956), // Language ("und", undetermined) u16(0) // Quality ]); }; var hdlr = (componentSubtype) => fullBox("hdlr", 0, 0, [ ascii("mhlr"), // Component type ascii(componentSubtype), // Component subtype u32(0), // Component manufacturer u32(0), // Component flags u32(0), // Component flags mask ascii("mp4-muxer-hdlr") // Component name ]); var minf = (track) => box("minf", null, [ track.info.type === "video" ? vmhd() : smhd(), dinf(), stbl(track) ]); 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 dinf = () => box("dinf", null, [ dref() ]); var dref = () => fullBox("dref", 0, 0, [ u32(1) // Entry count ], [ url() ]); var url = () => fullBox("url ", 0, 1); var stbl = (track) => box("stbl", null, [ stsd(track), stts(track), stss(track), stsc(track), stsz(track), stco(track) ]); var stsd = (track) => fullBox("stsd", 0, 0, [ u32(1) // Entry count ], [ track.info.type === "video" ? videoSampleDescription( VIDEO_CODEC_TO_BOX_NAME[track.info.codec], track ) : soundSampleDescription( AUDIO_CODEC_TO_BOX_NAME[track.info.codec], track ) ]); var videoSampleDescription = (compressionType, track) => 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(track.info.width), // Width u16(track.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[track.info.codec](track) ]); var avcC = (track) => track.codecPrivate && box("avcC", [...track.codecPrivate]); var hvcC = (track) => track.codecPrivate && box("hvcC", [...track.codecPrivate]); var vpcC = (track) => track.codecPrivate && box("vpcC", [...track.codecPrivate]); var av1C = (track) => track.codecPrivate && box("av1C", [...track.codecPrivate]); var soundSampleDescription = (compressionType, track) => box(compressionType, [ Array(6).fill(0), // Reserved u16(1), // Data reference index u16(0), // Version u16(0), // Revision level u32(0), // Vendor u16(track.info.numberOfChannels), // Number of channels u16(16), // Sample size (bits) u16(0), // Compression ID u16(0), // Packet size fixed_16_16(track.info.sampleRate) // Sample rate ], [ AUDIO_CODEC_TO_CONFIGURATION_BOX[track.info.codec](track) ]); var esds = (track) => fullBox("esds", 0, 0, [ // https://stackoverflow.com/a/54803118 u32(58753152), // TAG(3) = Object Descriptor ([2]) u8(32 + track.codecPrivate.byteLength), // length of this OD (which includes the next 2 tags) u16(1), // ES_ID = 1 u8(0), // flags etc = 0 u32(75530368), // TAG(4) = ES Descriptor ([2]) embedded in above OD u8(18 + track.codecPrivate.byteLength), // length of this ESD u8(64), // MPEG-4 Audio u8(21), // stream type(6bits)=5 audio, flags(2bits)=1 u24(0), // 24bit buffer size u32(130071), // max bitrate u32(130071), // avg bitrate u32(92307584), // TAG(5) = ASC ([2],[3]) embedded in above OD u8(track.codecPrivate.byteLength), // length ...track.codecPrivate, u32(109084800), // TAG(6) u8(1), // length u8(2) // data ]); var dOps = (track) => box("dOps", [ u8(0), // Version u8(track.info.numberOfChannels), // OutputChannelCount u16(3840), // PreSkip, should be at least 80 milliseconds worth of playback, measured in 48000 Hz samples u32(track.info.sampleRate), // InputSampleRate fixed_8_8(0), // OutputGain u8(0) // ChannelMappingFamily ]); var stts = (track) => { return fullBox("stts", 0, 0, [ u32(track.timeToSampleTable.length), // Number of entries track.timeToSampleTable.map((x) => [ // Time-to-sample table u32(x.sampleCount), // Sample count u32(x.sampleDelta) // Sample duration ]) ]); }; var stss = (track) => { if (track.samples.every((x) => x.type === "key")) return null; let keySamples = [...track.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 = (track) => { return fullBox("stsc", 0, 0, [ u32(track.compactlyCodedChunkTable.length), // Number of entries track.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 = (track) => fullBox("stsz", 0, 0, [ u32(0), // Sample size (0 means non-constant size) u32(track.samples.length), // Number of entries track.samples.map((x) => u32(x.size)) // Sample size table ]); var stco = (track) => { if (track.writtenChunks.length > 0 && last(track.writtenChunks).offset >= __pow(2, 32)) { return fullBox("co64", 0, 0, [ u32(track.writtenChunks.length), // Number of entries track.writtenChunks.map((x) => u64(x.offset)) // Chunk offset table ]); } return fullBox("stco", 0, 0, [ u32(track.writtenChunks.length), // Number of entries track.writtenChunks.map((x) => u32(x.offset)) // Chunk offset table ]); }; var VIDEO_CODEC_TO_BOX_NAME = { "avc": "avc1", "hevc": "hvc1", "vp9": "vp09", "av1": "av01" }; var VIDEO_CODEC_TO_CONFIGURATION_BOX = { "avc": avcC, "hevc": hvcC, "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 ArrayBufferTarget = class { constructor() { this.buffer = null; } }; var StreamTarget = class { constructor(onData, onDone, options) { this.onData = onData; this.onDone = onDone; this.options = options; } }; var FileSystemWritableFileStreamTarget = class { constructor(stream, options) { this.stream = stream; this.options = options; } }; var _helper; var _helperView; var Writer = class { constructor() { this.pos = 0; __privateAdd(this, _helper, new Uint8Array(8)); __privateAdd(this, _helperView, new DataView(__privateGet(this, _helper).buffer)); this.offsets = /* @__PURE__ */ new WeakMap(); } /** Sets the current position for future writes to a new one. */ seek(newPos) { this.pos = newPos; } writeU32(value) { __privateGet(this, _helperView).setUint32(0, value, false); this.write(__privateGet(this, _helper).subarray(0, 4)); } writeU64(value) { __privateGet(this, _helperView).setUint32(0, Math.floor(value / __pow(2, 32)), false); __privateGet(this, _helperView).setUint32(4, value, false); this.write(__privateGet(this, _helper).subarray(0, 8)); } writeAscii(text) { for (let i = 0; i < text.length; i++) { __privateGet(this, _helperView).setUint8(i % 8, text.charCodeAt(i)); if (i % 8 === 7) this.write(__privateGet(this, _helper)); } if (text.length % 8 !== 0) { this.write(__privateGet(this, _helper).subarray(0, text.length % 8)); } } writeBox(box2) { var _a, _b; this.offsets.set(box2, this.pos); if (box2.contents && !box2.children) { this.writeBoxHeader(box2, (_a = box2.size) != null ? _a : box2.contents.byteLength + 8); this.write(box2.contents); } else { let startPos = this.pos; this.writeBoxHeader(box2, 0); if (box2.contents) this.write(box2.contents); if (box2.children) { for (let child of box2.children) if (child) this.writeBox(child); } let endPos = this.pos; let size = (_b = box2.size) != null ? _b : endPos - startPos; this.seek(startPos); this.writeBoxHeader(box2, size); this.seek(endPos); } } writeBoxHeader(box2, size) { this.writeU32(box2.largeSize ? 1 : size); this.writeAscii(box2.type); if (box2.largeSize) this.writeU64(size); } patchBox(box2) { let endPos = this.pos; this.seek(this.offsets.get(box2)); this.writeBox(box2); this.seek(endPos); } }; _helper = /* @__PURE__ */ new WeakMap(); _helperView = /* @__PURE__ */ new WeakMap(); var _target; var _buffer; var _bytes; var _ensureSize; var ensureSize_fn; var ArrayBufferTargetWriter = class extends Writer { constructor(target) { super(); __privateAdd(this, _ensureSize); __privateAdd(this, _target, void 0); __privateAdd(this, _buffer, new ArrayBuffer(__pow(2, 16))); __privateAdd(this, _bytes, new Uint8Array(__privateGet(this, _buffer))); __privateSet(this, _target, target); } write(data) { __privateMethod(this, _ensureSize, ensureSize_fn).call(this, this.pos + data.byteLength); __privateGet(this, _bytes).set(data, this.pos); this.pos += data.byteLength; } finalize() { __privateMethod(this, _ensureSize, ensureSize_fn).call(this, this.pos); __privateGet(this, _target).buffer = __privateGet(this, _buffer).slice(0, this.pos); } }; _target = /* @__PURE__ */ new WeakMap(); _buffer = /* @__PURE__ */ new WeakMap(); _bytes = /* @__PURE__ */ new WeakMap(); _ensureSize = /* @__PURE__ */ new WeakSet(); ensureSize_fn = function(size) { let newLength = __privateGet(this, _buffer).byteLength; while (newLength < size) newLength *= 2; if (newLength === __privateGet(this, _buffer).byteLength) return; let newBuffer = new ArrayBuffer(newLength); let newBytes = new Uint8Array(newBuffer); newBytes.set(__privateGet(this, _bytes), 0); __privateSet(this, _buffer, newBuffer); __privateSet(this, _bytes, newBytes); }; var _target2; var _sections; var StreamTargetWriter = class extends Writer { constructor(target) { super(); __privateAdd(this, _target2, void 0); __privateAdd(this, _sections, []); __privateSet(this, _target2, target); } write(data) { __privateGet(this, _sections).push({ data: data.slice(), start: this.pos }); this.pos += data.byteLength; } flush() { if (__privateGet(this, _sections).length === 0) return; let chunks = []; let sorted = [...__privateGet(this, _sections)].sort((a, b) => a.start - b.start); chunks.push({ start: sorted[0].start, size: sorted[0].data.byteLength }); for (let i = 1; i < sorted.length; i++) { let lastChunk = chunks[chunks.length - 1]; let section = sorted[i]; 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 (let chunk of chunks) { chunk.data = new Uint8Array(chunk.size); for (let section of __privateGet(this, _sections)) { if (chunk.start <= section.start && section.start < chunk.start + chunk.size) { chunk.data.set(section.data, section.start - chunk.start); } } __privateGet(this, _target2).onData(chunk.data, chunk.start); } __privateGet(this, _sections).length = 0; } finalize() { var _a, _b; (_b = (_a = __privateGet(this, _target2)).onDone) == null ? void 0 : _b.call(_a); } }; _target2 = /* @__PURE__ */ new WeakMap(); _sections = /* @__PURE__ */ new WeakMap(); var DEFAULT_CHUNK_SIZE = __pow(2, 24); var MAX_CHUNKS_AT_ONCE = 2; var _target3; var _chunkSize; var _chunks; var _writeDataIntoChunks; var writeDataIntoChunks_fn; var _insertSectionIntoChunk; var insertSectionIntoChunk_fn; var _createChunk; var createChunk_fn; var _flushChunks; var flushChunks_fn; var ChunkedStreamTargetWriter = class extends Writer { constructor(target) { var _a, _b; super(); __privateAdd(this, _writeDataIntoChunks); __privateAdd(this, _insertSectionIntoChunk); __privateAdd(this, _createChunk); __privateAdd(this, _flushChunks); __privateAdd(this, _target3, void 0); __privateAdd(this, _chunkSize, void 0); __privateAdd(this, _chunks, []); __privateSet(this, _target3, target); __privateSet(this, _chunkSize, (_b = (_a = target.options) == null ? void 0 : _a.chunkSize) != null ? _b : DEFAULT_CHUNK_SIZE); if (!Number.isInteger(__privateGet(this, _chunkSize)) || __privateGet(this, _chunkSize) < __pow(2, 10)) { throw new Error("Invalid StreamTarget options: chunkSize must be an integer not smaller than 1024."); } } write(data) { __privateMethod(this, _writeDataIntoChunks, writeDataIntoChunks_fn).call(this, data, this.pos); __privateMethod(this, _flushChunks, flushChunks_fn).call(this); this.pos += data.byteLength; } finalize() { var _a, _b; __privateMethod(this, _flushChunks, flushChunks_fn).call(this, true); (_b = (_a = __privateGet(this, _target3)).onDone) == null ? void 0 : _b.call(_a); } }; _target3 = /* @__PURE__ */ new WeakMap(); _chunkSize = /* @__PURE__ */ new WeakMap(); _chunks = /* @__PURE__ */ new WeakMap(); _writeDataIntoChunks = /* @__PURE__ */ new WeakSet(); writeDataIntoChunks_fn = function(data, position) { let chunkIndex = __privateGet(this, _chunks).findIndex((x) => x.start <= position && position < x.start + __privateGet(this, _chunkSize)); if (chunkIndex === -1) chunkIndex = __privateMethod(this, _createChunk, createChunk_fn).call(this, position); let chunk = __privateGet(this, _chunks)[chunkIndex]; let relativePosition = position - chunk.start; let toWrite = data.subarray(0, Math.min(__privateGet(this, _chunkSize) - relativePosition, data.byteLength)); chunk.data.set(toWrite, relativePosition); let section = { start: relativePosition, end: relativePosition + toWrite.byteLength }; __privateMethod(this, _insertSectionIntoChunk, insertSectionIntoChunk_fn).call(this, chunk, section); if (chunk.written[0].start === 0 && chunk.written[0].end === __privateGet(this, _chunkSize)) { chunk.shouldFlush = true; } if (__privateGet(this, _chunks).length > MAX_CHUNKS_AT_ONCE) { for (let i = 0; i < __privateGet(this, _chunks).length - 1; i++) { __privateGet(this, _chunks)[i].shouldFlush = true; } __privateMethod(this, _flushChunks, flushChunks_fn).call(this); } if (toWrite.byteLength < data.byteLength) { __privateMethod(this, _writeDataIntoChunks, writeDataIntoChunks_fn).call(this, data.subarray(toWrite.byteLength), position + toWrite.byteLength); } }; _insertSectionIntoChunk = /* @__PURE__ */ new WeakSet(); insertSectionIntoChunk_fn = function(chunk, section) { let low = 0; let high = chunk.written.length - 1; let index = -1; while (low <= high) { let 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 = /* @__PURE__ */ new WeakSet(); createChunk_fn = function(includesPosition) { let start = Math.floor(includesPosition / __privateGet(this, _chunkSize)) * __privateGet(this, _chunkSize); let chunk = { start, data: new Uint8Array(__privateGet(this, _chunkSize)), written: [], shouldFlush: false }; __privateGet(this, _chunks).push(chunk); __privateGet(this, _chunks).sort((a, b) => a.start - b.start); return __privateGet(this, _chunks).indexOf(chunk); }; _flushChunks = /* @__PURE__ */ new WeakSet(); flushChunks_fn = function(force = false) { for (let i = 0; i < __privateGet(this, _chunks).length; i++) { let chunk = __privateGet(this, _chunks)[i]; if (!chunk.shouldFlush && !force) continue; for (let section of chunk.written) { __privateGet(this, _target3).onData( chunk.data.subarray(section.start, section.end), chunk.start + section.start ); } __privateGet(this, _chunks).splice(i--, 1); } }; var FileSystemWritableFileStreamTargetWriter = class extends ChunkedStreamTargetWriter { constructor(target) { var _a; super(new StreamTarget( (data, position) => target.stream.write({ type: "write", data, position }), void 0, { chunkSize: (_a = target.options) == null ? void 0 : _a.chunkSize } )); } }; var GLOBAL_TIMESCALE = 1e3; var SUPPORTED_VIDEO_CODECS2 = ["avc", "hevc", "vp9", "av1"]; var SUPPORTED_AUDIO_CODECS2 = ["aac", "opus"]; var TIMESTAMP_OFFSET = 2082844800; var MAX_CHUNK_DURATION = 0.5; var FIRST_TIMESTAMP_BEHAVIORS = ["strict", "offset"]; var _options; var _writer; var _mdat; var _videoTrack; var _audioTrack; var _creationTime; var _finalized; var _validateOptions; var validateOptions_fn; var _writeHeader; var writeHeader_fn; var _prepareTracks; var prepareTracks_fn; var _generateMpeg4AudioSpecificConfig; var generateMpeg4AudioSpecificConfig_fn; var _addSampleToTrack; var addSampleToTrack_fn; var _validateTimestamp; var validateTimestamp_fn; var _writeCurrentChunk; var writeCurrentChunk_fn; var _maybeFlushStreamingTargetWriter; var maybeFlushStreamingTargetWriter_fn; var _ensureNotFinalized; var ensureNotFinalized_fn; var Muxer = class { constructor(options) { __privateAdd(this, _validateOptions); __privateAdd(this, _writeHeader); __privateAdd(this, _prepareTracks); __privateAdd(this, _generateMpeg4AudioSpecificConfig); __privateAdd(this, _addSampleToTrack); __privateAdd(this, _validateTimestamp); __privateAdd(this, _writeCurrentChunk); __privateAdd(this, _maybeFlushStreamingTargetWriter); __privateAdd(this, _ensureNotFinalized); __privateAdd(this, _options, void 0); __privateAdd(this, _writer, void 0); __privateAdd(this, _mdat, void 0); __privateAdd(this, _videoTrack, null); __privateAdd(this, _audioTrack, null); __privateAdd(this, _creationTime, Math.floor(Date.now() / 1e3) + TIMESTAMP_OFFSET); __privateAdd(this, _finalized, false); var _a; __privateMethod(this, _validateOptions, validateOptions_fn).call(this, options); this.target = options.target; __privateSet(this, _options, __spreadValues({ firstTimestampBehavior: "strict" }, options)); if (options.target instanceof ArrayBufferTarget) { __privateSet(this, _writer, new ArrayBufferTargetWriter(options.target)); } else if (options.target instanceof StreamTarget) { __privateSet(this, _writer, ((_a = options.target.options) == null ? void 0 : _a.chunked) ? new ChunkedStreamTargetWriter(options.target) : new StreamTargetWriter(options.target)); } else if (options.target instanceof FileSystemWritableFileStreamTarget) { __privateSet(this, _writer, new FileSystemWritableFileStreamTargetWriter(options.target)); } else { throw new Error(`Invalid target: ${options.target}`); } __privateMethod(this, _writeHeader, writeHeader_fn).call(this); __privateMethod(this, _prepareTracks, prepareTracks_fn).call(this); } addVideoChunk(sample, meta, timestamp) { let data = new Uint8Array(sample.byteLength); sample.copyTo(data); this.addVideoChunkRaw(data, sample.type, timestamp != null ? timestamp : sample.timestamp, sample.duration, meta); } addVideoChunkRaw(data, type, timestamp, duration, meta) { __privateMethod(this, _ensureNotFinalized, ensureNotFinalized_fn).call(this); if (!__privateGet(this, _options).video) throw new Error("No video track declared."); __privateMethod(this, _addSampleToTrack, addSampleToTrack_fn).call(this, __privateGet(this, _videoTrack), data, type, timestamp, duration, meta); } addAudioChunk(sample, meta, timestamp) { let data = new Uint8Array(sample.byteLength); sample.copyTo(data); this.addAudioChunkRaw(data, sample.type, timestamp != null ? timestamp : sample.timestamp, sample.duration, meta); } addAudioChunkRaw(data, type, timestamp, duration, meta) { __privateMethod(this, _ensureNotFinalized, ensureNotFinalized_fn).call(this); if (!__privateGet(this, _options).audio) throw new Error("No audio track declared."); __privateMethod(this, _addSampleToTrack, addSampleToTrack_fn).call(this, __privateGet(this, _audioTrack), data, type, timestamp, duration, meta); } /** Finalizes the file, making it ready for use. Must be called after all video and audio chunks have been added. */ finalize() { if (__privateGet(this, _videoTrack)) __privateMethod(this, _writeCurrentChunk, writeCurrentChunk_fn).call(this, __privateGet(this, _videoTrack)); if (__privateGet(this, _audioTrack)) __privateMethod(this, _writeCurrentChunk, writeCurrentChunk_fn).call(this, __privateGet(this, _audioTrack)); let mdatPos = __privateGet(this, _writer).offsets.get(__privateGet(this, _mdat)); let mdatSize = __privateGet(this, _writer).pos - mdatPos; __privateGet(this, _mdat).size = mdatSize; __privateGet(this, _writer).patchBox(__privateGet(this, _mdat)); let movieBox = moov([__privateGet(this, _videoTrack), __privateGet(this, _audioTrack)].filter(Boolean), __privateGet(this, _creationTime)); __privateGet(this, _writer).writeBox(movieBox); __privateMethod(this, _maybeFlushStreamingTargetWriter, maybeFlushStreamingTargetWriter_fn).call(this); __privateGet(this, _writer).finalize(); __privateSet(this, _finalized, true); } }; _options = /* @__PURE__ */ new WeakMap(); _writer = /* @__PURE__ */ new WeakMap(); _mdat = /* @__PURE__ */ new WeakMap(); _videoTrack = /* @__PURE__ */ new WeakMap(); _audioTrack = /* @__PURE__ */ new WeakMap(); _creationTime = /* @__PURE__ */ new WeakMap(); _finalized = /* @__PURE__ */ new WeakMap(); _validateOptions = /* @__PURE__ */ new WeakSet(); validateOptions_fn = function(options) { if (options.video) { if (!SUPPORTED_VIDEO_CODECS2.includes(options.video.codec)) { throw new Error(`Unsupported video codec: ${options.video.codec}`); } if (options.video.rotation !== void 0 && ![0, 90, 180, 270].includes(options.video.rotation)) { throw new Error(`Invalid video rotation: ${options.video.rotation}. Has to be 0, 90, 180 or 270.`); } } if (options.audio && !SUPPORTED_AUDIO_CODECS2.includes(options.audio.codec)) { throw new Error(`Unsupported audio codec: ${options.audio.codec}`); } if (options.firstTimestampBehavior && !FIRST_TIMESTAMP_BEHAVIORS.includes(options.firstTimestampBehavior)) { throw new Error(`Invalid first timestamp behavior: ${options.firstTimestampBehavior}`); } }; _writeHeader = /* @__PURE__ */ new WeakSet(); writeHeader_fn = function() { var _a; let holdsHevc = ((_a = __privateGet(this, _options).video) == null ? void 0 : _a.codec) === "hevc"; __privateGet(this, _writer).writeBox(ftyp(holdsHevc)); __privateSet(this, _mdat, mdat()); __privateGet(this, _writer).writeBox(__privateGet(this, _mdat)); __privateMethod(this, _maybeFlushStreamingTargetWriter, maybeFlushStreamingTargetWriter_fn).call(this); }; _prepareTracks = /* @__PURE__ */ new WeakSet(); prepareTracks_fn = function() { var _a; if (__privateGet(this, _options).video) { __privateSet(this, _videoTrack, { id: 1, info: { type: "video", codec: __privateGet(this, _options).video.codec, width: __privateGet(this, _options).video.width, height: __privateGet(this, _options).video.height, rotation: (_a = __privateGet(this, _options).video.rotation) != null ? _a : 0 }, timescale: 720, // = lcm(24, 30, 60, 120, 144, 240, 360), so should fit with many framerates codecPrivate: new Uint8Array(0), samples: [], writtenChunks: [], currentChunk: null, firstTimestamp: void 0, lastTimestamp: -1, timeToSampleTable: [], lastTimescaleUnits: null, compactlyCodedChunkTable: [] }); } if (__privateGet(this, _options).audio) { let guessedCodecPrivate = __privateMethod(this, _generateMpeg4AudioSpecificConfig, generateMpeg4AudioSpecificConfig_fn).call( this, 2, // Object type for AAC-LC, since it's the most common __privateGet(this, _options).audio.sampleRate, __privateGet(this, _options).audio.numberOfChannels ); __privateSet(this, _audioTrack, { id: __privateGet(this, _options).video ? 2 : 1, info: { type: "audio", codec: __privateGet(this, _options).audio.codec, numberOfChannels: __privateGet(this, _options).audio.numberOfChannels, sampleRate: __privateGet(this, _options).audio.sampleRate }, timescale: __privateGet(this, _options).audio.sampleRate, codecPrivate: guessedCodecPrivate, samples: [], writtenChunks: [], currentChunk: null, firstTimestamp: void 0, lastTimestamp: -1, timeToSampleTable: [], lastTimescaleUnits: null, compactlyCodedChunkTable: [] }); } }; _generateMpeg4AudioSpecificConfig = /* @__PURE__ */ new WeakSet(); generateMpeg4AudioSpecificConfig_fn = function(objectType, sampleRate, numberOfChannels) { let frequencyIndices = [96e3, 88200, 64e3, 48e3, 44100, 32e3, 24e3, 22050, 16e3, 12e3, 11025, 8e3, 7350]; let frequencyIndex = frequencyIndices.indexOf(sampleRate); let channelConfig = numberOfChannels; let configBits = ""; configBits += objectType.toString(2).padStart(5, "0"); configBits += frequencyIndex.toString(2).padStart(4, "0"); if (frequencyIndex === 15) configBits += sampleRate.toString(2).padStart(24, "0"); configBits += channelConfig.toString(2).padStart(4, "0"); let paddingLength = Math.ceil(configBits.length / 8) * 8; configBits = configBits.padEnd(paddingLength, "0"); let configBytes = new Uint8Array(configBits.length / 8); for (let i = 0; i < configBits.length; i += 8) { configBytes[i / 8] = parseInt(configBits.slice(i, i + 8), 2); } return configBytes; }; _addSampleToTrack = /* @__PURE__ */ new WeakSet(); addSampleToTrack_fn = function(track, data, type, timestamp, duration, meta) { var _a; let timestampInSeconds = timestamp / 1e6; let durationInSeconds = duration / 1e6; if (track.firstTimestamp === void 0) track.firstTimestamp = timestampInSeconds; timestampInSeconds = __privateMethod(this, _validateTimestamp, validateTimestamp_fn).call(this, timestampInSeconds, track); track.lastTimestamp = timestampInSeconds; if (!track.currentChunk || timestampInSeconds - track.currentChunk.startTimestamp >= MAX_CHUNK_DURATION) { if (track.currentChunk) __privateMethod(this, _writeCurrentChunk, writeCurrentChunk_fn).call(this, track); track.currentChunk = { startTimestamp: timestampInSeconds, sampleData: [], sampleCount: 0 }; } track.currentChunk.sampleData.push(data); track.currentChunk.sampleCount++; if ((_a = meta == null ? void 0 : meta.decoderConfig) == null ? void 0 : _a.description) { track.codecPrivate = new Uint8Array(meta.decoderConfig.description); } track.samples.push({ timestamp: timestampInSeconds, duration: durationInSeconds, size: data.byteLength, type }); if (track.lastTimescaleUnits !== null) { let timescaleUnits = intoTimescale(timestampInSeconds, track.timescale, false); let delta = Math.round(timescaleUnits - track.lastTimescaleUnits); track.lastTimescaleUnits += delta; let lastTableEntry = last(track.timeToSampleTable); if (lastTableEntry.sampleCount === 1) { lastTableEntry.sampleDelta = delta; lastTableEntry.sampleCount++; } else if (lastTableEntry.sampleDelta === delta) { lastTableEntry.sampleCount++; } else { lastTableEntry.sampleCount--; track.timeToSampleTable.push({ sampleCount: 2, sampleDelta: delta }); } } else { track.lastTimescaleUnits = 0; track.timeToSampleTable.push({ sampleCount: 1, sampleDelta: intoTimescale(durationInSeconds, track.timescale) }); } }; _validateTimestamp = /* @__PURE__ */ new WeakSet(); validateTimestamp_fn = function(timestamp, track) { if (__privateGet(this, _options).firstTimestampBehavior === "strict" && track.lastTimestamp === -1 && timestamp !== 0) { throw new Error( `The first chunk for your media track must have a timestamp of 0 (received ${timestamp}). Non-zero first timestamps are often caused by directly piping frames or audio data from a MediaStreamTrack into the encoder. Their timestamps are typically relative to the age of the document, which is probably what you want. If you want to offset all timestamps of a track such that the first one is zero, set firstTimestampBehavior: 'offset' in the options. ` ); } else if (__privateGet(this, _options).firstTimestampBehavior === "offset") { timestamp -= track.firstTimestamp; } if (timestamp < track.lastTimestamp) { throw new Error( `Timestamps must be monotonically increasing (went from ${track.lastTimestamp * 1e6} to ${timestamp * 1e6}).` ); } return timestamp; }; _writeCurrentChunk = /* @__PURE__ */ new WeakSet(); writeCurrentChunk_fn = function(track) { if (!track.currentChunk) return; track.currentChunk.offset = __privateGet(this, _writer).pos; for (let bytes2 of track.currentChunk.sampleData) __privateGet(this, _writer).write(bytes2); track.currentChunk.sampleData = null; if (track.compactlyCodedChunkTable.length === 0 || last(track.compactlyCodedChunkTable).samplesPerChunk !== track.currentChunk.sampleCount) { track.compactlyCodedChunkTable.push({ firstChunk: track.writtenChunks.length + 1, // 1-indexed samplesPerChunk: track.currentChunk.sampleCount }); } track.writtenChunks.push(track.currentChunk); __privateMethod(this, _maybeFlushStreamingTargetWriter, maybeFlushStreamingTargetWriter_fn).call(this); }; _maybeFlushStreamingTargetWriter = /* @__PURE__ */ new WeakSet(); maybeFlushStreamingTargetWriter_fn = function() { if (__privateGet(this, _writer) instanceof StreamTargetWriter) { __privateGet(this, _writer).flush(); } }; _ensureNotFinalized = /* @__PURE__ */ new WeakSet(); ensureNotFinalized_fn = function() { if (__privateGet(this, _finalized)) { throw new Error("Cannot add new video or audio chunks after the file has been finalized."); } }; // src/muxers/mp4muxer.ts var Mp4MuxerWrapper = class { constructor(config, postMessageCallback, options) { this.videoConfigured = false; this.audioConfigured = false; this.firstAudioTimestamp = null; this.firstVideoTimestamp = null; this.firstTimestamp = null; this.config = config; this.postMessageToMain = postMessageCallback; let disableAudio = options?.disableAudio ?? false; const videoDisabled = config.width <= 0 || config.height <= 0 || config.videoBitrate <= 0; const audioCodecOption = config.codec?.audio ?? "aac"; const supportedAudioCodecsForMp4 = /* @__PURE__ */ new Set(["aac", "mp3"]); if (!supportedAudioCodecsForMp4.has(audioCodecOption)) { console.warn( `MP4 muxer: Audio codec ${audioCodecOption} is not supported. Disabling audio track.` ); disableAudio = true; } const muxerAudioCodec = audioCodecOption; const commonMuxerOptions = {}; if (!videoDisabled) { const videoCodecOption = config.codec?.video ?? "avc"; let muxerVideoCodec; switch (videoCodecOption) { case "hevc": muxerVideoCodec = "hevc"; break; case "vp9": muxerVideoCodec = "vp9"; break; case "av1": muxerVideoCodec = "av1"; break; case "avc": default: muxerVideoCodec = "avc"; break; } commonMuxerOptions.video = { codec: muxerVideoCodec, width: config.width, height: config.height // framerate is not directly a muxer option here, but good to have in config }; } if (!disableAudio) { commonMuxerOptions.audio = { codec: muxerAudioCodec, sampleRate: config.sampleRate, numberOfChannels: config.channels }; } if (config.latencyMode === "realtime") { this.target = new StreamTarget({ onData: (chunk, position) => { const chunkCopy = new Uint8Array(chunk.slice(0)); const isHeader = position === 0; const message = { type: "dataChunk", chunk: chunkCopy, offset: position, // Use position as offset isHeader, container: "mp4" }; this.postMessageToMain(message, [chunkCopy.buffer]); } }); this.muxer = new Muxer({ target: this.target, ...commonMuxerOptions, fastStart: "fragmented" }); } else { this.target = new ArrayBufferTarget(); this.muxer = new Muxer({ target: this.target, ...commonMuxerOptions, fastStart: "in-memory" // or false, depending on desired behavior for non-realtime }); } this.videoConfigured = !videoDisabled; this.audioConfigured = !disableAudio; } addVideoChunk(chunk, meta) { if (!this.videoConfigured) { this.postMessageToMain({ type: "error", errorDetail: { message: "MP4: Video track not configured.", type: "configuration-error" /* ConfigurationError */ } }); return; } try { let adjustedChunk = chunk; const adjustedMeta = meta; if (this.config.firstTimestampBehavior === "offset" && typeof chunk.timestamp === "number") { if (this.firstVideoTimestamp === null) { this.firstVideoTimestamp = chunk.timestamp; if (this.firstTimestamp === null) { this.firstTimestamp = chunk.timestamp; } else { this.firstTimestamp = Math.min( this.firstTimestamp, chunk.timestamp ); } } const newTimestamp = Math.max( 0, chunk.timestamp - (this.firstTimestamp || 0) ); const data = new Uint8Array(chunk.byteLength); chunk.copyTo(data.buffer); chunk.close?.(); adjustedChunk = new EncodedVideoChunk({ type: chunk.type, timestamp: newTimestamp, duration: chunk.duration ?? void 0, data: data.buffer }); } else if (typeof chunk.timestamp !== "number" && this.config.firstTimestampBehavior === "offset") { } this.muxer.addVideoChunk(adjustedChunk, adjustedMeta); } catch (e) { this.postMessageToMain({ type: "error", errorDetail: { message: `MP4: Error adding video chunk: ${e.message}`, type: "muxing-failed" /* MuxingFailed */, stack: e.stack } }); } } addAudioChunk(chunk, meta) { if (!this.audioConfigured) { return; } try { let adjustedChunk = chunk; const adjustedMeta = meta; if (this.config.firstTimestampBehavior === "offset" && typeof chunk.timestamp === "number") { if (this.firstAudioTimestamp === null) { this.firstAudioTimestamp = chunk.timestamp; if (this.firstTimestamp === null) { this.firstTimestamp = chunk.timestamp; } else { this.firstTimestamp = Math.min( this.firstTimestamp, chunk.timestamp ); } } const newTimestamp = Math.max( 0, chunk.timestamp - (this.firstTimestamp || 0) ); const data = new Uint8Array(chunk.byteLength); chunk.copyTo(data.buffer); chunk.close?.(); adjustedChunk = new EncodedAudioChunk({ type: chunk.type, timestamp: newTimestamp, duration: chunk.duration ?? void 0, data: data.buffer }); } else if (typeof chunk.timestamp !== "number" && this.config.firstTimestampBehavior === "offset") { } this.muxer.addAudioChunk(adjustedChunk, adjustedMeta); } catch (e) { this.postMessageToMain({ type: "error", errorDetail: { message: `MP4: Error adding audio chunk: ${e.message}`, type: "muxing-failed" /* MuxingFailed */, stack: e.stack } }); } } finalize() { if (this.config.latencyMode === "realtime") { try { this.muxer.finalize(); } catch (e) { this.postMessageToMain({ type: "error", errorDetail: { message: `MP4: Error finalizing muxer (realtime): ${e.message}`, type: "muxing-failed" /* MuxingFailed */, stack: e.stack } }); } return null; } if (!(this.target instanceof ArrayBufferTarget)) { this.postMessageToMain({ type: "error", errorDetail: { message: "MP4: Muxer target is not ArrayBufferTarget in non-realtime mode.", type: "unknown" /* Unknown */ } }); return null; } try { this.muxer.finalize(); const buffer = this.target.buffer; this.target = new ArrayBufferTarget(); return new Uint8Array(buffer); } catch (e) { this.postMessageToMain({ type: "error", errorDetail: { message: `MP4: Error finalizing muxer (non-realtime): ${e.message}`, type: "muxing-failed" /* MuxingFailed */, stack: e.stack } }); return null; } } }; // src/muxers/webmmuxer.ts var import_webm_muxer = __toESM(require_webm_muxer(), 1); var CallbackWritableStream = class { constructor(onData) { this.onData = onData; this.position = 0; } write({ data, position }) { this.onData(data, position); this.position = position + data.byteLength; } }; var WebMMuxerWrapper = class { constructor(config, postMessageCallback, options) { this.videoConfigured = false; this.audioConfigured = false; this.firstAudioTimestamp = null; this.firstVideoTimestamp = null; this.firstTimestamp = null; this.config = config; this.postMessageToMain = postMessageCallback; let disableAudio = options?.disableAudio ?? false; const videoCodecOption = config.codec?.video ?? "vp9"; let muxerVideoCodec; switch (videoCodecOption) { case "vp8": muxerVideoCodec = "V_VP8"; break; case "vp9": muxerVideoCodec = "V_VP9"; break; case "av1": muxerVideoCodec = "V_AV1"; break; default: muxerVideoCodec = "V_VP9"; break; } const requestedAudioCodec = config.codec?.audio ?? "opus"; let muxerAudioCodec = null; switch (requestedAudioCodec) { case "opus": muxerAudioCodec = "A_OPUS"; break; case "vorbis": muxerAudioCodec = "A_VORBIS"; break; case "flac": muxerAudioCodec = "A_FLAC"; break; default: if (!disableAudio) { console.warn( `WebM muxer: Audio codec ${requestedAudioCodec} is not supported. Disabling audio track.` ); disableAudio = true; } break; } const target = config.latencyMode === "realtime" ? new CallbackWritableStream((chunk, position) => { const chunkCopy = new Uint8Array(chunk.slice(0)); const isHeader = position === 0; const message = { type: "dataChunk", chunk: chunkCopy, offset: position, isHeader, container: "webm" }; this.postMessageToMain(message, [chunkCopy.buffer]); }) : "buffer"; const videoDisabled = config.width === 0 || config.height === 0 || config.videoBitrate === 0; const optionsForMuxer = { target }; if (!videoDisabled) { optionsForMuxer.video = { codec: muxerVideoCodec, width: config.width, height: config.height, frameRate: config.frameRate }; } if (!disableAudio && muxerAudioCodec) { optionsForMuxer.audio = { codec: muxerAudioCodec, numberOfChannels: config.channels, sampleRate: config.sampleRate }; } this.muxer = new import_webm_muxer.default(optionsForMuxer); this.videoConfigured = !videoDisabled; this.audioConfigured = !disableAudio; } addVideoChunk(chunk, meta) { if (!this.videoConfigured) { this.postMessageToMain({ type: "error", errorDetail: { message: "WebM: Video track not configured.", type: "configuration-error" /* ConfigurationError */ } }); return; } try { let adjustedChunk = chunk; const adjustedMeta = meta; if (this.config.firstTimestampBehavior === "offset" && typeof chunk.timestamp === "number") { if (this.firstVideoTimestamp === null) { this.firstVideoTimestamp = chunk.timestamp; if (this.firstTimestamp === null) { this.firstTimestamp = chunk.timestamp; } else { this.firstTimestamp = Math.min( this.firstTimestamp, chunk.timestamp ); } } const newTimestamp = Math.max( 0, chunk.timestamp - (this.firstTimestamp || 0) ); const data = new Uint8Array(chunk.byteLength); chunk.copyTo(data.buffer); chunk.close?.(); adjustedChunk = new EncodedVideoChunk({ type: chunk.type, timestamp: newTimestamp, duration: chunk.duration ?? void 0, data: data.buffer }); } this.muxer.addVideoChunk(adjustedChunk, adjustedMeta); } catch (e) { this.postMessageToMain({ type: "error", errorDetail: { message: `WebM: Error adding video chunk: ${e.message}`, type: "muxing-failed" /* MuxingFailed */, stack: e.stack } }); } } addAudioChunk(chunk, meta) { if (!this.audioConfigured) return; try { let adjustedChunk = chunk; const adjustedMeta = meta; if (this.config.firstTimestampBehavior === "offset" && typeof chunk.timestamp === "number") { if (this.firstAudioTimestamp === null) { this.firstAudioTimestamp = chunk.timestamp; if (this.firstTimestamp === null) { this.firstTimestamp = chunk.timestamp; } else { this.firstTimestamp = Math.min( this.firstTimestamp, chunk.timestamp ); } } const newTimestamp = Math.max( 0, chunk.timestamp - (this.firstTimestamp || 0) ); const data = new Uint8Array(chunk.byteLength); chunk.copyTo(data.buffer); chunk.close?.(); adjustedChunk = new EncodedAudioChunk({ type: chunk.type, timestamp: newTimestamp, duration: chunk.duration ?? void 0, data: data.buffer }); } this.muxer.addAudioChunk(adjustedChunk, adjustedMeta); } catch (e) { this.postMessageToMain({ type: "error", errorDetail: { message: `WebM: Error adding audio chunk: ${e.message}`, type: "muxing-failed" /* MuxingFailed */, stack: e.stack } }); } } finalize() { if (this.config.latencyMode === "realtime") { try { this.muxer.finalize(); } catch (e) { this.postMessageToMain({ type: "error", errorDetail: { message: `WebM: Error finalizing muxer (realtime): ${e.message}`, type: "muxing-failed" /* MuxingFailed */, stack: e.stack } }); } return null; } try { const buffer = this.muxer.finalize(); if (buffer) return new Uint8Array(buffer); this.postMessageToMain({ type: "error", errorDetail: { message: "WebM: Muxer finalized without output in non-realtime mode.", type: "muxing-failed" /* MuxingFailed */ } }); return null; } catch (e) { this.postMessageToMain({ type: "error", errorDetail: { message: `WebM: Error finalizing muxer (non-realtime): ${e.message}`, type: "muxing-failed" /* MuxingFailed */, stack: e.stack } }); return null; } } }; // src/worker/encoder-worker.ts if (typeof self !== "undefined" && typeof self.addEventListener === "function") { self.addEventListener("error", (event) => { console.error("Unhandled global error in worker. Event:", event); const message = event.message || `Unhandled global error${event.filename ? ` at ${event.filename}` : ""}`; self.postMessage({ type: "error", errorDetail: { message, type: "worker-error" /* WorkerError */, stack: event.error?.stack } }); }); } if (typeof self !== "undefined" && typeof self.addEventListener === "function") { self.addEventListener( "unhandledrejection", (event) => { console.error( "Unhandled promise rejection in worker. Reason:", event.reason ); const reason = event.reason; const message = reason instanceof Error ? `Unhandled promise rejection: ${reason.message}` : `Unhandled promise rejection: ${String(reason)}`; self.postMessage({ type: "error", errorDetail: { message, type: "worker-error" /* WorkerError */, stack: reason instanceof Error ? reason.stack : void 0 } }); } ); } var getVideoEncoder = () => self.VideoEncoder ?? globalThis.VideoEncoder; var getAudioEncoder = () => self.AudioEncoder ?? globalThis.AudioEncoder; var getAudioData = () => self.AudioData ?? globalThis.AudioData; var EncoderWorker = class { constructor() { this.videoEncoder = null; this.audioEncoder = null; this.muxer = null; this.currentConfig = null; this.processedFrames = 0; this.videoFrameCount = 0; this.isCancelled = false; } postMessageToMainThread(message, transfer) { if (transfer && transfer.length > 0) { self.postMessage(message, transfer); } else { self.postMessage(message); } } defaultAvcCodecString(width, height, frameRate, profile) { const mbPerSec = Math.ceil(width / 16) * Math.ceil(height / 16) * frameRate; let level; if (mbPerSec <= 108e3) level = 31; else if (mbPerSec <= 216e3) level = 32; else if (mbPerSec <= 245760) level = 40; else if (mbPerSec <= 589824) level = 50; else if (mbPerSec <= 983040) level = 51; else level = 52; const chosenProfile = profile ?? (width >= 1280 || height >= 720 ? "high" : "baseline"); const profileHex = chosenProfile === "high" ? "64" : chosenProfile === "main" ? "4d" : "42"; const levelHex = level.toString(16).padStart(2, "0"); return `avc1.${profileHex}00${levelHex}`; } getCodecString(codecType, width, height, frameRate) { switch (codecType) { case "avc": return this.defaultAvcCodecString(width, height, frameRate); case "vp9": return "vp09.00.50.08"; case "vp8": return "vp8"; case "hevc": return "hvc1"; case "av1": return "av01.0.04M.08"; default: return codecType; } } async isConfigSupportedWithHwFallback(Ctor, config, label) { let support = await Ctor.isConfigSupported(config); if (support?.supported && support.config) return support.config; const pref = config.hardwareAcceleration; if (pref) { let altPref; if (pref === "prefer-hardware") altPref = "prefer-software"; else if (pref === "prefer-software") altPref = "prefer-hardware"; if (altPref) { const opposite = { ...config, hardwareAcceleration: altPref }; support = await Ctor.isConfigSupported(opposite); if (support?.supported && support.config) { console.warn( `${label}: hardwareAcceleration preference '${pref}' not supported. Using '${altPref}'.` ); return support.config; } } const noPref = { ...config }; delete noPref.hardwareAcceleration; support = await Ctor.isConfigSupported(noPref); if (support?.supported && support.config) { console.warn( `${label}: hardwareAcceleration preference '${pref}' not supported. Using no preference.` ); return support.config; } console.warn( `${label}: Failed to find a supported hardware acceleration configuration for codec ${config.codec}.` ); } return null; } postQueueSize() { this.postMessageToMainThread({ type: "queueSize", videoQueueSize: this.videoEncoder?.encodeQueueSize ?? 0, audioQueueSize: this.audioEncoder?.encodeQueueSize ?? 0 }); } async prepareAudioCodec(container, audioDisabled) { if (audioDisabled) { return { audioDisabled: true, selectedCodec: null, finalConfig: null, encoderCtor: null }; } const AudioEncoderCtor = getAudioEncoder(); if (!AudioEncoderCtor) { this.postMessageToMainThread({ type: "error", errorDetail: { message: "Worker: AudioEncoder not available", type: "not-supported" /* NotSupported */ } }); return { audioDisabled: true, selectedCodec: null, finalConfig: null, encoderCtor: null }; } const config = this.currentConfig; if (!config) { return { audioDisabled: true, selectedCodec: null, finalConfig: null, encoderCtor: AudioEncoderCtor }; } const requestedCodec = config.codec?.audio; const requestedCodecString = config.codecString?.audio; const preference = buildAudioCodecPreference(container, requestedCodec); let attemptedDefaultCodec = false; for (const candidate of preference) { if (candidate === "aac") { attemptedDefaultCodec = true; } const codecString = requestedCodec && requestedCodecString && candidate === requestedCodec ? requestedCodecString : getAudioEncoderCodecStringFromAudioCodec(candidate); const baseConfig = { codec: codecString, sampleRate: config.sampleRate, numberOfChannels: config.channels, bitrate: config.audioBitrate, ...config.audioBitrateMode && { bitrateMode: config.audioBitrateMode }, ...config.hardwareAcceleration && { hardwareAcceleration: config.hardwareAcceleration }, ...getAudioEncoderConfigOverridesForCodec( candidate, config.audioEncoderConfig ) }; const support = await this.isConfigSupportedWithHwFallback( AudioEncoderCtor, baseConfig, "AudioEncoder" ); if (!support) { if (candidate === "aac" && container === "mp4") { console.warn( "Worker: AAC audio codec is not supported. Falling back to MP3." ); const mp3AttemptConfig = { codec: getAudioEncoderCodecStringFromAudioCodec("mp3"), sampleRate: config.sampleRate, numberOfChannels: config.channels, bitrate: config.audioBitrate, ...config.audioBitrateMode && { bitrateMode: config.audioBitrateMode }, ...config.hardwareAcceleration && { hardwareAcceleration: config.hardwareAcceleration }, ...getAudioEncoderConfigOverridesForCodec( "mp3", config.audioEncoderConfig ) }; const mp3Support = await this.isConfigSupportedWithHwFallback( AudioEncoderCtor, mp3AttemptConfig, "AudioEncoder" ); if (mp3Support) { const resolvedMp3Config = mp3Support; if (resolvedMp3Config.numberOfChannels !== void 0 && resolvedMp3Config.numberOfChannels !== config.channels) { this.postMessageToMainThread({ type: "error", errorDetail: { message: `AudioEncoder reported numberOfChannels (${resolvedMp3Config.numberOfChannels}) does not match configured channels (${config.channels}).`, type: "configuration-error" /* ConfigurationError */ } }); return { audioDisabled: true, selectedCodec: null, finalConfig: null, encoderCtor: AudioEncoderCtor }; } if (resolvedMp3Config.sampleRate !== void 0 && resolvedMp3Config.sampleRate !== config.sampleRate) { this.postMessageToMainThread({ type: "error", errorDetail: { message: `AudioEncoder reported sampleRate (${resolvedMp3Config.sampleRate}) does not match configured sampleRate (${config.sampleRate}).`, type: "configuration-error" /* ConfigurationError */ } }); return { audioDisabled: true, selectedCodec: null, finalConfig: null, encoderCtor: AudioEncoderCtor }; } if (!isAudioCodecMuxerCompatible(container, "mp3")) { console.warn( "Worker: Audio codec mp3 is not compatible with MP4 muxer. Audio will be disabled." ); } else { console.warn("Worker: Falling back to MP3 for MP4 container."); return { audioDisabled: false, selectedCodec: "mp3", finalConfig: resolvedMp3Config, encoderCtor: AudioEncoderCtor }; } } } else { console.warn( `Worker: Audio codec ${candidate} not supported or config invalid.` ); } continue; } const resolvedConfig = support; if (resolvedConfig.numberOfChannels !== void 0 && resolvedConfig.numberOfChannels !== config.channels) { this.postMessageToMainThread({ type: "error", errorDetail: { message: `AudioEncoder reported numberOfChannels (${resolvedConfig.numberOfChannels}) does not match configured channels (${config.channels}).`, type: "configuration-error" /* ConfigurationError */ } }); return { audioDisabled: true, selectedCodec: null, finalConfig: null, encoderCtor: AudioEncoderCtor }; } if (resolvedConfig.sampleRate !== void 0 && resolvedConfig.sampleRate !== config.sampleRate) { this.postMessageToMainThread({ type: "error", errorDetail: { message: `AudioEncoder reported sampleRate (${resolvedConfig.sampleRate}) does not match configured sampleRate (${config.sampleRate}).`, type: "configuration-error" /* ConfigurationError */ } }); return { audioDisabled: true, selectedCodec: null, finalConfig: null, encoderCtor: AudioEncoderCtor }; } if (!isAudioCodecMuxerCompatible(container, candidate)) { console.warn( `Worker: Audio codec ${candidate} is not compatible with ${container.toUpperCase()} muxer. Trying fallback codec.` ); continue; } if (candidate === "aac") { attemptedDefaultCodec = true; if (container === "mp4" && requestedCodec && requestedCodec !== "aac") { console.warn("Worker: Falling back to AAC for MP4 container."); } } if (container === "mp4" && candidate === "mp3" && attemptedDefaultCodec) { console.warn("Worker: Falling back to MP3 for MP4 container."); } return { audioDisabled: false, selectedCodec: candidate, finalConfig: resolvedConfig, encoderCtor: AudioEncoderCtor }; } const defaultCodec = DEFAULT_AUDIO_CODEC_BY_CONTAINER[container]; const noCodecMessage = container === "mp4" ? "Worker: No supported audio codec (AAC, MP3) found for MP4 container." : `Worker: No supported audio codec found. Requested: ${requestedCodec ?? "(auto)"}. Tried: ${preference.join(", ")}.`; this.postMessageToMainThread({ type: "error", errorDetail: { message: noCodecMessage, type: "not-supported" /* NotSupported */ } }); console.warn( `Worker: Disabling audio. Consider using ${defaultCodec} for container ${container}.` ); return { audioDisabled: true, selectedCodec: null, finalConfig: null, encoderCtor: AudioEncoderCtor }; } async initializeEncoders(data) { this.currentConfig = data.config; this.totalFramesToProcess = data.totalFrames; this.processedFrames = 0; this.videoFrameCount = 0; this.isCancelled = false; if (!this.currentConfig) { this.postMessageToMainThread({ type: "error", errorDetail: { message: "Worker: Configuration is missing.", type: "initialization-failed" /* InitializationFailed */ } }); return; } let audioDisabled = !this.currentConfig.audioBitrate || this.currentConfig.audioBitrate <= 0 || !this.currentConfig.channels || this.currentConfig.channels <= 0 || !this.currentConfig.sampleRate || this.currentConfig.sampleRate <= 0 || !this.currentConfig.codec?.audio; const containerType = getContainerType(this.currentConfig.container); const audioOriginallyDisabled = audioDisabled; const audioPlan = await this.prepareAudioCodec( containerType, audioDisabled ); audioDisabled = audioPlan.audioDisabled; let selectedAudioCodec = audioPlan.selectedCodec; let finalAudioEncoderConfig = audioPlan.finalConfig; const preparedAudioEncoderCtor = audioPlan.encoderCtor; if (audioDisabled) { selectedAudioCodec = null; finalAudioEncoderConfig = null; } if (!audioOriginallyDisabled && audioDisabled) { this.cleanup(); return; } const videoDisabled = this.currentConfig.width === 0 || this.currentConfig.height === 0 || this.currentConfig.videoBitrate === 0; let videoCodec = this.currentConfig.codec?.video ?? (this.currentConfig.container === "webm" ? "vp9" : "avc"); const requestedVideoCodec = videoCodec; let finalVideoEncoderConfig = null; let resolvedVideoCodecString = null; let VideoEncoderCtor; if (!videoDisabled) { if (this.currentConfig.container === "webm" && (videoCodec === "avc" || videoCodec === "hevc")) { console.warn( `Worker: Video codec ${videoCodec} not compatible with WebM. Switching to VP9.` ); videoCodec = "vp9"; } resolvedVideoCodecString = (this.currentConfig.codecString?.video && videoCodec === requestedVideoCodec ? this.currentConfig.codecString.video : void 0) ?? (videoCodec === "avc" ? this.defaultAvcCodecString( this.currentConfig.width, this.currentConfig.height, this.currentConfig.frameRate ) : videoCodec === "vp9" ? "vp09.00.50.08" : videoCodec === "vp8" ? "vp8" : videoCodec === "hevc" ? "hvc1" : videoCodec === "av1" ? "av01.0.04M.08" : videoCodec); const videoEncoderConfig = { codec: resolvedVideoCodecString, width: this.currentConfig.width, height: this.currentConfig.height, bitrate: this.currentConfig.videoBitrate, framerate: this.currentConfig.frameRate, ...this.currentConfig.container === "mp4" && videoCodec === "avc" ? { avc: { format: "avc" } } : {}, ...this.currentConfig.hardwareAcceleration ? { hardwareAcceleration: this.currentConfig.hardwareAcceleration } : {}, ...getVideoEncoderConfigOverridesForCodec( videoCodec, this.currentConfig.videoEncoderConfig ) }; VideoEncoderCtor = getVideoEncoder(); if (!VideoEncoderCtor) { this.postMessageToMainThread({ type: "error", errorDetail: { message: "Worker: VideoEncoder not available", type: "not-supported" /* NotSupported */ } }); this.cleanup(); return; } const initialSupport = await VideoEncoderCtor.isConfigSupported(videoEncoderConfig); if (initialSupport?.supported && initialSupport.config) { finalVideoEncoderConfig = initialSupport.config; } else { if (videoCodec === "vp9" || videoCodec === "vp8" || videoCodec === "av1") { console.warn( "Worker: Video codec " + videoCodec + " not supported or config invalid. Looking for fallback..." ); let fallbackSuccessful = false; if (this.currentConfig.container === "webm") { const webmCodecs = ["vp9", "vp8"]; for (const fallbackCodec of webmCodecs) { if (fallbackCodec === videoCodec) continue; console.warn( `Worker: Trying fallback to ${fallbackCodec} for WebM container.` ); const fallbackCodecString = this.getCodecString( fallbackCodec, this.currentConfig.width, this.currentConfig.height, this.currentConfig.frameRate ); const fallbackConfig = { ...videoEncoderConfig, codec: fallbackCodecString, ...getVideoEncoderConfigOverridesForCodec( fallbackCodec, this.currentConfig.videoEncoderConfig ) }; const support = await this.isConfigSupportedWithHwFallback( VideoEncoderCtor, fallbackConfig, "VideoEncoder" ); if (support) { console.warn( `Worker: Successfully fell back to ${fallbackCodec} for WebM.` ); videoCodec = fallbackCodec; finalVideoEncoderConfig = support; fallbackSuccessful = true; break; } } if (!fallbackSuccessful) { this.postMessageToMainThread({ type: "error", errorDetail: { message: "Worker: No compatible video codec (VP9, VP8) found for WebM container.", type: "not-supported" /* NotSupported */ } }); this.cleanup(); return; } } else { console.warn("Worker: Falling back to AVC for MP4 container."); videoCodec = "avc"; const avcCodecString = this.defaultAvcCodecString( this.currentConfig.width, this.currentConfig.height, this.currentConfig.frameRate ); const avcConfig = { ...videoEncoderConfig, codec: avcCodecString, ...this.currentConfig.container === "mp4" ? { avc: { format: "avc" } } : {}, ...getVideoEncoderConfigOverridesForCodec( "avc", this.currentConfig.videoEncoderConfig ) }; const support = await this.isConfigSupportedWithHwFallback( VideoEncoderCtor, avcConfig, "VideoEncoder" ); if (support) { finalVideoEncoderConfig = support; fallbackSuccessful = true; } else { this.postMessageToMainThread({ type: "error", errorDetail: { message: "Worker: AVC (H.264) video codec is not supported after fallback.", type: "not-supported" /* NotSupported */ } }); this.cleanup(); return; } } } else { const result = await this.isConfigSupportedWithHwFallback( VideoEncoderCtor, videoEncoderConfig, "VideoEncoder" ); if (result) { finalVideoEncoderConfig = result; } else { this.postMessageToMainThread({ type: "error", errorDetail: { message: `Worker: Video codec ${videoCodec} config not supported.`, type: "not-supported" /* NotSupported */ } }); this.cleanup(); return; } } } } else { videoCodec = void 0; } const codecConfigForMuxer = { ...this.currentConfig.codec ?? {}, video: videoDisabled ? void 0 : videoCodec, audio: audioDisabled || !selectedAudioCodec ? void 0 : selectedAudioCodec }; this.currentConfig.codec = codecConfigForMuxer; try { const MuxerCtor = containerType === "webm" ? WebMMuxerWrapper : Mp4MuxerWrapper; this.muxer = new MuxerCtor( this.currentConfig, this.postMessageToMainThread.bind(this), { disableAudio: audioDisabled } ); } catch (e) { this.postMessageToMainThread({ type: "error", errorDetail: { message: `Worker: Failed to initialize Muxer: ${e.message}`, type: "initialization-failed" /* InitializationFailed */, stack: e.stack } }); this.cleanup(); return; } if (!videoDisabled) { try { if (!VideoEncoderCtor) { this.postMessageToMainThread({ type: "error", errorDetail: { message: "Worker: VideoEncoder not available", type: "not-supported" /* NotSupported */ } }); this.cleanup(); return; } this.videoEncoder = new VideoEncoderCtor({ output: (chunk, meta) => { if (this.isCancelled || !this.muxer) return; this.muxer.addVideoChunk(chunk, meta); }, error: (error) => { if (this.isCancelled) return; this.postMessageToMainThread({ type: "error", errorDetail: { message: `VideoEncoder error: ${error.message}`, type: "video-encoding-error" /* VideoEncodingError */, stack: error.stack } }); this.cleanup(); } }); if (finalVideoEncoderConfig) { if (this.videoEncoder) { this.videoEncoder.configure(finalVideoEncoderConfig); } else { this.postMessageToMainThread({ type: "error", errorDetail: { message: "Worker: VideoEncoder instance is null after creation.", type: "initialization-failed" /* InitializationFailed */ } }); this.cleanup(); return; } } else { this.postMessageToMainThread({ type: "error", errorDetail: { message: `Worker: VideoEncoder: Failed to find a supported hardware acceleration configuration for codec ${resolvedVideoCodecString ?? "(unknown)"}`, type: "not-supported" /* NotSupported */ } }); this.cleanup(); return; } } catch (e) { this.postMessageToMainThread({ type: "error", errorDetail: { message: `Worker: Failed to initialize VideoEncoder: ${e.message}`, type: "initialization-failed" /* InitializationFailed */, stack: e.stack } }); this.cleanup(); return; } } if (!audioDisabled) { if (!selectedAudioCodec || !finalAudioEncoderConfig || !preparedAudioEncoderCtor) { this.cleanup(); return; } try { this.audioEncoder = new preparedAudioEncoderCtor({ output: (chunk, meta) => { if (this.isCancelled || !this.muxer) return; this.muxer.addAudioChunk(chunk, meta); }, error: (error) => { if (this.isCancelled) return; this.postMessageToMainThread({ type: "error", errorDetail: { message: `AudioEncoder error: ${error.message}`, type: "audio-encoding-error" /* AudioEncodingError */, stack: error.stack } }); this.cleanup(); } }); if (this.audioEncoder) { this.audioEncoder.configure(finalAudioEncoderConfig); } else { this.postMessageToMainThread({ type: "error", errorDetail: { message: "Worker: AudioEncoder instance is null after creation.", type: "initialization-failed" /* InitializationFailed */ } }); this.cleanup(); return; } } catch (e) { this.postMessageToMainThread({ type: "error", errorDetail: { message: `Worker: Failed to initialize AudioEncoder: ${e.message}`, type: "initialization-failed" /* InitializationFailed */, stack: e.stack } }); this.cleanup(); return; } } this.postMessageToMainThread({ type: "initialized", actualVideoCodec: finalVideoEncoderConfig?.codec, actualAudioCodec: audioDisabled ? null : finalAudioEncoderConfig?.codec }); console.warn("Worker: Initialized successfully"); } async handleAddVideoFrame(data) { if (this.isCancelled || !this.videoEncoder || !this.currentConfig) return; try { const frame = data.frame; const currentQueueSize = this.videoEncoder.encodeQueueSize; const maxQueueSize = this.currentConfig.maxVideoQueueSize || 30; const strategy = this.currentConfig.backpressureStrategy || "drop"; if (currentQueueSize >= maxQueueSize) { if (strategy === "drop") { console.warn( `Video queue full (${currentQueueSize}/${maxQueueSize}), dropping frame` ); try { frame.close(); } catch (closeErr) { console.warn( "Worker: Ignored error closing dropped VideoFrame", closeErr ); } return; } else if (strategy === "wait") { let waitTime = 10; const maxWaitTime = 100; const maxRetries = 5; let attempts = 0; while (this.videoEncoder.encodeQueueSize >= maxQueueSize && attempts < maxRetries) { await new Promise((resolve) => setTimeout(resolve, waitTime)); waitTime = Math.min(waitTime * 1.5, maxWaitTime); attempts++; } if (this.videoEncoder.encodeQueueSize >= maxQueueSize) { console.warn( `Video queue still full after waiting, dropping frame` ); try { frame.close(); } catch (closeErr) { console.warn( "Worker: Ignored error closing waited VideoFrame", closeErr ); } return; } } } const interval = this.currentConfig.keyFrameInterval; const opts = interval && this.videoFrameCount % interval === 0 ? { keyFrame: true } : void 0; this.videoEncoder.encode(frame, opts); try { frame.close(); } catch (closeErr) { console.warn("Worker: Ignored error closing VideoFrame", closeErr); } this.videoFrameCount++; this.processedFrames++; const progressMessage = { type: "progress", processedFrames: this.processedFrames }; if (typeof this.totalFramesToProcess !== "undefined") { progressMessage.totalFrames = this.totalFramesToProcess; } this.postMessageToMainThread(progressMessage); this.postQueueSize(); } catch (error) { this.postMessageToMainThread({ type: "error", errorDetail: { message: `Error encoding video frame: ${error.message}`, type: "video-encoding-error" /* VideoEncodingError */, stack: error.stack } }); this.cleanup(); } } async handleAddAudioData(data) { if (this.isCancelled || !this.audioEncoder || !this.currentConfig) return; if (data.audio) { const audioData = data.audio; let audioClosed = false; const closeAudioData = (context) => { if (audioClosed) return; try { audioData.close(); audioClosed = true; } catch (closeErr) { console.warn(`Worker: Ignored error closing ${context}`, closeErr); } }; try { const currentQueueSize = this.audioEncoder.encodeQueueSize; const maxQueueSize = this.currentConfig.maxAudioQueueSize || 30; const strategy = this.currentConfig.backpressureStrategy || "drop"; if (currentQueueSize >= maxQueueSize) { if (strategy === "drop") { console.warn( `Audio queue full (${currentQueueSize}/${maxQueueSize}), dropping audio data` ); closeAudioData("dropped AudioData"); return; } else if (strategy === "wait") { let waitTime = 10; const maxWaitTime = 100; const maxRetries = 5; let attempts = 0; while (this.audioEncoder.encodeQueueSize >= maxQueueSize && attempts < maxRetries) { await new Promise((resolve) => setTimeout(resolve, waitTime)); waitTime = Math.min(waitTime * 1.5, maxWaitTime); attempts++; } if (this.audioEncoder.encodeQueueSize >= maxQueueSize) { console.warn( `Audio queue still full after waiting, dropping audio data` ); closeAudioData("waited AudioData"); return; } } } this.audioEncoder.encode(audioData); this.postQueueSize(); } catch (error) { this.postMessageToMainThread({ type: "error", errorDetail: { message: `Error encoding audio data: ${error.message}`, type: "audio-encoding-error" /* AudioEncodingError */, stack: error.stack } }); this.cleanup(); } finally { closeAudioData("AudioData"); } return; } if (!data.audioData || data.audioData.length === 0) return; if (data.audioData.length !== this.currentConfig.channels) { this.postMessageToMainThread({ type: "error", errorDetail: { message: `Audio data channel count (${data.audioData.length}) does not match configured channels (${this.currentConfig.channels}).`, type: "configuration-error" /* ConfigurationError */ } }); return; } const AudioDataCtor = getAudioData(); if (!AudioDataCtor) { this.postMessageToMainThread({ type: "error", errorDetail: { message: "Worker: AudioData not available", type: "not-supported" /* NotSupported */ } }); this.cleanup(); return; } try { const interleaveFloat32Arrays = (planarArrays) => { if (!planarArrays || planarArrays.length === 0) { return new Float32Array(0); } const numChannels = planarArrays.length; const numFrames = Math.min(...planarArrays.map((arr) => arr.length)); const interleaved = new Float32Array(numFrames * numChannels); for (let i = 0; i < numFrames; i++) { for (let ch = 0; ch < numChannels; ch++) { interleaved[i * numChannels + ch] = planarArrays[ch][i]; } } return interleaved; }; const interleavedData = interleaveFloat32Arrays(data.audioData); const audioData = new AudioDataCtor({ format: "f32", sampleRate: data.sampleRate, numberOfFrames: data.numberOfFrames, numberOfChannels: data.numberOfChannels, timestamp: data.timestamp, data: interleavedData.buffer }); try { this.audioEncoder.encode(audioData); this.postQueueSize(); } finally { audioData.close(); } } catch (error) { this.postMessageToMainThread({ type: "error", errorDetail: { message: `Error encoding audio data: ${error.message}`, type: "audio-encoding-error" /* AudioEncodingError */, stack: error.stack } }); this.cleanup(); } } async handleFinalize(_message) { if (this.isCancelled) return; try { if (this.videoEncoder) await this.videoEncoder.flush(); if (this.audioEncoder) await this.audioEncoder.flush(); if (this.muxer) { const uint8ArrayOrNullOutput = this.muxer.finalize(); if (uint8ArrayOrNullOutput) { this.postMessageToMainThread( { type: "finalized", output: uint8ArrayOrNullOutput }, [uint8ArrayOrNullOutput.buffer] ); } else if (this.currentConfig?.latencyMode === "realtime") { this.postMessageToMainThread({ type: "finalized", output: null }); } else { this.postMessageToMainThread({ type: "error", errorDetail: { message: "Muxer finalized without output in non-realtime mode.", type: "muxing-failed" /* MuxingFailed */ } }); } } else { this.postMessageToMainThread({ type: "error", errorDetail: { message: "Muxer not initialized during finalize.", type: "muxing-failed" /* MuxingFailed */ } }); } } catch (error) { this.postMessageToMainThread({ type: "error", errorDetail: { message: `Error during finalization: ${error.message}`, type: "muxing-failed" /* MuxingFailed */, stack: error.stack } }); } finally { this.cleanup(); } } handleCancel(_message) { if (this.isCancelled) return; this.isCancelled = true; console.warn("Worker: Received cancel signal."); this.postMessageToMainThread({ type: "cancelled" }); this.videoEncoder?.close(); this.audioEncoder?.close(); this.cleanup(); } cleanup() { console.warn("Worker: Cleaning up resources."); if (this.videoEncoder && this.videoEncoder.state !== "closed") this.videoEncoder.close(); if (this.audioEncoder && this.audioEncoder.state !== "closed") this.audioEncoder.close(); this.videoEncoder = null; this.audioEncoder = null; this.muxer = null; this.currentConfig = null; this.totalFramesToProcess = void 0; this.processedFrames = 0; this.videoFrameCount = 0; } async handleMessage(eventData) { if (this.isCancelled && eventData.type !== "initialize" && eventData.type !== "cancel") { console.warn( `Worker: Ignoring message type '${eventData.type}' because worker is cancelled.` ); return; } try { switch (eventData.type) { case "initialize": this.isCancelled = false; this.cleanup(); await this.initializeEncoders(eventData); break; case "addVideoFrame": await this.handleAddVideoFrame(eventData); break; case "addAudioData": await this.handleAddAudioData(eventData); break; case "finalize": await this.handleFinalize(eventData); break; case "cancel": this.handleCancel(eventData); break; default: console.warn( "Worker received unknown message type:", eventData.type ); } } catch (error) { this.postMessageToMainThread({ type: "error", errorDetail: { message: `Unhandled error in worker onmessage: ${error.message}`, type: "unknown" /* Unknown */, stack: error.stack } }); this.cleanup(); } } }; var encoder = new EncoderWorker(); self.onmessage = async (event) => { await encoder.handleMessage(event.data); }; var DEFAULT_AUDIO_CODEC_BY_CONTAINER = { mp4: "aac", webm: "opus" }; var AUDIO_ENCODER_CODEC_MAP = { aac: "mp4a.40.2", opus: "opus", flac: "flac", mp3: "mp3", vorbis: "vorbis", pcm: "pcm", ulaw: "ulaw", alaw: "alaw" }; var MUXER_COMPATIBLE_AUDIO = { mp4: /* @__PURE__ */ new Set(["aac", "mp3"]), webm: /* @__PURE__ */ new Set(["opus", "vorbis", "flac"]) }; function getAudioEncoderCodecStringFromAudioCodec(codec) { return AUDIO_ENCODER_CODEC_MAP[codec] ?? codec; } function getVideoEncoderConfigOverridesForCodec(codec, overrides) { if (!overrides) { return {}; } const sanitized = { ...overrides }; if (codec !== "avc") { delete sanitized.avc; } if (codec !== "hevc") { delete sanitized.hevc; } return sanitized; } function getAudioEncoderConfigOverridesForCodec(codec, overrides) { if (!overrides) { return {}; } const sanitized = { ...overrides }; if (codec !== "aac") { delete sanitized.aac; } return sanitized; } function getContainerType(container) { return container === "webm" ? "webm" : "mp4"; } function buildAudioCodecPreference(container, requested) { const preference = []; const addCodec = (codec) => { if (!preference.includes(codec)) { preference.push(codec); } }; if (requested) { addCodec(requested); } addCodec(DEFAULT_AUDIO_CODEC_BY_CONTAINER[container]); for (const codec of MUXER_COMPATIBLE_AUDIO[container]) { addCodec(codec); } if (container === "mp4") { addCodec("aac"); addCodec("mp3"); } else { addCodec("opus"); addCodec("vorbis"); addCodec("flac"); addCodec("aac"); } return preference; } function isAudioCodecMuxerCompatible(container, codec) { return MUXER_COMPATIBLE_AUDIO[container].has(codec); } })(); //# sourceMappingURL=worker.js.map