Merge main into release for tag v1.55.2

This commit is contained in:
github-actions[bot]
2026-08-21 18:21:55 +00:00
19 changed files with 95 additions and 33 deletions
+1 -1
View File
@@ -2,7 +2,7 @@ node_modules
/dist
/dist-docs
.DS_Store
/docs/.vitepress/cache
docs/.vitepress/cache
/docs/api
*.tsbuildinfo
+1 -1
View File
@@ -18,7 +18,7 @@
});
const track = await input.getPrimaryVideoTrack();
console.log(await track.computeFrameRateMetrics());
console.log(await track.getDecoderConfig());
/*
+10 -10
View File
@@ -1,12 +1,12 @@
{
"name": "mediabunny",
"version": "1.55.1",
"version": "1.55.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "mediabunny",
"version": "1.55.1",
"version": "1.55.2",
"license": "MPL-2.0",
"workspaces": [
".",
@@ -12958,7 +12958,7 @@
},
"packages/aac-encoder": {
"name": "@mediabunny/aac-encoder",
"version": "1.55.1",
"version": "1.55.2",
"license": "MPL-2.0",
"devDependencies": {
"@types/emscripten": "^1.40.1"
@@ -12973,7 +12973,7 @@
},
"packages/ac3": {
"name": "@mediabunny/ac3",
"version": "1.55.1",
"version": "1.55.2",
"license": "MPL-2.0",
"devDependencies": {
"@types/emscripten": "^1.40.1"
@@ -12988,7 +12988,7 @@
},
"packages/dts": {
"name": "@mediabunny/dts",
"version": "1.55.1",
"version": "1.55.2",
"license": "MPL-2.0",
"devDependencies": {
"@types/emscripten": "^1.40.1"
@@ -13003,7 +13003,7 @@
},
"packages/flac-encoder": {
"name": "@mediabunny/flac-encoder",
"version": "1.55.1",
"version": "1.55.2",
"license": "MPL-2.0",
"devDependencies": {
"@types/emscripten": "^1.40.1"
@@ -13018,7 +13018,7 @@
},
"packages/mp3-encoder": {
"name": "@mediabunny/mp3-encoder",
"version": "1.55.1",
"version": "1.55.2",
"license": "MPL-2.0",
"devDependencies": {
"@types/emscripten": "^1.40.1"
@@ -13033,7 +13033,7 @@
},
"packages/prores": {
"name": "@mediabunny/prores",
"version": "1.55.1",
"version": "1.55.2",
"license": "MPL-2.0",
"dependencies": {
"turbores": "^1.2.2"
@@ -13048,10 +13048,10 @@
},
"packages/server": {
"name": "@mediabunny/server",
"version": "1.55.1",
"version": "1.55.2",
"license": "MPL-2.0",
"dependencies": {
"@mediabunny/prores": "^1.55.1",
"@mediabunny/prores": "^1.55.2",
"node-av": "^6.0.0"
},
"funding": {
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "mediabunny",
"author": "Vanilagy",
"version": "1.55.1",
"version": "1.55.2",
"description": "Pure TypeScript media toolkit for reading, writing, and converting media files, directly in the browser.",
"type": "module",
"workspaces": [
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@mediabunny/aac-encoder",
"author": "Vanilagy",
"version": "1.55.1",
"version": "1.55.2",
"description": "AAC encoder extension for Mediabunny, based on libavcodec.",
"main": "./dist/bundles/mediabunny-aac-encoder.mjs",
"module": "./dist/bundles/mediabunny-aac-encoder.mjs",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@mediabunny/ac3",
"author": "Vanilagy",
"version": "1.55.1",
"version": "1.55.2",
"description": "AC-3 and E-AC-3 (Dolby Digital) decoder and encoder extension for Mediabunny, based on libavcodec.",
"main": "./dist/bundles/mediabunny-ac3.mjs",
"module": "./dist/bundles/mediabunny-ac3.mjs",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@mediabunny/dts",
"author": "Vanilagy",
"version": "1.55.1",
"version": "1.55.2",
"description": "DTS decoder and encoder extension for Mediabunny, based on libavcodec.",
"main": "./dist/bundles/mediabunny-dts.mjs",
"module": "./dist/bundles/mediabunny-dts.mjs",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@mediabunny/flac-encoder",
"author": "Vanilagy",
"version": "1.55.1",
"version": "1.55.2",
"description": "FLAC encoder extension for Mediabunny, based on libFLAC.",
"main": "./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",
"author": "Vanilagy",
"version": "1.55.1",
"version": "1.55.2",
"description": "MP3 encoder extension for Mediabunny, based on LAME.",
"main": "./dist/bundles/mediabunny-mp3-encoder.mjs",
"module": "./dist/bundles/mediabunny-mp3-encoder.mjs",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@mediabunny/prores",
"author": "Vanilagy",
"version": "1.55.1",
"version": "1.55.2",
"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.55.1",
"version": "1.55.2",
"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.55.1"
"@mediabunny/prores": "^1.55.2"
},
"peerDependencies": {
"mediabunny": "^1.45.0"
+21 -1
View File
@@ -39,6 +39,8 @@ import {
parseDtsSpecificBox,
DTS_SPECIFIC_BOX_SIZE,
AC3_ACMOD_CHANNEL_COUNTS,
extractAvcDecoderConfigurationRecord,
extractHevcDecoderConfigurationRecord,
} from '../codec-data';
import { Demuxer } from '../demuxer';
import { Input } from '../input';
@@ -1390,6 +1392,11 @@ export class IsobmffDemuxer extends Demuxer {
}
assert(track.info);
if (boxInfo.contentSize === 0) {
// avcC box is empty, let's treat this like an Annex B stream
break;
}
track.info.codecDescription = readBytes(slice, boxInfo.contentSize);
}; break;
@@ -1400,6 +1407,11 @@ export class IsobmffDemuxer extends Demuxer {
}
assert(track.info);
if (boxInfo.contentSize === 0) {
// hvcC box is empty, let's treat this like an Annex B stream
break;
}
track.info.codecDescription = readBytes(slice, boxInfo.contentSize);
}; break;
@@ -3407,7 +3419,15 @@ class IsobmffVideoTrackBacking extends IsobmffTrackBacking implements InputVideo
}
return this.decoderConfigPromise ??= (async (): Promise<VideoDecoderConfig> => {
if (this.internalTrack.info.codec === 'vp9' && !this.internalTrack.info.vp9CodecInfo) {
if (this.internalTrack.info.codec === 'avc' && !this.internalTrack.info.codecDescription) {
const firstPacket = await this.getFirstPacket({});
this.internalTrack.info.avcCodecInfo
= firstPacket && extractAvcDecoderConfigurationRecord(firstPacket.data);
} else if (this.internalTrack.info.codec === 'hevc' && !this.internalTrack.info.codecDescription) {
const firstPacket = await this.getFirstPacket({});
this.internalTrack.info.hevcCodecInfo
= firstPacket && extractHevcDecoderConfigurationRecord(firstPacket.data);
} else if (this.internalTrack.info.codec === 'vp9' && !this.internalTrack.info.vp9CodecInfo) {
const firstPacket = await this.getFirstPacket({});
this.internalTrack.info.vp9CodecInfo = firstPacket && extractVp9CodecInfoFromPacket(firstPacket.data);
} else if (this.internalTrack.info.codec === 'av1' && !this.internalTrack.info.av1CodecInfo) {
+11 -2
View File
@@ -38,6 +38,7 @@ import {
isWebKit,
last,
mapAsyncGenerator,
missingWebCodecsClassMessage,
promiseWithResolvers,
removeItem,
Rotation,
@@ -1691,8 +1692,12 @@ export class VideoSampleSink extends BaseMediaSampleSink<VideoSample> {
onError: (error: unknown) => unknown,
) {
if (!(await this._track.canDecode())) {
if (typeof VideoDecoder === 'undefined') {
throw new Error(missingWebCodecsClassMessage('VideoDecoder'));
}
throw new Error(
'This video track cannot be decoded by this browser. Make sure to check decodability before using'
'This video track cannot be decoded in this environment. Make sure to check decodability before using'
+ ' a track.',
);
}
@@ -2426,8 +2431,12 @@ export class AudioSampleSink extends BaseMediaSampleSink<AudioSample> {
onError: (error: unknown) => unknown,
) {
if (!(await this._track.canDecode())) {
if (typeof AudioDecoder === 'undefined') {
throw new Error(missingWebCodecsClassMessage('AudioDecoder'));
}
throw new Error(
'This audio track cannot be decoded by this browser. Make sure to check decodability before using'
'This audio track cannot be decoded in this environment. Make sure to check decodability before using'
+ ' a track.',
);
}
+8 -6
View File
@@ -29,6 +29,7 @@ import {
clearIntervalUnthrottled,
floorToDivisor,
last,
missingWebCodecsClassMessage,
promiseWithResolvers,
roundToDivisor,
setInt24,
@@ -718,7 +719,7 @@ class VideoEncoderWrapper {
if (!selected) {
if (typeof VideoEncoder === 'undefined') {
throw new Error('VideoEncoder is not supported by this browser.');
throw new Error(missingWebCodecsClassMessage('VideoEncoder'));
}
// The candidates only differ in their rate control, so we describe them as one config with a
@@ -731,7 +732,7 @@ class VideoEncoderWrapper {
throw new Error(
`This specific encoder configuration (${firstConfig.codec}, ${rateControls.join(' / ')},`
+ ` ${firstConfig.width}x${firstConfig.height}, hardware acceleration:`
+ ` ${firstConfig.hardwareAcceleration ?? 'no-preference'}) is not supported by this browser.`
+ ` ${firstConfig.hardwareAcceleration ?? 'no-preference'}) is not supported in this environment.`
+ ` Consider using another codec or changing your video parameters.`,
);
}
@@ -1694,8 +1695,8 @@ export class MediaStreamVideoTrackSource extends VideoSource {
});
} else {
throw new Error(
'When no explicit frame rate is set, MediaStreamTrackProcessor is required; but it\'s not supported'
+ ' by this browser.',
'When no explicit frame rate is set, MediaStreamTrackProcessor is required; but it\'s not available'
+ ' in this environment.',
);
}
}
@@ -2210,7 +2211,7 @@ class AudioEncoderWrapper {
this.initPcmEncoder();
} else {
if (typeof AudioEncoder === 'undefined') {
throw new Error('AudioEncoder is not supported by this browser.');
throw new Error(missingWebCodecsClassMessage('AudioEncoder'));
}
let supported: boolean;
@@ -2226,7 +2227,8 @@ class AudioEncoderWrapper {
throw new Error(
`This specific encoder configuration (${encoderConfig.codec}, ${encoderConfig.bitrate} bps,`
+ ` ${encoderConfig.numberOfChannels} channels, ${encoderConfig.sampleRate} Hz) is not`
+ ` supported by this browser. Consider using another codec or changing your audio parameters.`,
+ ` supported in this environment. Consider using another codec or changing your`
+ ` audio parameters.`,
);
}
+10
View File
@@ -721,6 +721,16 @@ export const getChromiumVersion = () => {
return chromiumVersionCache = Number(match[1]!);
};
export const missingWebCodecsClassMessage = (className: string) => {
if (typeof globalThis.isSecureContext !== 'undefined' && !globalThis.isSecureContext) {
// WebCodecs is not exposed in insecure contexts
return `${className} is not available in this environment; this may be because this page is running in an`
+ ` insecure context. Try serving your page over HTTPS or use localhost.`;
}
return `${className} is not available in this environment.`;
};
/**
* T or a promise that resolves to T.
* @group Miscellaneous
+3 -3
View File
@@ -155,7 +155,7 @@ export class EncodedPacket {
throw new TypeError('Metadata-only packets cannot be converted to a video chunk.');
}
if (typeof EncodedVideoChunk === 'undefined') {
throw new Error('Your browser does not support EncodedVideoChunk.');
throw new Error('EncodedVideoChunk is not available in this environment.');
}
return new EncodedVideoChunk({
@@ -179,7 +179,7 @@ export class EncodedPacket {
throw new TypeError('Metadata-only packets cannot be converted to a video chunk.');
}
if (typeof EncodedVideoChunk === 'undefined') {
throw new Error('Your browser does not support EncodedVideoChunk.');
throw new Error('EncodedVideoChunk is not available in this environment.');
}
return new EncodedVideoChunk({
@@ -198,7 +198,7 @@ export class EncodedPacket {
throw new TypeError('Metadata-only packets cannot be converted to an audio chunk.');
}
if (typeof EncodedAudioChunk === 'undefined') {
throw new Error('Your browser does not support EncodedAudioChunk.');
throw new Error('EncodedAudioChunk is not available in this environment.');
}
return new EncodedAudioChunk({
+1
View File
@@ -76,6 +76,7 @@ const convertVideo = async (quality: Quality, codec: VideoCodec, allowMissingQua
trim: { start: 0, end: 2 },
video: { codec, quality },
audio: { discard: true },
showWarnings: false,
});
if (allowMissingQuantizerSupport && !conversion.isValid) {
+20
View File
@@ -140,3 +140,23 @@ test('QuickTime nclc color information', async () => {
});
expect(await track.hasHighDynamicRange()).toBe(true);
});
// Annex B isn't supposed to exist in MP4, but some files have it anyway, with an empty avcC box
test('Annex B', async () => {
const filePath = path.join(__dirname, '..', 'public/annex-b.mp4');
using input = new Input({
source: new FilePathSource(filePath),
formats: ALL_FORMATS,
});
const track = await input.getPrimaryVideoTrack();
if (!track) throw new Error('No video track found');
const decoderConfig = (await track.getDecoderConfig())!;
expect(decoderConfig.codec).toBe('avc1.424028');
expect(decoderConfig.description).toBeUndefined();
const sink = new EncodedPacketSink(track);
const firstPacket = (await sink.getFirstPacket())!;
expect([...firstPacket.data.slice(0, 4)]).toEqual([0, 0, 0, 1]);
});
Binary file not shown.