mirror of
https://github.com/arcodange-org/mediabunny.git
synced 2026-10-10 09:13:47 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
605c258029 | ||
|
|
4860a4bfe7 | ||
|
|
68a6202269 | ||
|
|
3a4153f35b | ||
|
|
03a9073c40 | ||
|
|
f8d236888a | ||
|
|
a00386d402 | ||
|
|
69fc996c17 | ||
|
|
9c0ce3458b | ||
|
|
09190b4e6e | ||
|
|
e13f3ecead | ||
|
|
3d97d089a3 | ||
|
|
6c388a9915 | ||
|
|
e992502d21 | ||
|
|
15f49ed538 | ||
|
|
4ef2d2719c |
@@ -3,6 +3,7 @@
|
|||||||
[](https://www.npmjs.com/package/mediabunny)
|
[](https://www.npmjs.com/package/mediabunny)
|
||||||
[](https://bundlephobia.com/package/mediabunny)
|
[](https://bundlephobia.com/package/mediabunny)
|
||||||
[](https://www.npmjs.com/package/mediabunny)
|
[](https://www.npmjs.com/package/mediabunny)
|
||||||
|
[](https://discord.gg/hmpkyYuS4U)
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<img src="./docs/public/mediabunny-logo.svg" width="180" height="180">
|
<img src="./docs/public/mediabunny-logo.svg" width="180" height="180">
|
||||||
|
|||||||
+6
-7
@@ -18,15 +18,13 @@
|
|||||||
const file = fileInput.files[0];
|
const file = fileInput.files[0];
|
||||||
|
|
||||||
const source = new Mediabunny.BlobSource(file);
|
const source = new Mediabunny.BlobSource(file);
|
||||||
const target = new Mediabunny.NullTarget() ?? new Mediabunny.BufferTarget() ?? new Mediabunny.StreamTarget(new WritableStream({
|
const target = new Mediabunny.BufferTarget() ?? new Mediabunny.StreamTarget(new WritableStream({
|
||||||
write: console.log
|
write: console.log
|
||||||
}), {
|
}), {
|
||||||
chunked: true,
|
chunked: true,
|
||||||
chunkSize: 2**20
|
chunkSize: 2**20
|
||||||
});
|
});
|
||||||
const outputFormat = new Mediabunny.Mp4OutputFormat({
|
const outputFormat = new Mediabunny.Mp4OutputFormat({});
|
||||||
onMoov: console.log
|
|
||||||
});
|
|
||||||
|
|
||||||
const button = document.createElement('button');
|
const button = document.createElement('button');
|
||||||
button.textContent = 'Cancel';
|
button.textContent = 'Cancel';
|
||||||
@@ -76,7 +74,8 @@
|
|||||||
},
|
},
|
||||||
*/
|
*/
|
||||||
video: () => ({
|
video: () => ({
|
||||||
//codec: 'avc',
|
forceTranscode: true,
|
||||||
|
codec: 'avc',
|
||||||
//fit: 'contain',
|
//fit: 'contain',
|
||||||
//frameRate: 27.123,
|
//frameRate: 27.123,
|
||||||
//width: 320,
|
//width: 320,
|
||||||
@@ -98,8 +97,8 @@
|
|||||||
//height: 100,
|
//height: 100,
|
||||||
}),
|
}),
|
||||||
trim: {
|
trim: {
|
||||||
//start: 0,
|
start: 0,
|
||||||
//end: 20
|
end: 5
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
console.log(conversion);
|
console.log(conversion);
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ export default withMermaid({
|
|||||||
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: 'Examples', link: '/examples', activeMatch: '/examples' },
|
{ text: 'Examples', link: '/examples', activeMatch: '/examples' },
|
||||||
{ text: 'Sponsors', link: '/#sponsors', activeMatch: '/#sponsors' },
|
{ text: 'Sponsors', link: '/#sponsors', activeMatch: '/#sponsors' },
|
||||||
{ text: 'License', link: 'https://github.com/Vanilagy/mediabunny#license' },
|
{ text: 'License', link: 'https://github.com/Vanilagy/mediabunny#license' },
|
||||||
@@ -121,7 +122,11 @@ export default withMermaid({
|
|||||||
plugins: [
|
plugins: [
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
tailwindcss() as any,
|
tailwindcss() as any,
|
||||||
llmstxt(),
|
llmstxt({
|
||||||
|
ignoreFiles: [
|
||||||
|
'api/*',
|
||||||
|
],
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
outDir: '../dist-docs',
|
outDir: '../dist-docs',
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 80 KiB |
@@ -103,6 +103,7 @@ const sponsors = {
|
|||||||
{ image: 'https://avatars.githubusercontent.com/u/84167135', name: 'Memenome', url: 'https://github.com/memenome' },
|
{ image: 'https://avatars.githubusercontent.com/u/84167135', name: 'Memenome', url: 'https://github.com/memenome' },
|
||||||
{ image: 'https://avatars.githubusercontent.com/u/5913254', name: 'Brandon McConnell', url: 'https://github.com/brandonmcconnell' },
|
{ image: 'https://avatars.githubusercontent.com/u/5913254', name: 'Brandon McConnell', url: 'https://github.com/brandonmcconnell' },
|
||||||
{ image: 'https://avatars.githubusercontent.com/u/9549394', name: 'studnitz', url: 'https://github.com/studnitz' },
|
{ image: 'https://avatars.githubusercontent.com/u/9549394', name: 'studnitz', url: 'https://github.com/studnitz' },
|
||||||
|
{ image: 'https://avatars.githubusercontent.com/u/504909', name: 'Hirbod', url: 'https://github.com/hirbod' },
|
||||||
{ image: 'https://avatars.githubusercontent.com/u/30229596', name: 'Pablo Bonilla', url: 'https://github.com/devPablo' },
|
{ image: 'https://avatars.githubusercontent.com/u/30229596', name: 'Pablo Bonilla', url: 'https://github.com/devPablo' },
|
||||||
{ image: 'https://avatars.githubusercontent.com/u/63088713', name: 'taf2000', url: 'https://github.com/taf2000' },
|
{ image: 'https://avatars.githubusercontent.com/u/63088713', name: 'taf2000', url: 'https://github.com/taf2000' },
|
||||||
{ image: 'https://avatars.githubusercontent.com/u/58149663', name: 'H7GhosT', url: 'https://github.com/H7GhosT' },
|
{ image: 'https://avatars.githubusercontent.com/u/58149663', name: 'H7GhosT', url: 'https://github.com/H7GhosT' },
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# Mediabunny and LLMs
|
||||||
|
|
||||||
|
<div class="grid place-items-center my-14">
|
||||||
|
<div class="relative">
|
||||||
|
<div class="absolute top-0 left-0 size-full rounded-full" style="background: var(--vp-home-hero-image-background-image); filter: var(--vp-home-hero-image-filter)"></div>
|
||||||
|
<img src="./assets/mechabunny.svg" width="250" class="relative">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
While Mediabunny is proudly human-generated, we want to encourage any and all usage of Mediabunny, even when the vibes are high.
|
||||||
|
|
||||||
|
Mediabunny is still new and is unlikely to be in the training data of modern LLMs, but we can still make the AI perform extremely well by just giving it a little more context.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Give one or more of these files to your LLM:
|
||||||
|
|
||||||
|
### [mediabunny.d.ts](/mediabunny.d.ts)
|
||||||
|
|
||||||
|
This file contains the entire public TypeScript API of Mediabunny and is commented extremely thoroughly.
|
||||||
|
|
||||||
|
### [llms.txt](/llms.txt)
|
||||||
|
|
||||||
|
This file provides an index of Mediabunny's guide, which the AI can then further dive into if it wants to.
|
||||||
|
|
||||||
|
### [llms-full.txt](/llms-full.txt)
|
||||||
|
|
||||||
|
This is just the entire Mediabunny guide in a single file.
|
||||||
@@ -191,6 +191,7 @@ const initMediaPlayer = async (resource: File | string) => {
|
|||||||
if (!videoSink) {
|
if (!videoSink) {
|
||||||
// If there's only an audio track, always show the controls
|
// If there's only an audio track, always show the controls
|
||||||
controlsElement.style.opacity = '1';
|
controlsElement.style.opacity = '1';
|
||||||
|
controlsElement.style.pointerEvents = '';
|
||||||
playerContainer.style.cursor = '';
|
playerContainer.style.cursor = '';
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -642,6 +643,13 @@ const formatSeconds = (seconds: number) => {
|
|||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
if (totalDuration) {
|
||||||
|
updateProgressBarTime(getPlaybackTime());
|
||||||
|
durationElement.textContent = formatSeconds(totalDuration);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
/** === FILE SELECTION LOGIC === */
|
/** === FILE SELECTION LOGIC === */
|
||||||
|
|
||||||
selectMediaButton.addEventListener('click', () => {
|
selectMediaButton.addEventListener('click', () => {
|
||||||
|
|||||||
Generated
+6
-6
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "mediabunny",
|
"name": "mediabunny",
|
||||||
"version": "1.13.1",
|
"version": "1.13.3",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "mediabunny",
|
"name": "mediabunny",
|
||||||
"version": "1.13.1",
|
"version": "1.13.3",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
@@ -7749,9 +7749,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/mediabunny": {
|
"node_modules/mediabunny": {
|
||||||
"version": "1.13.0",
|
"version": "1.13.2",
|
||||||
"resolved": "https://registry.npmjs.org/mediabunny/-/mediabunny-1.13.0.tgz",
|
"resolved": "https://registry.npmjs.org/mediabunny/-/mediabunny-1.13.2.tgz",
|
||||||
"integrity": "sha512-eAtCVOzceN3VkExQmYrDB+3bM6d98PfB6AhgueojYG6T5SNzc4cRR2gWm0oZ1HjhNN0J4hjtvtEpJ+AAMXF/dQ==",
|
"integrity": "sha512-quUwxiyA76+iAh2REQAFifHNthgVqzET37ys6v+GC8ljJpOXuP/g/XNsITc+/6r18gGuYQyF/9C4T/CeiTf7gA==",
|
||||||
"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.13.1",
|
"version": "1.13.3",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/emscripten": "^1.40.1"
|
"@types/emscripten": "^1.40.1"
|
||||||
|
|||||||
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "mediabunny",
|
"name": "mediabunny",
|
||||||
"author": "Vanilagy",
|
"author": "Vanilagy",
|
||||||
"version": "1.13.1",
|
"version": "1.13.3",
|
||||||
"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": [
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
"check": "tsc -p src --noEmit && tsc -p packages/mp3-encoder/src --noEmit && tsc -p scripts --noEmit && tsc -p tsconfig.vite.json --noEmit && rm tsconfig.vite.tsbuildinfo",
|
"check": "tsc -p src --noEmit && tsc -p packages/mp3-encoder/src --noEmit && tsc -p scripts --noEmit && tsc -p tsconfig.vite.json --noEmit && rm tsconfig.vite.tsbuildinfo",
|
||||||
"check-docblocks": "tsx scripts/check-docblocks.ts dist/mediabunny.d.ts",
|
"check-docblocks": "tsx scripts/check-docblocks.ts dist/mediabunny.d.ts",
|
||||||
"docs:dev": "vitepress dev docs",
|
"docs:dev": "vitepress dev docs",
|
||||||
"docs:build": "npm run docs:generate && vitepress build docs && npm run examples:build",
|
"docs:build": "npm run build && npm run docs:generate && vitepress build docs && npm run examples:build && cp dist/mediabunny.d.ts dist-docs/",
|
||||||
"docs:preview": "vitepress preview docs",
|
"docs:preview": "vitepress preview docs",
|
||||||
"docs:generate": "tsx scripts/generate-api-docs.ts src/index.ts packages/mp3-encoder/src/index.ts docs/api-config.json",
|
"docs:generate": "tsx scripts/generate-api-docs.ts src/index.ts packages/mp3-encoder/src/index.ts docs/api-config.json",
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@mediabunny/mp3-encoder",
|
"name": "@mediabunny/mp3-encoder",
|
||||||
"author": "Vanilagy",
|
"author": "Vanilagy",
|
||||||
"version": "1.13.1",
|
"version": "1.13.3",
|
||||||
"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",
|
||||||
|
|||||||
@@ -785,47 +785,67 @@ const generateDocs = (entryFiles: string[], apiConfigFile: string) => {
|
|||||||
const constructorBlocks: string[] = [];
|
const constructorBlocks: string[] = [];
|
||||||
const allReferencedTypes: string[] = [];
|
const allReferencedTypes: string[] = [];
|
||||||
|
|
||||||
|
// First, process constructor parameters with visibility modifiers as properties from any constructor
|
||||||
|
// that has them (typically the implementation or the main signature)
|
||||||
|
let processedProperties = false;
|
||||||
constructorOverloads.forEach((ctor) => {
|
constructorOverloads.forEach((ctor) => {
|
||||||
// Skip the implementation (the one with a body) unless it's the only constructor
|
// Process constructor parameters with visibility modifiers as properties (only once)
|
||||||
if (ctor.body && constructorOverloads.length > 1) {
|
if (!processedProperties) {
|
||||||
// Process constructor parameters with visibility modifiers as properties (only from implementation)
|
const hasVisibilityParams = ctor.parameters.some(param =>
|
||||||
ctor.parameters.forEach((param) => {
|
param.modifiers?.some(mod =>
|
||||||
// Check if parameter has visibility modifier (public, private, protected, readonly)
|
|
||||||
const hasVisibilityModifier = param.modifiers?.some(mod =>
|
|
||||||
mod.kind === ts.SyntaxKind.PublicKeyword
|
mod.kind === ts.SyntaxKind.PublicKeyword
|
||||||
|| mod.kind === ts.SyntaxKind.PrivateKeyword
|
|| mod.kind === ts.SyntaxKind.PrivateKeyword
|
||||||
|| mod.kind === ts.SyntaxKind.ProtectedKeyword
|
|| mod.kind === ts.SyntaxKind.ProtectedKeyword
|
||||||
|| mod.kind === ts.SyntaxKind.ReadonlyKeyword,
|
|| mod.kind === ts.SyntaxKind.ReadonlyKeyword,
|
||||||
);
|
),
|
||||||
|
);
|
||||||
|
|
||||||
if (!hasVisibilityModifier) return;
|
if (hasVisibilityParams) {
|
||||||
|
ctor.parameters.forEach((param) => {
|
||||||
|
// Check if parameter has visibility modifier (public, private, protected, readonly)
|
||||||
|
const hasVisibilityModifier = param.modifiers?.some(mod =>
|
||||||
|
mod.kind === ts.SyntaxKind.PublicKeyword
|
||||||
|
|| mod.kind === ts.SyntaxKind.PrivateKeyword
|
||||||
|
|| mod.kind === ts.SyntaxKind.ProtectedKeyword
|
||||||
|
|| mod.kind === ts.SyntaxKind.ReadonlyKeyword,
|
||||||
|
);
|
||||||
|
|
||||||
// Skip private parameters
|
if (!hasVisibilityModifier) return;
|
||||||
const isPrivate = param.modifiers?.some(mod => mod.kind === ts.SyntaxKind.PrivateKeyword);
|
|
||||||
if (isPrivate) return;
|
|
||||||
|
|
||||||
const paramName = param.name.getText();
|
// Skip private parameters
|
||||||
const hasQuestionToken = param.questionToken !== undefined;
|
const isPrivate = param.modifiers?.some(mod => mod.kind === ts.SyntaxKind.PrivateKeyword);
|
||||||
const isReadonly = param.modifiers?.some(mod => mod.kind === ts.SyntaxKind.ReadonlyKeyword);
|
if (isPrivate) return;
|
||||||
const rawParamType = param.type ? param.type.getText() : typeChecker.typeToString(typeChecker.getTypeAtLocation(param));
|
|
||||||
const paramType = cleanOptionalType(rawParamType, hasQuestionToken);
|
|
||||||
|
|
||||||
const propertyDef = `${isReadonly ? 'readonly ' : ''}${paramName}${hasQuestionToken ? '?' : ''}: ${paramType};`;
|
const paramName = param.name.getText();
|
||||||
|
const hasQuestionToken = param.questionToken !== undefined;
|
||||||
|
const isReadonly = param.modifiers?.some(mod => mod.kind === ts.SyntaxKind.ReadonlyKeyword);
|
||||||
|
const rawParamType = param.type ? param.type.getText() : typeChecker.typeToString(typeChecker.getTypeAtLocation(param));
|
||||||
|
const paramType = cleanOptionalType(rawParamType, hasQuestionToken);
|
||||||
|
|
||||||
// Get description from JSDoc comment on the parameter
|
const propertyDef = `${isReadonly ? 'readonly ' : ''}${paramName}${hasQuestionToken ? '?' : ''}: ${paramType};`;
|
||||||
const paramJsDoc = ts.getJSDocCommentsAndTags(param)[0];
|
|
||||||
let desc = '';
|
|
||||||
if (paramJsDoc && ts.isJSDoc(paramJsDoc) && typeof paramJsDoc.comment === 'string') {
|
|
||||||
desc = processLinkTags(paramJsDoc.comment.trim(), className);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find referenced types
|
// Get description from JSDoc comment on the parameter
|
||||||
const references = filterToExportedTypes(findAllTypeReferences(paramType), className);
|
const paramJsDoc = ts.getJSDocCommentsAndTags(param)[0];
|
||||||
const referencesText = formatReferences(references);
|
let desc = '';
|
||||||
|
if (paramJsDoc && ts.isJSDoc(paramJsDoc) && typeof paramJsDoc.comment === 'string') {
|
||||||
|
desc = processLinkTags(paramJsDoc.comment.trim(), className);
|
||||||
|
}
|
||||||
|
|
||||||
const propertyContent = `### \`${paramName}\`\n\n\`\`\`ts\n${propertyDef}\n\`\`\`${desc ? `\n\n${desc}` : ''}${referencesText}`;
|
// Find referenced types
|
||||||
properties.push(propertyContent);
|
const references = filterToExportedTypes(findAllTypeReferences(paramType), className);
|
||||||
});
|
const referencesText = formatReferences(references);
|
||||||
|
|
||||||
|
const propertyContent = `### \`${paramName}\`\n\n\`\`\`ts\n${propertyDef}\n\`\`\`${desc ? `\n\n${desc}` : ''}${referencesText}`;
|
||||||
|
properties.push(propertyContent);
|
||||||
|
});
|
||||||
|
processedProperties = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
constructorOverloads.forEach((ctor) => {
|
||||||
|
// Skip the implementation (the one with a body) unless it's the only constructor
|
||||||
|
if (ctor.body && constructorOverloads.length > 1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+42
-8
@@ -46,7 +46,9 @@ import {
|
|||||||
Rotation,
|
Rotation,
|
||||||
} from './misc';
|
} from './misc';
|
||||||
import { Output, TrackType } from './output';
|
import { Output, TrackType } from './output';
|
||||||
|
import { Mp4OutputFormat } from './output-format';
|
||||||
import { AudioSample, VideoSample } from './sample';
|
import { AudioSample, VideoSample } from './sample';
|
||||||
|
import { NullTarget } from './target';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The options for media file conversion.
|
* The options for media file conversion.
|
||||||
@@ -582,7 +584,7 @@ export class Conversion {
|
|||||||
|| this._startTimestamp > 0
|
|| this._startTimestamp > 0
|
||||||
|| firstTimestamp < 0
|
|| firstTimestamp < 0
|
||||||
|| !!trackOptions.frameRate;
|
|| !!trackOptions.frameRate;
|
||||||
const needsRerender = width !== originalWidth
|
let needsRerender = width !== originalWidth
|
||||||
|| height !== originalHeight
|
|| height !== originalHeight
|
||||||
|| (totalRotation !== 0 && !outputSupportsRotation);
|
|| (totalRotation !== 0 && !outputSupportsRotation);
|
||||||
|
|
||||||
@@ -662,6 +664,43 @@ export class Conversion {
|
|||||||
const source = new VideoSampleSource(encodingConfig);
|
const source = new VideoSampleSource(encodingConfig);
|
||||||
videoSource = source;
|
videoSource = source;
|
||||||
|
|
||||||
|
if (!needsRerender) {
|
||||||
|
// If we're directly passing decoded samples back to the encoder, sometimes the encoder may error due
|
||||||
|
// to lack of support of certain video frame formats, like when HDR is at play. To check for this, we
|
||||||
|
// first try to pass a single frame to the encoder to see how it behaves. If it throws, we then fall
|
||||||
|
// back to the rerender path.
|
||||||
|
//
|
||||||
|
// Creating a new temporary Output is sort of hacky, but due to a lack of an isolated encoder API right
|
||||||
|
// now, this is the simplest way. Will refactor in the future!
|
||||||
|
|
||||||
|
const tempOutput = new Output({
|
||||||
|
format: new Mp4OutputFormat(), // Supports all video codecs
|
||||||
|
target: new NullTarget(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const tempSource = new VideoSampleSource(encodingConfig);
|
||||||
|
tempOutput.addVideoTrack(tempSource);
|
||||||
|
|
||||||
|
await tempOutput.start();
|
||||||
|
|
||||||
|
const sink = new VideoSampleSink(track);
|
||||||
|
const firstSample = await sink.getSample(this._startTimestamp);
|
||||||
|
|
||||||
|
if (firstSample) {
|
||||||
|
try {
|
||||||
|
await tempSource.add(firstSample);
|
||||||
|
firstSample.close();
|
||||||
|
await tempOutput.finalize();
|
||||||
|
} catch (error) {
|
||||||
|
console.info('Error when probing encoder support. Falling back to rerender path.', error);
|
||||||
|
needsRerender = true;
|
||||||
|
void tempOutput.cancel();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await tempOutput.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (needsRerender) {
|
if (needsRerender) {
|
||||||
this._trackPromises.push((async () => {
|
this._trackPromises.push((async () => {
|
||||||
await this._started;
|
await this._started;
|
||||||
@@ -1084,13 +1123,8 @@ export class Conversion {
|
|||||||
|
|
||||||
this._maxTimestamps.set(trackId, Math.max(endTimestamp, this._maxTimestamps.get(trackId) ?? -Infinity));
|
this._maxTimestamps.set(trackId, Math.max(endTimestamp, this._maxTimestamps.get(trackId) ?? -Infinity));
|
||||||
|
|
||||||
let totalTimestamps = 0;
|
const minTimestamp = Math.min(...this._maxTimestamps.values());
|
||||||
for (const [, timestamp] of this._maxTimestamps) {
|
const newProgress = clamp(minTimestamp / this._totalDuration, 0, 1);
|
||||||
totalTimestamps += timestamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
const averageTimestamp = totalTimestamps / this._totalTrackCount;
|
|
||||||
const newProgress = clamp(averageTimestamp / this._totalDuration, 0, 1);
|
|
||||||
|
|
||||||
if (newProgress !== this._lastProgress) {
|
if (newProgress !== this._lastProgress) {
|
||||||
this._lastProgress = newProgress;
|
this._lastProgress = newProgress;
|
||||||
|
|||||||
+16
-8
@@ -353,6 +353,7 @@ class VideoEncoderWrapper {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const encoderError = new Error();
|
||||||
return this.ensureEncoderPromise = (async () => {
|
return this.ensureEncoderPromise = (async () => {
|
||||||
const encoderConfig = buildVideoEncoderConfig({
|
const encoderConfig = buildVideoEncoderConfig({
|
||||||
width: videoSample.codedWidth,
|
width: videoSample.codedWidth,
|
||||||
@@ -411,7 +412,7 @@ class VideoEncoderWrapper {
|
|||||||
void this.muxer!.addEncodedVideoPacket(this.source._connectedTrack!, packet, meta);
|
void this.muxer!.addEncodedVideoPacket(this.source._connectedTrack!, packet, meta);
|
||||||
},
|
},
|
||||||
error: (error) => {
|
error: (error) => {
|
||||||
error.stack = new Error().stack; // Provide a more useful stack trace
|
error.stack = encoderError.stack; // Provide a more useful stack trace
|
||||||
this.encoderError ??= error;
|
this.encoderError ??= error;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -426,7 +427,7 @@ class VideoEncoderWrapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async flushAndClose(forceClose: boolean) {
|
async flushAndClose(forceClose: boolean) {
|
||||||
this.checkForEncoderError();
|
if (!forceClose) this.checkForEncoderError();
|
||||||
|
|
||||||
if (this.customEncoder) {
|
if (this.customEncoder) {
|
||||||
if (!forceClose) {
|
if (!forceClose) {
|
||||||
@@ -439,10 +440,12 @@ class VideoEncoderWrapper {
|
|||||||
await this.encoder.flush();
|
await this.encoder.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.encoder.close();
|
if (this.encoder.state !== 'closed') {
|
||||||
|
this.encoder.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.checkForEncoderError();
|
if (!forceClose) this.checkForEncoderError();
|
||||||
}
|
}
|
||||||
|
|
||||||
getQueueSize() {
|
getQueueSize() {
|
||||||
@@ -455,6 +458,7 @@ class VideoEncoderWrapper {
|
|||||||
|
|
||||||
checkForEncoderError() {
|
checkForEncoderError() {
|
||||||
if (this.encoderError) {
|
if (this.encoderError) {
|
||||||
|
this.encoderError.stack = new Error().stack; // Provide an even more useful stack trace
|
||||||
throw this.encoderError;
|
throw this.encoderError;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -990,6 +994,7 @@ class AudioEncoderWrapper {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const encoderError = new Error();
|
||||||
return this.ensureEncoderPromise = (async () => {
|
return this.ensureEncoderPromise = (async () => {
|
||||||
const { numberOfChannels, sampleRate } = audioSample;
|
const { numberOfChannels, sampleRate } = audioSample;
|
||||||
|
|
||||||
@@ -1050,7 +1055,7 @@ class AudioEncoderWrapper {
|
|||||||
void this.muxer!.addEncodedAudioPacket(this.source._connectedTrack!, packet, meta);
|
void this.muxer!.addEncodedAudioPacket(this.source._connectedTrack!, packet, meta);
|
||||||
},
|
},
|
||||||
error: (error) => {
|
error: (error) => {
|
||||||
error.stack = new Error().stack; // Provide a more useful stack trace
|
error.stack = encoderError.stack; // Provide a more useful stack trace
|
||||||
this.encoderError ??= error;
|
this.encoderError ??= error;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -1158,7 +1163,7 @@ class AudioEncoderWrapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async flushAndClose(forceClose: boolean) {
|
async flushAndClose(forceClose: boolean) {
|
||||||
this.checkForEncoderError();
|
if (!forceClose) this.checkForEncoderError();
|
||||||
|
|
||||||
if (this.customEncoder) {
|
if (this.customEncoder) {
|
||||||
if (!forceClose) {
|
if (!forceClose) {
|
||||||
@@ -1171,10 +1176,12 @@ class AudioEncoderWrapper {
|
|||||||
await this.encoder.flush();
|
await this.encoder.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.encoder.close();
|
if (this.encoder.state !== 'closed') {
|
||||||
|
this.encoder.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.checkForEncoderError();
|
if (!forceClose) this.checkForEncoderError();
|
||||||
}
|
}
|
||||||
|
|
||||||
getQueueSize() {
|
getQueueSize() {
|
||||||
@@ -1189,6 +1196,7 @@ class AudioEncoderWrapper {
|
|||||||
|
|
||||||
checkForEncoderError() {
|
checkForEncoderError() {
|
||||||
if (this.encoderError) {
|
if (this.encoderError) {
|
||||||
|
this.encoderError.stack = new Error().stack; // Provide an even more useful stack trace
|
||||||
throw this.encoderError;
|
throw this.encoderError;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-12
@@ -75,9 +75,7 @@ export class Mp3Demuxer extends Demuxer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const startPos = this.lastLoadedPos;
|
const result = await readNextFrameHeader(this.reader, this.lastLoadedPos, this.reader.fileSize);
|
||||||
|
|
||||||
const result = await readNextFrameHeader(this.reader, startPos, this.reader.fileSize);
|
|
||||||
if (!result) {
|
if (!result) {
|
||||||
this.lastSampleLoaded = true;
|
this.lastSampleLoaded = true;
|
||||||
return;
|
return;
|
||||||
@@ -89,16 +87,16 @@ export class Mp3Demuxer extends Demuxer {
|
|||||||
|
|
||||||
const xingOffset = getXingOffset(header.mpegVersionId, header.channel);
|
const xingOffset = getXingOffset(header.mpegVersionId, header.channel);
|
||||||
|
|
||||||
let slice = this.reader.requestSlice(startPos + xingOffset, 4);
|
let slice = this.reader.requestSlice(result.startPos + xingOffset, 4);
|
||||||
if (slice instanceof Promise) slice = await slice;
|
if (slice instanceof Promise) slice = await slice;
|
||||||
assert(slice);
|
if (slice) {
|
||||||
|
const word = readU32Be(slice);
|
||||||
|
const isXing = word === XING || word === INFO;
|
||||||
|
|
||||||
const word = readU32Be(slice);
|
if (isXing) {
|
||||||
const isXing = word === XING || word === INFO;
|
// There's no actual audio data in this frame, so let's skip it
|
||||||
|
return;
|
||||||
if (isXing) {
|
}
|
||||||
// There's no actual audio data in this frame, so let's skip it
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.firstFrameHeader) {
|
if (!this.firstFrameHeader) {
|
||||||
@@ -109,7 +107,7 @@ export class Mp3Demuxer extends Demuxer {
|
|||||||
const sample: Sample = {
|
const sample: Sample = {
|
||||||
timestamp: this.nextTimestampInSamples / header.sampleRate,
|
timestamp: this.nextTimestampInSamples / header.sampleRate,
|
||||||
duration: sampleDuration,
|
duration: sampleDuration,
|
||||||
dataStart: startPos,
|
dataStart: result.startPos,
|
||||||
dataSize: header.totalSize,
|
dataSize: header.totalSize,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -114,11 +114,11 @@ export class VideoSample {
|
|||||||
* in `init`.
|
* in `init`.
|
||||||
*/
|
*/
|
||||||
constructor(
|
constructor(
|
||||||
data: BufferSource,
|
data: AllowSharedBufferSource,
|
||||||
init: SetRequired<VideoSampleInit, 'format' | 'codedWidth' | 'codedHeight' | 'timestamp'>
|
init: SetRequired<VideoSampleInit, 'format' | 'codedWidth' | 'codedHeight' | 'timestamp'>
|
||||||
);
|
);
|
||||||
constructor(
|
constructor(
|
||||||
data: VideoFrame | CanvasImageSource | BufferSource,
|
data: VideoFrame | CanvasImageSource | AllowSharedBufferSource,
|
||||||
init?: VideoSampleInit,
|
init?: VideoSampleInit,
|
||||||
) {
|
) {
|
||||||
if (data instanceof ArrayBuffer || ArrayBuffer.isView(data)) {
|
if (data instanceof ArrayBuffer || ArrayBuffer.isView(data)) {
|
||||||
|
|||||||
Reference in New Issue
Block a user