New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

mp4-muxer

Package Overview
Dependencies
Maintainers
1
Versions
39
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

mp4-muxer - npm Package Compare versions

Comparing version 3.0.5 to 4.0.0

44

build/mp4-muxer.d.ts

@@ -35,12 +35,6 @@ declare interface VideoOptions {

type NoInfer<T> = T extends infer S ? S : never;
/**
* Describes the properties used to configure an instance of `Muxer`.
*/
declare type MuxerOptions<
T extends Target,
V extends VideoOptions | undefined = undefined,
A extends AudioOptions | undefined = undefined
> = {
declare type MuxerOptions<T extends Target> = {
/**

@@ -54,3 +48,3 @@ * Specifies what happens with the data created by the muxer.

*/
video?: V,
video?: VideoOptions,

@@ -60,3 +54,3 @@ /**

*/
audio?: A,
audio?: AudioOptions,

@@ -74,2 +68,8 @@ /**

*
* Use `'fragmented'` to place metadata at the start of the file by creating a fragmented "fMP4" file. In a
* fragmented file, chunks of media and their metadata are written to the file in "fragments", eliminating the need
* to put all metadata in one place. Fragmented files are useful for streaming, as they allow for better random
* access. Furthermore, they remain lightweight to create even for very large files, as they don't require all media
* to be kept in memory. However, fragmented files are not as widely supported as regular MP4 files.
*
* Use an object to produce a file with Fast Start by reserving space for metadata when muxing starts. In order to

@@ -79,6 +79,6 @@ * know how much space needs to be reserved, you'll need to tell it the upper bound of how many media chunks will be

*/
fastStart: false | 'in-memory' | (
(NoInfer<V> extends undefined ? { expectedVideoChunks?: never } : { expectedVideoChunks: number })
& (NoInfer<A> extends undefined ? { expectedAudioChunks?: never } : { expectedAudioChunks: number })
),
fastStart: false | 'in-memory' | 'fragmented' | {
expectedVideoChunks?: number,
expectedAudioChunks?: number
},

@@ -114,7 +114,7 @@ /**

declare class StreamTarget {
constructor(
onData: (data: Uint8Array, position: number) => void,
onDone?: () => void,
options?: { chunked?: boolean, chunkSize?: number }
);
constructor(options: {
onData?: (data: Uint8Array, position: number) => void,
chunked?: boolean,
chunkSize?: number
});
}

@@ -138,7 +138,3 @@

*/
declare class Muxer<
T extends Target,
V extends VideoOptions | undefined = undefined,
A extends AudioOptions | undefined = undefined
> {
declare class Muxer<T extends Target> {
target: T;

@@ -150,3 +146,3 @@

*/
constructor(options: MuxerOptions<T, V, A>);
constructor(options: MuxerOptions<T>);

@@ -153,0 +149,0 @@ /**

@@ -38,2 +38,10 @@ "use strict";

};
var __privateWrapper = (obj, member, setter, getter) => ({
set _(value) {
__privateSet(obj, member, value, setter);
},
get _() {
return __privateGet(obj, member, getter);
}
});
var __privateMethod = (obj, member, method) => {

@@ -144,2 +152,5 @@ __accessCheck(obj, member, "access private method");

};
var isU32 = (value) => {
return value >= 0 && value < 2 ** 32;
};

@@ -157,13 +168,14 @@ // src/box.ts

);
var ftyp = (holdsHevc) => {
if (holdsHevc)
var ftyp = (details) => {
let minorVersion = 512;
if (details.fragmented)
return box("ftyp", [
ascii("isom"),
ascii("iso5"),
// Major brand
u32(0),
u32(minorVersion),
// Minor version
ascii("iso4"),
// Compatible brand 1
ascii("hvc1")
// Compatible brand 2
// Compatible brands
ascii("iso5"),
ascii("iso6"),
ascii("mp41")
]);

@@ -173,10 +185,8 @@ return box("ftyp", [

// Major brand
u32(0),
u32(minorVersion),
// Minor version
// Compatible brands
ascii("isom"),
// Compatible brand 1
ascii("avc1"),
// Compatible brand 2
details.holdsAvc ? ascii("avc1") : [],
ascii("mp41")
// Compatible brand 3
]);

@@ -186,5 +196,6 @@ };

var free = (size) => ({ type: "free", size });
var moov = (tracks, creationTime) => box("moov", null, [
var moov = (tracks, creationTime, fragmented = false) => box("moov", null, [
mvhd(creationTime, tracks),
...tracks.map((x) => trak(x, creationTime))
...tracks.map((x) => trak(x, creationTime)),
fragmented ? mvex(tracks) : null
]);

@@ -197,10 +208,12 @@ var mvhd = (creationTime, tracks) => {

let nextTrackId = Math.max(...tracks.map((x) => x.id)) + 1;
return fullBox("mvhd", 0, 0, [
u32(creationTime),
let needsU64 = !isU32(creationTime) || !isU32(duration);
let u32OrU64 = needsU64 ? u64 : u32;
return fullBox("mvhd", +needsU64, 0, [
u32OrU64(creationTime),
// Creation time
u32(creationTime),
u32OrU64(creationTime),
// Modification time
u32(GLOBAL_TIMESCALE),
// Timescale
u32(duration),
u32OrU64(duration),
// Duration

@@ -231,6 +244,8 @@ fixed_16_16(1),

);
return fullBox("tkhd", 0, 3, [
u32(creationTime),
let needsU64 = !isU32(creationTime) || !isU32(durationInGlobalTimescale);
let u32OrU64 = needsU64 ? u64 : u32;
return fullBox("tkhd", +needsU64, 3, [
u32OrU64(creationTime),
// Creation time
u32(creationTime),
u32OrU64(creationTime),
// Modification time

@@ -241,3 +256,3 @@ u32(track.id),

// Reserved
u32(durationInGlobalTimescale),
u32OrU64(durationInGlobalTimescale),
// Duration

@@ -273,10 +288,12 @@ Array(8).fill(0),

);
return fullBox("mdhd", 0, 0, [
u32(creationTime),
let needsU64 = !isU32(creationTime) || !isU32(localDuration);
let u32OrU64 = needsU64 ? u64 : u32;
return fullBox("mdhd", +needsU64, 0, [
u32OrU64(creationTime),
// Creation time
u32(creationTime),
u32OrU64(creationTime),
// Modification time
u32(track.timescale),
// Timescale
u32(localDuration),
u32OrU64(localDuration),
// Duration

@@ -300,3 +317,3 @@ u16(21956),

// Component flags mask
ascii("mp4-muxer-hdlr")
ascii("mp4-muxer-hdlr", true)
// Component name

@@ -528,2 +545,151 @@ ]);

};
var mvex = (tracks) => {
return box("mvex", null, tracks.map(trex));
};
var trex = (track) => {
return fullBox("trex", 0, 0, [
u32(track.id),
// Track ID
u32(1),
// Default sample description index
u32(0),
// Default sample duration
u32(0),
// Default sample size
u32(0)
// Default sample flags
]);
};
var moof = (sequenceNumber, tracks) => {
return box("moof", null, [
mfhd(sequenceNumber),
...tracks.map(traf)
]);
};
var mfhd = (sequenceNumber) => {
return fullBox("mfhd", 0, 0, [
u32(sequenceNumber)
// Sequence number
]);
};
var fragmentSampleFlags = (sample) => {
let byte1 = 0;
let byte2 = 0;
let byte3 = 0;
let byte4 = 0;
let sampleIsDifferenceSample = sample.type === "delta";
byte2 |= +sampleIsDifferenceSample;
if (sampleIsDifferenceSample) {
byte1 |= 1;
} else {
byte1 |= 2;
}
return byte1 << 24 | byte2 << 16 | byte3 << 8 | byte4;
};
var traf = (track) => {
return box("traf", null, [
tfhd(track),
tfdt(track),
trun(track)
]);
};
var tfhd = (track) => {
let tfFlags = 0;
tfFlags |= 8;
tfFlags |= 16;
tfFlags |= 32;
tfFlags |= 131072;
let referenceSample = track.currentChunk.samples[1] ?? track.currentChunk.samples[0];
let referenceSampleInfo = {
duration: referenceSample.timescaleUnitsToNextSample,
size: referenceSample.size,
flags: fragmentSampleFlags(referenceSample)
};
return fullBox("tfhd", 0, tfFlags, [
u32(track.id),
// Track ID
u32(referenceSampleInfo.duration),
// Default sample duration
u32(referenceSampleInfo.size),
// Default sample size
u32(referenceSampleInfo.flags)
// Default sample flags
]);
};
var tfdt = (track) => {
return fullBox("tfdt", 1, 0, [
u64(intoTimescale(track.currentChunk.startTimestamp, track.timescale))
// Base Media Decode Time
]);
};
var trun = (track) => {
let allSampleDurations = track.currentChunk.samples.map((x) => x.timescaleUnitsToNextSample);
let allSampleSizes = track.currentChunk.samples.map((x) => x.size);
let allSampleFlags = track.currentChunk.samples.map(fragmentSampleFlags);
let uniqueSampleDurations = new Set(allSampleDurations);
let uniqueSampleSizes = new Set(allSampleSizes);
let uniqueSampleFlags = new Set(allSampleFlags);
let firstSampleFlagsPresent = uniqueSampleFlags.size === 2 && allSampleFlags[0] !== allSampleFlags[1];
let sampleDurationPresent = uniqueSampleDurations.size > 1;
let sampleSizePresent = uniqueSampleSizes.size > 1;
let sampleFlagsPresent = !firstSampleFlagsPresent && uniqueSampleFlags.size > 1;
let flags = 0;
flags |= 1;
flags |= 4 * +firstSampleFlagsPresent;
flags |= 256 * +sampleDurationPresent;
flags |= 512 * +sampleSizePresent;
flags |= 1024 * +sampleFlagsPresent;
return fullBox("trun", 0, flags, [
u32(track.currentChunk.samples.length),
// Sample count
u32(track.currentChunk.offset - track.currentChunk.moofOffset || 0),
// Data offset
firstSampleFlagsPresent ? u32(allSampleFlags[0]) : [],
track.currentChunk.samples.map((_, i) => [
sampleDurationPresent ? u32(allSampleDurations[i]) : [],
// Sample duration
sampleSizePresent ? u32(allSampleSizes[i]) : [],
// Sample size
sampleFlagsPresent ? u32(allSampleFlags[i]) : []
// Sample flags
])
]);
};
var mfra = (tracks) => {
return box("mfra", null, [
...tracks.map(tfra),
mfro()
]);
};
var tfra = (track, trackIndex) => {
let version = 1;
return fullBox("tfra", version, 0, [
u32(track.id),
// Track ID
u32(63),
// This specifies that traf number, trun number and sample number are 32-bit ints
u32(track.finalizedChunks.length),
// Number of entries
track.finalizedChunks.map((chunk) => [
u64(intoTimescale(chunk.startTimestamp, track.timescale)),
// Time
u64(chunk.moofOffset),
// moof offset
u32(trackIndex + 1),
// traf number
u32(1),
// trun number
u32(1)
// Sample number
])
]);
};
var mfro = () => {
return fullBox("mfro", 0, 0, [
// This value needs to be overwritten manually from the outside, where the actual size of the enclosing mfra box
// is known
u32(0)
// Size
]);
};
var VIDEO_CODEC_TO_BOX_NAME = {

@@ -557,5 +723,3 @@ "avc": "avc1",

var StreamTarget = class {
constructor(onData, onDone, options) {
this.onData = onData;
this.onDone = onDone;
constructor(options) {
this.options = options;

@@ -745,3 +909,3 @@ }

}
__privateGet(this, _target2).onData(chunk.data, chunk.start);
__privateGet(this, _target2).options.onData?.(chunk.data, chunk.start);
}

@@ -751,3 +915,2 @@ __privateGet(this, _sections).length = 0;

finalize() {
__privateGet(this, _target2).onDone?.();
}

@@ -787,3 +950,2 @@ };

__privateMethod(this, _flushChunks, flushChunks_fn).call(this, true);
__privateGet(this, _target3).onDone?.();
}

@@ -863,3 +1025,3 @@ };

for (let section of chunk.written) {
__privateGet(this, _target3).onData(
__privateGet(this, _target3).options.onData?.(
chunk.data.subarray(section.start, section.end),

@@ -874,4 +1036,4 @@ chunk.start + section.start

constructor(target) {
super(new StreamTarget(
(data, position) => target.stream.write({
super(new StreamTarget({
onData: (data, position) => target.stream.write({
type: "write",

@@ -881,5 +1043,4 @@ data,

}),
void 0,
{ chunkSize: target.options?.chunkSize }
));
chunkSize: target.options?.chunkSize
}));
}

@@ -893,5 +1054,4 @@ };

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 _options, _writer, _ftypSize, _mdat, _videoTrack, _audioTrack, _creationTime, _finalizedChunks, _nextFragmentNumber, _videoSampleQueue, _audioSampleQueue, _finalized, _validateOptions, validateOptions_fn, _writeHeader, writeHeader_fn, _computeMoovSizeUpperBound, computeMoovSizeUpperBound_fn, _prepareTracks, prepareTracks_fn, _generateMpeg4AudioSpecificConfig, generateMpeg4AudioSpecificConfig_fn, _createSampleForTrack, createSampleForTrack_fn, _addSampleToTrack, addSampleToTrack_fn, _validateTimestamp, validateTimestamp_fn, _finalizeCurrentChunk, finalizeCurrentChunk_fn, _finalizeFragment, finalizeFragment_fn, _maybeFlushStreamingTargetWriter, maybeFlushStreamingTargetWriter_fn, _ensureNotFinalized, ensureNotFinalized_fn;
var Muxer = class {

@@ -905,5 +1065,7 @@ constructor(options) {

__privateAdd(this, _generateMpeg4AudioSpecificConfig);
__privateAdd(this, _createSampleForTrack);
__privateAdd(this, _addSampleToTrack);
__privateAdd(this, _validateTimestamp);
__privateAdd(this, _finalizeCurrentChunk);
__privateAdd(this, _finalizeFragment);
__privateAdd(this, _maybeFlushStreamingTargetWriter);

@@ -919,2 +1081,6 @@ __privateAdd(this, _ensureNotFinalized);

__privateAdd(this, _finalizedChunks, []);
// Fields for fragmented MP4:
__privateAdd(this, _nextFragmentNumber, 1);
__privateAdd(this, _videoSampleQueue, []);
__privateAdd(this, _audioSampleQueue, []);
__privateAdd(this, _finalized, false);

@@ -939,4 +1105,4 @@ __privateMethod(this, _validateOptions, validateOptions_fn).call(this, options);

}
__privateMethod(this, _prepareTracks, prepareTracks_fn).call(this);
__privateMethod(this, _writeHeader, writeHeader_fn).call(this);
__privateMethod(this, _prepareTracks, prepareTracks_fn).call(this);
}

@@ -955,3 +1121,16 @@ addVideoChunk(sample, meta, timestamp) {

}
__privateMethod(this, _addSampleToTrack, addSampleToTrack_fn).call(this, __privateGet(this, _videoTrack), data, type, timestamp, duration, meta);
let videoSample = __privateMethod(this, _createSampleForTrack, createSampleForTrack_fn).call(this, __privateGet(this, _videoTrack), data, type, timestamp, duration, meta);
if (__privateGet(this, _options).fastStart === "fragmented" && __privateGet(this, _audioTrack)) {
while (__privateGet(this, _audioSampleQueue).length > 0 && __privateGet(this, _audioSampleQueue)[0].timestamp <= videoSample.timestamp) {
let audioSample = __privateGet(this, _audioSampleQueue).shift();
__privateMethod(this, _addSampleToTrack, addSampleToTrack_fn).call(this, __privateGet(this, _audioTrack), audioSample);
}
if (videoSample.timestamp <= __privateGet(this, _audioTrack).lastTimestamp) {
__privateMethod(this, _addSampleToTrack, addSampleToTrack_fn).call(this, __privateGet(this, _videoTrack), videoSample);
} else {
__privateGet(this, _videoSampleQueue).push(videoSample);
}
} else {
__privateMethod(this, _addSampleToTrack, addSampleToTrack_fn).call(this, __privateGet(this, _videoTrack), videoSample);
}
}

@@ -970,3 +1149,16 @@ addAudioChunk(sample, meta, timestamp) {

}
__privateMethod(this, _addSampleToTrack, addSampleToTrack_fn).call(this, __privateGet(this, _audioTrack), data, type, timestamp, duration, meta);
let audioSample = __privateMethod(this, _createSampleForTrack, createSampleForTrack_fn).call(this, __privateGet(this, _audioTrack), data, type, timestamp, duration, meta);
if (__privateGet(this, _options).fastStart === "fragmented" && __privateGet(this, _videoTrack)) {
while (__privateGet(this, _videoSampleQueue).length > 0 && __privateGet(this, _videoSampleQueue)[0].timestamp <= audioSample.timestamp) {
let videoSample = __privateGet(this, _videoSampleQueue).shift();
__privateMethod(this, _addSampleToTrack, addSampleToTrack_fn).call(this, __privateGet(this, _videoTrack), videoSample);
}
if (audioSample.timestamp <= __privateGet(this, _videoTrack).lastTimestamp) {
__privateMethod(this, _addSampleToTrack, addSampleToTrack_fn).call(this, __privateGet(this, _audioTrack), audioSample);
} else {
__privateGet(this, _audioSampleQueue).push(audioSample);
}
} else {
__privateMethod(this, _addSampleToTrack, addSampleToTrack_fn).call(this, __privateGet(this, _audioTrack), audioSample);
}
}

@@ -978,6 +1170,14 @@ /** Finalizes the file, making it ready for use. Must be called after all video and audio chunks have been added. */

}
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));
if (__privateGet(this, _options).fastStart === "fragmented") {
for (let videoSample of __privateGet(this, _videoSampleQueue))
__privateMethod(this, _addSampleToTrack, addSampleToTrack_fn).call(this, __privateGet(this, _videoTrack), videoSample);
for (let audioSample of __privateGet(this, _audioSampleQueue))
__privateMethod(this, _addSampleToTrack, addSampleToTrack_fn).call(this, __privateGet(this, _audioTrack), audioSample);
__privateMethod(this, _finalizeFragment, finalizeFragment_fn).call(this, false);
} else {
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);

@@ -993,5 +1193,5 @@ if (__privateGet(this, _options).fastStart === "in-memory") {

chunk.offset = currentChunkPos;
for (let bytes2 of chunk.sampleData) {
currentChunkPos += bytes2.byteLength;
mdatSize += bytes2.byteLength;
for (let { data } of chunk.samples) {
currentChunkPos += data.byteLength;
mdatSize += data.byteLength;
}

@@ -1009,6 +1209,14 @@ }

for (let chunk of __privateGet(this, _finalizedChunks)) {
for (let bytes2 of chunk.sampleData)
__privateGet(this, _writer).write(bytes2);
chunk.sampleData = null;
for (let sample of chunk.samples) {
__privateGet(this, _writer).write(sample.data);
sample.data = null;
}
}
} else if (__privateGet(this, _options).fastStart === "fragmented") {
let startPos = __privateGet(this, _writer).pos;
let mfraBox = mfra(tracks);
__privateGet(this, _writer).writeBox(mfraBox);
let mfraBoxSize = __privateGet(this, _writer).pos - startPos;
__privateGet(this, _writer).seek(__privateGet(this, _writer).pos - 4);
__privateGet(this, _writer).writeU32(mfraBoxSize);
} else {

@@ -1043,2 +1251,5 @@ let mdatPos = __privateGet(this, _writer).offsets.get(__privateGet(this, _mdat));

_finalizedChunks = new WeakMap();
_nextFragmentNumber = new WeakMap();
_videoSampleQueue = new WeakMap();
_audioSampleQueue = new WeakMap();
_finalized = new WeakMap();

@@ -1068,4 +1279,4 @@ _validateOptions = new WeakSet();

}
} else if (![false, "in-memory"].includes(options.fastStart)) {
throw new Error(`'fastStart' option must be false, 'in-memory' or an object.`);
} else if (![false, "in-memory", "fragmented"].includes(options.fastStart)) {
throw new Error(`'fastStart' option must be false, 'in-memory', 'fragmented' or an object.`);
}

@@ -1075,7 +1286,10 @@ };

