Merge main into release for tag v1.34.5

This commit is contained in:
github-actions[bot]
2026-02-24 12:15:08 +00:00
8 changed files with 172 additions and 16 deletions
+7 -7
View File
@@ -1,12 +1,12 @@
{ {
"name": "mediabunny", "name": "mediabunny",
"version": "1.34.4", "version": "1.34.5",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "mediabunny", "name": "mediabunny",
"version": "1.34.4", "version": "1.34.5",
"license": "MPL-2.0", "license": "MPL-2.0",
"workspaces": [ "workspaces": [
"packages/*" "packages/*"
@@ -7743,9 +7743,9 @@
} }
}, },
"node_modules/mediabunny": { "node_modules/mediabunny": {
"version": "1.34.3", "version": "1.34.4",
"resolved": "https://registry.npmjs.org/mediabunny/-/mediabunny-1.34.3.tgz", "resolved": "https://registry.npmjs.org/mediabunny/-/mediabunny-1.34.4.tgz",
"integrity": "sha512-Jb0x3jyvadGVpo8aZSn53NZduPbPdbx9foQsAGOmKBz7Hai9B38Kf+7eXbxXFcXoXxcTyNaZeDhoA0agoBQldA==", "integrity": "sha512-f1B95A60YoCsZQO/JQYxPDorybEz2Sjasf4RrpwGSMmJW6JVyhI/iJDri9LF6kk5WwUovF8oiTvRNM6xGjWo5w==",
"license": "MPL-2.0", "license": "MPL-2.0",
"peer": true, "peer": true,
"workspaces": [ "workspaces": [
@@ -12069,7 +12069,7 @@
}, },
"packages/ac3": { "packages/ac3": {
"name": "@mediabunny/ac3", "name": "@mediabunny/ac3",
"version": "1.34.4", "version": "1.34.5",
"license": "MPL-2.0", "license": "MPL-2.0",
"devDependencies": { "devDependencies": {
"@types/emscripten": "^1.40.1" "@types/emscripten": "^1.40.1"
@@ -12084,7 +12084,7 @@
}, },
"packages/mp3-encoder": { "packages/mp3-encoder": {
"name": "@mediabunny/mp3-encoder", "name": "@mediabunny/mp3-encoder",
"version": "1.34.4", "version": "1.34.5",
"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.34.4", "version": "1.34.5",
"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": [
+4 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "@mediabunny/ac3", "name": "@mediabunny/ac3",
"author": "Vanilagy", "author": "Vanilagy",
"version": "1.34.4", "version": "1.34.5",
"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",
@@ -18,6 +18,9 @@
"dist", "dist",
"src" "src"
], ],
"browser": {
"worker_threads": false
},
"sideEffects": false, "sideEffects": false,
"license": "MPL-2.0", "license": "MPL-2.0",
"repository": { "repository": {
+4 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "@mediabunny/mp3-encoder", "name": "@mediabunny/mp3-encoder",
"author": "Vanilagy", "author": "Vanilagy",
"version": "1.34.4", "version": "1.34.5",
"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",
@@ -18,6 +18,9 @@
"dist", "dist",
"src" "src"
], ],
"browser": {
"worker_threads": false
},
"sideEffects": false, "sideEffects": false,
"license": "MPL-2.0", "license": "MPL-2.0",
"repository": { "repository": {
+5 -3
View File
@@ -39,11 +39,10 @@ export default async function inlineWorker(scriptText) {
// Node, Bun (Bun's Worker is flaky, worker_threads works much better) // Node, Bun (Bun's Worker is flaky, worker_threads works much better)
let Worker; let Worker;
const workerModule = 'node:worker_threads';
try { try {
Worker = (await import(workerModule)).Worker; Worker = (await import('worker_threads')).Worker;
} catch { } catch {
Worker = require(workerModule).Worker; Worker = require('worker_threads').Worker;
} }
const worker = new Worker(scriptText, { eval: true }); const worker = new Worker(scriptText, { eval: true });
@@ -56,6 +55,9 @@ export default async function inlineWorker(scriptText) {
build.onResolve({ filter: /^__inline-worker$/ }, ({ path }) => { build.onResolve({ filter: /^__inline-worker$/ }, ({ path }) => {
return { path, namespace: 'inline-worker' }; return { path, namespace: 'inline-worker' };
}); });
build.onResolve({ filter: /^worker_threads$/ }, ({ path }) => {
return { path, external: true }; // Keep it in the bundle
});
build.onLoad({ filter: /.*/, namespace: 'inline-worker' }, () => { build.onLoad({ filter: /.*/, namespace: 'inline-worker' }, () => {
return { contents: inlineWorkerFunctionCode, loader: 'js' }; return { contents: inlineWorkerFunctionCode, loader: 'js' };
}); });
+4 -1
View File
@@ -111,5 +111,8 @@ export class AdtsMuxer extends Muxer {
throw new Error('ADTS does not support subtitles.'); throw new Error('ADTS does not support subtitles.');
} }
async finalize() {} async finalize() {
const release = await this.mutex.acquire(); // Required so that finalize() can't resolve before other calls
release();
}
} }
+17 -2
View File
@@ -215,6 +215,7 @@ export class StreamTargetWriter extends Writer {
private lastWriteEnd = 0; private lastWriteEnd = 0;
private lastFlushEnd = 0; private lastFlushEnd = 0;
private writer: WritableStreamDefaultWriter<StreamTargetChunk> | null = null; private writer: WritableStreamDefaultWriter<StreamTargetChunk> | null = null;
private writeError: unknown = null;
// These variables regard chunked mode: // These variables regard chunked mode:
private chunked: boolean; private chunked: boolean;
@@ -267,6 +268,11 @@ export class StreamTargetWriter extends Writer {
} }
async flush() { async flush() {
if (this.writeError !== null) {
// eslint-disable-next-line @typescript-eslint/only-throw-error
throw this.writeError;
}
if (this.pos > this.lastWriteEnd) { if (this.pos > this.lastWriteEnd) {
// There's a "void" between the last written byte and the next byte we're about to write. Let's pad that // There's a "void" between the last written byte and the next byte we're about to write. Let's pad that
// void with zeroes explicitly. // void with zeroes explicitly.
@@ -329,11 +335,12 @@ export class StreamTargetWriter extends Writer {
throw new Error('Internal error: Monotonicity violation.'); throw new Error('Internal error: Monotonicity violation.');
} }
// Write out the data immediately
void this.writer.write({ void this.writer.write({
type: 'write', type: 'write',
data: chunk.data, data: chunk.data,
position: chunk.start, position: chunk.start,
}).catch((error) => {
this.writeError ??= error;
}); });
this.lastFlushEnd = chunk.start + chunk.data.byteLength; this.lastFlushEnd = chunk.start + chunk.data.byteLength;
@@ -440,6 +447,8 @@ export class StreamTargetWriter extends Writer {
type: 'write', type: 'write',
data: chunk.data.subarray(section.start, section.end), data: chunk.data.subarray(section.start, section.end),
position, position,
}).catch((error) => {
this.writeError ??= error;
}); });
this.lastFlushEnd = chunk.start + section.end; this.lastFlushEnd = chunk.start + section.end;
@@ -449,12 +458,18 @@ export class StreamTargetWriter extends Writer {
} }
} }
finalize() { async finalize() {
if (this.chunked) { if (this.chunked) {
this.tryToFlushChunks(true); this.tryToFlushChunks(true);
} }
if (this.writeError !== null) {
// eslint-disable-next-line @typescript-eslint/only-throw-error
throw this.writeError;
}
assert(this.writer); assert(this.writer);
await this.writer.ready;
return this.writer.close(); return this.writer.close();
} }
+130
View File
@@ -0,0 +1,130 @@
import { expect, test } from 'vitest';
import { Input } from '../../src/input.js';
import { BufferSource, UrlSource } from '../../src/source.js';
import { ALL_FORMATS } from '../../src/input-format.js';
import { EncodedPacketSink } from '../../src/media-sink.js';
import { EncodedAudioPacketSource } from '../../src/media-source.js';
import { Output } from '../../src/output.js';
import { StreamTarget, type StreamTargetChunk } from '../../src/target.js';
import { AdtsOutputFormat } from '../../src/output-format.js';
import { assert } from '../../src/misc.js';
const createBufferingStreamTarget = () => {
const written = new Map<number, Uint8Array>();
const stream = new WritableStream<StreamTargetChunk>({
async write(chunk: StreamTargetChunk) {
written.set(chunk.position, chunk.data.slice());
},
});
const toBuffer = () => {
let maxEnd = 0;
for (const [offset, data] of written) {
maxEnd = Math.max(maxEnd, offset + data.byteLength);
}
const buffer = new Uint8Array(maxEnd);
for (const [offset, data] of written) {
buffer.set(data, offset);
}
return buffer;
};
return { stream, toBuffer };
};
test('ADTS with metadata over StreamTarget', async () => {
const target = createBufferingStreamTarget();
const output = new Output({
format: new AdtsOutputFormat(),
target: new StreamTarget(target.stream),
});
output.setMetadataTags({ comment: 'Remotion' });
const audioSource = new EncodedAudioPacketSource('aac');
output.addAudioTrack(audioSource);
await output.start();
using input = new Input({
source: new UrlSource('/sample3.aac'),
formats: ALL_FORMATS,
});
const audioTrack = await input.getPrimaryAudioTrack();
assert(audioTrack);
const sink = new EncodedPacketSink(audioTrack);
let isFirst = true;
for await (const packet of sink.packets()) {
await audioSource.add(packet, {
decoderConfig: isFirst ? (await audioTrack.getDecoderConfig())! : undefined,
});
isFirst = false;
}
await output.finalize();
const buffer = target.toBuffer();
using outputAsInput = new Input({
source: new BufferSource(buffer.buffer),
formats: ALL_FORMATS,
});
const readTags = await outputAsInput.getMetadataTags();
expect(readTags.comment).toBe('Remotion');
const outputAudioTrack = await outputAsInput.getPrimaryAudioTrack();
assert(outputAudioTrack);
expect(outputAudioTrack.codec).toBe('aac');
});
// Previously, write handler rejections were silently swallowed and surfaced as
// "Cannot write to a closing writable stream" instead of the actual error.
test('StreamTarget write errors surface directly', async () => {
let writeCount = 0;
const stream = new WritableStream<StreamTargetChunk>({
async write() {
writeCount++;
if (writeCount === 2) {
throw new Error('OPFS write failed');
}
},
});
const output = new Output({
format: new AdtsOutputFormat(),
target: new StreamTarget(stream),
});
const audioSource = new EncodedAudioPacketSource('aac');
output.addAudioTrack(audioSource);
await output.start();
using input = new Input({
source: new UrlSource('/sample3.aac'),
formats: ALL_FORMATS,
});
const audioTrack = await input.getPrimaryAudioTrack();
assert(audioTrack);
const sink = new EncodedPacketSink(audioTrack);
const run = async () => {
let isFirst = true;
for await (const packet of sink.packets()) {
await audioSource.add(packet, {
decoderConfig: isFirst ? (await audioTrack.getDecoderConfig())! : undefined,
});
isFirst = false;
}
await output.finalize();
};
await expect(run()).rejects.toThrow('OPFS write failed');
});