Merge main into release for tag v1.50.9

This commit is contained in:
github-actions[bot]
2026-07-18 21:55:04 +00:00
27 changed files with 344 additions and 48 deletions
@@ -9,8 +9,15 @@ import {
Conversion,
QUALITY_VERY_LOW,
} from 'mediabunny';
import { registerAc3Decoder } from '@mediabunny/ac3';
import { registerProresDecoder } from '@mediabunny/prores';
import SampleFileUrl from '../../docs/assets/big-buck-bunny-trimmed.mp4';
// Enable codecs that aren't natively supported by WebCodecs.
registerAc3Decoder();
registerProresDecoder();
(document.querySelector('#sample-file-download') as HTMLAnchorElement).href = SampleFileUrl;
const selectMediaButton = document.querySelector('#select-file') as HTMLButtonElement;
@@ -137,7 +144,7 @@ const compressFile = async (resource: File | string) => {
selectMediaButton.addEventListener('click', () => {
const fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.accept = 'video/*,video/x-matroska,video/mp2t,.ts,audio/*,audio/aac';
fileInput.accept = 'video/*,video/x-matroska,video/mp2t,.mkv,.ts,audio/*,audio/aac,.aac';
fileInput.addEventListener('change', () => {
const file = fileInput.files?.[0];
if (!file) {
+8 -1
View File
@@ -15,8 +15,15 @@ import {
QUALITY_LOW,
QUALITY_VERY_LOW,
} from 'mediabunny';
import { registerAc3Decoder } from '@mediabunny/ac3';
import { registerProresDecoder } from '@mediabunny/prores';
import SampleFileUrl from '../../docs/assets/big-buck-bunny-trimmed.mp4';
// Enable codecs that aren't natively supported by WebCodecs.
registerAc3Decoder();
registerProresDecoder();
(document.querySelector('#sample-file-download') as HTMLAnchorElement).href = SampleFileUrl;
declare global {
@@ -233,7 +240,7 @@ selectDirectoryButton.addEventListener('click', async () => {
selectMediaButton.addEventListener('click', () => {
const fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.accept = 'video/*,video/x-matroska,video/mp2t,.ts';
fileInput.accept = 'video/*,video/x-matroska,video/mp2t,.mkv,.ts';
fileInput.addEventListener('change', () => {
const file = fileInput.files![0];
if (file) {
+8 -1
View File
@@ -8,8 +8,15 @@ import {
WrappedAudioBuffer,
WrappedCanvas,
} from 'mediabunny';
import { registerAc3Decoder } from '@mediabunny/ac3';
import { registerProresDecoder } from '@mediabunny/prores';
import SampleFileUrl from '../../docs/assets/big-buck-bunny-trimmed.mp4';
// Enable codecs that aren't natively supported by WebCodecs.
registerAc3Decoder();
registerProresDecoder();
(document.querySelector('#sample-file-download') as HTMLAnchorElement).href = SampleFileUrl;
const selectMediaButton = document.querySelector('#select-file') as HTMLButtonElement;
@@ -732,7 +739,7 @@ window.addEventListener('resize', () => {
selectMediaButton.addEventListener('click', () => {
const fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.accept = 'video/*,video/x-matroska,video/mp2t,.ts,audio/*,audio/aac';
fileInput.accept = 'video/*,video/x-matroska,video/mp2t,.mkv,.ts,audio/*,audio/aac,.aac';
fileInput.addEventListener('change', () => {
const file = fileInput.files?.[0];
if (!file) {
@@ -1,6 +1,13 @@
import { Input, ALL_FORMATS, BlobSource, UrlSource, CanvasSink } from 'mediabunny';
import { registerAc3Decoder } from '@mediabunny/ac3';
import { registerProresDecoder } from '@mediabunny/prores';
import SampleFileUrl from '../../docs/assets/big-buck-bunny-trimmed.mp4';
// Enable codecs that aren't natively supported by WebCodecs.
registerAc3Decoder();
registerProresDecoder();
(document.querySelector('#sample-file-download') as HTMLAnchorElement).href = SampleFileUrl;
const selectMediaButton = document.querySelector('#select-file') as HTMLButtonElement;
@@ -122,7 +129,7 @@ const generateThumbnails = async (resource: File | string) => {
selectMediaButton.addEventListener('click', () => {
const fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.accept = 'video/*,video/x-matroska,video/mp2t,.ts,audio/*,audio/aac';
fileInput.accept = 'video/*,video/x-matroska,video/mp2t,.mkv,.ts,audio/*,audio/aac,.aac';
fileInput.addEventListener('change', () => {
const file = fileInput.files?.[0];
if (!file) {
+69 -9
View File
@@ -1,12 +1,12 @@
{
"name": "mediabunny",
"version": "1.50.8",
"version": "1.50.9",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "mediabunny",
"version": "1.50.8",
"version": "1.50.9",
"license": "MPL-2.0",
"workspaces": [
".",
@@ -2715,6 +2715,66 @@
"node": ">=14.0.0"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": {
"version": "1.4.3",
"dev": true,
"inBundle": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@emnapi/wasi-threads": "1.0.2",
"tslib": "^2.4.0"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": {
"version": "1.4.3",
"dev": true,
"inBundle": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": {
"version": "1.0.2",
"dev": true,
"inBundle": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": {
"version": "0.2.9",
"dev": true,
"inBundle": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@emnapi/core": "^1.4.0",
"@emnapi/runtime": "^1.4.0",
"@tybys/wasm-util": "^0.9.0"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": {
"version": "0.9.0",
"dev": true,
"inBundle": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": {
"version": "2.8.0",
"dev": true,
"inBundle": true,
"license": "0BSD",
"optional": true
},
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
"version": "4.1.7",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.7.tgz",
@@ -12894,7 +12954,7 @@
},
"packages/aac-encoder": {
"name": "@mediabunny/aac-encoder",
"version": "1.50.8",
"version": "1.50.9",
"license": "MPL-2.0",
"devDependencies": {
"@types/emscripten": "^1.40.1"
@@ -12909,7 +12969,7 @@
},
"packages/ac3": {
"name": "@mediabunny/ac3",
"version": "1.50.8",
"version": "1.50.9",
"license": "MPL-2.0",
"devDependencies": {
"@types/emscripten": "^1.40.1"
@@ -12924,7 +12984,7 @@
},
"packages/flac-encoder": {
"name": "@mediabunny/flac-encoder",
"version": "1.50.8",
"version": "1.50.9",
"license": "MPL-2.0",
"devDependencies": {
"@types/emscripten": "^1.40.1"
@@ -12939,7 +12999,7 @@
},
"packages/mp3-encoder": {
"name": "@mediabunny/mp3-encoder",
"version": "1.50.8",
"version": "1.50.9",
"license": "MPL-2.0",
"devDependencies": {
"@types/emscripten": "^1.40.1"
@@ -12954,7 +13014,7 @@
},
"packages/prores": {
"name": "@mediabunny/prores",
"version": "1.50.8",
"version": "1.50.9",
"license": "MPL-2.0",
"dependencies": {
"turbores": "^1.2.2"
@@ -12969,10 +13029,10 @@
},
"packages/server": {
"name": "@mediabunny/server",
"version": "1.50.8",
"version": "1.50.9",
"license": "MPL-2.0",
"dependencies": {
"@mediabunny/prores": "^1.50.8",
"@mediabunny/prores": "^1.50.9",
"node-av": "^6.0.0"
},
"funding": {
+5 -5
View File
@@ -1,7 +1,7 @@
{
"name": "mediabunny",
"author": "Vanilagy",
"version": "1.50.8",
"version": "1.50.9",
"description": "Pure TypeScript media toolkit for reading, writing, and converting media files, directly in the browser.",
"type": "module",
"workspaces": [
@@ -56,10 +56,10 @@
"examples:build": "vite build",
"fix-build-import-paths": "tsx scripts/add-import-extensions.ts",
"append-namespace": "echo 'export as namespace Mediabunny;' >> dist/mediabunny.d.ts",
"bump-patch": "npm version patch --no-git-tag-version --workspaces && tsx scripts/sync-workspace-deps.ts && npm i",
"bump-minor": "npm version minor --no-git-tag-version --workspaces && tsx scripts/sync-workspace-deps.ts && npm i",
"bump-major": "npm version major --no-git-tag-version --workspaces && tsx scripts/sync-workspace-deps.ts && npm i",
"set-version": "npm version --no-git-tag-version --workspaces && tsx scripts/sync-workspace-deps.ts && npm i"
"bump-patch": "npm version patch --no-git-tag-version --workspaces && tsx scripts/sync-workspace-deps.ts && npm i --package-lock-only",
"bump-minor": "npm version minor --no-git-tag-version --workspaces && tsx scripts/sync-workspace-deps.ts && npm i --package-lock-only",
"bump-major": "npm version major --no-git-tag-version --workspaces && tsx scripts/sync-workspace-deps.ts && npm i --package-lock-only",
"set-version": "npm version --no-git-tag-version --workspaces && tsx scripts/sync-workspace-deps.ts && npm i --package-lock-only"
},
"license": "MPL-2.0",
"repository": {
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@mediabunny/aac-encoder",
"author": "Vanilagy",
"version": "1.50.8",
"version": "1.50.9",
"description": "AAC encoder extension for Mediabunny, based on FFmpeg.",
"main": "./dist/bundles/mediabunny-aac-encoder.mjs",
"module": "./dist/bundles/mediabunny-aac-encoder.mjs",
@@ -192,3 +192,7 @@ if (parentPort) {
} else {
self.addEventListener('message', event => onMessage(event.data as { id: number; command: WorkerCommand }));
}
// Prevents the worker for being randomly closed by Firefox
// https://github.com/Vanilagy/mediabunny/issues/435
setInterval(() => {}, 1000);
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@mediabunny/ac3",
"author": "Vanilagy",
"version": "1.50.8",
"version": "1.50.9",
"description": "AC-3 and E-AC-3 (Dolby Digital) decoder and encoder extension for Mediabunny, based on FFmpeg.",
"main": "./dist/bundles/mediabunny-ac3.mjs",
"module": "./dist/bundles/mediabunny-ac3.mjs",
+4
View File
@@ -304,3 +304,7 @@ if (parentPort) {
} else {
self.addEventListener('message', event => onMessage(event.data as { id: number; command: WorkerCommand }));
}
// Prevents the worker for being randomly closed by Firefox
// https://github.com/Vanilagy/mediabunny/issues/435
setInterval(() => {}, 1000);
+1
View File
@@ -46,6 +46,7 @@ export const unrefWorker = async () => {
if (worker) {
if (worker.unref) {
worker.unref(); // If we don't do this, then the Node process never terminates by itself
// Keep the worker around tho
} else if (typeof window === 'undefined') {
// Non-browser environment without unref - terminate instead
worker.terminate();
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@mediabunny/flac-encoder",
"author": "Vanilagy",
"version": "1.50.8",
"version": "1.50.9",
"description": "FLAC encoder extension for Mediabunny, based on libFLAC.",
"main": "./dist/bundles/mediabunny-flac-encoder.mjs",
"module": "./dist/bundles/mediabunny-flac-encoder.mjs",
@@ -192,3 +192,7 @@ if (parentPort) {
} else {
self.addEventListener('message', event => onMessage(event.data as { id: number; command: WorkerCommand }));
}
// Prevents the worker for being randomly closed by Firefox
// https://github.com/Vanilagy/mediabunny/issues/435
setInterval(() => {}, 1000);
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@mediabunny/mp3-encoder",
"author": "Vanilagy",
"version": "1.50.8",
"version": "1.50.9",
"description": "MP3 encoder extension for Mediabunny, based on LAME.",
"main": "./dist/bundles/mediabunny-mp3-encoder.mjs",
"module": "./dist/bundles/mediabunny-mp3-encoder.mjs",
@@ -219,3 +219,7 @@ if (parentPort) {
} else {
self.addEventListener('message', event => onMessage(event.data as { id: number; command: WorkerCommand }));
}
// Prevents the worker for being randomly closed by Firefox
// https://github.com/Vanilagy/mediabunny/issues/435
setInterval(() => {}, 1000);
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@mediabunny/prores",
"author": "Vanilagy",
"version": "1.50.8",
"version": "1.50.9",
"description": "Apple ProRes decoder extension for Mediabunny, based on TurboRes.",
"main": "./dist/bundles/mediabunny-prores.mjs",
"module": "./dist/bundles/mediabunny-prores.mjs",
+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "@mediabunny/server",
"author": "Vanilagy",
"version": "1.50.8",
"version": "1.50.9",
"description": "Adds full video and audio decoder and encoder support to Mediabunny for use in server-side environments (Node, Bun, Deno). Based on NodeAV.",
"main": "./dist/bundles/mediabunny-server.cjs",
"module": "./dist/bundles/mediabunny-server.mjs",
@@ -35,7 +35,7 @@
},
"dependencies": {
"node-av": "^6.0.0",
"@mediabunny/prores": "^1.50.8"
"@mediabunny/prores": "^1.50.9"
},
"peerDependencies": {
"mediabunny": "^1.45.0"
+14 -1
View File
@@ -2207,9 +2207,22 @@ const OPUS_FRAME_DURATION_TABLE = [
export const parseOpusTocByte = (packet: Uint8Array) => {
const config = packet[0]! >> 3;
const code = packet[0]! & 0b11;
// A packet may pack more than one frame, in which case its duration is the frame duration times the number of
// frames it carries. See https://datatracker.ietf.org/doc/html/rfc6716, section 3.2.
let frameCount: number;
if (code === 0) {
frameCount = 1;
} else if (code === 1 || code === 2) {
frameCount = 2;
} else {
// Code 3: the frame count sits in the six low bits of the frame count byte
frameCount = packet[1]! & 0b111111;
}
return {
durationInSamples: OPUS_FRAME_DURATION_TABLE[config]!,
durationInSamples: OPUS_FRAME_DURATION_TABLE[config]! * frameCount,
};
};
+17 -14
View File
@@ -1321,7 +1321,7 @@ export class Conversion {
await tempOutput.start();
const sink = new VideoSampleSink(track);
const firstSample = await sink.getSample(firstTimestamp); // Let's just use the first sample
using firstSample = await sink.getSample(firstTimestamp); // Let's just use the first sample
if (firstSample) {
try {
@@ -1375,9 +1375,8 @@ export class Conversion {
const sink = new VideoSampleSink(track);
for await (const sample of sink.samples(this._startTimestamp, this._endTimestamp)) {
for await (using sample of sink.samples(this._startTimestamp, this._endTimestamp)) {
if (this._canceled) {
sample.close();
return;
}
@@ -1386,14 +1385,13 @@ export class Conversion {
this._reportProgress(outputTrackId, sample.timestamp + sample.duration);
await source.add(sample);
sample.close();
if (lastSampleTimestamp !== null) {
if (this._synchronizer.shouldWait(outputTrackId, lastSampleTimestamp)) {
await this._synchronizer.wait(lastSampleTimestamp);
}
}
sample.close();
}
source.close();
@@ -1595,9 +1593,8 @@ export class Conversion {
await this._started;
const sink = new AudioSampleSink(track);
for await (let sample of sink.samples(this._startTimestamp, this._endTimestamp)) {
for await (using sample of sink.samples(this._startTimestamp, this._endTimestamp)) {
if (this._canceled) {
sample.close();
return;
}
@@ -1612,7 +1609,7 @@ export class Conversion {
data.fill(2 ** 7); // Fill it with the silent value
}
const silentSample = new AudioSample({
using silentSample = new AudioSample({
data,
// Use the same format the decoder is spitting out. This avoids feeding changing sample
// formats to the audio encoder.
@@ -1636,22 +1633,28 @@ export class Conversion {
endFrame = Math.round((this._endTimestamp - sample.timestamp) * sample.sampleRate);
}
// Can't assign to "using" identifiers so we gotta do this
let finalSampleLet: AudioSample;
if (startFrame > 0 || endFrame < sample.numberOfFrames) {
// Trim the sample if it sticks out of the trim region on either end
const trimmedSample = sample.trim(startFrame, endFrame);
sample.close();
sample = trimmedSample;
finalSampleLet = trimmedSample;
if (sample.numberOfFrames === 0) {
sample.close();
if (trimmedSample.numberOfFrames === 0) {
trimmedSample.close();
continue;
}
} else {
finalSampleLet = sample;
}
// Offset the timestamp as needed
sample.setTimestamp(sample.timestamp - this._startTimestamp);
using finalSample = finalSampleLet;
await this._registerAudioSample(sample, source, outputTrackId, () => lastSampleTimestamp);
// Offset the timestamp as needed
finalSample.setTimestamp(finalSample.timestamp - this._startTimestamp);
await this._registerAudioSample(finalSample, source, outputTrackId, () => lastSampleTimestamp);
}
source.close();
+2
View File
@@ -206,6 +206,8 @@ export class IsobmffMuxer extends Muxer {
this.isCmaf = format instanceof CmafOutputFormat;
this.minimumFragmentDuration = format._options.minimumFragmentDuration
?? (format instanceof CmafOutputFormat ? Infinity : 1);
this.auxWriter.start();
}
async start() {
+4 -1
View File
@@ -1168,7 +1168,10 @@ export class MatroskaMuxer extends Muxer {
const msDuration = Math.round(1000 * chunk.duration);
if (!chunk.additions) {
// Subtitle cues need an explicit BlockDuration (a SimpleBlock has none)
const needsBlockGroup = !!chunk.additions || trackData.type === 'subtitle';
if (!needsBlockGroup) {
// No additions, we can write out a SimpleBlock
view.setUint8(3, Number(chunk.type === 'key') << 7); // Flags (keyframe flag only present for SimpleBlock)
+1 -1
View File
@@ -799,7 +799,7 @@ export class WavOutputFormat extends OutputFormat {
getSupportedCodecs(): MediaCodec[] {
return [
...PCM_AUDIO_CODECS.filter(codec =>
['pcm-s16', 'pcm-s24', 'pcm-s32', 'pcm-f32', 'pcm-u8', 'ulaw', 'alaw'].includes(codec),
['pcm-s16', 'pcm-s24', 'pcm-s32', 'pcm-f32', 'pcm-f64', 'pcm-u8', 'ulaw', 'alaw'].includes(codec),
),
];
}
+25 -1
View File
@@ -173,6 +173,28 @@ export class WaveDemuxer extends Demuxer {
bitsPerSample = 8;
}
if (
formatTag !== WaveFormat.PCM
&& formatTag !== WaveFormat.IEEE_FLOAT
&& formatTag !== WaveFormat.ALAW
&& formatTag !== WaveFormat.MULAW
) {
throw new Error(
`Unsupported WAVE codec (format tag ${formatTag}). Only integer/float PCM, A-law, and μ-law are`
+ ` supported.`,
);
}
if (formatTag === WaveFormat.PCM && ![8, 16, 24, 32].includes(bitsPerSample)) {
throw new Error(
`Unsupported WAVE PCM bit depth (${bitsPerSample}). Only 8, 16, 24, and 32 bits are supported.`,
);
}
if (formatTag === WaveFormat.IEEE_FLOAT && ![32, 64].includes(bitsPerSample)) {
throw new Error(
`Unsupported WAVE float bit depth (${bitsPerSample}). Only 32 and 64 bits are supported.`,
);
}
this.audioInfo = {
format: formatTag,
numberOfChannels: numChannels,
@@ -322,10 +344,12 @@ export class WaveDemuxer extends Demuxer {
if (this.audioInfo.format === WaveFormat.IEEE_FLOAT) {
if (this.audioInfo.sampleSizeInBytes === 4) {
return 'pcm-f32';
} else if (this.audioInfo.sampleSizeInBytes === 8) {
return 'pcm-f64';
}
}
return null;
assert(false);
}
async getMimeType() {
+87 -2
View File
@@ -1,8 +1,14 @@
import { expect, test } from 'vitest';
import { Output } from '../../src/output.js';
import { OggOutputFormat } from '../../src/output-format.js';
import { NullTarget } from '../../src/target.js';
import { AudioBufferSource } from '../../src/media-source.js';
import { BufferTarget, NullTarget } from '../../src/target.js';
import { AudioBufferSource, EncodedAudioPacketSource } from '../../src/media-source.js';
import { EncodedPacket } from '../../src/packet.js';
import { assert } from '../../src/misc.js';
import { Input } from '../../src/input.js';
import { BufferSource } from '../../src/source.js';
import { ALL_FORMATS, OggInputFormat } from '../../src/input-format.js';
import { EncodedPacketSink } from '../../src/media-sink.js';
test('maximumPageDuration option', async () => {
const sampleRate = 48000;
@@ -55,3 +61,82 @@ test('maximumPageDuration option', async () => {
expect(pageCountWithoutOption).toBe(3);
expect(pageCountWithOption).toBe(23); // It created more pages
});
test('Multi-frame Opus packets', async () => {
const SAMPLE_RATE = 48000;
const SAMPLES_PER_FRAME = 960; // 20 ms at 48 kHz
const createOpusHead = () => {
const bytes = new Uint8Array(19);
const view = new DataView(bytes.buffer);
bytes.set([0x4f, 0x70, 0x75, 0x73, 0x48, 0x65, 0x61, 0x64], 0); // 'OpusHead'
bytes[8] = 1; // Version
bytes[9] = 1; // Channel count
view.setUint16(10, 312, true); // Pre-skip
view.setUint32(12, SAMPLE_RATE, true); // Input sample rate
view.setInt16(16, 0, true); // Output gain
bytes[18] = 0; // Channel mapping family
return bytes;
};
const createOpusPacket = (frameCount: number) => {
const data = new Uint8Array(2 + 3 * frameCount);
data[0] = (31 << 3) | 0b11; // TOC byte: config 31 (CELT fullband, 20 ms), code 3
data[1] = frameCount; // CBR, no padding, `frameCount` frames
return data;
};
const framesPerPacket = 3;
const packetCount = 10;
const packetDuration = (framesPerPacket * SAMPLES_PER_FRAME) / SAMPLE_RATE;
const output = new Output({
format: new OggOutputFormat(),
target: new BufferTarget(),
});
const audioSource = new EncodedAudioPacketSource('opus');
output.addAudioTrack(audioSource);
await output.start();
for (let i = 0; i < packetCount; i++) {
await audioSource.add(
new EncodedPacket(
createOpusPacket(framesPerPacket),
'key',
i * packetDuration,
packetDuration,
),
{
decoderConfig: {
codec: 'opus',
numberOfChannels: 1,
sampleRate: SAMPLE_RATE,
description: createOpusHead(),
},
},
);
}
audioSource.close();
await output.finalize();
assert(output.target.buffer);
const input = new Input({
source: new BufferSource(output.target.buffer),
formats: ALL_FORMATS,
});
expect(await input.getFormat()).toBeInstanceOf(OggInputFormat);
const sink = new EncodedPacketSink((await input.getPrimaryAudioTrack())!);
const packet = await sink.getFirstPacket();
expect(packet?.duration).toBe(packetDuration);
});
+57
View File
@@ -0,0 +1,57 @@
import { test } from 'vitest';
import { Output } from '../../src/output.js';
import { MkvOutputFormat, Mp4OutputFormat } from '../../src/output-format.js';
import { BufferTarget } from '../../src/target.js';
import { TextSubtitleSource } from '../../src/media-source.js';
test('ISOBMFF muxing', async () => {
const output = new Output({
format: new Mp4OutputFormat(),
target: new BufferTarget(),
});
const source = new TextSubtitleSource('webvtt');
output.addSubtitleTrack(source);
await output.start();
await source.add(`WEBVTT
00:00.000 --> 00:00.900
Hildy!
00:01.000 --> 00:01.400
How are you?
00:01.500 --> 00:02.900
Tell me, is the lord of the universe in?
`);
await output.finalize();
});
test('Matroska muxing', async () => {
const output = new Output({
format: new MkvOutputFormat(),
target: new BufferTarget(),
});
const source = new TextSubtitleSource('webvtt');
output.addSubtitleTrack(source);
await output.start();
await source.add(`WEBVTT
00:00.000 --> 00:00.900
Hildy!
00:01.000 --> 00:01.400
How are you?
00:01.500 --> 00:02.900
Tell me, is the lord of the universe in?
`);
await output.finalize();
});
+5 -3
View File
@@ -19,6 +19,8 @@
"./examples/**/*.ts"
],
"references": [
{ "path": "./src" }
]
}
{ "path": "./src" },
{ "path": "./packages/ac3" },
{ "path": "./packages/prores" }
]
}
+2
View File
@@ -21,6 +21,8 @@ export default defineConfig({
resolve: {
alias: {
'mediabunny': path.resolve(__dirname, './dist/bundles/mediabunny.mjs'),
'@mediabunny/ac3':
path.resolve(__dirname, './packages/ac3/dist/bundles/mediabunny-ac3.mjs'),
'@mediabunny/aac-encoder':
path.resolve(__dirname, './packages/aac-encoder/dist/bundles/mediabunny-aac-encoder.mjs'),
'@mediabunny/flac-encoder':