Merge main into release for tag v1.44.1

This commit is contained in:
github-actions[bot]
2026-05-07 09:13:03 +00:00
17 changed files with 54 additions and 67 deletions
+9 -9
View File
@@ -1,12 +1,12 @@
{ {
"name": "mediabunny", "name": "mediabunny",
"version": "1.44.0", "version": "1.44.1",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "mediabunny", "name": "mediabunny",
"version": "1.44.0", "version": "1.44.1",
"license": "MPL-2.0", "license": "MPL-2.0",
"workspaces": [ "workspaces": [
"packages/*" "packages/*"
@@ -7751,9 +7751,9 @@
} }
}, },
"node_modules/mediabunny": { "node_modules/mediabunny": {
"version": "1.43.1", "version": "1.44.0",
"resolved": "https://registry.npmjs.org/mediabunny/-/mediabunny-1.43.1.tgz", "resolved": "https://registry.npmjs.org/mediabunny/-/mediabunny-1.44.0.tgz",
"integrity": "sha512-TuxRDvj2FrQjVZ5Zi1nNJ2NcpRbbc3FpDpNcwWAygeA4ND9w1xICOXnze8XUk0VlFhAMkQPm5Gy+yZL+BJ/Gtg==", "integrity": "sha512-rs54ixLAe5MSa6q35MVsVXyXZ+aYkEZYhAw57bseil+mmxFuqAS8YrELaWBcsqAJLCTb8Tse4AoYEhtkwCHYlQ==",
"license": "MPL-2.0", "license": "MPL-2.0",
"peer": true, "peer": true,
"workspaces": [ "workspaces": [
@@ -12077,7 +12077,7 @@
}, },
"packages/aac-encoder": { "packages/aac-encoder": {
"name": "@mediabunny/aac-encoder", "name": "@mediabunny/aac-encoder",
"version": "1.44.0", "version": "1.44.1",
"license": "MPL-2.0", "license": "MPL-2.0",
"devDependencies": { "devDependencies": {
"@types/emscripten": "^1.40.1" "@types/emscripten": "^1.40.1"
@@ -12092,7 +12092,7 @@
}, },
"packages/ac3": { "packages/ac3": {
"name": "@mediabunny/ac3", "name": "@mediabunny/ac3",
"version": "1.44.0", "version": "1.44.1",
"license": "MPL-2.0", "license": "MPL-2.0",
"devDependencies": { "devDependencies": {
"@types/emscripten": "^1.40.1" "@types/emscripten": "^1.40.1"
@@ -12107,7 +12107,7 @@
}, },
"packages/flac-encoder": { "packages/flac-encoder": {
"name": "@mediabunny/flac-encoder", "name": "@mediabunny/flac-encoder",
"version": "1.44.0", "version": "1.44.1",
"license": "MPL-2.0", "license": "MPL-2.0",
"devDependencies": { "devDependencies": {
"@types/emscripten": "^1.40.1" "@types/emscripten": "^1.40.1"
@@ -12122,7 +12122,7 @@
}, },
"packages/mp3-encoder": { "packages/mp3-encoder": {
"name": "@mediabunny/mp3-encoder", "name": "@mediabunny/mp3-encoder",
"version": "1.44.0", "version": "1.44.1",
"license": "MPL-2.0", "license": "MPL-2.0",
"devDependencies": { "devDependencies": {
"@types/emscripten": "^1.40.1" "@types/emscripten": "^1.40.1"
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "mediabunny", "name": "mediabunny",
"author": "Vanilagy", "author": "Vanilagy",
"version": "1.44.0", "version": "1.44.1",
"description": "Pure TypeScript media toolkit for reading, writing, and converting media files, directly in the browser.", "description": "Pure TypeScript media toolkit for reading, writing, and converting media files, directly in the browser.",
"type": "module", "type": "module",
"workspaces": [ "workspaces": [
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "@mediabunny/aac-encoder", "name": "@mediabunny/aac-encoder",
"author": "Vanilagy", "author": "Vanilagy",
"version": "1.44.0", "version": "1.44.1",
"description": "AAC encoder extension for Mediabunny, based on FFmpeg.", "description": "AAC encoder extension for Mediabunny, based on FFmpeg.",
"main": "./dist/bundles/mediabunny-aac-encoder.mjs", "main": "./dist/bundles/mediabunny-aac-encoder.mjs",
"module": "./dist/bundles/mediabunny-aac-encoder.mjs", "module": "./dist/bundles/mediabunny-aac-encoder.mjs",
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "@mediabunny/ac3", "name": "@mediabunny/ac3",
"author": "Vanilagy", "author": "Vanilagy",
"version": "1.44.0", "version": "1.44.1",
"description": "AC-3 and E-AC-3 (Dolby Digital) decoder and encoder extension for Mediabunny, based on FFmpeg.", "description": "AC-3 and E-AC-3 (Dolby Digital) decoder and encoder extension for Mediabunny, based on FFmpeg.",
"main": "./dist/bundles/mediabunny-ac3.mjs", "main": "./dist/bundles/mediabunny-ac3.mjs",
"module": "./dist/bundles/mediabunny-ac3.mjs", "module": "./dist/bundles/mediabunny-ac3.mjs",
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "@mediabunny/flac-encoder", "name": "@mediabunny/flac-encoder",
"author": "Vanilagy", "author": "Vanilagy",
"version": "1.44.0", "version": "1.44.1",
"description": "FLAC encoder extension for Mediabunny, based on libFLAC.", "description": "FLAC encoder extension for Mediabunny, based on libFLAC.",
"main": "./dist/bundles/mediabunny-flac-encoder.mjs", "main": "./dist/bundles/mediabunny-flac-encoder.mjs",
"module": "./dist/bundles/mediabunny-flac-encoder.mjs", "module": "./dist/bundles/mediabunny-flac-encoder.mjs",
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "@mediabunny/mp3-encoder", "name": "@mediabunny/mp3-encoder",
"author": "Vanilagy", "author": "Vanilagy",
"version": "1.44.0", "version": "1.44.1",
"description": "MP3 encoder extension for Mediabunny, based on LAME.", "description": "MP3 encoder extension for Mediabunny, based on LAME.",
"main": "./dist/bundles/mediabunny-mp3-encoder.mjs", "main": "./dist/bundles/mediabunny-mp3-encoder.mjs",
"module": "./dist/bundles/mediabunny-mp3-encoder.mjs", "module": "./dist/bundles/mediabunny-mp3-encoder.mjs",
+1 -1
View File
@@ -60,7 +60,7 @@ export class AdtsMuxer extends Muxer {
const release = await this.mutex.acquire(); const release = await this.mutex.acquire();
try { try {
this.validateAndNormalizeTimestamp(track, packet.timestamp, packet.type === 'key'); this.validateTimestamp(track, packet.timestamp, packet.type === 'key');
// First packet - determine input format from metadata // First packet - determine input format from metadata
if (this.inputIsAdts === null) { if (this.inputIsAdts === null) {
+1 -1
View File
@@ -214,7 +214,7 @@ export class FlacMuxer extends Muxer {
const release = await this.mutex.acquire(); const release = await this.mutex.acquire();
try { try {
this.validateAndNormalizeTimestamp( this.validateTimestamp(
track, track,
packet.timestamp, packet.timestamp,
packet.type === 'key', packet.type === 'key',
+8 -12
View File
@@ -614,17 +614,15 @@ export class HlsMuxer extends Muxer {
const release = await playlist.mutex.acquire(); const release = await playlist.mutex.acquire();
try { try {
const timestamp = this.validateAndNormalizeTimestamp(track, packet.timestamp, packet.type === 'key'); this.validateTimestamp(track, packet.timestamp, packet.type === 'key');
const adjustedPacket = packet.clone({ timestamp }); trackData.packets.push(packet);
trackData.packets.push(adjustedPacket);
if (playlist.currentSegmentStartTimestamp === null) { if (playlist.currentSegmentStartTimestamp === null) {
playlist.currentSegmentStartTimestamp = adjustedPacket.timestamp; playlist.currentSegmentStartTimestamp = packet.timestamp;
} else if (!playlist.currentSegmentStartTimestampIsFixed) { } else if (!playlist.currentSegmentStartTimestampIsFixed) {
playlist.currentSegmentStartTimestamp = Math.min( playlist.currentSegmentStartTimestamp = Math.min(
playlist.currentSegmentStartTimestamp, playlist.currentSegmentStartTimestamp,
adjustedPacket.timestamp, packet.timestamp,
); );
} }
@@ -645,17 +643,15 @@ export class HlsMuxer extends Muxer {
const release = await playlist.mutex.acquire(); const release = await playlist.mutex.acquire();
try { try {
const timestamp = this.validateAndNormalizeTimestamp(track, packet.timestamp, packet.type === 'key'); this.validateTimestamp(track, packet.timestamp, packet.type === 'key');
const adjustedPacket = packet.clone({ timestamp }); trackData.packets.push(packet);
trackData.packets.push(adjustedPacket);
if (playlist.currentSegmentStartTimestamp === null) { if (playlist.currentSegmentStartTimestamp === null) {
playlist.currentSegmentStartTimestamp = adjustedPacket.timestamp; playlist.currentSegmentStartTimestamp = packet.timestamp;
} else if (!playlist.currentSegmentStartTimestampIsFixed) { } else if (!playlist.currentSegmentStartTimestampIsFixed) {
playlist.currentSegmentStartTimestamp = Math.min( playlist.currentSegmentStartTimestamp = Math.min(
playlist.currentSegmentStartTimestamp, playlist.currentSegmentStartTimestamp,
adjustedPacket.timestamp, packet.timestamp,
); );
} }
+6 -4
View File
@@ -600,7 +600,7 @@ export class IsobmffMuxer extends Muxer {
packetData = concatNalUnitsInLengthPrefixed(nalUnits, 4); packetData = concatNalUnitsInLengthPrefixed(nalUnits, 4);
} }
const timestamp = this.validateAndNormalizeTimestamp( this.validateTimestamp(
trackData.track, trackData.track,
packet.timestamp, packet.timestamp,
packet.type === 'key', packet.type === 'key',
@@ -608,7 +608,7 @@ export class IsobmffMuxer extends Muxer {
const internalSample = this.createSampleForTrack( const internalSample = this.createSampleForTrack(
trackData, trackData,
packetData, packetData,
timestamp, packet.timestamp,
packet.duration, packet.duration,
packet.type, packet.type,
); );
@@ -638,11 +638,13 @@ export class IsobmffMuxer extends Muxer {
packetData = packetData.subarray(headerLength); packetData = packetData.subarray(headerLength);
} }
let timestamp = this.validateAndNormalizeTimestamp( this.validateTimestamp(
trackData.track, trackData.track,
packet.timestamp, packet.timestamp,
packet.type === 'key', packet.type === 'key',
); );
let timestamp = packet.timestamp;
let duration = packet.duration; let duration = packet.duration;
if (trackData.info.requiresPcmTransformation) { if (trackData.info.requiresPcmTransformation) {
@@ -718,7 +720,7 @@ export class IsobmffMuxer extends Muxer {
try { try {
const trackData = this.getSubtitleTrackData(track, meta); const trackData = this.getSubtitleTrackData(track, meta);
this.validateAndNormalizeTimestamp(trackData.track, cue.timestamp, true); this.validateTimestamp(trackData.track, cue.timestamp, true);
if (track.source._codec === 'webvtt') { if (track.source._codec === 'webvtt') {
trackData.cueQueue.push(cue); trackData.cueQueue.push(cue);
+8 -6
View File
@@ -907,7 +907,9 @@ export class MatroskaMuxer extends Muxer {
const trackData = this.getVideoTrackData(track, packet, meta); const trackData = this.getVideoTrackData(track, packet, meta);
const isKeyFrame = packet.type === 'key'; const isKeyFrame = packet.type === 'key';
let timestamp = this.validateAndNormalizeTimestamp(trackData.track, packet.timestamp, isKeyFrame); this.validateTimestamp(trackData.track, packet.timestamp, isKeyFrame);
let timestamp = packet.timestamp;
let duration = packet.duration; let duration = packet.duration;
if (track.metadata.frameRate !== undefined) { if (track.metadata.frameRate !== undefined) {
@@ -950,9 +952,9 @@ export class MatroskaMuxer extends Muxer {
} }
const isKeyFrame = packet.type === 'key'; const isKeyFrame = packet.type === 'key';
const timestamp = this.validateAndNormalizeTimestamp(trackData.track, packet.timestamp, isKeyFrame); this.validateTimestamp(trackData.track, packet.timestamp, isKeyFrame);
const audioChunk = this.createInternalChunk(packetData, timestamp, packet.duration, packet.type);
const audioChunk = this.createInternalChunk(packetData, packet.timestamp, packet.duration, packet.type);
trackData.chunkQueue.push(audioChunk); trackData.chunkQueue.push(audioChunk);
await this.interleaveChunks(); await this.interleaveChunks();
} finally { } finally {
@@ -966,10 +968,10 @@ export class MatroskaMuxer extends Muxer {
try { try {
const trackData = this.getSubtitleTrackData(track, meta); const trackData = this.getSubtitleTrackData(track, meta);
const timestamp = this.validateAndNormalizeTimestamp(trackData.track, cue.timestamp, true); this.validateTimestamp(trackData.track, cue.timestamp, true);
let bodyText = cue.text; let bodyText = cue.text;
const timestampMs = Math.round(timestamp * 1000); const timestampMs = Math.round(cue.timestamp * 1000);
// Replace in-body timestamps so that they're relative to the cue start time // Replace in-body timestamps so that they're relative to the cue start time
inlineTimestampRegex.lastIndex = 0; inlineTimestampRegex.lastIndex = 0;
@@ -985,7 +987,7 @@ export class MatroskaMuxer extends Muxer {
const subtitleChunk = this.createInternalChunk( const subtitleChunk = this.createInternalChunk(
body, body,
timestamp, cue.timestamp,
cue.duration, cue.duration,
'key', 'key',
additions.trim() ? textEncoder.encode(additions) : null, additions.trim() ? textEncoder.encode(additions) : null,
+8 -17
View File
@@ -81,11 +81,6 @@ export abstract class MediaSource {
_closingPromise: Promise<void> | null = null; _closingPromise: Promise<void> | null = null;
/** @internal */ /** @internal */
_closed = false; _closed = false;
/**
* @internal
* A time offset in seconds that is added to all timestamps generated by this source.
*/
_timestampOffset = 0;
/** @internal */ /** @internal */
_ensureValidAdd() { _ensureValidAdd() {
@@ -1476,7 +1471,7 @@ export class MediaStreamVideoTrackSource extends VideoSource {
let frameCount = 0; let frameCount = 0;
let errored = false; let errored = false;
let lastSampleTimestamp: number | null = null; let lastSampleTimestamp: number | null = null;
let pauseOffset = 0; let timestampOffset = 0;
const tick = () => { const tick = () => {
assert(frameRate !== null); assert(frameRate !== null);
@@ -1544,9 +1539,7 @@ export class MediaStreamVideoTrackSource extends VideoSource {
// pause. Doing it like this instead of simply keeping track of the paused time is better since // pause. Doing it like this instead of simply keeping track of the paused time is better since
// it retains the frame rate of the underlying source. // it retains the frame rate of the underlying source.
const timeDelta = currentTimestamp - lastSampleTimestamp; const timeDelta = currentTimestamp - lastSampleTimestamp;
// We modify this field instead of _timestampOffset since we still might have data in flight timestampOffset -= timeDelta;
// in the encoder, with which we don't want to mess.
pauseOffset -= timeDelta;
} }
lastSampleTimestamp = currentTimestamp; lastSampleTimestamp = currentTimestamp;
} }
@@ -1574,7 +1567,7 @@ export class MediaStreamVideoTrackSource extends VideoSource {
} }
} }
this._timestampOffset = target - firstVideoFrameTimestamp; timestampOffset = target - firstVideoFrameTimestamp;
} }
lastSampleTimestamp = currentTimestamp; lastSampleTimestamp = currentTimestamp;
@@ -1586,7 +1579,7 @@ export class MediaStreamVideoTrackSource extends VideoSource {
} }
const sample = new VideoSample(videoFrame, { const sample = new VideoSample(videoFrame, {
timestamp: currentTimestamp + pauseOffset, timestamp: currentTimestamp + timestampOffset,
}); });
void this._encoder.add(sample, true) void this._encoder.add(sample, true)
@@ -2575,7 +2568,7 @@ export class MediaStreamAudioTrackSource extends AudioSource {
let firstAudioDataTimestamp: number | null = null; let firstAudioDataTimestamp: number | null = null;
let errored = false; let errored = false;
let lastSampleTimestamp: number | null = null; let lastSampleTimestamp: number | null = null;
let pauseOffset = 0; let timestampOffset = 0;
const onAudioSample = (audioSample: AudioSample) => { const onAudioSample = (audioSample: AudioSample) => {
if (errored) { if (errored) {
@@ -2593,9 +2586,7 @@ export class MediaStreamAudioTrackSource extends AudioSource {
// the pause. Doing it like this instead of simply keeping track of the paused time is better // the pause. Doing it like this instead of simply keeping track of the paused time is better
// since it retains the sample rate of the underlying source. // since it retains the sample rate of the underlying source.
const timeDelta = currentTimestamp - lastSampleTimestamp; const timeDelta = currentTimestamp - lastSampleTimestamp;
// We modify this field instead of _timestampOffset since we still might have data in flight timestampOffset -= timeDelta;
// in the encoder, with which we don't want to mess.
pauseOffset -= timeDelta;
} }
lastSampleTimestamp = currentTimestamp; lastSampleTimestamp = currentTimestamp;
} }
@@ -2623,7 +2614,7 @@ export class MediaStreamAudioTrackSource extends AudioSource {
} }
} }
this._timestampOffset = target - firstAudioDataTimestamp; timestampOffset = target - firstAudioDataTimestamp;
} }
lastSampleTimestamp = currentTimestamp; lastSampleTimestamp = currentTimestamp;
@@ -2634,7 +2625,7 @@ export class MediaStreamAudioTrackSource extends AudioSource {
return; return;
} }
audioSample.setTimestamp(currentTimestamp + pauseOffset); audioSample.setTimestamp(currentTimestamp + timestampOffset);
void this._encoder.add(audioSample, true) void this._encoder.add(audioSample, true)
.catch((error) => { .catch((error) => {
+1 -1
View File
@@ -110,7 +110,7 @@ export class Mp3Muxer extends Muxer {
this.frameCount++; this.frameCount++;
} }
this.validateAndNormalizeTimestamp(track, packet.timestamp, packet.type === 'key'); this.validateTimestamp(track, packet.timestamp, packet.type === 'key');
if (writeXingHeader) { if (writeXingHeader) {
this.framePositions.push(this.writer.getPos()); this.framePositions.push(this.writer.getPos());
+4 -4
View File
@@ -227,7 +227,7 @@ export class MpegTsMuxer extends Muxer {
try { try {
const trackData = this.getVideoTrackData(track, meta); const trackData = this.getVideoTrackData(track, meta);
const timestamp = this.validateAndNormalizeTimestamp( this.validateTimestamp(
trackData.track, trackData.track,
packet.timestamp, packet.timestamp,
packet.type === 'key', packet.type === 'key',
@@ -241,7 +241,7 @@ export class MpegTsMuxer extends Muxer {
trackData.timestampProcessingQueue.push({ trackData.timestampProcessingQueue.push({
data: preparedData, data: preparedData,
presentationTimestamp: timestamp, presentationTimestamp: packet.timestamp,
decodeTimestamp: null, decodeTimestamp: null,
isKeyframe: packet.type === 'key', isKeyframe: packet.type === 'key',
}); });
@@ -260,7 +260,7 @@ export class MpegTsMuxer extends Muxer {
try { try {
const trackData = this.getAudioTrackData(track, meta); const trackData = this.getAudioTrackData(track, meta);
const timestamp = this.validateAndNormalizeTimestamp( this.validateTimestamp(
trackData.track, trackData.track,
packet.timestamp, packet.timestamp,
packet.type === 'key', packet.type === 'key',
@@ -274,7 +274,7 @@ export class MpegTsMuxer extends Muxer {
trackData.timestampProcessingQueue.push({ trackData.timestampProcessingQueue.push({
data: preparedData, data: preparedData,
presentationTimestamp: timestamp, presentationTimestamp: packet.timestamp,
decodeTimestamp: null, decodeTimestamp: null,
isKeyframe: packet.type === 'key', isKeyframe: packet.type === 'key',
}); });
+1 -5
View File
@@ -42,9 +42,7 @@ export abstract class Muxer {
maxTimestampBeforeLastKeyPacket: number | null; maxTimestampBeforeLastKeyPacket: number | null;
}>(); }>();
protected validateAndNormalizeTimestamp(track: OutputTrack, timestampInSeconds: number, isKeyPacket: boolean) { protected validateTimestamp(track: OutputTrack, timestampInSeconds: number, isKeyPacket: boolean) {
timestampInSeconds += track.source._timestampOffset;
if (timestampInSeconds < 0) { if (timestampInSeconds < 0) {
throw new Error(`Timestamps must be non-negative (got ${timestampInSeconds}s).`); throw new Error(`Timestamps must be non-negative (got ${timestampInSeconds}s).`);
} }
@@ -78,7 +76,5 @@ export abstract class Muxer {
timestampInfo.maxTimestamp = Math.max(timestampInfo.maxTimestamp, timestampInSeconds); timestampInfo.maxTimestamp = Math.max(timestampInfo.maxTimestamp, timestampInSeconds);
} }
return timestampInSeconds;
} }
} }
+1 -1
View File
@@ -267,7 +267,7 @@ export class OggMuxer extends Muxer {
try { try {
const trackData = this.getTrackData(track, meta); const trackData = this.getTrackData(track, meta);
this.validateAndNormalizeTimestamp(trackData.track, packet.timestamp, packet.type === 'key'); this.validateTimestamp(trackData.track, packet.timestamp, packet.type === 'key');
const currentTimestampInSamples = trackData.currentTimestampInSamples; const currentTimestampInSamples = trackData.currentTimestampInSamples;
+1 -1
View File
@@ -79,7 +79,7 @@ export class WaveMuxer extends Muxer {
this.headerWritten = true; this.headerWritten = true;
} }
this.validateAndNormalizeTimestamp(track, packet.timestamp, packet.type === 'key'); this.validateTimestamp(track, packet.timestamp, packet.type === 'key');
if (!this.isRf64 && this.writer.getPos() + packet.data.byteLength >= 2 ** 32) { if (!this.isRf64 && this.writer.getPos() + packet.data.byteLength >= 2 ** 32) {
throw new Error( throw new Error(