writeHeader_fn = function() {
let holdsHevc = __privateGet(this, _options).video?.codec === "hevc";
__privateGet(this, _writer).writeBox(ftyp(holdsHevc));
__privateGet(this, _writer).writeBox(ftyp({
holdsAvc: __privateGet(this, _options).video?.codec === "avc",
fragmented: __privateGet(this, _options).fastStart === "fragmented"
}));
__privateSet(this, _ftypSize, __privateGet(this, _writer).pos);
if (__privateGet(this, _options).fastStart === "in-memory") {
__privateSet(this, _mdat, mdat(false));
} else if (__privateGet(this, _options).fastStart === "fragmented") {
} else {

@@ -1124,4 +1338,4 @@ if (typeof __privateGet(this, _options).fastStart === "object") {

},
timescale: 720,
// = lcm(24, 30, 60, 120, 144, 240, 360), so should fit with many framerates
timescale: 11520,
// Timescale used by FFmpeg, contains many common frame rates as factors
codecPrivate: new Uint8Array(0),

@@ -1135,2 +1349,3 @@ samples: [],

lastTimescaleUnits: null,
lastSample: null,
compactlyCodedChunkTable: []

@@ -1164,2 +1379,3 @@ });

lastTimescaleUnits: null,
lastSample: null,
compactlyCodedChunkTable: []

