mirror of
https://github.com/arcodange-org/mediabunny.git
synced 2026-09-27 02:43:48 +02:00
Create @mediabunny/mp3-encoder, restructure and extend build system
This commit is contained in:
+4
-2
@@ -1,5 +1,7 @@
|
||||
/node_modules
|
||||
node_modules
|
||||
/dist
|
||||
/dist-docs
|
||||
.DS_Store
|
||||
/docs/.vitepress/cache
|
||||
/docs/.vitepress/cache
|
||||
|
||||
packages/mp3-encoder/dist
|
||||
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"files.associations": {
|
||||
"lame.h": "c"
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
|
||||
"mainEntryPointFilePath": "dist/modules/index.d.ts",
|
||||
"mainEntryPointFilePath": "dist/modules/src/index.d.ts",
|
||||
"bundledPackages": [],
|
||||
"compiler": {},
|
||||
"apiReport": {
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
import * as esbuild from 'esbuild';
|
||||
import process from 'node:process';
|
||||
|
||||
const baseConfig = {
|
||||
entryPoints: ['src/index.ts'],
|
||||
bundle: true,
|
||||
logLevel: 'info',
|
||||
banner: {
|
||||
js: `/*!
|
||||
* Copyright (c) 2025-present, Vanilagy and contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/`,
|
||||
},
|
||||
legalComments: 'none',
|
||||
};
|
||||
|
||||
const umdConfig = {
|
||||
...baseConfig,
|
||||
format: 'iife',
|
||||
|
||||
// The following are hacks to basically make this an UMD module. No native support for that in esbuild as of today
|
||||
globalName: 'Mediabunny',
|
||||
|
||||
footer: {
|
||||
js:
|
||||
`if (typeof module === "object" && typeof module.exports === "object") Object.assign(module.exports, Mediabunny)`,
|
||||
},
|
||||
};
|
||||
|
||||
const esmConfig = {
|
||||
...baseConfig,
|
||||
format: 'esm',
|
||||
};
|
||||
|
||||
const ctxUmd = await esbuild.context({
|
||||
...umdConfig,
|
||||
outfile: 'dist/bundles/mediabunny.cjs',
|
||||
});
|
||||
const ctxEsm = await esbuild.context({
|
||||
...esmConfig,
|
||||
outfile: 'dist/bundles/mediabunny.mjs',
|
||||
});
|
||||
const ctxUmdMinified = await esbuild.context({
|
||||
...umdConfig,
|
||||
outfile: 'dist/bundles/mediabunny.min.cjs',
|
||||
minify: true,
|
||||
});
|
||||
const ctxEsmMinified = await esbuild.context({
|
||||
...esmConfig,
|
||||
outfile: 'dist/bundles/mediabunny.min.mjs',
|
||||
minify: true,
|
||||
});
|
||||
|
||||
if (process.argv[2] === '--watch') {
|
||||
await Promise.all([
|
||||
ctxUmd.watch(),
|
||||
ctxEsm.watch(),
|
||||
ctxUmdMinified.watch(),
|
||||
ctxEsmMinified.watch(),
|
||||
]);
|
||||
} else {
|
||||
ctxUmd.rebuild();
|
||||
ctxEsm.rebuild();
|
||||
ctxUmdMinified.rebuild();
|
||||
ctxEsmMinified.rebuild();
|
||||
|
||||
await Promise.all([
|
||||
ctxUmd.dispose(),
|
||||
ctxEsm.dispose(),
|
||||
ctxUmdMinified.dispose(),
|
||||
ctxEsmMinified.dispose(),
|
||||
]);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# This script must be executed via `npm run build`
|
||||
|
||||
# Clear the stuff from last build
|
||||
rm -rf dist
|
||||
rm -rf packages/mp3-encoder/dist
|
||||
|
||||
# Ensure license headers on all source files
|
||||
tsx scripts/ensure-license-headers.ts
|
||||
|
||||
# Type check & generate .js and .d.ts files
|
||||
tsc -p src
|
||||
tsc -p packages/mp3-encoder/src
|
||||
|
||||
# So that the resulting files use valid ESM imports with file extension. This only runs for the core Mediabunny as only
|
||||
# it ships the individual files to npm (for tree shaking, because it's large)
|
||||
npm run fix-build-import-paths
|
||||
|
||||
# Creates bundles for all packages
|
||||
tsx scripts/bundle.ts
|
||||
|
||||
# Declaration file rollup and checks
|
||||
api-extractor run
|
||||
api-extractor run -c packages/mp3-encoder/api-extractor.json
|
||||
|
||||
# Checks that all symbols are documented
|
||||
tsx scripts/check-docblocks.ts dist/mediabunny.d.ts
|
||||
tsx scripts/check-docblocks.ts packages/mp3-encoder/dist/mediabunny-mp3-encoder.d.ts
|
||||
|
||||
# Appends stuff to the declaration files to register the global variables these libraries expose
|
||||
echo 'export as namespace Mediabunny;' >> dist/mediabunny.d.ts
|
||||
echo 'export as namespace MediabunnyMp3Encoder;' >> packages/mp3-encoder/dist/mediabunny-mp3-encoder.d.ts
|
||||
+5
-2
@@ -1,8 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<script src="../dist/bundles/mediabunny.cjs"></script>
|
||||
<script src="../packages/mp3-encoder/dist/bundles/mediabunny-mp3-encoder.js"></script>
|
||||
|
||||
<script type="module">
|
||||
MediabunnyMp3Encoder.registerMp3Encoder();
|
||||
|
||||
const fileInput = document.createElement('input');
|
||||
fileInput.type = 'file';
|
||||
document.body.append(fileInput);
|
||||
@@ -21,7 +24,7 @@
|
||||
chunked: true,
|
||||
chunkSize: 2**20
|
||||
});
|
||||
const outputFormat = new Mediabunny.Mp4OutputFormat({});
|
||||
const outputFormat = new Mediabunny.Mp3OutputFormat({});
|
||||
|
||||
const button = document.createElement('button');
|
||||
button.textContent = 'Cancel';
|
||||
@@ -70,7 +73,7 @@
|
||||
},
|
||||
*/
|
||||
video: {
|
||||
frameRate: 27.123,
|
||||
//frameRate: 27.123,
|
||||
//width: 320,
|
||||
//forceTranscode: true,
|
||||
//codec: 'av1',
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
source
|
||||
});
|
||||
|
||||
/*
|
||||
const audioTrack = await input.getPrimaryAudioTrack();
|
||||
const sink = new Mediabunny.EncodedPacketSink(audioTrack);
|
||||
|
||||
@@ -28,6 +29,7 @@
|
||||
|
||||
}
|
||||
console.log("done")
|
||||
*/
|
||||
|
||||
/*
|
||||
for await (const packet of sink.packets()) {
|
||||
|
||||
+3
-4
@@ -34,10 +34,9 @@ export default tseslint.config(
|
||||
{
|
||||
ignores: [
|
||||
'dist',
|
||||
'build',
|
||||
'api-sketch.ts',
|
||||
'build.mjs',
|
||||
'append-namespace.mjs',
|
||||
'dist-docs',
|
||||
'packages/mp3-encoder/dist',
|
||||
'packages/mp3-encoder/build',
|
||||
'eslint.config.mjs',
|
||||
'docs/.vitepress/cache',
|
||||
]
|
||||
|
||||
Generated
+217
-2
@@ -1,13 +1,16 @@
|
||||
{
|
||||
"name": "mediabunny",
|
||||
"version": "1.4.4",
|
||||
"version": "1.5.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "mediabunny",
|
||||
"version": "1.4.4",
|
||||
"version": "1.5.0",
|
||||
"license": "MPL-2.0",
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
"dependencies": {
|
||||
"@types/dom-mediacapture-transform": "^0.1.11",
|
||||
"@types/dom-webcodecs": "0.1.13"
|
||||
@@ -22,7 +25,9 @@
|
||||
"@types/markdown-it-footnote": "^3.0.4",
|
||||
"@types/node": "^22.13.10",
|
||||
"esbuild": "^0.25.1",
|
||||
"esbuild-plugin-external-global": "^1.0.1",
|
||||
"eslint": "^9.22.0",
|
||||
"find-cache-dir": "^5.0.0",
|
||||
"markdown-it-footnote": "^4.0.0",
|
||||
"markdown-it-mathjax3": "^4.3.2",
|
||||
"mermaid": "^11.6.0",
|
||||
@@ -1276,6 +1281,10 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@mediabunny/mp3-encoder": {
|
||||
"resolved": "packages/mp3-encoder",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@mermaid-js/mermaid-mindmap": {
|
||||
"version": "9.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@mermaid-js/mermaid-mindmap/-/mermaid-mindmap-9.3.0.tgz",
|
||||
@@ -2448,6 +2457,13 @@
|
||||
"integrity": "sha512-O5hkiFIcjjszPIYyUSyvScyvrBoV3NOEEZx/pMlsu44TKzWNkLVBBxnxJz42in5n3QIolYOcBYFCPZZ0h8SkwQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/emscripten": {
|
||||
"version": "1.40.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-1.40.1.tgz",
|
||||
"integrity": "sha512-sr53lnYkQNhjHNN0oJDdUm5564biioI5DuOpycufDVK7D3y+GR3oUswe2rlwY1nPNyusHbrJ9WoTyIHl4/Bpwg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
|
||||
@@ -3472,6 +3488,13 @@
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/common-path-prefix": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz",
|
||||
"integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
@@ -4307,6 +4330,13 @@
|
||||
"@esbuild/win32-x64": "0.25.1"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-plugin-external-global": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-plugin-external-global/-/esbuild-plugin-external-global-1.0.1.tgz",
|
||||
"integrity": "sha512-NDzYHRoShpvLqNcrgV8ZQh61sMIFAry5KLTQV83BPG5iTXCCu7h72SCfJ97bW0GqtuqDD/1aqLbKinI/rNgUsg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/escalade": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
|
||||
@@ -4678,6 +4708,23 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/find-cache-dir": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-5.0.0.tgz",
|
||||
"integrity": "sha512-OuWNfjfP05JcpAP3JPgAKUhWefjMRfI5iAoSsvE24ANYWJaepAtlSgWECSVEuRgSXpyNEc9DJwG/TZpgcOqyig==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"common-path-prefix": "^3.0.0",
|
||||
"pkg-dir": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/find-up": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
|
||||
@@ -5155,6 +5202,15 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
||||
@@ -5617,6 +5673,21 @@
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/loose-envify": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"js-tokens": "^3.0.0 || ^4.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"loose-envify": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/lru-cache": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||
@@ -5828,6 +5899,21 @@
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/mediabunny": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/mediabunny/-/mediabunny-1.5.0.tgz",
|
||||
"integrity": "sha512-59gA8jMk8fjU1XdIwCDOI1v3M3nUE1r2Hl4jLtGZNLdxFv7sbDiliuneOPdhacCv/THheCOA6y5IdiIsF0250g==",
|
||||
"license": "MPL-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/dom-mediacapture-transform": "^0.1.11",
|
||||
"@types/dom-webcodecs": "0.1.13"
|
||||
},
|
||||
"funding": {
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/Vanilagy"
|
||||
}
|
||||
},
|
||||
"node_modules/mensch": {
|
||||
"version": "0.3.4",
|
||||
"resolved": "https://registry.npmjs.org/mensch/-/mensch-0.3.4.tgz",
|
||||
@@ -6736,6 +6822,110 @@
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/pkg-dir": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-7.0.0.tgz",
|
||||
"integrity": "sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"find-up": "^6.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/pkg-dir/node_modules/find-up": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz",
|
||||
"integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"locate-path": "^7.1.0",
|
||||
"path-exists": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/pkg-dir/node_modules/locate-path": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz",
|
||||
"integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"p-locate": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/pkg-dir/node_modules/p-limit": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz",
|
||||
"integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"yocto-queue": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/pkg-dir/node_modules/p-locate": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz",
|
||||
"integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"p-limit": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/pkg-dir/node_modules/path-exists": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz",
|
||||
"integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/pkg-dir/node_modules/yocto-queue": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.1.tgz",
|
||||
"integrity": "sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12.20"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/pkg-types": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.1.0.tgz",
|
||||
@@ -6869,6 +7059,21 @@
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react": {
|
||||
"version": "18.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
|
||||
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/regex": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/regex/-/regex-6.0.1.tgz",
|
||||
@@ -8806,6 +9011,16 @@
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"packages/mp3-encoder": {
|
||||
"name": "@mediabunny/mp3-encoder",
|
||||
"version": "0.0.1",
|
||||
"devDependencies": {
|
||||
"@types/emscripten": "^1.40.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"mediabunny": "^1.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+12
-7
@@ -4,12 +4,15 @@
|
||||
"version": "1.5.0",
|
||||
"description": "Pure TypeScript media toolkit for reading, writing, and converting media files, directly in the browser.",
|
||||
"type": "module",
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
"main": "./dist/bundles/mediabunny.cjs",
|
||||
"module": "./dist/modules/index.js",
|
||||
"types": "./dist/modules/index.d.ts",
|
||||
"module": "./dist/modules/src/index.js",
|
||||
"types": "./dist/modules/src/index.d.ts",
|
||||
"exports": {
|
||||
"types": "./dist/modules/index.d.ts",
|
||||
"import": "./dist/modules/index.js",
|
||||
"types": "./dist/modules/src/index.d.ts",
|
||||
"import": "./dist/modules/src/index.js",
|
||||
"require": "./dist/bundles/mediabunny.cjs"
|
||||
},
|
||||
"files": [
|
||||
@@ -21,10 +24,10 @@
|
||||
],
|
||||
"sideEffects": false,
|
||||
"scripts": {
|
||||
"build": "rm -rf dist && tsx scripts/ensure-license-headers.ts && tsc -p src && npm run fix-build-import-paths && node build.mjs && api-extractor run && npm run check-docblocks && npm run append-namespace",
|
||||
"watch": "node build.mjs --watch",
|
||||
"build": "./build.sh",
|
||||
"watch": "tsx scripts/bundle.ts --watch",
|
||||
"lint": "eslint .",
|
||||
"check": "tsc -p 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",
|
||||
"docs:dev": "vitepress dev docs",
|
||||
"docs:build": "vitepress build docs && npm run examples:build",
|
||||
@@ -61,7 +64,9 @@
|
||||
"@types/markdown-it-footnote": "^3.0.4",
|
||||
"@types/node": "^22.13.10",
|
||||
"esbuild": "^0.25.1",
|
||||
"esbuild-plugin-external-global": "^1.0.1",
|
||||
"eslint": "^9.22.0",
|
||||
"find-cache-dir": "^5.0.0",
|
||||
"markdown-it-footnote": "^4.0.0",
|
||||
"markdown-it-mathjax3": "^4.3.2",
|
||||
"mermaid": "^11.6.0",
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
build/* linguist-generated
|
||||
@@ -0,0 +1,373 @@
|
||||
Mozilla Public License Version 2.0
|
||||
==================================
|
||||
|
||||
1. Definitions
|
||||
--------------
|
||||
|
||||
1.1. "Contributor"
|
||||
means each individual or legal entity that creates, contributes to
|
||||
the creation of, or owns Covered Software.
|
||||
|
||||
1.2. "Contributor Version"
|
||||
means the combination of the Contributions of others (if any) used
|
||||
by a Contributor and that particular Contributor's Contribution.
|
||||
|
||||
1.3. "Contribution"
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. "Covered Software"
|
||||
means Source Code Form to which the initial Contributor has attached
|
||||
the notice in Exhibit A, the Executable Form of such Source Code
|
||||
Form, and Modifications of such Source Code Form, in each case
|
||||
including portions thereof.
|
||||
|
||||
1.5. "Incompatible With Secondary Licenses"
|
||||
means
|
||||
|
||||
(a) that the initial Contributor has attached the notice described
|
||||
in Exhibit B to the Covered Software; or
|
||||
|
||||
(b) that the Covered Software was made available under the terms of
|
||||
version 1.1 or earlier of the License, but not also under the
|
||||
terms of a Secondary License.
|
||||
|
||||
1.6. "Executable Form"
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. "Larger Work"
|
||||
means a work that combines Covered Software with other material, in
|
||||
a separate file or files, that is not Covered Software.
|
||||
|
||||
1.8. "License"
|
||||
means this document.
|
||||
|
||||
1.9. "Licensable"
|
||||
means having the right to grant, to the maximum extent possible,
|
||||
whether at the time of the initial grant or subsequently, any and
|
||||
all of the rights conveyed by this License.
|
||||
|
||||
1.10. "Modifications"
|
||||
means any of the following:
|
||||
|
||||
(a) any file in Source Code Form that results from an addition to,
|
||||
deletion from, or modification of the contents of Covered
|
||||
Software; or
|
||||
|
||||
(b) any new file in Source Code Form that contains any Covered
|
||||
Software.
|
||||
|
||||
1.11. "Patent Claims" of a Contributor
|
||||
means any patent claim(s), including without limitation, method,
|
||||
process, and apparatus claims, in any patent Licensable by such
|
||||
Contributor that would be infringed, but for the grant of the
|
||||
License, by the making, using, selling, offering for sale, having
|
||||
made, import, or transfer of either its Contributions or its
|
||||
Contributor Version.
|
||||
|
||||
1.12. "Secondary License"
|
||||
means either the GNU General Public License, Version 2.0, the GNU
|
||||
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||
Public License, Version 3.0, or any later versions of those
|
||||
licenses.
|
||||
|
||||
1.13. "Source Code Form"
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. "You" (or "Your")
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, "You" includes any entity that
|
||||
controls, is controlled by, or is under common control with You. For
|
||||
purposes of this definition, "control" means (a) the power, direct
|
||||
or indirect, to cause the direction or management of such entity,
|
||||
whether by contract or otherwise, or (b) ownership of more than
|
||||
fifty percent (50%) of the outstanding shares or beneficial
|
||||
ownership of such entity.
|
||||
|
||||
2. License Grants and Conditions
|
||||
--------------------------------
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
(a) under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or
|
||||
as part of a Larger Work; and
|
||||
|
||||
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||
for sale, have made, import, and otherwise transfer either its
|
||||
Contributions or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution
|
||||
become effective for each Contribution on the date the Contributor first
|
||||
distributes such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under
|
||||
this License. No additional rights or licenses will be implied from the
|
||||
distribution or licensing of Covered Software under this License.
|
||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||
Contributor:
|
||||
|
||||
(a) for any code that a Contributor has removed from Covered Software;
|
||||
or
|
||||
|
||||
(b) for infringements caused by: (i) Your and any other third party's
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||
its Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks,
|
||||
or logos of any Contributor (except as may be necessary to comply with
|
||||
the notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this
|
||||
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||
permitted under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its
|
||||
Contributions are its original creation(s) or it has sufficient rights
|
||||
to grant the rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under
|
||||
applicable copyright doctrines of fair use, fair dealing, or other
|
||||
equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||
in Section 2.1.
|
||||
|
||||
3. Responsibilities
|
||||
-------------------
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under
|
||||
the terms of this License. You must inform recipients that the Source
|
||||
Code Form of the Covered Software is governed by the terms of this
|
||||
License, and how they can obtain a copy of this License. You may not
|
||||
attempt to alter or restrict the recipients' rights in the Source Code
|
||||
Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
(a) such Covered Software must also be made available in Source Code
|
||||
Form, as described in Section 3.1, and You must inform recipients of
|
||||
the Executable Form how they can obtain a copy of such Source Code
|
||||
Form by reasonable means in a timely manner, at a charge no more
|
||||
than the cost of distribution to the recipient; and
|
||||
|
||||
(b) You may distribute such Executable Form under the terms of this
|
||||
License, or sublicense it under different terms, provided that the
|
||||
license for the Executable Form does not attempt to limit or alter
|
||||
the recipients' rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for
|
||||
the Covered Software. If the Larger Work is a combination of Covered
|
||||
Software with a work governed by one or more Secondary Licenses, and the
|
||||
Covered Software is not Incompatible With Secondary Licenses, this
|
||||
License permits You to additionally distribute such Covered Software
|
||||
under the terms of such Secondary License(s), so that the recipient of
|
||||
the Larger Work may, at their option, further distribute the Covered
|
||||
Software under the terms of either this License or such Secondary
|
||||
License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices
|
||||
(including copyright notices, patent notices, disclaimers of warranty,
|
||||
or limitations of liability) contained within the Source Code Form of
|
||||
the Covered Software, except that You may alter any license notices to
|
||||
the extent required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on
|
||||
behalf of any Contributor. You must make it absolutely clear that any
|
||||
such warranty, support, indemnity, or liability obligation is offered by
|
||||
You alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
---------------------------------------------------
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this
|
||||
License with respect to some or all of the Covered Software due to
|
||||
statute, judicial order, or regulation then You must: (a) comply with
|
||||
the terms of this License to the maximum extent possible; and (b)
|
||||
describe the limitations and the code they affect. Such description must
|
||||
be placed in a text file included with all distributions of the Covered
|
||||
Software under this License. Except to the extent prohibited by statute
|
||||
or regulation, such description must be sufficiently detailed for a
|
||||
recipient of ordinary skill to be able to understand it.
|
||||
|
||||
5. Termination
|
||||
--------------
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically
|
||||
if You fail to comply with any of its terms. However, if You become
|
||||
compliant, then the rights granted under this License from a particular
|
||||
Contributor are reinstated (a) provisionally, unless and until such
|
||||
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||
ongoing basis, if such Contributor fails to notify You of the
|
||||
non-compliance by some reasonable means prior to 60 days after You have
|
||||
come back into compliance. Moreover, Your grants from a particular
|
||||
Contributor are reinstated on an ongoing basis if such Contributor
|
||||
notifies You of the non-compliance by some reasonable means, this is the
|
||||
first time You have received notice of non-compliance with this License
|
||||
from such Contributor, and You become compliant prior to 30 days after
|
||||
Your receipt of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions,
|
||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||
directly or indirectly infringes any patent, then the rights granted to
|
||||
You by any and all Contributors for the Covered Software under Section
|
||||
2.1 of this License shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||
end user license agreements (excluding distributors and resellers) which
|
||||
have been validly granted by You or Your distributors under this License
|
||||
prior to termination shall survive termination.
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 6. Disclaimer of Warranty *
|
||||
* ------------------------- *
|
||||
* *
|
||||
* Covered Software is provided under this License on an "as is" *
|
||||
* basis, without warranty of any kind, either expressed, implied, or *
|
||||
* statutory, including, without limitation, warranties that the *
|
||||
* Covered Software is free of defects, merchantable, fit for a *
|
||||
* particular purpose or non-infringing. The entire risk as to the *
|
||||
* quality and performance of the Covered Software is with You. *
|
||||
* Should any Covered Software prove defective in any respect, You *
|
||||
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||
* essential part of this License. No use of any Covered Software is *
|
||||
* authorized under this License except under this disclaimer. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 7. Limitation of Liability *
|
||||
* -------------------------- *
|
||||
* *
|
||||
* Under no circumstances and under no legal theory, whether tort *
|
||||
* (including negligence), contract, or otherwise, shall any *
|
||||
* Contributor, or anyone who distributes Covered Software as *
|
||||
* permitted above, be liable to You for any direct, indirect, *
|
||||
* special, incidental, or consequential damages of any character *
|
||||
* including, without limitation, damages for lost profits, loss of *
|
||||
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||
* and all other commercial damages or losses, even if such party *
|
||||
* shall have been informed of the possibility of such damages. This *
|
||||
* limitation of liability shall not apply to liability for death or *
|
||||
* personal injury resulting from such party's negligence to the *
|
||||
* extent applicable law prohibits such limitation. Some *
|
||||
* jurisdictions do not allow the exclusion or limitation of *
|
||||
* incidental or consequential damages, so this exclusion and *
|
||||
* limitation may not apply to You. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
8. Litigation
|
||||
-------------
|
||||
|
||||
Any litigation relating to this License may be brought only in the
|
||||
courts of a jurisdiction where the defendant maintains its principal
|
||||
place of business and such litigation shall be governed by laws of that
|
||||
jurisdiction, without reference to its conflict-of-law provisions.
|
||||
Nothing in this Section shall prevent a party's ability to bring
|
||||
cross-claims or counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
----------------
|
||||
|
||||
This License represents the complete agreement concerning the subject
|
||||
matter hereof. If any provision of this License is held to be
|
||||
unenforceable, such provision shall be reformed only to the extent
|
||||
necessary to make it enforceable. Any law or regulation which provides
|
||||
that the language of a contract shall be construed against the drafter
|
||||
shall not be used to construe this License against a Contributor.
|
||||
|
||||
10. Versions of the License
|
||||
---------------------------
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version
|
||||
of the License under which You originally received the Covered Software,
|
||||
or under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a
|
||||
modified version of this License if you rename the license and remove
|
||||
any references to the name of the license steward (except to note that
|
||||
such modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||
Licenses
|
||||
|
||||
If You choose to distribute Source Code Form that is Incompatible With
|
||||
Secondary Licenses under the terms of this version of the License, the
|
||||
notice described in Exhibit B of this License must be attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
-------------------------------------------
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular
|
||||
file, then You may include the notice in a location (such as a LICENSE
|
||||
file in a relevant directory) where a recipient would be likely to look
|
||||
for such a notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
---------------------------------------------------------
|
||||
|
||||
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
defined by the Mozilla Public License, v. 2.0.
|
||||
@@ -0,0 +1,32 @@
|
||||
Build
|
||||
|
||||
Compiling LAME:
|
||||
```
|
||||
emconfigure ./configure \
|
||||
CFLAGS="-DNDEBUG -DNO_STDIO -O3 -msimd128" \
|
||||
--disable-dependency-tracking \
|
||||
--disable-shared \
|
||||
--disable-gtktest \
|
||||
--disable-analyzer-hooks \
|
||||
--disable-decoder \
|
||||
--disable-frontend
|
||||
|
||||
emmake make clean
|
||||
emmake make
|
||||
```
|
||||
|
||||
Then extract the file `libmp3lame/.libs/libmp3lame.a`, which you'll need to build the LAME bridge.
|
||||
|
||||
Compiling the LAME bridge:
|
||||
```
|
||||
emcc src/lame-bridge.c build/libmp3lame.a \
|
||||
-s MODULARIZE=1 \
|
||||
-s EXPORT_ES6=1 \
|
||||
-s SINGLE_FILE=1 \
|
||||
-s ENVIRONMENT=web,worker \
|
||||
-s EXPORTED_RUNTIME_METHODS=cwrap,HEAPU8 \
|
||||
-s EXPORTED_FUNCTIONS=_malloc,_free \
|
||||
-msimd128 \
|
||||
-O3 \
|
||||
-o build/lame.js
|
||||
```
|
||||
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
|
||||
"mainEntryPointFilePath": "dist/modules/src/index.d.ts",
|
||||
"bundledPackages": [],
|
||||
"compiler": {},
|
||||
"apiReport": {
|
||||
"enabled": false
|
||||
},
|
||||
"docModel": {
|
||||
"enabled": false
|
||||
},
|
||||
"dtsRollup": {
|
||||
"enabled": true,
|
||||
"untrimmedFilePath": "dist/mediabunny-mp3-encoder.d.ts"
|
||||
},
|
||||
"tsdocMetadata": {
|
||||
"enabled": false
|
||||
},
|
||||
"messages": {
|
||||
"compilerMessageReporting": {
|
||||
"default": {
|
||||
"logLevel": "warning"
|
||||
}
|
||||
},
|
||||
"extractorMessageReporting": {
|
||||
"default": {
|
||||
"logLevel": "warning"
|
||||
}
|
||||
},
|
||||
"tsdocMessageReporting": {
|
||||
"default": {
|
||||
"logLevel": "warning"
|
||||
}
|
||||
}
|
||||
},
|
||||
"newlineKind": "lf"
|
||||
}
|
||||
Generated
+2
File diff suppressed because one or more lines are too long
Generated
BIN
Binary file not shown.
Executable
+1342
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "@mediabunny/mp3-encoder",
|
||||
"version": "0.0.1",
|
||||
"main": "./dist/bundles/mediabunny-mp3-encoder.mjs",
|
||||
"module": "./dist/bundles/mediabunny-mp3-encoder.mjs",
|
||||
"types": "./dist/modules/src/index.d.ts",
|
||||
"exports": {
|
||||
"types": "./dist/modules/src/index.d.ts",
|
||||
"import": "./dist/bundles/mediabunny-mp3-encoder.mjs",
|
||||
"require": "./dist/bundles/mediabunny-mp3-encoder.mjs"
|
||||
},
|
||||
"files": [
|
||||
"README.md",
|
||||
"package.json",
|
||||
"LICENSE",
|
||||
"dist",
|
||||
"src"
|
||||
],
|
||||
"sideEffects": false,
|
||||
"peerDependencies": {
|
||||
"mediabunny": "^1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/emscripten": "^1.40.1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
/*!
|
||||
* Copyright (c) 2025-present, Vanilagy and contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import createModule from '../build/lame';
|
||||
import type { WorkerCommand, WorkerResponse, WorkerResponseData } from './shared';
|
||||
|
||||
type ExtendedEmscriptenModule = EmscriptenModule & {
|
||||
cwrap: typeof cwrap;
|
||||
};
|
||||
|
||||
let module: ExtendedEmscriptenModule;
|
||||
let lamePtr: number;
|
||||
let storedNumberOfChannels: number;
|
||||
let storedSampleRate: number;
|
||||
let storedBitrate: number;
|
||||
let needsReset = false;
|
||||
|
||||
let initLame: (
|
||||
numberOfChannels: number,
|
||||
sampleRate: number,
|
||||
bitrate: number
|
||||
) => number;
|
||||
let encodeSamples: (
|
||||
lame: number,
|
||||
leftPtr: number,
|
||||
rightPtr: number,
|
||||
sampleCount: number,
|
||||
outputPtr: number,
|
||||
outputSize: number
|
||||
) => number;
|
||||
let flushLame: (lame: number, outputPtr: number, outputSize: number) => number;
|
||||
let closeLame: (lame: number) => void;
|
||||
|
||||
let inputSlice: Slice | null = null;
|
||||
let outputSlice: Slice | null = null;
|
||||
|
||||
const init = async (numberOfChannels: number, sampleRate: number, bitrate: number) => {
|
||||
storedNumberOfChannels = numberOfChannels;
|
||||
storedSampleRate = sampleRate;
|
||||
storedBitrate = bitrate;
|
||||
|
||||
module = (await createModule()) as ExtendedEmscriptenModule;
|
||||
|
||||
// Set up the functions
|
||||
initLame = module.cwrap('init_lame', 'number', ['number', 'number', 'number']);
|
||||
encodeSamples = module.cwrap(
|
||||
'encode_samples',
|
||||
'number',
|
||||
['number', 'number', 'number', 'number', 'number', 'number'],
|
||||
);
|
||||
flushLame = module.cwrap('flush_lame', 'number', ['number', 'number', 'number']);
|
||||
closeLame = module.cwrap('close_lame', null, ['number']);
|
||||
|
||||
lamePtr = initLame(numberOfChannels, sampleRate, bitrate);
|
||||
};
|
||||
|
||||
const reset = () => {
|
||||
closeLame(lamePtr);
|
||||
lamePtr = initLame(storedNumberOfChannels, storedSampleRate, storedBitrate);
|
||||
};
|
||||
|
||||
const encode = (audioData: ArrayBuffer, numberOfFrames: number) => {
|
||||
if (needsReset) {
|
||||
reset();
|
||||
needsReset = false;
|
||||
}
|
||||
|
||||
const audioBytes = new Uint8Array(audioData);
|
||||
const sizePerChannel = audioBytes.length / storedNumberOfChannels;
|
||||
|
||||
inputSlice = maybeGrowSlice(inputSlice, audioBytes.length);
|
||||
module.HEAPU8.set(audioBytes, inputSlice.ptr);
|
||||
|
||||
const requiredOutputSize = Math.ceil(1.25 * numberOfFrames + 7200);
|
||||
outputSlice = maybeGrowSlice(outputSlice, requiredOutputSize);
|
||||
|
||||
const bytesWritten = encodeSamples(
|
||||
lamePtr,
|
||||
inputSlice.ptr,
|
||||
inputSlice.ptr + (storedNumberOfChannels - 1) * sizePerChannel,
|
||||
numberOfFrames,
|
||||
outputSlice.ptr,
|
||||
requiredOutputSize,
|
||||
);
|
||||
|
||||
const result = module.HEAPU8.slice(outputSlice.ptr, outputSlice.ptr + bytesWritten);
|
||||
return result.buffer;
|
||||
};
|
||||
|
||||
const flush = () => {
|
||||
if (needsReset) {
|
||||
reset();
|
||||
needsReset = false;
|
||||
}
|
||||
|
||||
const requiredOutputSize = 7200;
|
||||
outputSlice = maybeGrowSlice(outputSlice, requiredOutputSize);
|
||||
|
||||
const bytesWritten = flushLame(lamePtr, outputSlice.ptr, requiredOutputSize);
|
||||
|
||||
const result = module.HEAPU8.slice(outputSlice.ptr, outputSlice.ptr + bytesWritten);
|
||||
needsReset = true; // After a flush, the encoder must be prepared to start a new encoding process
|
||||
|
||||
return result.buffer;
|
||||
};
|
||||
|
||||
/** A "fat pointer" type thing. */
|
||||
type Slice = {
|
||||
ptr: number;
|
||||
size: number;
|
||||
};
|
||||
|
||||
/** Either returns the existing slice, or allocates a new one if there's no existing slice or it was too small. */
|
||||
const maybeGrowSlice = (slice: Slice | null, requiredSize: number) => {
|
||||
if (!slice || slice.size < requiredSize) {
|
||||
if (slice) {
|
||||
module._free(slice.ptr);
|
||||
}
|
||||
|
||||
return {
|
||||
ptr: module._malloc(requiredSize),
|
||||
size: requiredSize,
|
||||
};
|
||||
}
|
||||
|
||||
return slice;
|
||||
};
|
||||
|
||||
const onMessage = (data: { id: number; command: WorkerCommand }) => {
|
||||
const { id, command } = data;
|
||||
|
||||
const handleCommand = async (): Promise<void> => {
|
||||
try {
|
||||
let result: WorkerResponseData;
|
||||
const transferables: Transferable[] = [];
|
||||
|
||||
switch (command.type) {
|
||||
case 'init': {
|
||||
await init(
|
||||
command.data.numberOfChannels,
|
||||
command.data.sampleRate,
|
||||
command.data.bitrate,
|
||||
);
|
||||
result = { success: true };
|
||||
}; break;
|
||||
|
||||
case 'encode': {
|
||||
const encodedData = encode(
|
||||
command.data.audioData,
|
||||
command.data.numberOfFrames,
|
||||
);
|
||||
result = { encodedData };
|
||||
transferables.push(encodedData);
|
||||
}; break;
|
||||
|
||||
case 'flush': {
|
||||
const flushedData = flush();
|
||||
result = { flushedData };
|
||||
transferables.push(flushedData);
|
||||
}; break;
|
||||
}
|
||||
|
||||
const response: WorkerResponse = {
|
||||
id,
|
||||
success: true,
|
||||
data: result,
|
||||
};
|
||||
sendMessage(response, transferables);
|
||||
} catch (error) {
|
||||
const response: WorkerResponse = {
|
||||
id,
|
||||
success: false,
|
||||
error,
|
||||
};
|
||||
sendMessage(response);
|
||||
}
|
||||
};
|
||||
|
||||
void handleCommand();
|
||||
};
|
||||
|
||||
const sendMessage = (data: unknown, transferables?: Transferable[]) => {
|
||||
if (parentPort) {
|
||||
parentPort.postMessage(data, transferables ?? []);
|
||||
} else {
|
||||
self.postMessage(data, { transfer: transferables ?? [] });
|
||||
}
|
||||
};
|
||||
|
||||
let parentPort: {
|
||||
postMessage: (data: unknown, transferables?: Transferable[]) => void;
|
||||
on: (event: string, listener: (data: never) => void) => void;
|
||||
} | null = null;
|
||||
|
||||
if (typeof self === 'undefined') {
|
||||
// We're in Node.js (or a runtime that mimics it)
|
||||
const workerModule = 'worker_threads';
|
||||
// eslint-disable-next-line @stylistic/max-len
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
|
||||
parentPort = require(workerModule).parentPort;
|
||||
}
|
||||
|
||||
if (parentPort) {
|
||||
parentPort.on('message', onMessage);
|
||||
} else {
|
||||
self.addEventListener('message', event => onMessage(event.data as { id: number; command: WorkerCommand }));
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
/*!
|
||||
* Copyright (c) 2025-present, Vanilagy and contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { CustomAudioEncoder, AudioCodec, AudioSample, EncodedPacket, registerEncoder } from 'mediabunny';
|
||||
import { FRAME_HEADER_SIZE, readFrameHeader } from '../../../shared/mp3-misc';
|
||||
import type { WorkerCommand, WorkerResponse, WorkerResponseData } from './shared';
|
||||
// @ts-expect-error An esbuild plugin handles this, TypeScript doesn't need to understand
|
||||
import createWorker from './encode.worker';
|
||||
|
||||
class Mp3Encoder extends CustomAudioEncoder {
|
||||
private worker!: Worker;
|
||||
private nextMessageId = 0;
|
||||
private pendingMessages = new Map<number, {
|
||||
resolve: (value: WorkerResponseData) => void;
|
||||
reject: (reason?: unknown) => void;
|
||||
}>();
|
||||
|
||||
private buffer = new Uint8Array(2 ** 16);
|
||||
private currentBufferOffset = 0;
|
||||
private currentTimestamp = 0;
|
||||
private chunkMetadata!: EncodedAudioChunkMetadata;
|
||||
|
||||
static override supports(codec: AudioCodec, config: AudioDecoderConfig): boolean {
|
||||
return codec === 'mp3'
|
||||
&& (config.numberOfChannels === 1 || config.numberOfChannels === 2)
|
||||
&& (config.sampleRate === 32000 || config.sampleRate === 44100 || config.sampleRate === 48000);
|
||||
}
|
||||
|
||||
async init() {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
||||
this.worker = (await createWorker()) as Worker; // The actual encoding takes place in this worker
|
||||
|
||||
const onMessage = (data: WorkerResponse) => {
|
||||
const pending = this.pendingMessages.get(data.id);
|
||||
assert(pending !== undefined);
|
||||
|
||||
this.pendingMessages.delete(data.id);
|
||||
if (data.success) {
|
||||
pending.resolve(data.data);
|
||||
} else {
|
||||
pending.reject(data.error);
|
||||
}
|
||||
};
|
||||
|
||||
if (this.worker.addEventListener) {
|
||||
this.worker.addEventListener('message', event => onMessage(event.data as WorkerResponse));
|
||||
} else {
|
||||
const nodeWorker = this.worker as unknown as {
|
||||
on: (event: string, listener: (data: never) => void) => void;
|
||||
};
|
||||
nodeWorker.on('message', onMessage);
|
||||
}
|
||||
|
||||
assert(this.config.bitrate);
|
||||
|
||||
await this.sendCommand({
|
||||
type: 'init',
|
||||
data: {
|
||||
numberOfChannels: this.config.numberOfChannels,
|
||||
sampleRate: this.config.sampleRate,
|
||||
bitrate: this.config.bitrate,
|
||||
},
|
||||
});
|
||||
|
||||
this.chunkMetadata = {
|
||||
decoderConfig: {
|
||||
codec: 'mp3',
|
||||
numberOfChannels: this.config.numberOfChannels,
|
||||
sampleRate: this.config.sampleRate,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async encode(audioSample: AudioSample) {
|
||||
const sizePerChannel = audioSample.allocationSize({
|
||||
format: 's16-planar',
|
||||
planeIndex: 0,
|
||||
});
|
||||
|
||||
const requiredBytes = audioSample.numberOfChannels * sizePerChannel;
|
||||
const audioData = new ArrayBuffer(requiredBytes);
|
||||
const audioBytes = new Uint8Array(audioData);
|
||||
|
||||
for (let i = 0; i < audioSample.numberOfChannels; i++) {
|
||||
audioSample.copyTo(audioBytes.subarray(i * sizePerChannel), {
|
||||
format: 's16-planar', // LAME wants it in this format
|
||||
planeIndex: i,
|
||||
});
|
||||
}
|
||||
|
||||
const result = await this.sendCommand({
|
||||
type: 'encode',
|
||||
data: {
|
||||
audioData,
|
||||
numberOfFrames: audioSample.numberOfFrames,
|
||||
},
|
||||
}, [audioData]);
|
||||
|
||||
assert('encodedData' in result);
|
||||
this.digestOutput(new Uint8Array(result.encodedData));
|
||||
}
|
||||
|
||||
async flush() {
|
||||
const result = await this.sendCommand({ type: 'flush' });
|
||||
|
||||
assert('flushedData' in result);
|
||||
this.digestOutput(new Uint8Array(result.flushedData));
|
||||
}
|
||||
|
||||
close() {
|
||||
this.worker.terminate();
|
||||
}
|
||||
|
||||
/**
|
||||
* LAME returns data in chunks, but a chunk doesn't need to contain a full MP3 frame. Therefore, we must accumulate
|
||||
* these chunks and extract the MP3 frames only when they're complete.
|
||||
*/
|
||||
private digestOutput(bytes: Uint8Array) {
|
||||
const requiredBufferSize = this.currentBufferOffset + bytes.length;
|
||||
if (requiredBufferSize > this.buffer.length) {
|
||||
// Grow the buffer to the required size
|
||||
const newSize = 1 << Math.ceil(Math.log2(requiredBufferSize));
|
||||
const newBuffer = new Uint8Array(newSize);
|
||||
newBuffer.set(this.buffer);
|
||||
this.buffer = newBuffer;
|
||||
}
|
||||
|
||||
this.buffer.set(bytes, this.currentBufferOffset);
|
||||
this.currentBufferOffset = requiredBufferSize;
|
||||
|
||||
let pos = 0;
|
||||
while (pos <= this.currentBufferOffset - FRAME_HEADER_SIZE) {
|
||||
const word = new DataView(this.buffer.buffer).getUint32(pos, false);
|
||||
const header = readFrameHeader(word, { pos, fileSize: null });
|
||||
if (!header) {
|
||||
break;
|
||||
}
|
||||
|
||||
const fits = header.totalSize <= this.currentBufferOffset - pos;
|
||||
if (!fits) {
|
||||
// The frame isn't complete yet
|
||||
break;
|
||||
}
|
||||
|
||||
const data = this.buffer.slice(pos, pos + header.totalSize);
|
||||
const duration = header.audioSamplesInFrame / header.sampleRate;
|
||||
this.onPacket(new EncodedPacket(data, 'key', this.currentTimestamp, duration), this.chunkMetadata);
|
||||
|
||||
if (this.currentTimestamp === 0) {
|
||||
this.chunkMetadata = {}; // Mimic WebCodecs-like behavior
|
||||
}
|
||||
|
||||
this.currentTimestamp += duration;
|
||||
pos += header.totalSize;
|
||||
}
|
||||
|
||||
if (pos > 0) {
|
||||
// Shift the data
|
||||
this.buffer.set(this.buffer.subarray(pos, this.currentBufferOffset), 0);
|
||||
this.currentBufferOffset -= pos;
|
||||
}
|
||||
}
|
||||
|
||||
private sendCommand(
|
||||
command: WorkerCommand,
|
||||
transferables?: Transferable[],
|
||||
) {
|
||||
return new Promise<WorkerResponseData>((resolve, reject) => {
|
||||
const id = this.nextMessageId++;
|
||||
this.pendingMessages.set(id, { resolve, reject });
|
||||
|
||||
if (transferables) {
|
||||
this.worker.postMessage({ id, command }, transferables);
|
||||
} else {
|
||||
this.worker.postMessage({ id, command });
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the LAME MP3 encoder, which Mediabunny will then use automatically when applicable. Make sure to call this
|
||||
* function before starting any encoding task.
|
||||
*
|
||||
* Preferably, wrap the call in a condition to avoid overriding any native MP3 encoder:
|
||||
*
|
||||
* ```ts
|
||||
* import { canEncodeAudio } from 'mediabunny';
|
||||
* import { registerMp3Encoder } from '@mediabunny/mp3-encoder';
|
||||
*
|
||||
* if (!(await canEncodeAudio('mp3'))) {
|
||||
* registerMp3Encoder();
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const registerMp3Encoder = () => {
|
||||
registerEncoder(Mp3Encoder);
|
||||
};
|
||||
|
||||
function assert(x: unknown): asserts x {
|
||||
if (!x) {
|
||||
throw new Error('Assertion failed.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*!
|
||||
* Copyright (c) 2025-present, Vanilagy and contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include <emscripten.h>
|
||||
#include "../lib/lame.h"
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
lame_global_flags *init_lame(int number_of_channels, int sample_rate, int bitrate) {
|
||||
lame_global_flags *gfp = lame_init();
|
||||
|
||||
lame_set_num_channels(gfp, number_of_channels);
|
||||
lame_set_in_samplerate(gfp, sample_rate);
|
||||
lame_set_out_samplerate(gfp, sample_rate);
|
||||
lame_set_brate(gfp, bitrate / 1000); // MP3 wants "kilobitrate"
|
||||
lame_set_bWriteVbrTag(gfp, 0);
|
||||
|
||||
int ret_code = lame_init_params(gfp);
|
||||
|
||||
return gfp;
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
int encode_samples(lame_global_flags *gfp, short int left_buf[], short int right_buf[], int sample_count, unsigned char *dest_buf, int dest_buf_size) {
|
||||
return lame_encode_buffer(gfp, left_buf, right_buf, sample_count, dest_buf, dest_buf_size);
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
int flush_lame(lame_global_flags *gfp, unsigned char *dest_buf, int dest_buf_size) {
|
||||
return lame_encode_flush(gfp, dest_buf, dest_buf_size);
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
void close_lame(lame_global_flags *gfp) {
|
||||
lame_close(gfp);
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*!
|
||||
* Copyright (c) 2025-present, Vanilagy and contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
export type WorkerCommand = {
|
||||
type: 'init';
|
||||
data: {
|
||||
numberOfChannels: number;
|
||||
sampleRate: number;
|
||||
bitrate: number;
|
||||
};
|
||||
} | {
|
||||
type: 'encode';
|
||||
data: {
|
||||
audioData: ArrayBuffer;
|
||||
numberOfFrames: number;
|
||||
};
|
||||
} | {
|
||||
type: 'flush';
|
||||
};
|
||||
|
||||
export type WorkerResponseData = {
|
||||
success: boolean;
|
||||
} | {
|
||||
encodedData: ArrayBuffer;
|
||||
} | {
|
||||
flushedData: ArrayBuffer;
|
||||
};
|
||||
|
||||
export type WorkerResponse = {
|
||||
id: number;
|
||||
} & ({
|
||||
success: true;
|
||||
data: WorkerResponseData;
|
||||
} | {
|
||||
success: false;
|
||||
error: unknown;
|
||||
});
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"extends": "../../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../dist/modules",
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"stripInternal": true,
|
||||
"noEmit": false,
|
||||
"moduleResolution": "nodenext",
|
||||
"module": "nodenext",
|
||||
"allowJs": true,
|
||||
"paths": {
|
||||
"mediabunny": ["../../../src/index.ts"],
|
||||
},
|
||||
},
|
||||
"include": [
|
||||
"**/*",
|
||||
"../../../shared/**/*"
|
||||
],
|
||||
"references": [
|
||||
{ "path": "../../../src" }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
import * as esbuild from 'esbuild';
|
||||
import process from 'node:process';
|
||||
import PluginExternalGlobal from 'esbuild-plugin-external-global';
|
||||
import { inlineWorkerPlugin } from './esbuild/inlined-workers.js';
|
||||
|
||||
/** Creates UMD and ESM variants, each unminified and minified. */
|
||||
const createVariants = async (
|
||||
entryPoint: string,
|
||||
globalName: string,
|
||||
outfileBase: string,
|
||||
umdExtension: string,
|
||||
specificUmdConfig: esbuild.BuildOptions = {},
|
||||
specificEsmConfig: esbuild.BuildOptions = {},
|
||||
) => {
|
||||
const baseConfig: esbuild.BuildOptions = {
|
||||
entryPoints: [entryPoint],
|
||||
bundle: true,
|
||||
logLevel: 'info',
|
||||
banner: {
|
||||
js: `/*!
|
||||
* Copyright (c) 2025-present, Vanilagy and contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/`,
|
||||
},
|
||||
legalComments: 'none',
|
||||
};
|
||||
|
||||
const umdConfig: esbuild.BuildOptions = {
|
||||
...baseConfig,
|
||||
format: 'iife',
|
||||
globalName,
|
||||
footer: {
|
||||
js:
|
||||
`if (typeof module === "object" && typeof module.exports === "object") Object.assign(module.exports, ${globalName})`,
|
||||
},
|
||||
};
|
||||
|
||||
const esmConfig: esbuild.BuildOptions = {
|
||||
...baseConfig,
|
||||
format: 'esm',
|
||||
};
|
||||
|
||||
const umdVariant = await esbuild.context({
|
||||
...umdConfig,
|
||||
...specificUmdConfig,
|
||||
outfile: `${outfileBase}.${umdExtension}`,
|
||||
});
|
||||
|
||||
const esmVariant = await esbuild.context({
|
||||
...esmConfig,
|
||||
...specificEsmConfig,
|
||||
outfile: `${outfileBase}.mjs`,
|
||||
});
|
||||
|
||||
const umdMinifiedVariant = await esbuild.context({
|
||||
...umdConfig,
|
||||
...specificUmdConfig,
|
||||
outfile: `${outfileBase}.min.${umdExtension}`,
|
||||
minify: true,
|
||||
});
|
||||
|
||||
const esmMinifiedVariant = await esbuild.context({
|
||||
...esmConfig,
|
||||
...specificEsmConfig,
|
||||
outfile: `${outfileBase}.min.mjs`,
|
||||
minify: true,
|
||||
});
|
||||
|
||||
return [umdVariant, esmVariant, umdMinifiedVariant, esmMinifiedVariant];
|
||||
};
|
||||
|
||||
const mediabunnyVariants = await createVariants(
|
||||
'src/index.ts',
|
||||
'Mediabunny',
|
||||
'dist/bundles/mediabunny',
|
||||
'cjs',
|
||||
);
|
||||
|
||||
const mp3EncoderVariants = await createVariants(
|
||||
'packages/mp3-encoder/src/index.ts',
|
||||
'MediabunnyMp3Encoder',
|
||||
'packages/mp3-encoder/dist/bundles/mediabunny-mp3-encoder',
|
||||
'js', // The bundles are purely for the browser, not for Node (due to the peer dependecy)
|
||||
{
|
||||
plugins: [
|
||||
PluginExternalGlobal.externalGlobalPlugin({
|
||||
mediabunny: 'Mediabunny',
|
||||
}),
|
||||
inlineWorkerPlugin({
|
||||
define: {
|
||||
'import.meta.url': '""',
|
||||
},
|
||||
legalComments: 'none',
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
external: ['mediabunny'],
|
||||
plugins: [
|
||||
inlineWorkerPlugin({
|
||||
define: {
|
||||
'import.meta.url': '""',
|
||||
},
|
||||
legalComments: 'none',
|
||||
}),
|
||||
],
|
||||
},
|
||||
);
|
||||
|
||||
const contexts = [
|
||||
...mediabunnyVariants,
|
||||
...mp3EncoderVariants,
|
||||
];
|
||||
|
||||
if (process.argv[2] === '--watch') {
|
||||
await Promise.all(contexts.map(ctx => ctx.watch()));
|
||||
} else {
|
||||
for (const ctx of contexts) {
|
||||
await ctx.rebuild();
|
||||
await ctx.dispose();
|
||||
}
|
||||
}
|
||||
@@ -23,17 +23,20 @@ const checkFile = (filePath: string) => {
|
||||
|
||||
const checkDirectory = (dirPath: string) => {
|
||||
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
||||
|
||||
for (const entry of entries) {
|
||||
const fullPath = path.join(dirPath, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
checkDirectory(fullPath);
|
||||
} else if (entry.name.endsWith('.ts')) {
|
||||
} else if (entry.name.endsWith('.ts') || entry.name.endsWith('.c')) {
|
||||
checkFile(fullPath);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
checkDirectory(path.join(__dirname, '..', 'src'));
|
||||
checkDirectory(path.join(__dirname, '..', 'packages', 'mp3-encoder', 'src'));
|
||||
checkDirectory(path.join(__dirname, '..', 'shared'));
|
||||
|
||||
if (missingFiles.length > 0) {
|
||||
console.error('Files missing license header:');
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
// Adapted from https://github.com/mitschabaude/esbuild-plugin-inline-worker
|
||||
|
||||
import * as esbuild from 'esbuild';
|
||||
import findCacheDir from 'find-cache-dir';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
export function inlineWorkerPlugin(extraConfig: esbuild.BuildOptions): esbuild.Plugin {
|
||||
return {
|
||||
name: 'esbuild-plugin-inline-worker',
|
||||
|
||||
setup(build) {
|
||||
build.onLoad(
|
||||
{ filter: /\.worker\.(js|jsx|ts|tsx)$/ },
|
||||
async ({ path: workerPath }) => {
|
||||
const workerCode = await buildWorker(workerPath, extraConfig);
|
||||
return {
|
||||
contents: `import inlineWorker from '__inline-worker'
|
||||
export default function Worker() {
|
||||
return inlineWorker(${JSON.stringify(workerCode)});
|
||||
}
|
||||
`,
|
||||
loader: 'js',
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
const inlineWorkerFunctionCode = `
|
||||
export default async function inlineWorker(scriptText) {
|
||||
if (typeof Worker !== 'undefined' && typeof Bun === 'undefined') {
|
||||
// Browser, Deno
|
||||
|
||||
const blob = new Blob([scriptText], { type: "text/javascript" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const worker = new Worker(url, { type: typeof Deno !== 'undefined' ? 'module' : undefined }); // module for Deno
|
||||
URL.revokeObjectURL(url);
|
||||
return worker;
|
||||
} else {
|
||||
// Node, Bun (Bun's Worker is flaky, worker_threads works much better)
|
||||
|
||||
let Worker;
|
||||
try {
|
||||
Worker = (await import('worker_threads')).Worker;
|
||||
} catch {
|
||||
const workerModule = 'worker_threads';
|
||||
Worker = require(workerModule).Worker;
|
||||
}
|
||||
|
||||
const worker = new Worker(scriptText, { eval: true });
|
||||
|
||||
return worker;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
build.onResolve({ filter: /^__inline-worker$/ }, ({ path }) => {
|
||||
return { path, namespace: 'inline-worker' };
|
||||
});
|
||||
build.onLoad({ filter: /.*/, namespace: 'inline-worker' }, () => {
|
||||
return { contents: inlineWorkerFunctionCode, loader: 'js' };
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const cacheDir = findCacheDir({
|
||||
name: 'esbuild-plugin-inline-worker',
|
||||
create: true,
|
||||
});
|
||||
if (cacheDir === undefined) {
|
||||
throw new Error('Cache directory not found.');
|
||||
}
|
||||
|
||||
async function buildWorker(workerPath: string, extraConfig: esbuild.BuildOptions) {
|
||||
const scriptNameParts = path.basename(workerPath).split('.');
|
||||
scriptNameParts.pop();
|
||||
scriptNameParts.push('js');
|
||||
const scriptName = scriptNameParts.join('.');
|
||||
const bundlePath = path.resolve(cacheDir!, scriptName);
|
||||
|
||||
if (extraConfig) {
|
||||
delete extraConfig.entryPoints;
|
||||
delete extraConfig.outfile;
|
||||
delete extraConfig.outdir;
|
||||
}
|
||||
|
||||
await esbuild.build({
|
||||
entryPoints: [workerPath],
|
||||
bundle: true,
|
||||
minify: true,
|
||||
outfile: bundlePath,
|
||||
target: 'es2017',
|
||||
format: 'esm',
|
||||
...extraConfig,
|
||||
});
|
||||
|
||||
return fs.promises.readFile(bundlePath, { encoding: 'utf-8' });
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"noEmit": false,
|
||||
},
|
||||
"include": [
|
||||
"**/*"
|
||||
]
|
||||
}
|
||||
+32
-4
@@ -138,9 +138,23 @@ export const customAudioEncoders: typeof CustomAudioEncoder[] = [];
|
||||
*/
|
||||
export const registerDecoder = (decoder: typeof CustomVideoDecoder | typeof CustomAudioDecoder) => {
|
||||
if (decoder.prototype instanceof CustomVideoDecoder) {
|
||||
customVideoDecoders.push(decoder as typeof CustomVideoDecoder);
|
||||
const casted = decoder as typeof CustomVideoDecoder;
|
||||
|
||||
if (customVideoDecoders.includes(casted)) {
|
||||
console.warn('Video decoder already registered.');
|
||||
return;
|
||||
}
|
||||
|
||||
customVideoDecoders.push(casted);
|
||||
} else if (decoder.prototype instanceof CustomAudioDecoder) {
|
||||
customAudioDecoders.push(decoder as typeof CustomAudioDecoder);
|
||||
const casted = decoder as typeof CustomAudioDecoder;
|
||||
|
||||
if (customAudioDecoders.includes(casted)) {
|
||||
console.warn('Audio decoder already registered.');
|
||||
return;
|
||||
}
|
||||
|
||||
customAudioDecoders.push(casted);
|
||||
} else {
|
||||
throw new TypeError('Decoder must be a CustomVideoDecoder or CustomAudioDecoder.');
|
||||
}
|
||||
@@ -153,9 +167,23 @@ export const registerDecoder = (decoder: typeof CustomVideoDecoder | typeof Cust
|
||||
*/
|
||||
export const registerEncoder = (encoder: typeof CustomVideoEncoder | typeof CustomAudioEncoder) => {
|
||||
if (encoder.prototype instanceof CustomVideoEncoder) {
|
||||
customVideoEncoders.push(encoder as typeof CustomVideoEncoder);
|
||||
const casted = encoder as typeof CustomVideoEncoder;
|
||||
|
||||
if (customVideoEncoders.includes(casted)) {
|
||||
console.warn('Video encoder already registered.');
|
||||
return;
|
||||
}
|
||||
|
||||
customVideoEncoders.push(casted);
|
||||
} else if (encoder.prototype instanceof CustomAudioEncoder) {
|
||||
customAudioEncoders.push(encoder as typeof CustomAudioEncoder);
|
||||
const casted = encoder as typeof CustomAudioEncoder;
|
||||
|
||||
if (customAudioEncoders.includes(casted)) {
|
||||
console.warn('Audio encoder already registered.');
|
||||
return;
|
||||
}
|
||||
|
||||
customAudioEncoders.push(casted);
|
||||
} else {
|
||||
throw new TypeError('Encoder must be a CustomVideoEncoder or CustomAudioEncoder.');
|
||||
}
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ import { IsobmffReader } from './isobmff/isobmff-reader';
|
||||
import { EBMLId, EBMLReader } from './matroska/ebml';
|
||||
import { MatroskaDemuxer } from './matroska/matroska-demuxer';
|
||||
import { Mp3Demuxer } from './mp3/mp3-demuxer';
|
||||
import { FRAME_HEADER_SIZE } from './mp3/mp3-misc';
|
||||
import { FRAME_HEADER_SIZE } from '../shared/mp3-misc';
|
||||
import { Mp3Reader } from './mp3/mp3-reader';
|
||||
import { OggDemuxer } from './ogg/ogg-demuxer';
|
||||
import { OggReader } from './ogg/ogg-reader';
|
||||
|
||||
+1
-3
@@ -1527,9 +1527,7 @@ type MediaStreamTrackProcessorControllerMessage = {
|
||||
const mediaStreamTrackProcessorWorkerCode = () => {
|
||||
const sendMessage = (message: MediaStreamTrackProcessorWorkerMessage, transfer?: Transferable[]) => {
|
||||
if (transfer) {
|
||||
// The error is bullshit, it's using the wrong postMessage
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
|
||||
self.postMessage(message, transfer as any);
|
||||
self.postMessage(message, { transfer });
|
||||
} else {
|
||||
self.postMessage(message);
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import { InputAudioTrack, InputAudioTrackBacking } from '../input-track';
|
||||
import { PacketRetrievalOptions } from '../media-sink';
|
||||
import { assert, binarySearchExact, binarySearchLessOrEqual, last, UNDETERMINED_LANGUAGE } from '../misc';
|
||||
import { EncodedPacket, PLACEHOLDER_DATA } from '../packet';
|
||||
import { FrameHeader, getXingOffset, INFO, XING } from './mp3-misc';
|
||||
import { FrameHeader, getXingOffset, INFO, XING } from '../../shared/mp3-misc';
|
||||
import { Mp3Reader } from './mp3-reader';
|
||||
|
||||
type Sample = {
|
||||
|
||||
@@ -12,7 +12,7 @@ import { Output, OutputAudioTrack } from '../output';
|
||||
import { Mp3OutputFormat } from '../output-format';
|
||||
import { EncodedPacket } from '../packet';
|
||||
import { Writer } from '../writer';
|
||||
import { getXingOffset, INFO, readFrameHeader, XING } from './mp3-misc';
|
||||
import { getXingOffset, INFO, readFrameHeader, XING } from '../../shared/mp3-misc';
|
||||
import { Mp3Writer, XingFrameData } from './mp3-writer';
|
||||
|
||||
export class Mp3Muxer extends Muxer {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
import { assert } from '../misc';
|
||||
import { Reader } from '../reader';
|
||||
import { FRAME_HEADER_SIZE, FrameHeader, readFrameHeader } from './mp3-misc';
|
||||
import { FRAME_HEADER_SIZE, FrameHeader, readFrameHeader } from '../../shared/mp3-misc';
|
||||
|
||||
export class Mp3Reader {
|
||||
pos = 0;
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
MPEG_V2_BITRATES,
|
||||
SAMPLING_RATES,
|
||||
XING,
|
||||
} from './mp3-misc';
|
||||
} from '../../shared/mp3-misc';
|
||||
|
||||
export type XingFrameData = {
|
||||
mpegVersionId: number;
|
||||
|
||||
+5
-2
@@ -2,12 +2,15 @@
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../dist/modules",
|
||||
"rootDir": "..",
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"stripInternal": true,
|
||||
"noEmit": false
|
||||
"noEmit": false,
|
||||
"composite": true
|
||||
},
|
||||
"include": [
|
||||
"**/*"
|
||||
"**/*",
|
||||
"../shared/**/*"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
SWALLOWED ERRORS FOR CUSTOM ENCODESRS (like in init)
|
||||
|
||||
- mention in the docs how the delcaration file can be used together with the <script> include
|
||||
+6
-3
@@ -6,12 +6,15 @@
|
||||
"composite": true,
|
||||
"noEmit": false,
|
||||
"paths": {
|
||||
"mediabunny": ["./dist/mediabunny.d.ts"],
|
||||
"mediabunny": ["./src/index.ts"],
|
||||
},
|
||||
"types": ["vite/client"]
|
||||
},
|
||||
"include": [
|
||||
"./vite.config.ts",
|
||||
"./examples/**/*.ts",
|
||||
]
|
||||
"./examples/**/*.ts"
|
||||
],
|
||||
"references": [
|
||||
{ "path": "./src" }
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user