mirror of
https://github.com/arcodange-org/mediabunny.git
synced 2026-10-02 05:13:50 +02:00
Merge main into release for tag v1.34.5
This commit is contained in:
Generated
+7
-7
@@ -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
@@ -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": [
|
||||||
|
|||||||
@@ -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": {
|
||||||
|
|||||||
@@ -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": {
|
||||||
|
|||||||
@@ -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' };
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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
@@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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');
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user