From d2507a5a72c70dfd7cd36fe83beb76648633cec0 Mon Sep 17 00:00:00 2001 From: Vanilagy <1696106+Vanilagy@users.noreply.github.com> Date: Mon, 1 Sep 2025 16:39:19 +0200 Subject: [PATCH] Fix incorrect decoded audio sample timestamps in Firefox --- package-lock.json | 12 ++++++------ package.json | 2 +- packages/mp3-encoder/package.json | 2 +- src/media-sink.ts | 19 +++++++++++++++++-- 4 files changed, 25 insertions(+), 10 deletions(-) diff --git a/package-lock.json b/package-lock.json index dbc4aae..bd1b3e0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "mediabunny", - "version": "1.12.0", + "version": "1.12.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "mediabunny", - "version": "1.12.0", + "version": "1.12.1", "license": "MPL-2.0", "workspaces": [ "packages/*" @@ -6147,9 +6147,9 @@ } }, "node_modules/mediabunny": { - "version": "1.11.2", - "resolved": "https://registry.npmjs.org/mediabunny/-/mediabunny-1.11.2.tgz", - "integrity": "sha512-dZpaq+YMKo5dUz6HlwWtZJfKPf3rZCq9BXurC7/zVmgjB3sTW5l32VT65gWm/ojv0vUwH+WbtolkmOQX1viz5g==", + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/mediabunny/-/mediabunny-1.12.0.tgz", + "integrity": "sha512-BARnydaYDY9iCar+ZRdnaKER31c2J4otsEVcSRsizXRrFLv97LXSd5ilD5FQF52AaM6FiqLm7iB3Zl1C+a5u9w==", "license": "MPL-2.0", "peer": true, "workspaces": [ @@ -9478,7 +9478,7 @@ }, "packages/mp3-encoder": { "name": "@mediabunny/mp3-encoder", - "version": "1.12.0", + "version": "1.12.1", "license": "MPL-2.0", "devDependencies": { "@types/emscripten": "^1.40.1" diff --git a/package.json b/package.json index 7b443cd..8e85294 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "mediabunny", "author": "Vanilagy", - "version": "1.12.0", + "version": "1.12.1", "description": "Pure TypeScript media toolkit for reading, writing, and converting media files, directly in the browser.", "type": "module", "workspaces": [ diff --git a/packages/mp3-encoder/package.json b/packages/mp3-encoder/package.json index b860afd..f1be561 100644 --- a/packages/mp3-encoder/package.json +++ b/packages/mp3-encoder/package.json @@ -1,7 +1,7 @@ { "name": "@mediabunny/mp3-encoder", "author": "Vanilagy", - "version": "1.12.0", + "version": "1.12.1", "description": "MP3 encoder extension for Mediabunny, based on LAME.", "main": "./dist/bundles/mediabunny-mp3-encoder.mjs", "module": "./dist/bundles/mediabunny-mp3-encoder.mjs", diff --git a/src/media-sink.ts b/src/media-sink.ts index 6506f4f..b419e46 100644 --- a/src/media-sink.ts +++ b/src/media-sink.ts @@ -1217,6 +1217,10 @@ class AudioDecoderWrapper extends DecoderWrapper { customDecoderCallSerializer = new CallSerializer(); customDecoderQueueSize = 0; + // Internal state to accumulate a precise current timestamp based on audio durations, not the (potentially + // inaccurate) packet timestamps. + currentTimestamp: number | null = null; + constructor( onSample: (sample: AudioSample) => unknown, onError: (error: DOMException) => unknown, @@ -1226,6 +1230,17 @@ class AudioDecoderWrapper extends DecoderWrapper { super(onSample, onError); const sampleHandler = (sample: AudioSample) => { + if ( + this.currentTimestamp === null + || Math.abs(sample.timestamp - this.currentTimestamp) >= sample.duration + ) { + // We need to sync with the sample timestamp again + this.currentTimestamp = sample.timestamp; + } + + const preciseTimestamp = this.currentTimestamp; + this.currentTimestamp += sample.duration; + if (sample.numberOfFrames === 0) { // We skip zero-data (empty) AudioSamples. These are sometimes emitted, for example, by Firefox when it // decodes Vorbis (at the start). @@ -1235,7 +1250,7 @@ class AudioDecoderWrapper extends DecoderWrapper { // Round the timestamp to the sample rate const sampleRate = decoderConfig.sampleRate; - sample.setTimestamp(Math.round(sample.timestamp * sampleRate) / sampleRate); + sample.setTimestamp(Math.round(preciseTimestamp * sampleRate) / sampleRate); onSample(sample); }; @@ -1320,7 +1335,7 @@ class PcmAudioDecoderWrapper extends DecoderWrapper { writeOutputValue: (view: DataView, byteOffset: number, value: number) => void; // Internal state to accumulate a precise current timestamp based on audio durations, not the (potentially - // inaccurate) sample timestamps. + // inaccurate) packet timestamps. currentTimestamp: number | null = null; constructor(