You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

1264 lines
45 KiB

"use strict";
var Mp4Muxer = (() => {
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
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;
};
// src/index.ts
var src_exports = {};
__export(src_exports, {
ArrayBufferTarget: () => ArrayBufferTarget,
FileSystemWritableFileStreamTarget: () => FileSystemWritableFileStreamTarget,
Muxer: () => Muxer,
StreamTarget: () => StreamTarget
});
// src/misc.ts
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 / 2 ** 32), false);
view.setUint32(4, value, false);
return [bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7]];
};
var fixed_8_8 = (value) => {
view.setInt16(0, 2 ** 8 * value, false);
return [bytes[0], bytes[1]];
};
var fixed_16_16 = (value) => {
view.setInt32(0, 2 ** 16 * value, false);
return [bytes[0], bytes[1], bytes[2], bytes[3]];
};
var fixed_2_30 = (value) => {
view.setInt32(0, 2 ** 30 * value, false);
return [bytes[0], bytes[1], bytes[2], bytes[3]];
};
var 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 deepClone = (x) => {
if (!x)
return x;
if (typeof x !== "object")
return x;
if (Array.isArray(x))
return x.map(deepClone);
return Object.fromEntries(Object.entries(x).map(([key, value]) => [key, deepClone(value)]));
};
// src/box.ts
var box = (type, contents, children) => ({
type,
contents: contents && new Uint8Array(contents.flat(10)),
children
});
var fullBox = (type, version, flags, contents, children) => box(
type,
[u8(version), u24(flags), contents ?? []],
children
);
var ftyp = (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 = (reserveLargeSize) => ({ type: "mdat", largeSize: reserveLargeSize });
var free = (size) => ({ type: "free", size });
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.finalizedChunks.length > 0 && last(track.finalizedChunks).offset >= 2 ** 32) {
return fullBox("co64", 0, 0, [
u32(track.finalizedChunks.length),
// Number of entries
track.finalizedChunks.map((x) => u64(x.offset))
// Chunk offset table
]);
}
return fullBox("stco", 0, 0, [
u32(track.finalizedChunks.length),
// Number of entries
track.finalizedChunks.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
};
// src/target.ts
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;
}
};
// src/writer.ts
var _helper, _helperView;
var Writer = class {
constructor() {
this.pos = 0;
__privateAdd(this, _helper, new Uint8Array(8));
__privateAdd(this, _helperView, new DataView(__privateGet(this, _helper).buffer));
/**
* Stores the position from the start of the file to where boxes elements have been written. This is used to
* rewrite/edit elements that were already added before, and to measure sizes of things.
*/
this.offsets = /* @__PURE__ */ new WeakMap();
}
/** 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 / 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) {
this.offsets.set(box2, this.pos);
if (box2.contents && !box2.children) {
this.writeBoxHeader(box2, box2.size ?? 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 = box2.size ?? 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);
}
measureBoxHeader(box2) {
return 8 + (box2.largeSize ? 8 : 0);
}
patchBox(box2) {
let endPos = this.pos;
this.seek(this.offsets.get(box2));
this.writeBox(box2);
this.seek(endPos);
}
measureBox(box2) {
if (box2.contents && !box2.children) {
let headerSize = this.measureBoxHeader(box2);
return headerSize + box2.contents.byteLength;
} else {
let result = this.measureBoxHeader(box2);
if (box2.contents)
result += box2.contents.byteLength;
if (box2.children) {
for (let child of box2.children)
if (child)
result += this.measureBox(child);
}
return result;
}
}
};
_helper = new WeakMap();
_helperView = new WeakMap();
var _target, _buffer, _bytes, _maxPos, _ensureSize, ensureSize_fn;
var ArrayBufferTargetWriter = class extends Writer {
constructor(target) {
super();
__privateAdd(this, _ensureSize);
__privateAdd(this, _target, void 0);
__privateAdd(this, _buffer, new ArrayBuffer(2 ** 16));
__privateAdd(this, _bytes, new Uint8Array(__privateGet(this, _buffer)));
__privateAdd(this, _maxPos, 0);
__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;
__privateSet(this, _maxPos, Math.max(__privateGet(this, _maxPos), this.pos));
}
finalize() {
__privateMethod(this, _ensureSize, ensureSize_fn).call(this, this.pos);
__privateGet(this, _target).buffer = __privateGet(this, _buffer).slice(0, Math.max(__privateGet(this, _maxPos), this.pos));
}
};
_target = new WeakMap();
_buffer = new WeakMap();
_bytes = new WeakMap();
_maxPos = new WeakMap();
_ensureSize = 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, _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() {
__privateGet(this, _target2).onDone?.();
}
};
_target2 = new WeakMap();
_sections = new WeakMap();
var DEFAULT_CHUNK_SIZE = 2 ** 24;
var MAX_CHUNKS_AT_ONCE = 2;
var _target3, _chunkSize, _chunks, _writeDataIntoChunks, writeDataIntoChunks_fn, _insertSectionIntoChunk, insertSectionIntoChunk_fn, _createChunk, createChunk_fn, _flushChunks, flushChunks_fn;
var ChunkedStreamTargetWriter = class extends Writer {
constructor(target) {
super();
__privateAdd(this, _writeDataIntoChunks);
__privateAdd(this, _insertSectionIntoChunk);
__privateAdd(this, _createChunk);
__privateAdd(this, _flushChunks);
__privateAdd(this, _target3, void 0);
__privateAdd(this, _chunkSize, void 0);
/**
* The data is divided up into fixed-size chunks, whose contents are first filled in RAM and then flushed out.
* A chunk is flushed if all of its contents have been written.
*/
__privateAdd(this, _chunks, []);
__privateSet(this, _target3, target);
__privateSet(this, _chunkSize, target.options?.chunkSize ?? DEFAULT_CHUNK_SIZE);
if (!Number.isInteger(__privateGet(this, _chunkSize)) || __privateGet(this, _chunkSize) < 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() {
__privateMethod(this, _flushChunks, flushChunks_fn).call(this, true);
__privateGet(this, _target3).onDone?.();
}
};
_target3 = new WeakMap();
_chunkSize = new WeakMap();
_chunks = new WeakMap();
_writeDataIntoChunks = 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 = 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 = 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 = 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) {
super(new StreamTarget(
(data, position) => target.stream.write({
type: "write",
data,
position
}),
void 0,
{ chunkSize: target.options?.chunkSize }
));
}
};
// src/muxer.ts
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, _writer, _ftypSize, _mdat, _videoTrack, _audioTrack, _creationTime, _finalizedChunks, _finalized, _validateOptions, validateOptions_fn, _writeHeader, writeHeader_fn, _computeMoovSizeUpperBound, computeMoovSizeUpperBound_fn, _prepareTracks, prepareTracks_fn, _generateMpeg4AudioSpecificConfig, generateMpeg4AudioSpecificConfig_fn, _addSampleToTrack, addSampleToTrack_fn, _validateTimestamp, validateTimestamp_fn, _finalizeCurrentChunk, finalizeCurrentChunk_fn, _maybeFlushStreamingTargetWriter, maybeFlushStreamingTargetWriter_fn, _ensureNotFinalized, ensureNotFinalized_fn;
var Muxer = class {
constructor(options) {
__privateAdd(this, _validateOptions);
__privateAdd(this, _writeHeader);
__privateAdd(this, _computeMoovSizeUpperBound);
__privateAdd(this, _prepareTracks);
// https://wiki.multimedia.cx/index.php/MPEG-4_Audio
__privateAdd(this, _generateMpeg4AudioSpecificConfig);
__privateAdd(this, _addSampleToTrack);
__privateAdd(this, _validateTimestamp);
__privateAdd(this, _finalizeCurrentChunk);
__privateAdd(this, _maybeFlushStreamingTargetWriter);
__privateAdd(this, _ensureNotFinalized);
__privateAdd(this, _options, void 0);
__privateAdd(this, _writer, void 0);
__privateAdd(this, _ftypSize, 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, _finalizedChunks, []);
__privateAdd(this, _finalized, false);
__privateMethod(this, _validateOptions, validateOptions_fn).call(this, options);
options.video = deepClone(options.video);
options.audio = deepClone(options.audio);
options.fastStart = deepClone(options.fastStart);
this.target = options.target;
__privateSet(this, _options, {
firstTimestampBehavior: "strict",
...options
});
if (options.target instanceof ArrayBufferTarget) {
__privateSet(this, _writer, new ArrayBufferTargetWriter(options.target));
} else if (options.target instanceof StreamTarget) {
__privateSet(this, _writer, options.target.options?.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 ?? 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.");
if (typeof __privateGet(this, _options).fastStart === "object" && __privateGet(this, _videoTrack).samples.length === __privateGet(this, _options).fastStart.expectedVideoChunks) {
throw new Error(`Cannot add more video chunks than specified in 'fastStart' (${__privateGet(this, _options).fastStart.expectedVideoChunks}).`);
}
__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 ?? 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.");
if (typeof __privateGet(this, _options).fastStart === "object" && __privateGet(this, _audioTrack).samples.length === __privateGet(this, _options).fastStart.expectedAudioChunks) {
throw new Error(`Cannot add more audio chunks than specified in 'fastStart' (${__privateGet(this, _options).fastStart.expectedAudioChunks}).`);
}
__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, _finalized)) {
throw new Error("Cannot finalize a muxer more than once.");
}
if (__privateGet(this, _videoTrack))
__privateMethod(this, _finalizeCurrentChunk, finalizeCurrentChunk_fn).call(this, __privateGet(this, _videoTrack));
if (__privateGet(this, _audioTrack))
__privateMethod(this, _finalizeCurrentChunk, finalizeCurrentChunk_fn).call(this, __privateGet(this, _audioTrack));
let tracks = [__privateGet(this, _videoTrack), __privateGet(this, _audioTrack)].filter(Boolean);
if (__privateGet(this, _options).fastStart === "in-memory") {
let mdatSize;
for (let i = 0; i < 2; i++) {
let movieBox2 = moov(tracks, __privateGet(this, _creationTime));
let movieBoxSize = __privateGet(this, _writer).measureBox(movieBox2);
mdatSize = __privateGet(this, _writer).measureBox(__privateGet(this, _mdat));
let currentChunkPos = __privateGet(this, _writer).pos + movieBoxSize + mdatSize;
for (let chunk of __privateGet(this, _finalizedChunks)) {
chunk.offset = currentChunkPos;
for (let bytes2 of chunk.sampleData) {
currentChunkPos += bytes2.byteLength;
mdatSize += bytes2.byteLength;
}
}
if (currentChunkPos < 2 ** 32)
break;
if (mdatSize >= 2 ** 32)
__privateGet(this, _mdat).largeSize = true;
}
let movieBox = moov(tracks, __privateGet(this, _creationTime));
__privateGet(this, _writer).writeBox(movieBox);
__privateGet(this, _mdat).size = mdatSize;
__privateGet(this, _writer).writeBox(__privateGet(this, _mdat));
for (let chunk of __privateGet(this, _finalizedChunks)) {
for (let bytes2 of chunk.sampleData)
__privateGet(this, _writer).write(bytes2);
chunk.sampleData = null;
}
} else {
let mdatPos = __privateGet(this, _writer).offsets.get(__privateGet(this, _mdat));
let mdatSize = __privateGet(this, _writer).pos - mdatPos;
__privateGet(this, _mdat).size = mdatSize;
__privateGet(this, _mdat).largeSize = mdatSize >= 2 ** 32;
__privateGet(this, _writer).patchBox(__privateGet(this, _mdat));
let movieBox = moov(tracks, __privateGet(this, _creationTime));
if (typeof __privateGet(this, _options).fastStart === "object") {
__privateGet(this, _writer).seek(__privateGet(this, _ftypSize));
__privateGet(this, _writer).writeBox(movieBox);
let remainingBytes = mdatPos - __privateGet(this, _writer).pos;
__privateGet(this, _writer).writeBox(free(remainingBytes));
} else {
__privateGet(this, _writer).writeBox(movieBox);
}
}
__privateMethod(this, _maybeFlushStreamingTargetWriter, maybeFlushStreamingTargetWriter_fn).call(this);
__privateGet(this, _writer).finalize();
__privateSet(this, _finalized, true);
}
};
_options = new WeakMap();
_writer = new WeakMap();
_ftypSize = new WeakMap();
_mdat = new WeakMap();
_videoTrack = new WeakMap();
_audioTrack = new WeakMap();
_creationTime = new WeakMap();
_finalizedChunks = new WeakMap();
_finalized = new WeakMap();
_validateOptions = 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}`);
}
if (typeof options.fastStart === "object") {
if (options.video && options.fastStart.expectedVideoChunks === void 0) {
throw new Error(`'fastStart' is an object but is missing property 'expectedVideoChunks'.`);
}
if (options.audio && options.fastStart.expectedAudioChunks === void 0) {
throw new Error(`'fastStart' is an object but is missing property 'expectedAudioChunks'.`);
}
} else if (![false, "in-memory"].includes(options.fastStart)) {
throw new Error(`'fastStart' option must be false, 'in-memory' or an object.`);
}
};
_writeHeader = new WeakSet();
writeHeader_fn = function() {
let holdsHevc = __privateGet(this, _options).video?.codec === "hevc";
__privateGet(this, _writer).writeBox(ftyp(holdsHevc));
__privateSet(this, _ftypSize, __privateGet(this, _writer).pos);
if (__privateGet(this, _options).fastStart === "in-memory") {
__privateSet(this, _mdat, mdat(false));
} else {
if (typeof __privateGet(this, _options).fastStart === "object") {
let moovSizeUpperBound = __privateMethod(this, _computeMoovSizeUpperBound, computeMoovSizeUpperBound_fn).call(this);
__privateGet(this, _writer).seek(__privateGet(this, _writer).pos + moovSizeUpperBound);
}
__privateSet(this, _mdat, mdat(true));
__privateGet(this, _writer).writeBox(__privateGet(this, _mdat));
}
__privateMethod(this, _maybeFlushStreamingTargetWriter, maybeFlushStreamingTargetWriter_fn).call(this);
};
_computeMoovSizeUpperBound = new WeakSet();
computeMoovSizeUpperBound_fn = function() {
if (typeof __privateGet(this, _options).fastStart !== "object")
return;
let upperBound = 0;
let sampleCounts = [
__privateGet(this, _options).fastStart.expectedVideoChunks,
__privateGet(this, _options).fastStart.expectedAudioChunks
];
for (let n of sampleCounts) {
if (!n)
continue;
upperBound += (4 + 4) * Math.ceil(2 / 3 * n);
upperBound += 4 * n;
upperBound += (4 + 4 + 4) * Math.ceil(2 / 3 * n);
upperBound += 4 * n;
upperBound += 8 * n;
}
upperBound += 4096;
return upperBound;
};
_prepareTracks = new WeakSet();
prepareTracks_fn = function() {
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: __privateGet(this, _options).video.rotation ?? 0
},
timescale: 720,
// = lcm(24, 30, 60, 120, 144, 240, 360), so should fit with many framerates
codecPrivate: new Uint8Array(0),
samples: [],
finalizedChunks: [],
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: [],
finalizedChunks: [],
currentChunk: null,
firstTimestamp: void 0,
lastTimestamp: -1,
timeToSampleTable: [],
lastTimescaleUnits: null,
compactlyCodedChunkTable: []
});
}
};
_generateMpeg4AudioSpecificConfig = 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 = new WeakSet();
addSampleToTrack_fn = function(track, data, type, timestamp, duration, meta) {
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, _finalizeCurrentChunk, finalizeCurrentChunk_fn).call(this, track);
track.currentChunk = {
startTimestamp: timestampInSeconds,
sampleData: [],
sampleCount: 0
};
}
track.currentChunk.sampleData.push(data);
track.currentChunk.sampleCount++;
if (meta?.decoderConfig?.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 = 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;
};
_finalizeCurrentChunk = new WeakSet();
finalizeCurrentChunk_fn = function(track) {
if (!track.currentChunk)
return;
if (track.compactlyCodedChunkTable.length === 0 || last(track.compactlyCodedChunkTable).samplesPerChunk !== track.currentChunk.sampleCount) {
track.compactlyCodedChunkTable.push({
firstChunk: track.finalizedChunks.length + 1,
// 1-indexed
samplesPerChunk: track.currentChunk.sampleCount
});
}
track.finalizedChunks.push(track.currentChunk);
__privateGet(this, _finalizedChunks).push(track.currentChunk);
if (__privateGet(this, _options).fastStart === "in-memory") {
track.currentChunk.offset = 0;
return;
}
track.currentChunk.offset = __privateGet(this, _writer).pos;
for (let bytes2 of track.currentChunk.sampleData)
__privateGet(this, _writer).write(bytes2);
track.currentChunk.sampleData = null;
__privateMethod(this, _maybeFlushStreamingTargetWriter, maybeFlushStreamingTargetWriter_fn).call(this);
};
_maybeFlushStreamingTargetWriter = new WeakSet();
maybeFlushStreamingTargetWriter_fn = function() {
if (__privateGet(this, _writer) instanceof StreamTargetWriter) {
__privateGet(this, _writer).flush();
}
};
_ensureNotFinalized = 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.");
}
};
return __toCommonJS(src_exports);
})();
if (typeof module === "object" && typeof module.exports === "object") Object.assign(module.exports, Mp4Muxer)