mirror of
https://github.com/arcodange-org/mediabunny.git
synced 2026-10-03 13:53:53 +02:00
Add configurable minimum MP4 fragment duration and Matroska cluster duration
This commit is contained in:
+3
-1
@@ -16,7 +16,9 @@
|
|||||||
|
|
||||||
const source = new Metamuxer.BlobSource(file);
|
const source = new Metamuxer.BlobSource(file);
|
||||||
const target = new Metamuxer.BufferTarget();
|
const target = new Metamuxer.BufferTarget();
|
||||||
const outputFormat = new Metamuxer.WebMOutputFormat()
|
const outputFormat = new Metamuxer.Mp4OutputFormat({
|
||||||
|
fastStart: 'fragmented'
|
||||||
|
})
|
||||||
|
|
||||||
const button = document.createElement('button');
|
const button = document.createElement('button');
|
||||||
button.textContent = 'Cancel';
|
button.textContent = 'Cancel';
|
||||||
|
|||||||
+4
-17
@@ -41,8 +41,8 @@
|
|||||||
let format = new Metamuxer.MkvOutputFormat({ streamable: false });
|
let format = new Metamuxer.MkvOutputFormat({ streamable: false });
|
||||||
format = new Metamuxer.Mp4OutputFormat({ fastStart: 'fragmented' }); // new Metamuxer.MkvOutputFormat();// new Metamuxer.Mp4OutputFormat({ fastStart: false });
|
format = new Metamuxer.Mp4OutputFormat({ fastStart: 'fragmented' }); // new Metamuxer.MkvOutputFormat();// new Metamuxer.Mp4OutputFormat({ fastStart: false });
|
||||||
format = new Metamuxer.OggOutputFormat();
|
format = new Metamuxer.OggOutputFormat();
|
||||||
format = new Metamuxer.Mp4OutputFormat();
|
format = new Metamuxer.Mp4OutputFormat({ fastStart: 'fragmented', minimumFragmentDuration: 2 });
|
||||||
format = new Metamuxer.WebMOutputFormat();
|
format = new Metamuxer.WebMOutputFormat({ minimumClusterDuration: 2 });
|
||||||
let target = new Metamuxer.BufferTarget();
|
let target = new Metamuxer.BufferTarget();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -86,12 +86,10 @@
|
|||||||
download(new Blob([target.buffer]), 'test.mkv');
|
download(new Blob([target.buffer]), 'test.mkv');
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
|
||||||
let videoSource = new Metamuxer.CanvasSource(canvas, {
|
let videoSource = new Metamuxer.CanvasSource(canvas, {
|
||||||
codec: 'av1',
|
codec: 'av1',
|
||||||
bitrate: 1e6
|
bitrate: 1e6
|
||||||
});*/
|
});
|
||||||
let videoSource = new Metamuxer.EncodedVideoPacketSource('av1');
|
|
||||||
let audioSource = new Metamuxer.AudioBufferSource({
|
let audioSource = new Metamuxer.AudioBufferSource({
|
||||||
codec: 'opus',
|
codec: 'opus',
|
||||||
bitrate: 128e3,
|
bitrate: 128e3,
|
||||||
@@ -162,22 +160,11 @@ Testing... <00:17.350>One... <00:18.125>Two...
|
|||||||
//subtitleSource.add(simpleWebvttFile);
|
//subtitleSource.add(simpleWebvttFile);
|
||||||
//subtitleSource.close();
|
//subtitleSource.close();
|
||||||
|
|
||||||
/*
|
|
||||||
for (let i = 0; i < 100; i++) {
|
for (let i = 0; i < 100; i++) {
|
||||||
context.fillStyle = ['red', 'green', 'blue', 'yellow'][i % 4];
|
context.fillStyle = ['red', 'green', 'blue', 'yellow'][i % 4];
|
||||||
context.fillRect(canvas.width * Math.random(), canvas.height * Math.random(), canvas.width * Math.random(), canvas.height * Math.random());
|
context.fillRect(canvas.width * Math.random(), canvas.height * Math.random(), canvas.width * Math.random(), canvas.height * Math.random());
|
||||||
|
|
||||||
await videoSource.add(i / 10, 1 / 10);
|
await videoSource.add(i / 10, 1 / 10, { keyFrame: true });
|
||||||
}
|
|
||||||
*/
|
|
||||||
for (let i = 0; i < 40; i += 0.1) {
|
|
||||||
await videoSource.add(new Metamuxer.EncodedPacket(new Uint8Array(1234), i === 0 ? 'key' : 'delta', i, 0.1), {
|
|
||||||
decoderConfig: {
|
|
||||||
codec: 'vp09.00.10.08',
|
|
||||||
codedWidth: 64,
|
|
||||||
codedHeight: 64
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let audioContext = new AudioContext();
|
let audioContext = new AudioContext();
|
||||||
|
|||||||
@@ -118,6 +118,7 @@ export class IsobmffMuxer extends Muxer {
|
|||||||
private nextFragmentNumber = 1;
|
private nextFragmentNumber = 1;
|
||||||
// Only relevant for fragmented files, to make sure new fragments start with the highest timestamp seen so far
|
// Only relevant for fragmented files, to make sure new fragments start with the highest timestamp seen so far
|
||||||
private maxWrittenTimestamp = -Infinity;
|
private maxWrittenTimestamp = -Infinity;
|
||||||
|
private minimumFragmentDuration: number;
|
||||||
|
|
||||||
constructor(output: Output, format: IsobmffOutputFormat) {
|
constructor(output: Output, format: IsobmffOutputFormat) {
|
||||||
super(output);
|
super(output);
|
||||||
@@ -136,6 +137,8 @@ export class IsobmffMuxer extends Muxer {
|
|||||||
if (this.fastStart === 'in-memory' || this.isFragmented) {
|
if (this.fastStart === 'in-memory' || this.isFragmented) {
|
||||||
this.writer.ensureMonotonicity = true;
|
this.writer.ensureMonotonicity = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.minimumFragmentDuration = format._options.minimumFragmentDuration ?? 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
async start() {
|
async start() {
|
||||||
@@ -683,7 +686,7 @@ export class IsobmffMuxer extends Muxer {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (
|
if (
|
||||||
currentChunkDuration >= 1.0
|
currentChunkDuration >= this.minimumFragmentDuration
|
||||||
&& keyFrameQueuedEverywhere
|
&& keyFrameQueuedEverywhere
|
||||||
&& sample.timestamp > this.maxWrittenTimestamp
|
&& sample.timestamp > this.maxWrittenTimestamp
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -703,7 +703,7 @@ export class MatroskaMuxer extends Muxer {
|
|||||||
// CURRENT chunk, meaning that starting the next cluster at the same timestamp is forbidden (since
|
// CURRENT chunk, meaning that starting the next cluster at the same timestamp is forbidden (since
|
||||||
// the already-written block would belong into it instead).
|
// the already-written block would belong into it instead).
|
||||||
&& msTimestamp > this.currentClusterMaxMsTimestamp
|
&& msTimestamp > this.currentClusterMaxMsTimestamp
|
||||||
&& relativeTimestamp >= 1000
|
&& relativeTimestamp >= 1000 * (this.format._options.minimumClusterDuration ?? 1)
|
||||||
)
|
)
|
||||||
// The cluster would exceed its maximum allowed length. This puts us in an unfortunate position and forces
|
// The cluster would exceed its maximum allowed length. This puts us in an unfortunate position and forces
|
||||||
// us to begin the next cluster with a delta frame. Although this is undesirable, it is not forbidden by the
|
// us to begin the next cluster with a delta frame. Although this is undesirable, it is not forbidden by the
|
||||||
|
|||||||
@@ -110,6 +110,12 @@ export type IsobmffOutputFormatOptions = {
|
|||||||
* the type of output target used.
|
* the type of output target used.
|
||||||
*/
|
*/
|
||||||
fastStart?: false | 'in-memory' | 'fragmented';
|
fastStart?: false | 'in-memory' | 'fragmented';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When using `fastStart: 'fragmented'`, this field controls the minimum duration of each fragment, in seconds.
|
||||||
|
* New fragments will only be created when the current fragment is longer than this value. Defaults to 1 second.
|
||||||
|
*/
|
||||||
|
minimumFragmentDuration?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -127,6 +133,12 @@ export abstract class IsobmffOutputFormat extends OutputFormat {
|
|||||||
if (options.fastStart !== undefined && ![false, 'in-memory', 'fragmented'].includes(options.fastStart)) {
|
if (options.fastStart !== undefined && ![false, 'in-memory', 'fragmented'].includes(options.fastStart)) {
|
||||||
throw new TypeError('options.fastStart, when provided, must be false, "in-memory", or "fragmented".');
|
throw new TypeError('options.fastStart, when provided, must be false, "in-memory", or "fragmented".');
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
options.minimumFragmentDuration !== undefined
|
||||||
|
&& (!Number.isFinite(options.minimumFragmentDuration) || options.minimumFragmentDuration < 0)
|
||||||
|
) {
|
||||||
|
throw new TypeError('options.minimumFragmentDuration, when provided, must be a non-negative number.');
|
||||||
|
}
|
||||||
|
|
||||||
super();
|
super();
|
||||||
|
|
||||||
@@ -226,6 +238,12 @@ export type MkvOutputFormatOptions = {
|
|||||||
* option when you want to write out a file for later use.
|
* option when you want to write out a file for later use.
|
||||||
*/
|
*/
|
||||||
streamable?: boolean;
|
streamable?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This field controls the minimum duration of each Matroska cluster, in seconds. New clusters will only be created
|
||||||
|
* when the current cluster is longer than this value. Defaults to 1 second.
|
||||||
|
*/
|
||||||
|
minimumClusterDuration?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -243,6 +261,12 @@ export class MkvOutputFormat extends OutputFormat {
|
|||||||
if (options.streamable !== undefined && typeof options.streamable !== 'boolean') {
|
if (options.streamable !== undefined && typeof options.streamable !== 'boolean') {
|
||||||
throw new TypeError('options.streamable, when provided, must be a boolean.');
|
throw new TypeError('options.streamable, when provided, must be a boolean.');
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
options.minimumClusterDuration !== undefined
|
||||||
|
&& (!Number.isFinite(options.minimumClusterDuration) || options.minimumClusterDuration < 0)
|
||||||
|
) {
|
||||||
|
throw new TypeError('options.minimumClusterDuration, when provided, must be a non-negative number.');
|
||||||
|
}
|
||||||
|
|
||||||
super();
|
super();
|
||||||
|
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
- https://github.com/Vanilagy/mp4-muxer/issues/83 tell him it's possible now
|
- https://github.com/Vanilagy/mp4-muxer/issues/83 tell him it's possible now
|
||||||
- is this fixed? https://github.com/Vanilagy/webm-muxer/issues/50
|
- is this fixed? https://github.com/Vanilagy/webm-muxer/issues/50
|
||||||
- cross-track offset for streaming sources
|
- cross-track offset for streaming sources
|
||||||
- configurable fragmented mp4 fragment size, like the mp4-muxer PR
|
- textsubtitlesource, chunked piping
|
||||||
- textsubtitlesource, chunked piping
|
- More efficient MP3 loading when reading sequentially
|
||||||
Reference in New Issue
Block a user