diff --git a/dev/convert.html b/dev/convert.html
index f3036a2..22e8437 100644
--- a/dev/convert.html
+++ b/dev/convert.html
@@ -16,7 +16,9 @@
const source = new Metamuxer.BlobSource(file);
const target = new Metamuxer.BufferTarget();
- const outputFormat = new Metamuxer.WebMOutputFormat()
+ const outputFormat = new Metamuxer.Mp4OutputFormat({
+ fastStart: 'fragmented'
+ })
const button = document.createElement('button');
button.textContent = 'Cancel';
diff --git a/dev/mux.html b/dev/mux.html
index 7d05a50..fb38b1c 100644
--- a/dev/mux.html
+++ b/dev/mux.html
@@ -41,8 +41,8 @@
let format = new Metamuxer.MkvOutputFormat({ streamable: false });
format = new Metamuxer.Mp4OutputFormat({ fastStart: 'fragmented' }); // new Metamuxer.MkvOutputFormat();// new Metamuxer.Mp4OutputFormat({ fastStart: false });
format = new Metamuxer.OggOutputFormat();
- format = new Metamuxer.Mp4OutputFormat();
- format = new Metamuxer.WebMOutputFormat();
+ format = new Metamuxer.Mp4OutputFormat({ fastStart: 'fragmented', minimumFragmentDuration: 2 });
+ format = new Metamuxer.WebMOutputFormat({ minimumClusterDuration: 2 });
let target = new Metamuxer.BufferTarget();
/*
@@ -86,12 +86,10 @@
download(new Blob([target.buffer]), 'test.mkv');
*/
- /*
let videoSource = new Metamuxer.CanvasSource(canvas, {
codec: 'av1',
bitrate: 1e6
- });*/
- let videoSource = new Metamuxer.EncodedVideoPacketSource('av1');
+ });
let audioSource = new Metamuxer.AudioBufferSource({
codec: 'opus',
bitrate: 128e3,
@@ -162,22 +160,11 @@ Testing... <00:17.350>One... <00:18.125>Two...
//subtitleSource.add(simpleWebvttFile);
//subtitleSource.close();
- /*
for (let i = 0; i < 100; i++) {
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());
- await videoSource.add(i / 10, 1 / 10);
- }
- */
- 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
- }
- });
+ await videoSource.add(i / 10, 1 / 10, { keyFrame: true });
}
let audioContext = new AudioContext();
diff --git a/src/isobmff/isobmff-muxer.ts b/src/isobmff/isobmff-muxer.ts
index 3b0d239..2283d8a 100644
--- a/src/isobmff/isobmff-muxer.ts
+++ b/src/isobmff/isobmff-muxer.ts
@@ -118,6 +118,7 @@ export class IsobmffMuxer extends Muxer {
private nextFragmentNumber = 1;
// Only relevant for fragmented files, to make sure new fragments start with the highest timestamp seen so far
private maxWrittenTimestamp = -Infinity;
+ private minimumFragmentDuration: number;
constructor(output: Output, format: IsobmffOutputFormat) {
super(output);
@@ -136,6 +137,8 @@ export class IsobmffMuxer extends Muxer {
if (this.fastStart === 'in-memory' || this.isFragmented) {
this.writer.ensureMonotonicity = true;
}
+
+ this.minimumFragmentDuration = format._options.minimumFragmentDuration ?? 1;
}
async start() {
@@ -683,7 +686,7 @@ export class IsobmffMuxer extends Muxer {
});
if (
- currentChunkDuration >= 1.0
+ currentChunkDuration >= this.minimumFragmentDuration
&& keyFrameQueuedEverywhere
&& sample.timestamp > this.maxWrittenTimestamp
) {
diff --git a/src/matroska/matroska-muxer.ts b/src/matroska/matroska-muxer.ts
index 3139e0f..09e12f7 100644
--- a/src/matroska/matroska-muxer.ts
+++ b/src/matroska/matroska-muxer.ts
@@ -703,7 +703,7 @@ export class MatroskaMuxer extends Muxer {
// CURRENT chunk, meaning that starting the next cluster at the same timestamp is forbidden (since
// the already-written block would belong into it instead).
&& 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
// us to begin the next cluster with a delta frame. Although this is undesirable, it is not forbidden by the
diff --git a/src/output-format.ts b/src/output-format.ts
index 9ecc6fd..ea16392 100644
--- a/src/output-format.ts
+++ b/src/output-format.ts
@@ -110,6 +110,12 @@ export type IsobmffOutputFormatOptions = {
* the type of output target used.
*/
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)) {
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();
@@ -226,6 +238,12 @@ export type MkvOutputFormatOptions = {
* option when you want to write out a file for later use.
*/
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') {
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();
diff --git a/todo.txt b/todo.txt
index 07318e7..e444d1e 100644
--- a/todo.txt
+++ b/todo.txt
@@ -2,5 +2,5 @@
- 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
- cross-track offset for streaming sources
-- configurable fragmented mp4 fragment size, like the mp4-muxer PR
-- textsubtitlesource, chunked piping
\ No newline at end of file
+- textsubtitlesource, chunked piping
+- More efficient MP3 loading when reading sequentially
\ No newline at end of file