Merge main into release for tag v1.25.2

This commit is contained in:
github-actions[bot]
2025-11-25 16:28:37 +00:00
8 changed files with 79 additions and 17 deletions
+4 -1
View File
@@ -14,6 +14,9 @@ export default withMermaid({
title: 'Mediabunny', title: 'Mediabunny',
description: DESCRIPTION, description: DESCRIPTION,
cleanUrls: true, cleanUrls: true,
sitemap: {
hostname: 'https://mediabunny.dev',
},
head: [ head: [
['link', { rel: 'icon', type: 'image/png', href: '/mediabunny-logo.png' }], ['link', { rel: 'icon', type: 'image/png', href: '/mediabunny-logo.png' }],
['link', { rel: 'icon', type: 'image/svg+xml', href: '/mediabunny-logo.svg' }], ['link', { rel: 'icon', type: 'image/svg+xml', href: '/mediabunny-logo.svg' }],
@@ -34,7 +37,7 @@ export default withMermaid({
// https://vitepress.dev/reference/default-theme-config // https://vitepress.dev/reference/default-theme-config
nav: [ nav: [
{ text: 'Guide', link: '/guide/introduction', activeMatch: '/guide' }, { text: 'Guide', link: '/guide/introduction', activeMatch: '/guide' },
{ text: 'API', link: '/api', activeMatch: '/api' }, { text: 'API', link: '/api/', activeMatch: '/api' },
{ text: 'LLMs', link: '/llms', activeMatch: '/llms' }, { text: 'LLMs', link: '/llms', activeMatch: '/llms' },
{ text: 'Examples', link: '/examples', activeMatch: '/examples' }, { text: 'Examples', link: '/examples', activeMatch: '/examples' },
{ text: 'Sponsors', link: '/#sponsors', activeMatch: '/#sponsors' }, { text: 'Sponsors', link: '/#sponsors', activeMatch: '/#sponsors' },
+6 -6
View File
@@ -9,37 +9,37 @@ hero:
features: features:
- title: Metadata extraction - title: Metadata extraction
details: Extract various metadata from an input media file. details: Extract various metadata from an input media file.
link: /examples/metadata-extraction link: /examples/metadata-extraction/
target: _self target: _self
icon: icon:
src: /mingcute--file-info-line.svg src: /mingcute--file-info-line.svg
- title: Thumbnail generation - title: Thumbnail generation
details: Generate multiple small thumbnails for a video track. details: Generate multiple small thumbnails for a video track.
link: /examples/thumbnail-generation link: /examples/thumbnail-generation/
target: _self target: _self
icon: icon:
src: /mingcute--photo-album-line.svg src: /mingcute--photo-album-line.svg
- title: Media player (advanced) - title: Media player (advanced)
details: "A full video & audio media player, implemented from scratch with Mediabunny, with microsecond playback accuracy." details: "A full video & audio media player, implemented from scratch with Mediabunny, with microsecond playback accuracy."
link: /examples/media-player link: /examples/media-player/
target: _self target: _self
icon: icon:
src: /mingcute--video-line.svg src: /mingcute--video-line.svg
- title: File compression - title: File compression
details: Convert an input file to a highly-compressed MP4 file. details: Convert an input file to a highly-compressed MP4 file.
link: /examples/file-compression link: /examples/file-compression/
target: _self target: _self
icon: icon:
src: /mingcute--file-zip-line.svg src: /mingcute--file-zip-line.svg
- title: Procedural video generation - title: Procedural video generation
details: Generate a video file as fast as the hardware allows. details: Generate a video file as fast as the hardware allows.
link: /examples/procedural-generation link: /examples/procedural-generation/
target: _self target: _self
icon: icon:
src: /mingcute--magic-3-line.svg src: /mingcute--magic-3-line.svg
- title: Live recording & streaming - title: Live recording & streaming
details: Record a video from live sources and stream it to a video element. details: Record a video from live sources and stream it to a video element.
link: /examples/live-recording link: /examples/live-recording/
target: _self target: _self
icon: icon:
src: /mingcute--microphone-line.svg src: /mingcute--microphone-line.svg
+6 -6
View File
@@ -1,12 +1,12 @@
{ {
"name": "mediabunny", "name": "mediabunny",
"version": "1.25.1", "version": "1.25.2",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "mediabunny", "name": "mediabunny",
"version": "1.25.1", "version": "1.25.2",
"license": "MPL-2.0", "license": "MPL-2.0",
"workspaces": [ "workspaces": [
"packages/*" "packages/*"
@@ -7749,9 +7749,9 @@
} }
}, },
"node_modules/mediabunny": { "node_modules/mediabunny": {
"version": "1.25.0", "version": "1.25.1",
"resolved": "https://registry.npmjs.org/mediabunny/-/mediabunny-1.25.0.tgz", "resolved": "https://registry.npmjs.org/mediabunny/-/mediabunny-1.25.1.tgz",
"integrity": "sha512-ozaqk6zS2Vbf3+3+OoxKfnCVeZRcv5PO8DgQtBrM5vpWIbpEK+kMVV6pgfo4mC3XtMwvQEMbhj3zEf0LNklh9w==", "integrity": "sha512-0Rrd47PMCVJbTPA7IJaXPCupV5/RZ/icgr+a0qExRJAr0n5vB4fsGSo+fdHIehG0CrddXtVRvNZwFtJz709yfA==",
"license": "MPL-2.0", "license": "MPL-2.0",
"peer": true, "peer": true,
"workspaces": [ "workspaces": [
@@ -12242,7 +12242,7 @@
}, },
"packages/mp3-encoder": { "packages/mp3-encoder": {
"name": "@mediabunny/mp3-encoder", "name": "@mediabunny/mp3-encoder",
"version": "1.25.1", "version": "1.25.2",
"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.25.1", "version": "1.25.2",
"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/mp3-encoder", "name": "@mediabunny/mp3-encoder",
"author": "Vanilagy", "author": "Vanilagy",
"version": "1.25.1", "version": "1.25.2",
"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",
+40 -1
View File
@@ -22,6 +22,7 @@ import {
VideoCodec, VideoCodec,
} from './codec'; } from './codec';
import { customAudioEncoders, customVideoEncoders } from './custom-coder'; import { customAudioEncoders, customVideoEncoders } from './custom-coder';
import { isFirefox } from './misc';
import { EncodedPacket } from './packet'; import { EncodedPacket } from './packet';
/** /**
@@ -529,7 +530,45 @@ export const canEncodeVideo = async (
}); });
const support = await VideoEncoder.isConfigSupported(encoderConfig); const support = await VideoEncoder.isConfigSupported(encoderConfig);
return support.supported === true; if (!support.supported) {
return false;
}
if (isFirefox()) {
// isConfigSupported on Firefox appears to unreliably indicate if encoding will actually succeed. Therefore, we
// just try encoding a frame to see if it actually works.
// https://github.com/Vanilagy/mediabunny/issues/222
// eslint-disable-next-line @typescript-eslint/no-misused-promises, no-async-promise-executor
return new Promise<boolean>(async (resolve) => {
try {
const encoder = new VideoEncoder({
output: () => {},
error: () => resolve(false),
});
encoder.configure(encoderConfig);
const frameData = new Uint8Array(width * height * 4);
const frame = new VideoFrame(frameData, {
format: 'RGBA',
codedWidth: width,
codedHeight: height,
timestamp: 0,
});
encoder.encode(frame);
frame.close();
await encoder.flush();
resolve(true);
} catch {
resolve(false);
}
});
} else {
return true;
}
}; };
/** /**
+9
View File
@@ -584,6 +584,7 @@ export const retriedFetch = async (
url: string | URL | Request, url: string | URL | Request,
requestInit: RequestInit, requestInit: RequestInit,
getRetryDelay: (previousAttempts: number, error: unknown, url: string | URL | Request) => number | null, getRetryDelay: (previousAttempts: number, error: unknown, url: string | URL | Request) => number | null,
shouldStop: () => boolean,
) => { ) => {
let attempts = 0; let attempts = 0;
@@ -591,6 +592,10 @@ export const retriedFetch = async (
try { try {
return await fetchFn(url, requestInit); return await fetchFn(url, requestInit);
} catch (error) { } catch (error) {
if (shouldStop()) {
throw error;
}
attempts++; attempts++;
const retryDelayInSeconds = getRetryDelay(attempts, error, url); const retryDelayInSeconds = getRetryDelay(attempts, error, url);
@@ -607,6 +612,10 @@ export const retriedFetch = async (
if (retryDelayInSeconds > 0) { if (retryDelayInSeconds > 0) {
await new Promise(resolve => setTimeout(resolve, 1000 * retryDelayInSeconds)); await new Promise(resolve => setTimeout(resolve, 1000 * retryDelayInSeconds));
} }
if (shouldStop()) {
throw error;
}
} }
} }
}; };
+12 -1
View File
@@ -297,6 +297,10 @@ const DEFAULT_RETRY_DELAY
= typeof navigator !== 'undefined' && typeof navigator.onLine === 'boolean' ? navigator.onLine : true; = typeof navigator !== 'undefined' && typeof navigator.onLine === 'boolean' ? navigator.onLine : true;
if (isOnline && originOfSrc !== null && originOfSrc !== window.location.origin) { if (isOnline && originOfSrc !== null && originOfSrc !== window.location.origin) {
console.warn(
`Request will not be retried because a CORS error was suspected due to different origins. You can`
+ ` modify this behavior by providing your own function for the 'getRetryDelay' option.`,
);
return null; return null;
} }
} }
@@ -425,6 +429,7 @@ export class UrlSource extends Source {
signal: abortController.signal, signal: abortController.signal,
}), }),
this._getRetryDelay, this._getRetryDelay,
() => this._disposed,
); );
if (!response.ok) { if (!response.ok) {
@@ -492,6 +497,7 @@ export class UrlSource extends Source {
signal: abortController.signal, signal: abortController.signal,
}), }),
this._getRetryDelay, this._getRetryDelay,
() => this._disposed,
); );
} }
@@ -539,6 +545,11 @@ export class UrlSource extends Source {
try { try {
readResult = await reader.read(); readResult = await reader.read();
} catch (error) { } catch (error) {
if (this._disposed) {
// No need to try to retry
throw error;
}
const retryDelayInSeconds = this._getRetryDelay(1, error, this._url); const retryDelayInSeconds = this._getRetryDelay(1, error, this._url);
if (retryDelayInSeconds !== null) { if (retryDelayInSeconds !== null) {
console.error('Error while reading response stream. Attempting to resume.', error); console.error('Error while reading response stream. Attempting to resume.', error);
@@ -639,7 +650,7 @@ export class FilePathSource extends Source {
_fileHandle: FileHandle | null = null; _fileHandle: FileHandle | null = null;
/** Creates a new {@link FilePathSource} backed by the file at the specified file path. */ /** Creates a new {@link FilePathSource} backed by the file at the specified file path. */
constructor(filePath: string, options: BlobSourceOptions = {}) { constructor(filePath: string, options: FilePathSourceOptions = {}) {
if (typeof filePath !== 'string') { if (typeof filePath !== 'string') {
throw new TypeError('filePath must be a string.'); throw new TypeError('filePath must be a string.');
} }