@@ -1188,54 +1404,81 @@ });

};
_addSampleToTrack = new WeakSet();
addSampleToTrack_fn = function(track, data, type, timestamp, duration, meta) {
_createSampleForTrack = new WeakSet();
createSampleForTrack_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({
let sample = {
timestamp: timestampInSeconds,
duration: durationInSeconds,
data,
size: data.byteLength,
type
});
type,
// Will be refined once the next sample comes in
timescaleUnitsToNextSample: intoTimescale(durationInSeconds, track.timescale)
};
return sample;
};
_addSampleToTrack = new WeakSet();
addSampleToTrack_fn = function(track, sample) {
if (__privateGet(this, _options).fastStart !== "fragmented") {
track.samples.push(sample);
}
if (track.lastTimescaleUnits !== null) {
let timescaleUnits = intoTimescale(timestampInSeconds, track.timescale, false);
let timescaleUnits = intoTimescale(sample.timestamp, 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.lastSample.timescaleUnitsToNextSample = delta;
if (__privateGet(this, _options).fastStart !== "fragmented") {
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;
if (__privateGet(this, _options).fastStart !== "fragmented") {
track.timeToSampleTable.push({
sampleCount: 2,
sampleDelta: delta
sampleCount: 1,
sampleDelta: intoTimescale(sample.duration, track.timescale)
});
}
}
track.lastSample = sample;
let beginNewChunk = false;
if (!track.currentChunk) {
beginNewChunk = true;
} else {
track.lastTimescaleUnits = 0;
track.timeToSampleTable.push({
sampleCount: 1,
sampleDelta: intoTimescale(durationInSeconds, track.timescale)
});
let currentChunkDuration = sample.timestamp - track.currentChunk.startTimestamp;
if (__privateGet(this, _options).fastStart === "fragmented") {
let mostImportantTrack = __privateGet(this, _videoTrack) ?? __privateGet(this, _audioTrack);
if (track === mostImportantTrack && sample.type === "key" && currentChunkDuration >= 1) {
beginNewChunk = true;
__privateMethod(this, _finalizeFragment, finalizeFragment_fn).call(this);
}
} else {
beginNewChunk = currentChunkDuration >= 0.5;
}
}
if (beginNewChunk) {
if (track.currentChunk) {
__privateMethod(this, _finalizeCurrentChunk, finalizeCurrentChunk_fn).call(this, track);
}
track.currentChunk = {
startTimestamp: sample.timestamp,
samples: []
};
}
track.currentChunk.samples.push(sample);
};

@@ -1252,2 +1495,5 @@ _validateTimestamp = new WeakSet();

} else if (__privateGet(this, _options).firstTimestampBehavior === "offset") {
if (track.firstTimestamp === void 0) {
track.firstTimestamp = timestamp;
}
timestamp -= track.firstTimestamp;

@@ -1260,2 +1506,3 @@ }

}
track.lastTimestamp = timestamp;
return timestamp;

