From 445911edd091fd9662546c24df029c39f0187222 Mon Sep 17 00:00:00 2001 From: Vanilagy <1696106+Vanilagy@users.noreply.github.com> Date: Mon, 3 Aug 2026 14:50:36 +0200 Subject: [PATCH] Fix ISOBMFF muxer track alternate groups (fixes #454), fixed mutex race condition in Output --- dev/mux.html | 14 +++++++------- src/isobmff/isobmff-boxes.ts | 13 ++++++++++++- src/output.ts | 5 +++-- 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/dev/mux.html b/dev/mux.html index e619bb9..1c39112 100644 --- a/dev/mux.html +++ b/dev/mux.html @@ -47,7 +47,7 @@ format = new Mediabunny.MkvOutputFormat(); format = new Mediabunny.MovOutputFormat(); format = new Mediabunny.Mp4OutputFormat({ fastStart: 'reserve' }); - format = new Mediabunny.WebMOutputFormat(); + format = new Mediabunny.Mp4OutputFormat(); let target = new Mediabunny.BufferTarget(); /* @@ -118,21 +118,21 @@ */ let videoSource = new Mediabunny.CanvasSource(canvas, { - codec: 'vp9', + codec: 'avc', //fullCodecString: 'avc1.42001f', bitrate: 1e6, alpha: 'keep', onEncoderConfig: console.log, }); let audioSource = new Mediabunny.AudioBufferSource({ - codec: 'opus', + codec: 'aac', bitrate: 128e3, }); let subtitleSource = new Mediabunny.TextSubtitleSource('webvtt'); output.addVideoTrack(videoSource, { languageCode: 'eng', name: 'Mononoké', maximumPacketCount: 100 }); output.addAudioTrack(audioSource, { name: 'Yooo', maximumPacketCount: 1000 }); - //output.addSubtitleTrack(subtitleSource); + output.addSubtitleTrack(subtitleSource); output.start(); @@ -191,8 +191,8 @@ Testing... <00:17.350>One... <00:18.125>Two... 9. justify (bottom, right). `; - //subtitleSource.add(simpleWebvttFile); - //subtitleSource.close(); + subtitleSource.add(simpleWebvttFile); + subtitleSource.close(); const p = document.createElement('p'); document.body.append(p); @@ -216,5 +216,5 @@ Testing... <00:17.350>One... <00:18.125>Two... await output.finalize(); console.log(target); - //download(new Blob([target.buffer]), 'test' + format.fileExtension); + download(new Blob([target.buffer]), 'test' + format.fileExtension); \ No newline at end of file diff --git a/src/isobmff/isobmff-boxes.ts b/src/isobmff/isobmff-boxes.ts index ebbc3a9..57fea8d 100644 --- a/src/isobmff/isobmff-boxes.ts +++ b/src/isobmff/isobmff-boxes.ts @@ -493,6 +493,17 @@ export const tkhd = ( flags |= 0x1; // Track enabled } + // Set the alternate group based on the track type; this mirror's how FFmpeg does it. A more advanced version would + // determine the alternate groups based on the actual track pairability graph. Note that it appears important that + // video get assigned to group 0, see https://github.com/Vanilagy/mediabunny/issues/454. + const alternateGroup = trackData.type === 'video' + ? 0 + : trackData.type === 'audio' + ? 1 + : trackData.type === 'subtitle' + ? 2 + : assertNever(trackData); + return fullBox('tkhd', +needsU64, flags, [ u32OrU64(creationTime), // Creation time u32OrU64(creationTime), // Modification time @@ -501,7 +512,7 @@ export const tkhd = ( u32OrU64(durationInGlobalTimescale), // Duration Array(8).fill(0), // Reserved u16(0), // Layer - u16(trackData.track.id), // Alternate group + u16(alternateGroup), // Alternate group fixed_8_8(trackData.type === 'audio' ? 1 : 0), // Volume u16(0), // Reserved matrixToBytes(matrix), // Matrix diff --git a/src/output.ts b/src/output.ts index 505e732..44f1a49 100644 --- a/src/output.ts +++ b/src/output.ts @@ -830,7 +830,8 @@ export class Output< return this._startPromise = (async () => { this.state = 'started'; - const release = await this._mutex.acquire(); + // We want to call muxer.start immediately, so we avoid using an await here + const releasePromise = this._mutex.acquire(); try { await this._muxer.start(); @@ -838,7 +839,7 @@ export class Output< const promises = this.tracks.map(track => track.source._start()); await Promise.all(promises); } finally { - release(); + (await releasePromise)(); } })(); }