From cbb2681d33c34d0bcd3b3b746a0e48618b04f3dc Mon Sep 17 00:00:00 2001
From: Vanilagy <1696106+Vanilagy@users.noreply.github.com>
Date: Sun, 10 Aug 2025 00:45:30 +0200
Subject: [PATCH 1/8] Create @mediabunny/mp3-encoder, restructure and extend
build system
---
.gitignore | 6 +-
.vscode/settings.json | 5 +
api-extractor.json | 2 +-
build.mjs | 76 --
build.sh | 34 +
dev/convert.html | 7 +-
dev/demux.html | 2 +
eslint.config.mjs | 7 +-
package-lock.json | 219 +++-
package.json | 19 +-
packages/mp3-encoder/.gitattributes | 1 +
packages/mp3-encoder/LICENSE | 373 ++++++
packages/mp3-encoder/README.md | 32 +
packages/mp3-encoder/api-extractor.json | 37 +
packages/mp3-encoder/build/lame.js | 2 +
packages/mp3-encoder/build/libmp3lame.a | Bin 0 -> 344180 bytes
packages/mp3-encoder/lib/lame.h | 1342 +++++++++++++++++++++
packages/mp3-encoder/package.json | 26 +
packages/mp3-encoder/src/encode.worker.ts | 212 ++++
packages/mp3-encoder/src/index.ts | 211 ++++
packages/mp3-encoder/src/lame-bridge.c | 40 +
packages/mp3-encoder/src/shared.ts | 42 +
packages/mp3-encoder/src/tsconfig.json | 23 +
scripts/bundle.ts | 125 ++
scripts/ensure-license-headers.ts | 5 +-
scripts/esbuild/inlined-workers.ts | 98 ++
{src/mp3 => shared}/mp3-misc.ts | 0
shared/tsconfig.json | 10 +
src/custom-coder.ts | 36 +-
src/input-format.ts | 2 +-
src/media-source.ts | 4 +-
src/mp3/mp3-demuxer.ts | 2 +-
src/mp3/mp3-muxer.ts | 2 +-
src/mp3/mp3-reader.ts | 2 +-
src/mp3/mp3-writer.ts | 2 +-
src/tsconfig.json | 7 +-
todo.txt | 3 +
tsconfig.vite.json | 9 +-
38 files changed, 2913 insertions(+), 112 deletions(-)
create mode 100644 .vscode/settings.json
delete mode 100644 build.mjs
create mode 100755 build.sh
create mode 100644 packages/mp3-encoder/.gitattributes
create mode 100644 packages/mp3-encoder/LICENSE
create mode 100644 packages/mp3-encoder/README.md
create mode 100644 packages/mp3-encoder/api-extractor.json
create mode 100644 packages/mp3-encoder/build/lame.js
create mode 100644 packages/mp3-encoder/build/libmp3lame.a
create mode 100755 packages/mp3-encoder/lib/lame.h
create mode 100644 packages/mp3-encoder/package.json
create mode 100644 packages/mp3-encoder/src/encode.worker.ts
create mode 100644 packages/mp3-encoder/src/index.ts
create mode 100644 packages/mp3-encoder/src/lame-bridge.c
create mode 100644 packages/mp3-encoder/src/shared.ts
create mode 100644 packages/mp3-encoder/src/tsconfig.json
create mode 100644 scripts/bundle.ts
create mode 100644 scripts/esbuild/inlined-workers.ts
rename {src/mp3 => shared}/mp3-misc.ts (100%)
create mode 100644 shared/tsconfig.json
create mode 100644 todo.txt
diff --git a/.gitignore b/.gitignore
index 9f717d8..3c737af 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,7 @@
-/node_modules
+node_modules
/dist
/dist-docs
.DS_Store
-/docs/.vitepress/cache
\ No newline at end of file
+/docs/.vitepress/cache
+
+packages/mp3-encoder/dist
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..be441af
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,5 @@
+{
+ "files.associations": {
+ "lame.h": "c"
+ }
+}
\ No newline at end of file
diff --git a/api-extractor.json b/api-extractor.json
index 4bd5a02..662a706 100644
--- a/api-extractor.json
+++ b/api-extractor.json
@@ -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": {
diff --git a/build.mjs b/build.mjs
deleted file mode 100644
index 4ac1cb5..0000000
--- a/build.mjs
+++ /dev/null
@@ -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(),
- ]);
-}
diff --git a/build.sh b/build.sh
new file mode 100755
index 0000000..7084915
--- /dev/null
+++ b/build.sh
@@ -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
\ No newline at end of file
diff --git a/dev/convert.html b/dev/convert.html
index 38b9d23..790d8e5 100644
--- a/dev/convert.html
+++ b/dev/convert.html
@@ -1,8 +1,11 @@
+
+```
+
Requires any JavaScript environment that can run ECMAScript 2021 or later. Mediabunny is expected to be run in modern browsers. For types, TypeScript 5.7 or later is required.
### Read file metadata
diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts
index c47ab5e..bcb0330 100644
--- a/docs/.vitepress/config.mts
+++ b/docs/.vitepress/config.mts
@@ -75,6 +75,12 @@ export default withMermaid({
{ text: 'Supported formats & codecs', link: '/guide/supported-formats-and-codecs' },
],
},
+ {
+ text: 'Extensions',
+ items: [
+ { text: 'mp3-encoder', link: '/guide/extensions/mp3-encoder' },
+ ],
+ },
],
socialLinks: [
diff --git a/docs/guide/extensions/mp3-encoder.md b/docs/guide/extensions/mp3-encoder.md
new file mode 100644
index 0000000..64a32b4
--- /dev/null
+++ b/docs/guide/extensions/mp3-encoder.md
@@ -0,0 +1,90 @@
+# @mediabunny/mp3-encoder
+
+Browsers typically have no support for MP3 encoding in their WebCodecs implementations. Given the ubiquity of the format, this extension package provides an MP3 encoder for use with Mediabunny. It is implemented using Mediabunny's [custom coder API](../supported-formats-and-codecs#custom-coders) and uses a highly-performant WASM build of the [LAME MP3 Encoder](https://lame.sourceforge.io/) under the hood.
+
+
+ GitHub page
+
+
+
+## Installation
+
+This library peer-depends on Mediabunny. Install both using npm:
+```bash
+npm install mediabunny @mediabunny/mp3-encoder
+```
+
+Alternatively, directly include them using a script tag:
+```html
+
+
+```
+
+This will expose the global objects `Mediabunny` and `MediabunnyMp3Encoder`. Use `mediabunny-mp3-encoder.d.ts` to provide types for these globals. You can download the built distribution files from the [releases page](https://github.com/Vanilagy/mediabunny/releases).
+
+## Usage
+
+```ts
+import { registerMp3Encoder } from '@mediabunny/mp3-encoder';
+
+registerMp3Encoder();
+```
+That's it - Mediabunny now uses the registered MP3 encoder automatically.
+
+If you want to be more correct, check for native browser support first:
+```ts
+import { canEncodeAudio } from 'mediabunny';
+import { registerMp3Encoder } from '@mediabunny/mp3-encoder';
+
+if (!(await canEncodeAudio('mp3'))) {
+ registerMp3Encoder();
+}
+```
+
+## Example
+
+Here, we convert an input file to an MP3:
+
+```ts
+import {
+ Input,
+ ALL_FORMATS,
+ BlobSource,
+ Output,
+ BufferTarget,
+ Mp3OutputFormat,
+ canEncodeAudio,
+ Conversion,
+} from 'mediabunny';
+import { registerMp3Encoder } from '@mediabunny/mp3-encoder';
+
+if (!(await canEncodeAudio('mp3'))) {
+ // Only register the custom encoder if there's no native support
+ registerMp3Encoder();
+}
+
+const input = new Input({
+ source: new BlobSource(file), // From a file picker, for example
+ formats: ALL_FORMATS,
+});
+const output = new Output({
+ format: new Mp3OutputFormat(),
+ target: new BufferTarget(),
+});
+
+const conversion = await Conversion.init({
+ input,
+ output,
+});
+await conversion.execute();
+
+output.target.buffer; // => ArrayBuffer containing the MP3 file
+```
+
+## Implementation details
+
+This library implements an MP3 encoder by registering a custom encoder class with Mediabunny. This class, when initialized, spawns a worker which then immediately loads a WASM build of the LAME MP3 encoder. Then, raw data is sent to the worker and encoded data is received from it. These encoded chunks are then concatenated in the main thread and properly split into separate MP3 frames.
+
+Great care was put into ensuring maximum compatibility of this package; it works with bundlers, directly in the browser, as well as in Node, Deno, and Bun. All code (including worker & WASM) are bundled into a single file, eliminating the need for CDNs or WASM path arguments. This packages therefore serves as a reference implementation of WASM-based encoder extensions for Mediabunny.
+
+The WASM build itself is a performance-optimized, SIMD-enabled build of LAME 3.100, with all unneeded features disabled. Because maximum performance was the priority, the build is slighter bigger, but ~130 kB gzipped is still very reasonable in my opinion. In my tests, it encodes 5 seconds of audio in ~90 milliseconds (55x real-time speed).
\ No newline at end of file
diff --git a/docs/guide/output-formats.md b/docs/guide/output-formats.md
index cc116a8..a086fb7 100644
--- a/docs/guide/output-formats.md
+++ b/docs/guide/output-formats.md
@@ -212,6 +212,10 @@ type Mp3OutputFormatOptions = {
- `onXingFrame`\
Will be called once the Xing metadata frame is finalized, which happens at the end of the writing process.
+::: info
+Most browsers don't support encoding MP3. Use the official [`@mediabunny/mp3-encoder`](./extensions/mp3-encoder) package to polyfill an encoder.
+:::
+
## WAVE
This output format creates WAVE (.wav) files.
diff --git a/packages/mp3-encoder/README.md b/packages/mp3-encoder/README.md
index d8ae514..4251d05 100644
--- a/packages/mp3-encoder/README.md
+++ b/packages/mp3-encoder/README.md
@@ -1,7 +1,120 @@
-Build
+# @mediabunny/mp3-encoder
-Compiling LAME:
+[](https://www.npmjs.com/package/@mediabunny/mp3-encoder)
+[](https://bundlephobia.com/package/@mediabunny/mp3-encoder)
+[](https://www.npmjs.com/package/@mediabunny/mp3-encoder)
+
+
+
+
+
+Browsers typically have no support for MP3 encoding in their WebCodecs implementations. Given the ubiquity of the format, this extension package provides an MP3 encoder for use with [Mediabunny](https://github.com/Vanilagy/mediabunny). It is implemented using Mediabunny's [custom coder API](https://mediabunny.dev/guide/supported-formats-and-codecs#custom-coders) and uses a highly-performant WASM build of the [LAME MP3 Encoder](https://lame.sourceforge.io/) under the hood.
+
+> This package, like the rest of Mediabunny, is enabled by its [sponsors](https://mediabunny.dev/#sponsors) and their donations. If you've derived value from this package, please consider leaving a donation! 💘
+
+## Installation
+
+This library peer-depends on Mediabunny. Install both using npm:
+```bash
+npm install mediabunny @mediabunny/mp3-encoder
```
+
+Alternatively, directly include them using a script tag:
+```html
+
+
+```
+
+This will expose the global objects `Mediabunny` and `MediabunnyMp3Encoder`. Use `mediabunny-mp3-encoder.d.ts` to provide types for these globals. You can download the built distribution files from the [releases page](https://github.com/Vanilagy/mediabunny/releases).
+
+## Usage
+
+```ts
+import { registerMp3Encoder } from '@mediabunny/mp3-encoder';
+
+registerMp3Encoder();
+```
+That's it - Mediabunny now uses the registered MP3 encoder automatically.
+
+If you want to be more correct, check for native browser support first:
+```ts
+import { canEncodeAudio } from 'mediabunny';
+import { registerMp3Encoder } from '@mediabunny/mp3-encoder';
+
+if (!(await canEncodeAudio('mp3'))) {
+ registerMp3Encoder();
+}
+```
+
+## Example
+
+Here, we convert an input file to an MP3:
+
+```ts
+import {
+ Input,
+ ALL_FORMATS,
+ BlobSource,
+ Output,
+ BufferTarget,
+ Mp3OutputFormat,
+ canEncodeAudio,
+ Conversion,
+} from 'mediabunny';
+import { registerMp3Encoder } from '@mediabunny/mp3-encoder';
+
+if (!(await canEncodeAudio('mp3'))) {
+ // Only register the custom encoder if there's no native support
+ registerMp3Encoder();
+}
+
+const input = new Input({
+ source: new BlobSource(file), // From a file picker, for example
+ formats: ALL_FORMATS,
+});
+const output = new Output({
+ format: new Mp3OutputFormat(),
+ target: new BufferTarget(),
+});
+
+const conversion = await Conversion.init({
+ input,
+ output,
+});
+await conversion.execute();
+
+output.target.buffer; // => ArrayBuffer containing the MP3 file
+```
+
+For more ways of using Mediabunny, refer to its [guide](https://mediabunny.dev/guide/introduction).
+
+## License
+
+`@mediabunny/mp3-encoder` uses [the same MPL-2.0 license](https://sourceforge.net/projects/lame/files/lame/) as Mediabunny. The LAME MP3 Encoder is licensed under LGPL. On their [license page](https://lame.sourceforge.io/license.txt), they ask that you give them a shoutout with a link.
+
+## Implementation details
+
+This library implements an MP3 encoder by registering a custom encoder class with Mediabunny. This class, when initialized, spawns a worker which then immediately loads a WASM build of the LAME MP3 encoder. Then, raw data is sent to the worker and encoded data is received from it. These encoded chunks are then concatenated in the main thread and properly split into separate MP3 frames.
+
+Great care was put into ensuring maximum compatibility of this package; it works with bundlers, directly in the browser, as well as in Node, Deno, and Bun. All code (including worker & WASM) are bundled into a single file, eliminating the need for CDNs or WASM path arguments. This packages therefore serves as a reference implementation of WASM-based encoder extensions for Mediabunny.
+
+The WASM build itself is a performance-optimized, SIMD-enabled build of LAME 3.100, with all unneeded features disabled. Because maximum performance was the priority, the build is slighter bigger, but ~130 kB gzipped is still very reasonable in my opinion. In my tests, it encodes 5 seconds of audio in ~90 milliseconds (55x real-time speed).
+
+## Building and development
+
+Building this library is done using the build commands in the [Mediabunny root](https://github.com/Vanilagy/mediabunny).
+
+For simplicity, all built WASM artifacts are included in the repo, since these rarely change. However, here are the instructions for building them from scratch:
+
+### Prerequisites
+
+[Install Emscripten](https://emscripten.org/docs/getting_started/downloads.html). The recommended way is using the emsdk, which involves cloning a repo and running a few commands.
+
+### Compiling LAME:
+
+[Download the LAME source code](https://sourceforge.net/projects/lame/files/lame/) from SourceForge. Then, in the folder:
+
+```bash
emconfigure ./configure \
CFLAGS="-DNDEBUG -DNO_STDIO -O3 -msimd128" \
--disable-dependency-tracking \
@@ -15,10 +128,13 @@ emmake make clean
emmake make
```
-Then extract the file `libmp3lame/.libs/libmp3lame.a`, which you'll need to build the LAME bridge.
+This generates `libmp3lame.a`, located in `libmp3lame/.libs`.
-Compiling the LAME bridge:
-```
+### Compiling the LAME bridge:
+
+Now with LAME built, we must build the C code at `src/lame-bridge.c` containing a lean bridge interface between JavaScript and LAME's API. Clone Mediabunny, and then, in `packages/mp3-encoder` and with `build/libmp3lame.a` in place, run:
+
+```bash
emcc src/lame-bridge.c build/libmp3lame.a \
-s MODULARIZE=1 \
-s EXPORT_ES6=1 \
@@ -29,4 +145,10 @@ emcc src/lame-bridge.c build/libmp3lame.a \
-msimd128 \
-O3 \
-o build/lame.js
-```
\ No newline at end of file
+```
+
+This generates `build/lame.js`, which contains both the JavaScript "glue code" as well as the compiled WASM encoded with Base64.
+
+### Building the package
+
+Then, the complete JavaScript package can be built alongside the rest of Mediabunny by running `npm run build` in Mediabunny's root.
\ No newline at end of file
diff --git a/packages/mp3-encoder/logo.svg b/packages/mp3-encoder/logo.svg
new file mode 100644
index 0000000..78a881f
--- /dev/null
+++ b/packages/mp3-encoder/logo.svg
@@ -0,0 +1,38 @@
+
\ No newline at end of file
From e1c50ef16ce5c035bd0ee58594304233c3cb2d3b Mon Sep 17 00:00:00 2001
From: Vanilagy <1696106+Vanilagy@users.noreply.github.com>
Date: Sun, 10 Aug 2025 02:07:09 +0200
Subject: [PATCH 3/8] Clarify declaration file thing
---
docs/guide/installation.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/guide/installation.md b/docs/guide/installation.md
index 8ab90bf..fb08176 100644
--- a/docs/guide/installation.md
+++ b/docs/guide/installation.md
@@ -34,6 +34,6 @@ You can also just include the library using a script tag in your HTML:
```
-This will add a `Mediabunny` object to the global scope.
+This will add a `Mediabunny` object to the global scope. You can provide types for this global using `mediabunny.d.ts`.
You can download a built distribution file from the [releases page](https://github.com/Vanilagy/mediabunny/releases). Use the `*.cjs` builds for normal script tag inclusion, or the `*.mjs` builds for script tags with `type="module"` or direct imports via ESM. Including the `mediabunny.d.ts` declaration file in your TypeScript project will declare a global `Mediabunny` namespace.
\ No newline at end of file
From 0b6c6ccb6c04815001a0734d6dd3ba3a5aa712f9 Mon Sep 17 00:00:00 2001
From: Vanilagy <1696106+Vanilagy@users.noreply.github.com>
Date: Sun, 10 Aug 2025 02:07:59 +0200
Subject: [PATCH 4/8] Gtfo
---
.vscode/settings.json | 5 -----
1 file changed, 5 deletions(-)
delete mode 100644 .vscode/settings.json
diff --git a/.vscode/settings.json b/.vscode/settings.json
deleted file mode 100644
index be441af..0000000
--- a/.vscode/settings.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "files.associations": {
- "lame.h": "c"
- }
-}
\ No newline at end of file
From 64f986bc1ba6a3ec16951d2aa45dd38dafe84bfc Mon Sep 17 00:00:00 2001
From: Vanilagy <1696106+Vanilagy@users.noreply.github.com>
Date: Sun, 10 Aug 2025 02:08:22 +0200
Subject: [PATCH 5/8] Gotta be sure
---
.gitignore | 1 +
1 file changed, 1 insertion(+)
diff --git a/.gitignore b/.gitignore
index 3c737af..177b87f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
+.vscode
node_modules
/dist
/dist-docs
From ba0491de3ad8e4a17c6455e9da10e1fbc4879016 Mon Sep 17 00:00:00 2001
From: Vanilagy <1696106+Vanilagy@users.noreply.github.com>
Date: Sun, 10 Aug 2025 03:24:17 +0200
Subject: [PATCH 6/8] Circumvent flush() being called on sources when
cancelling output, surface error before doing cleanup
---
package.json | 2 +-
packages/mp3-encoder/package.json | 2 +-
packages/mp3-encoder/src/index.ts | 8 +++--
src/conversion.ts | 2 +-
src/media-source.ts | 57 +++++++++++++++++++------------
src/output.ts | 4 +--
todo.txt | 4 +--
7 files changed, 46 insertions(+), 33 deletions(-)
diff --git a/package.json b/package.json
index 7a2f721..ddd8b0e 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "mediabunny",
"author": "Vanilagy",
- "version": "1.5.0",
+ "version": "1.6.0",
"description": "Pure TypeScript media toolkit for reading, writing, and converting media files, directly in the browser.",
"type": "module",
"workspaces": [
diff --git a/packages/mp3-encoder/package.json b/packages/mp3-encoder/package.json
index 5337f04..044d0ee 100644
--- a/packages/mp3-encoder/package.json
+++ b/packages/mp3-encoder/package.json
@@ -1,6 +1,6 @@
{
"name": "@mediabunny/mp3-encoder",
- "version": "0.0.1",
+ "version": "1.6.0",
"main": "./dist/bundles/mediabunny-mp3-encoder.mjs",
"module": "./dist/bundles/mediabunny-mp3-encoder.mjs",
"types": "./dist/modules/src/index.d.ts",
diff --git a/packages/mp3-encoder/src/index.ts b/packages/mp3-encoder/src/index.ts
index bb85cc6..40c1a3d 100644
--- a/packages/mp3-encoder/src/index.ts
+++ b/packages/mp3-encoder/src/index.ts
@@ -13,7 +13,7 @@ import type { WorkerCommand, WorkerResponse, WorkerResponseData } from './shared
import createWorker from './encode.worker';
class Mp3Encoder extends CustomAudioEncoder {
- private worker!: Worker;
+ private worker: Worker | null = null;
private nextMessageId = 0;
private pendingMessages = new Map void;
@@ -23,7 +23,7 @@ class Mp3Encoder extends CustomAudioEncoder {
private buffer = new Uint8Array(2 ** 16);
private currentBufferOffset = 0;
private currentTimestamp = 0;
- private chunkMetadata!: EncodedAudioChunkMetadata;
+ private chunkMetadata: EncodedAudioChunkMetadata = {};
static override supports(codec: AudioCodec, config: AudioDecoderConfig): boolean {
return codec === 'mp3'
@@ -113,7 +113,7 @@ class Mp3Encoder extends CustomAudioEncoder {
}
close() {
- this.worker.terminate();
+ this.worker?.terminate();
}
/**
@@ -174,6 +174,8 @@ class Mp3Encoder extends CustomAudioEncoder {
const id = this.nextMessageId++;
this.pendingMessages.set(id, { resolve, reject });
+ assert(this.worker);
+
if (transferables) {
this.worker.postMessage({ id, command }, transferables);
} else {
diff --git a/src/conversion.ts b/src/conversion.ts
index 7f4e156..c9a9b04 100644
--- a/src/conversion.ts
+++ b/src/conversion.ts
@@ -410,7 +410,7 @@ export class Conversion {
} catch (error) {
if (!this._canceled) {
// Make sure to cancel to stop other encoding processes and clean up resources
- await this.cancel();
+ void this.cancel();
}
throw error;
diff --git a/src/media-source.ts b/src/media-source.ts
index 72dd9b4..22ff4e7 100644
--- a/src/media-source.ts
+++ b/src/media-source.ts
@@ -80,7 +80,8 @@ export abstract class MediaSource {
/** @internal */
async _start() {}
/** @internal */
- async _flushAndClose() {}
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ async _flushAndClose(forceClose: boolean) {}
/**
* Closes this source. This prevents future samples from being added and signals to the output file that no further
@@ -103,7 +104,7 @@ export abstract class MediaSource {
}
this._closingPromise = (async () => {
- await this._flushAndClose();
+ await this._flushAndClose(false);
this._closed = true;
@@ -116,12 +117,12 @@ export abstract class MediaSource {
}
/** @internal */
- async _flushOrWaitForClose() {
+ async _flushOrWaitForOngoingClose(forceClose: boolean) {
if (this._closingPromise) {
// Since closing also flushes, we don't want to do it twice
return this._closingPromise;
} else {
- return this._flushAndClose();
+ return this._flushAndClose(forceClose);
}
}
}
@@ -451,14 +452,20 @@ class VideoEncoderWrapper {
})();
}
- async flushAndClose() {
+ async flushAndClose(forceClose: boolean) {
this.checkForEncoderError();
if (this.customEncoder) {
- void this.customEncoderCallSerializer.call(() => this.customEncoder!.flush());
+ if (!forceClose) {
+ void this.customEncoderCallSerializer.call(() => this.customEncoder!.flush());
+ }
+
await this.customEncoderCallSerializer.call(() => this.customEncoder!.close());
} else if (this.encoder) {
- await this.encoder.flush();
+ if (!forceClose) {
+ await this.encoder.flush();
+ }
+
this.encoder.close();
}
@@ -512,8 +519,8 @@ export class VideoSampleSource extends VideoSource {
}
/** @internal */
- override _flushAndClose() {
- return this._encoder.flushAndClose();
+ override _flushAndClose(forceClose: boolean) {
+ return this._encoder.flushAndClose(forceClose);
}
}
@@ -564,8 +571,8 @@ export class CanvasSource extends VideoSource {
}
/** @internal */
- override _flushAndClose() {
- return this._encoder.flushAndClose();
+ override _flushAndClose(forceClose: boolean) {
+ return this._encoder.flushAndClose(forceClose);
}
}
@@ -714,7 +721,7 @@ export class MediaStreamVideoTrackSource extends VideoSource {
}
/** @internal */
- override async _flushAndClose() {
+ override async _flushAndClose(forceClose: boolean) {
if (this._abortController) {
this._abortController.abort();
this._abortController = null;
@@ -746,7 +753,7 @@ export class MediaStreamVideoTrackSource extends VideoSource {
});
}
- await this._encoder.flushAndClose();
+ await this._encoder.flushAndClose(forceClose);
}
}
@@ -1226,14 +1233,20 @@ class AudioEncoderWrapper {
}
}
- async flushAndClose() {
+ async flushAndClose(forceClose: boolean) {
this.checkForEncoderError();
if (this.customEncoder) {
- void this.customEncoderCallSerializer.call(() => this.customEncoder!.flush());
+ if (!forceClose) {
+ void this.customEncoderCallSerializer.call(() => this.customEncoder!.flush());
+ }
+
await this.customEncoderCallSerializer.call(() => this.customEncoder!.close());
} else if (this.encoder) {
- await this.encoder.flush();
+ if (!forceClose) {
+ await this.encoder.flush();
+ }
+
this.encoder.close();
}
@@ -1289,8 +1302,8 @@ export class AudioSampleSource extends AudioSource {
}
/** @internal */
- override _flushAndClose() {
- return this._encoder.flushAndClose();
+ override _flushAndClose(forceClose: boolean) {
+ return this._encoder.flushAndClose(forceClose);
}
}
@@ -1333,8 +1346,8 @@ export class AudioBufferSource extends AudioSource {
}
/** @internal */
- override _flushAndClose() {
- return this._encoder.flushAndClose();
+ override _flushAndClose(forceClose: boolean) {
+ return this._encoder.flushAndClose(forceClose);
}
}
@@ -1480,7 +1493,7 @@ export class MediaStreamAudioTrackSource extends AudioSource {
}
/** @internal */
- override async _flushAndClose() {
+ override async _flushAndClose(forceClose: boolean) {
if (this._abortController) {
this._abortController.abort();
this._abortController = null;
@@ -1493,7 +1506,7 @@ export class MediaStreamAudioTrackSource extends AudioSource {
await this._audioContext.suspend();
}
- await this._encoder.flushAndClose();
+ await this._encoder.flushAndClose(forceClose);
}
}
diff --git a/src/output.ts b/src/output.ts
index 31e1153..e45b9b79 100644
--- a/src/output.ts
+++ b/src/output.ts
@@ -380,7 +380,7 @@ export class Output<
const release = await this._mutex.acquire();
- const promises = this._tracks.map(x => x.source._flushOrWaitForClose());
+ const promises = this._tracks.map(x => x.source._flushOrWaitForOngoingClose(true)); // Force close
await Promise.all(promises);
await this._writer.close();
@@ -410,7 +410,7 @@ export class Output<
const release = await this._mutex.acquire();
- const promises = this._tracks.map(x => x.source._flushOrWaitForClose());
+ const promises = this._tracks.map(x => x.source._flushOrWaitForOngoingClose(false));
await Promise.all(promises);
await this._muxer.finalize();
diff --git a/todo.txt b/todo.txt
index 8c5b634..5c0532a 100644
--- a/todo.txt
+++ b/todo.txt
@@ -1,3 +1 @@
-SWALLOWED ERRORS FOR CUSTOM ENCODESRS (like in init)
-
-- mention in the docs how the delcaration file can be used together with the