@@ -1265,13 +1512,16 @@ };

finalizeCurrentChunk_fn = function(track) {
if (__privateGet(this, _options).fastStart === "fragmented") {
throw new Error("Can't finalize individual chunks 'fastStart' is set to 'fragmented'.");
}
if (!track.currentChunk)
return;
if (track.compactlyCodedChunkTable.length === 0 || last(track.compactlyCodedChunkTable).samplesPerChunk !== track.currentChunk.sampleCount) {
track.finalizedChunks.push(track.currentChunk);
__privateGet(this, _finalizedChunks).push(track.currentChunk);
if (track.compactlyCodedChunkTable.length === 0 || last(track.compactlyCodedChunkTable).samplesPerChunk !== track.currentChunk.samples.length) {
track.compactlyCodedChunkTable.push({
firstChunk: track.finalizedChunks.length + 1,
firstChunk: track.finalizedChunks.length,
// 1-indexed
samplesPerChunk: track.currentChunk.sampleCount
samplesPerChunk: track.currentChunk.samples.length
});
}
track.finalizedChunks.push(track.currentChunk);
__privateGet(this, _finalizedChunks).push(track.currentChunk);
if (__privateGet(this, _options).fastStart === "in-memory") {

@@ -1282,7 +1532,62 @@ track.currentChunk.offset = 0;

track.currentChunk.offset = __privateGet(this, _writer).pos;
for (let bytes2 of track.currentChunk.sampleData)
__privateGet(this, _writer).write(bytes2);
track.currentChunk.sampleData = null;
for (let sample of track.currentChunk.samples) {
__privateGet(this, _writer).write(sample.data);
sample.data = null;
}
__privateMethod(this, _maybeFlushStreamingTargetWriter, maybeFlushStreamingTargetWriter_fn).call(this);
};
_finalizeFragment = new WeakSet();
finalizeFragment_fn = function(flushStreamingWriter = true) {
if (__privateGet(this, _options).fastStart !== "fragmented") {
throw new Error("Can't finalize a fragment unless 'fastStart' is set to 'fragmented'.");
}
let tracks = [__privateGet(this, _videoTrack), __privateGet(this, _audioTrack)].filter((track) => track && track.currentChunk);
if (tracks.length === 0)
return;
let fragmentNumber = __privateWrapper(this, _nextFragmentNumber)._++;
if (fragmentNumber === 1) {
let movieBox = moov(tracks, __privateGet(this, _creationTime), true);
__privateGet(this, _writer).writeBox(movieBox);
}
let moofOffset = __privateGet(this, _writer).pos;
let moofBox = moof(fragmentNumber, tracks);
__privateGet(this, _writer).writeBox(moofBox);
{
let mdatBox = mdat(false);
let totalTrackSampleSize = 0;
for (let track of tracks) {
for (let sample of track.currentChunk.samples) {
totalTrackSampleSize += sample.size;
}
}
let mdatSize = __privateGet(this, _writer).measureBox(mdatBox) + totalTrackSampleSize;
if (mdatSize >= 2 ** 32) {
mdatBox.largeSize = true;
mdatSize = __privateGet(this, _writer).measureBox(mdatBox) + totalTrackSampleSize;
}
mdatBox.size = mdatSize;
__privateGet(this, _writer).writeBox(mdatBox);
}
for (let track of tracks) {
track.currentChunk.offset = __privateGet(this, _writer).pos;
track.currentChunk.moofOffset = moofOffset;
for (let sample of track.currentChunk.samples) {
__privateGet(this, _writer).write(sample.data);
sample.data = null;
}
}
let endPos = __privateGet(this, _writer).pos;
__privateGet(this, _writer).seek(__privateGet(this, _writer).offsets.get(moofBox));
let newMoofBox = moof(fragmentNumber, tracks);
__privateGet(this, _writer).writeBox(newMoofBox);
__privateGet(this, _writer).seek(endPos);
for (let track of tracks) {
track.finalizedChunks.push(track.currentChunk);
__privateGet(this, _finalizedChunks).push(track.currentChunk);
track.currentChunk = null;
}
if (flushStreamingWriter) {
__privateMethod(this, _maybeFlushStreamingTargetWriter, maybeFlushStreamingTargetWriter_fn).call(this);
}
};
_maybeFlushStreamingTargetWriter = new WeakSet();

@@ -1289,0 +1594,0 @@ maybeFlushStreamingTargetWriter_fn = function() {

{
"name": "mp4-muxer",
"version": "3.0.5",
"version": "4.0.0",
"description": "MP4 multiplexer in pure TypeScript with support for WebCodecs API, video & audio.",

@@ -50,2 +50,3 @@ "main": "./build/mp4-muxer.js",

"mp4",
"fmp4",
"muxer",

@@ -52,0 +53,0 @@ "muxing",

@@ -8,7 +8,10 @@ # mp4-muxer - JavaScript MP4 multiplexer

The WebCodecs API provides low-level access to media codecs, but provides no way of actually packaging (multiplexing)
the encoded media into a playable file. This project implements an MP4 multiplexer in pure TypeScript which is
high-quality, fast and tiny, and supports both video and audio.
the encoded media into a playable file. This project implements an MP4 multiplexer in pure TypeScript, which is
high-quality, fast and tiny, and supports both video and audio as well as various internal layouts such as Fast Start or
fragmented MP4.
[Demo: Muxing into a file](https://vanilagy.github.io/mp4-muxer/demo/)
[Demo: Live streaming](https://vanilagy.github.io/mp4-muxer/demo-streaming)
> **Note:** If you're looking to create **WebM** files, check out [webm-muxer](https://github.com/Vanilagy/webm-muxer),

@@ -107,2 +110,3 @@ the sister library to mp4-muxer.

| 'in-memory'
| 'fragmented'
| { expectedVideoChunks?: number, expectedAudioChunks?: number }

@@ -136,19 +140,21 @@

```ts
constructor(
onData: (data: Uint8Array, position: number) => void,
onDone?: () => void,
options?: { chunked?: boolean, chunkSize?: number }
);
constructor(options: {
onData?: (data: Uint8Array, position: number) => void,
chunked?: boolean,
chunkSize?: number
});
```
The `position` argument specifies the offset in bytes at which the data has to be written. Since the data written by
the muxer is not entirely sequential, **make sure to respect this argument**.
`onData` is called for each new chunk of available data. The `position` argument specifies the offset in bytes at
which the data has to be written. Since the data written by the muxer is not always sequential, **make sure to
respect this argument**.
When using `chunked: true` in the options, data created by the muxer will first be accumulated and only written out
once it has reached sufficient size. This is useful for reducing the total amount of writes, at the cost of
latency. It using a default chunk size of 16 MiB, which can be overridden by manually setting `chunkSize` to the
desired byte length.
Note that this target is **not** intended for *live-streaming*, i.e. playback before muxing has finished.
When using `chunked: true`, data created by the muxer will first be accumulated and only written out once it has
reached sufficient size. This is useful for reducing the total amount of writes, at the cost of latency. It using a
default chunk size of 16 MiB, which can be overridden by manually setting `chunkSize` to the desired byte length.
If you want to use this target for *live-streaming*, i.e. playback before muxing has finished, you also need to set
`fastStart: 'fragmented'`.
Usage example:
```js

@@ -158,6 +164,5 @@ import { Muxer, StreamTarget } from 'mp4-muxer';

let muxer = new Muxer({
target: new StreamTarget(
(data, position) => { /* Do something with the data */ },
() => { /* Muxing has finished */ }
),
target: new StreamTarget({
onData: (data, position) => { /* Do something with the data */ }
}),
fastStart: false,

@@ -203,13 +208,19 @@ // ...

#### `fastStart` (required)
By default, MP4 metadata is stored at the end of the file in the `moov` box - this makes writing the file faster and
easier. However, placing this `moov` box at the _start_ of the file instead (known as "Fast Start") provides certain
benefits: The file becomes easier to stream over the web without range requests, and sites like YouTube can start
processing the video while it's uploading. This library provides full control over the placement of the `moov` box by
By default, MP4 metadata (track info, sample timing, etc.) is stored at the end of the file - this makes writing the
file faster and easier. However, placing this metadata at the _start_ of the file instead (known as "Fast Start")
provides certain benefits: The file becomes easier to stream over the web without range requests, and sites like YouTube
can start processing the video while it's uploading. This library provides full control over the placement of metadata
setting `fastStart` to one of these options:
- `false`: Disables Fast Start, placing metadata at the end of the file. This option is the fastest and uses the least
memory. This option is recommended for large, unbounded files that are streamed directly to disk.
- `false`: Disables Fast Start, placing all metadata at the end of the file. This option is the fastest and uses the
least memory. This option is recommended for large, unbounded files that are streamed directly to disk.
- `'in-memory'`: Produces a file with Fast Start by keeping all media chunks in memory until the file is finalized. This
option produces the most compact output possible at the cost of a more expensive finalization step and higher memory
requirements. You should _always_ use this option when using `ArrayBufferTarget` as it will result in a
higher-quality output with no change in memory footprint.
requirements. This is the preferred option when using `ArrayBufferTarget` as it will result in a higher-quality
output with no change in memory footprint.
- `'fragmented'`: Produces a _fragmented MP4 (fMP4)_ file, evenly placing sample metadata throughout the file by grouping
it into "fragments" (short sections of media), while placing general metadata at the beginning of the file.
Fragmented files are ideal for streaming, as they are optimized for random access with minimal to no seeking.
Furthermore, they remain lightweight to create no matter how large the file becomes, as they don't require media to
be kept in memory for very long. While fragmented files are not as widely supported as regular MP4 files, this
option provides powerful benefits with very little downsides. Further details [here](#fragmented-mp4-notes).
- `object`: Produces a file with Fast Start by reserving space for metadata when muxing begins. To know

@@ -306,2 +317,23 @@ how many bytes need to be reserved to be safe, you'll have to provide the following data:

### Additional notes about fragmented MP4 files
By breaking up the media and related metadata into small fragments, fMP4 files optimize for random access and are ideal
for streaming, while remaining cheap to write even for long files. However, you should keep these things in mind:
- **Media chunk buffering:**
When muxing a file with a video **and** an audio track, the muxer needs to wait for the chunks from _both_ media
to finalize any given fragment. In other words, it must buffer chunks of one medium if the other medium has not yet
encoded chunks up to that timestamp. For example, should you first encode all your video frames and then encode the
audio afterward, the multiplexer will have to hold all those video frames in memory until the audio chunks start
coming in. This might lead to memory exhaustion should your video be very long. When there is only one media track,
this issue does not arise. So, when muxing a multimedia file, make sure it is somewhat limited in size or the chunks
are encoded in a somewhat interleaved way (like is the case for live media). This will keep memory usage at a
constant low.
- **Video key frame frequency:**
Every track's first sample in a fragment must be a key frame in order to be able to play said fragment without the
knowledge of previous ones. However, this means that the muxer needs to wait for a video key frame to begin a new
fragment. If these key frames are too infrequent, fragments become too large, harming random access. Therefore,
every 5–10 seconds, you should force a video key frame like so:
```js
videoEncoder.encode(frame, { keyFrame: true });
```
## Implementation & development

@@ -311,3 +343,3 @@ MP4 files are based on the ISO Base Media Format, which structures its files as a hierarchy of boxes (or atoms). The

[ISO/IEC 14496-1](http://netmedia.zju.edu.cn/multimedia2013/mpeg-4/ISO%20IEC%2014496-1%20MPEG-4%20System%20Standard.pdf),
[ISO/IEC 14496-12](https://web.archive.org/web/20180219054429/http://l.web.umkc.edu/lizhu/teaching/2016sp.video-communication/ref/mp4.pdf)
[ISO/IEC 14496-12](https://web.archive.org/web/20231123030701/https://b.goeswhere.com/ISO_IEC_14496-12_2015.pdf)
and

@@ -314,0 +346,0 @@ [ISO/IEC 14496-14](https://github.com/OpenAnsible/rust-mp4/raw/master/docs/ISO_IEC_14496-14_2003-11-15.pdf).

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc