From c4398fb88c50d61562595c8283aec42a03338c1e Mon Sep 17 00:00:00 2001
From: Vanilagy <1696106+Vanilagy@users.noreply.github.com>
Date: Wed, 4 Mar 2026 17:05:34 +0100
Subject: [PATCH] Add @mediabunny/aac-encoder extension package
---
.github/workflows/release.yml | 23 +-
.gitignore | 3 +-
docs/.vitepress/config.mts | 1 +
docs/api-config.json | 3 +-
docs/guide/extensions/aac-encoder.md | 82 +++++
docs/guide/supported-formats-and-codecs.md | 22 +-
eslint.config.mjs | 2 +
package-lock.json | 19 ++
package.json | 2 +-
packages/aac-encoder/.gitattributes | 1 +
packages/aac-encoder/LICENSE | 373 +++++++++++++++++++++
packages/aac-encoder/README.md | 156 +++++++++
packages/aac-encoder/api-extractor.json | 37 ++
packages/aac-encoder/build/aac.js | Bin 0 -> 589600 bytes
packages/aac-encoder/package.json | 55 +++
packages/aac-encoder/src/bridge.c | 190 +++++++++++
packages/aac-encoder/src/encode.worker.ts | 204 +++++++++++
packages/aac-encoder/src/encoder.ts | 306 +++++++++++++++++
packages/aac-encoder/src/index.ts | 20 ++
packages/aac-encoder/src/shared.ts | 66 ++++
packages/aac-encoder/tsconfig.json | 24 ++
packages/aac-encoder/tsdoc.json | 4 +
packages/mp3-encoder/tsconfig.json | 1 -
scripts/build.sh | 7 +-
scripts/bundle.ts | 32 ++
scripts/check.sh | 6 +-
shared/aac-misc.ts | 146 ++++++++
shared/bitstream.ts | 85 +++++
src/adts/adts-demuxer.ts | 3 +-
src/adts/adts-misc.ts | 47 ---
src/adts/adts-muxer.ts | 7 +-
src/adts/adts-reader.ts | 2 +-
src/codec-data.ts | 2 +-
src/codec.ts | 102 +-----
src/conversion.ts | 6 +
src/flac/flac-demuxer.ts | 2 +-
src/flac/flac-misc.ts | 3 +-
src/flac/flac-muxer.ts | 2 +-
src/isobmff/isobmff-boxes.ts | 2 +-
src/isobmff/isobmff-demuxer.ts | 4 +-
src/isobmff/isobmff-muxer.ts | 4 +-
src/matroska/matroska-muxer.ts | 6 +-
src/media-source.ts | 3 +-
src/misc.ts | 81 +----
src/mpeg-ts/mpeg-ts-demuxer.ts | 5 +-
src/mpeg-ts/mpeg-ts-muxer.ts | 7 +-
test/node/aac-encoder-extension.test.ts | 82 +++++
tsconfig.vite.json | 1 +
tsconfig.vitest.json | 3 +-
vite.config.ts | 4 +-
vitest.config.ts | 2 +
51 files changed, 1976 insertions(+), 274 deletions(-)
create mode 100644 docs/guide/extensions/aac-encoder.md
create mode 100644 packages/aac-encoder/.gitattributes
create mode 100644 packages/aac-encoder/LICENSE
create mode 100644 packages/aac-encoder/README.md
create mode 100644 packages/aac-encoder/api-extractor.json
create mode 100644 packages/aac-encoder/build/aac.js
create mode 100644 packages/aac-encoder/package.json
create mode 100644 packages/aac-encoder/src/bridge.c
create mode 100644 packages/aac-encoder/src/encode.worker.ts
create mode 100644 packages/aac-encoder/src/encoder.ts
create mode 100644 packages/aac-encoder/src/index.ts
create mode 100644 packages/aac-encoder/src/shared.ts
create mode 100644 packages/aac-encoder/tsconfig.json
create mode 100644 packages/aac-encoder/tsdoc.json
create mode 100644 shared/aac-misc.ts
create mode 100644 shared/bitstream.ts
delete mode 100644 src/adts/adts-misc.ts
create mode 100644 test/node/aac-encoder-extension.test.ts
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 31b076b..441a113 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -59,7 +59,28 @@ jobs:
- name: Upload build artifacts
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- run: gh release upload ${{ github.event.release.tag_name }} dist/bundles/mediabunny.cjs dist/bundles/mediabunny.min.cjs dist/bundles/mediabunny.mjs dist/bundles/mediabunny.min.mjs dist/mediabunny.d.ts packages/mp3-encoder/dist/bundles/mediabunny-mp3-encoder.js packages/mp3-encoder/dist/bundles/mediabunny-mp3-encoder.min.js packages/mp3-encoder/dist/bundles/mediabunny-mp3-encoder.mjs packages/mp3-encoder/dist/bundles/mediabunny-mp3-encoder.min.mjs packages/mp3-encoder/dist/mediabunny-mp3-encoder.d.ts packages/ac3/dist/bundles/mediabunny-ac3.js packages/ac3/dist/bundles/mediabunny-ac3.min.js packages/ac3/dist/bundles/mediabunny-ac3.mjs packages/ac3/dist/bundles/mediabunny-ac3.min.mjs packages/ac3/dist/mediabunny-ac3.d.ts
+ run: >
+ gh release upload ${{ github.event.release.tag_name }}
+ dist/bundles/mediabunny.cjs
+ dist/bundles/mediabunny.min.cjs
+ dist/bundles/mediabunny.mjs
+ dist/bundles/mediabunny.min.mjs
+ dist/mediabunny.d.ts
+ packages/mp3-encoder/dist/bundles/mediabunny-mp3-encoder.js
+ packages/mp3-encoder/dist/bundles/mediabunny-mp3-encoder.min.js
+ packages/mp3-encoder/dist/bundles/mediabunny-mp3-encoder.mjs
+ packages/mp3-encoder/dist/bundles/mediabunny-mp3-encoder.min.mjs
+ packages/mp3-encoder/dist/mediabunny-mp3-encoder.d.ts
+ packages/ac3/dist/bundles/mediabunny-ac3.js
+ packages/ac3/dist/bundles/mediabunny-ac3.min.js
+ packages/ac3/dist/bundles/mediabunny-ac3.mjs
+ packages/ac3/dist/bundles/mediabunny-ac3.min.mjs
+ packages/ac3/dist/mediabunny-ac3.d.ts
+ packages/aac-encoder/dist/bundles/mediabunny-aac-encoder.js
+ packages/aac-encoder/dist/bundles/mediabunny-aac-encoder.min.js
+ packages/aac-encoder/dist/bundles/mediabunny-aac-encoder.mjs
+ packages/aac-encoder/dist/bundles/mediabunny-aac-encoder.min.mjs
+ packages/aac-encoder/dist/mediabunny-aac-encoder.d.ts
- name: Publish Mediabunny to npm
run: npm publish --access public
diff --git a/.gitignore b/.gitignore
index 289f126..e4b72d3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,4 +7,5 @@ node_modules
*.tsbuildinfo
packages/mp3-encoder/dist
-packages/ac3/dist
\ No newline at end of file
+packages/ac3/dist
+packages/aac-encoder/dist
\ No newline at end of file
diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts
index 7452915..a050b49 100644
--- a/docs/.vitepress/config.mts
+++ b/docs/.vitepress/config.mts
@@ -93,6 +93,7 @@ export default withMermaid({
text: 'Extensions',
items: [
{ text: 'mp3-encoder', link: '/guide/extensions/mp3-encoder' },
+ { text: 'aac-encoder', link: '/guide/extensions/aac-encoder' },
{ text: 'ac3', link: '/guide/extensions/ac3' },
],
},
diff --git a/docs/api-config.json b/docs/api-config.json
index 202768f..53ba487 100644
--- a/docs/api-config.json
+++ b/docs/api-config.json
@@ -20,5 +20,6 @@
"Miscellaneous": "Whatever's left.",
"@mediabunny/mp3-encoder": "Adds MP3 encoder support to Mediabunny.",
- "@mediabunny/ac3": "Adds AC-3/E-AC-3 decoder and encoder support to Mediabunny."
+ "@mediabunny/ac3": "Adds AC-3/E-AC-3 decoder and encoder support to Mediabunny.",
+ "@mediabunny/aac-encoder": "Adds AAC encoder support to Mediabunny."
}
diff --git a/docs/guide/extensions/aac-encoder.md b/docs/guide/extensions/aac-encoder.md
new file mode 100644
index 0000000..a8b3488
--- /dev/null
+++ b/docs/guide/extensions/aac-encoder.md
@@ -0,0 +1,82 @@
+# @mediabunny/aac-encoder
+
+Some browsers lack support for AAC encoding in their WebCodecs implementations. This extension package provides a reliable AAC-LC encoder for use with Mediabunny. It is implemented using Mediabunny's [custom coder API](../supported-formats-and-codecs#custom-coders) and uses a fast, size-optimized WASM build of [FFmpeg](https://ffmpeg.org/)'s AAC encoder under the hood.
+
+
+ GitHub page
+
+
+
+## Installation
+
+This library peer-depends on Mediabunny. Install both using npm:
+```bash
+npm install mediabunny @mediabunny/aac-encoder
+```
+
+Alternatively, directly include them using a script tag:
+```html
+
+
+```
+
+This will expose the global objects `Mediabunny` and `MediabunnyAacEncoder`. Use `mediabunny-aac-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 { registerAacEncoder } from '@mediabunny/aac-encoder';
+
+registerAacEncoder();
+```
+That's it - Mediabunny now uses the registered AAC encoder automatically.
+
+If you want to be more correct, check for native browser support first:
+```ts
+import { canEncodeAudio } from 'mediabunny';
+import { registerAacEncoder } from '@mediabunny/aac-encoder';
+
+if (!(await canEncodeAudio('aac'))) {
+ registerAacEncoder();
+}
+```
+
+## Example
+
+Here, we convert an input file to an MP4 with AAC audio:
+
+```ts
+import {
+ Input,
+ ALL_FORMATS,
+ BlobSource,
+ Output,
+ BufferTarget,
+ Mp4OutputFormat,
+ canEncodeAudio,
+ Conversion,
+} from 'mediabunny';
+import { registerAacEncoder } from '@mediabunny/aac-encoder';
+
+if (!(await canEncodeAudio('aac'))) {
+ // Only register the custom encoder if there's no native support
+ registerAacEncoder();
+}
+
+const input = new Input({
+ source: new BlobSource(file), // From a file picker, for example
+ formats: ALL_FORMATS,
+});
+const output = new Output({
+ format: new Mp4OutputFormat(),
+ target: new BufferTarget(),
+});
+
+const conversion = await Conversion.init({
+ input,
+ output,
+});
+await conversion.execute();
+
+output.target.buffer; // => ArrayBuffer containing the MP4 file
+```
diff --git a/docs/guide/supported-formats-and-codecs.md b/docs/guide/supported-formats-and-codecs.md
index dbaf533..1e2f5c3 100644
--- a/docs/guide/supported-formats-and-codecs.md
+++ b/docs/guide/supported-formats-and-codecs.md
@@ -37,13 +37,13 @@ Mediabunny ships with built-in decoders and encoders for all audio PCM codecs, m
### Audio codecs
-- `'aac'` - Advanced Audio Coding (AAC)
+- `'aac'` - Advanced Audio Coding (AAC) [^1]
- `'opus'` - Opus
-- `'mp3'` - MP3
+- `'mp3'` - MP3 [^2]
- `'vorbis'` - Vorbis
- `'flac'` - Free Lossless Audio Codec (FLAC)
-- `'ac3'` - Dolby Digital (AC-3) [^1]
-- `'eac3'` - Dolby Digital Plus (E-AC-3) [^1]
+- `'ac3'` - Dolby Digital (AC-3) [^3]
+- `'eac3'` - Dolby Digital Plus (E-AC-3) [^3]
- `'pcm-u8'` - 8-bit unsigned PCM
- `'pcm-s8'` - 8-bit signed PCM
- `'pcm-s16'` - 16-bit little-endian signed PCM
@@ -59,8 +59,6 @@ Mediabunny ships with built-in decoders and encoders for all audio PCM codecs, m
- `'ulaw'` - μ-law PCM
- `'alaw'` - A-law PCM
-[^1]: AC-3 and E-AC-3 are not natively supported by WebCodecs. To encode or decode these codecs, you can use the [`@mediabunny/ac3`](./extensions/ac3) extension package, or provide your own [custom coder](#custom-coders).
-
### Subtitle codecs
- `'webvtt'` - WebVTT
@@ -69,7 +67,7 @@ Mediabunny ships with built-in decoders and encoders for all audio PCM codecs, m
Not all codecs can be used with all containers. The following table specifies the supported codec-container combinations:
-| | .mp4 | .mov | .mkv | .webm[^2] | .ogg | .mp3 | .wav | .aac | .flac | .ts |
+| | .mp4 | .mov | .mkv | .webm[^4] | .ogg | .mp3 | .wav | .aac | .flac | .ts |
|:--------------:|:--------:|:-----:|:-----:|:---------:|:-----:|:-----:|:-----:|:-----:|:-----:|:-----:|
| `'avc'` | ✓ | ✓ | ✓ | | | | | | | ✓ |
| `'hevc'` | ✓ | ✓ | ✓ | | | | | | | ✓ |
@@ -97,11 +95,13 @@ Not all codecs can be used with all containers. The following table specifies th
| `'pcm-f64be'` | ✓ | ✓ | | | | | | | | |
| `'ulaw'` | | ✓ | | | | | ✓ | | | |
| `'alaw'` | | ✓ | | | | | ✓ | | | |
-| `'webvtt'`[^3] | (✓) | | (✓) | (✓) | | | | | | |
+| `'webvtt'`[^5] | (✓) | | (✓) | (✓) | | | | | | |
-
-[^2]: WebM only supports a small subset of the codecs supported by Matroska. However, this library can technically read all codecs from a WebM that are supported by Matroska.
-[^3]: WebVTT can only be written, not read.
+[^1]: In some browsers, AAC encoding is not supported by WebCodecs. You can polyfill it with the [`@mediabunny/aac-encoder`](./extensions/aac-encoder) extension package, or provide your own [custom coder](#custom-coders).
+[^2]: MP3 encoding is not supported by WebCodecs. You can polyfill it with the [`@mediabunny/mp3-encoder`](./extensions/mp3-encoder) extension package, or provide your own [custom coder](#custom-coders).
+[^3]: AC-3 and E-AC-3 are not natively supported by WebCodecs. To encode or decode these codecs, you can use the [`@mediabunny/ac3`](./extensions/ac3) extension package, or provide your own [custom coder](#custom-coders).
+[^4]: WebM only supports a small subset of the codecs supported by Matroska. However, this library can technically read all codecs from a WebM that are supported by Matroska.
+[^5]: WebVTT can only be written, not read.
## Querying codec encodability
diff --git a/eslint.config.mjs b/eslint.config.mjs
index fa924e2..95f80b1 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -40,6 +40,8 @@ export default tseslint.config(
'packages/mp3-encoder/build',
'packages/ac3/dist',
'packages/ac3/build',
+ 'packages/aac-encoder/dist',
+ 'packages/aac-encoder/build',
'eslint.config.mjs',
'docs/.vitepress/cache',
'test/public',
diff --git a/package-lock.json b/package-lock.json
index 7fb6a76..4cdbc8f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1413,6 +1413,10 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
+ "node_modules/@mediabunny/aac-encoder": {
+ "resolved": "packages/aac-encoder",
+ "link": true
+ },
"node_modules/@mediabunny/ac3": {
"resolved": "packages/ac3",
"link": true
@@ -12067,6 +12071,21 @@
"url": "https://github.com/sponsors/wooorm"
}
},
+ "packages/aac-encoder": {
+ "name": "@mediabunny/aac-encoder",
+ "version": "1.35.0",
+ "license": "MPL-2.0",
+ "devDependencies": {
+ "@types/emscripten": "^1.40.1"
+ },
+ "funding": {
+ "type": "individual",
+ "url": "https://github.com/sponsors/Vanilagy"
+ },
+ "peerDependencies": {
+ "mediabunny": "^1.0.0"
+ }
+ },
"packages/ac3": {
"name": "@mediabunny/ac3",
"version": "1.35.1",
diff --git a/package.json b/package.json
index 15fe549..536af7e 100644
--- a/package.json
+++ b/package.json
@@ -41,7 +41,7 @@
"docs:dev": "vitepress dev docs",
"docs:build": "npm run build && npm run docs:generate && vitepress build docs && npm run examples:build && cp dist/mediabunny.d.ts dist-docs/",
"docs:preview": "vitepress preview docs",
- "docs:generate": "tsx scripts/generate-api-docs.ts src/index.ts packages/mp3-encoder/src/index.ts packages/ac3/src/index.ts docs/api-config.json",
+ "docs:generate": "tsx scripts/generate-api-docs.ts src/index.ts packages/mp3-encoder/src/index.ts packages/ac3/src/index.ts packages/aac-encoder/src/index.ts docs/api-config.json",
"dev": "vite",
"examples:build": "vite build",
"fix-build-import-paths": "tsx scripts/add-import-extensions.ts",
diff --git a/packages/aac-encoder/.gitattributes b/packages/aac-encoder/.gitattributes
new file mode 100644
index 0000000..72ef19a
--- /dev/null
+++ b/packages/aac-encoder/.gitattributes
@@ -0,0 +1 @@
+build/* linguist-generated
diff --git a/packages/aac-encoder/LICENSE b/packages/aac-encoder/LICENSE
new file mode 100644
index 0000000..d0a1fa1
--- /dev/null
+++ b/packages/aac-encoder/LICENSE
@@ -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.
diff --git a/packages/aac-encoder/README.md b/packages/aac-encoder/README.md
new file mode 100644
index 0000000..69062c2
--- /dev/null
+++ b/packages/aac-encoder/README.md
@@ -0,0 +1,156 @@
+# @mediabunny/aac-encoder
+
+[](https://www.npmjs.com/package/@mediabunny/aac-encoder)
+[](https://bundlephobia.com/package/@mediabunny/aac-encoder)
+[](https://www.npmjs.com/package/@mediabunny/aac-encoder)
+[](https://discord.gg/hmpkyYuS4U)
+
+
+

+
+
+Some browsers lack support for AAC encoding in their WebCodecs implementations. This extension package provides a reliable AAC-LC 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 fast, size-optimized WASM build of [FFmpeg](https://ffmpeg.org/)'s AAC encoder 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](https://github.com/sponsors/Vanilagy)! 💘
+
+## Installation
+
+This library peer-depends on Mediabunny. Install both using npm:
+```bash
+npm install mediabunny @mediabunny/aac-encoder
+```
+
+Alternatively, directly include them using a script tag:
+```html
+
+
+```
+
+This will expose the global objects `Mediabunny` and `MediabunnyAacEncoder`. Use `mediabunny-aac-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 { registerAacEncoder } from '@mediabunny/aac-encoder';
+
+registerAacEncoder();
+```
+That's it - Mediabunny now uses the registered AAC encoder automatically.
+
+If you want to be more correct, check for native browser support first:
+```ts
+import { canEncodeAudio } from 'mediabunny';
+import { registerAacEncoder } from '@mediabunny/aac-encoder';
+
+if (!(await canEncodeAudio('aac'))) {
+ registerAacEncoder();
+}
+```
+
+## Example
+
+Here, we convert an input file to an MP4 with AAC audio:
+
+```ts
+import {
+ Input,
+ ALL_FORMATS,
+ BlobSource,
+ Output,
+ BufferTarget,
+ Mp4OutputFormat,
+ canEncodeAudio,
+ Conversion,
+} from 'mediabunny';
+import { registerAacEncoder } from '@mediabunny/aac-encoder';
+
+if (!(await canEncodeAudio('aac'))) {
+ // Only register the custom encoder if there's no native support
+ registerAacEncoder();
+}
+
+const input = new Input({
+ source: new BlobSource(file), // From a file picker, for example
+ formats: ALL_FORMATS,
+});
+const output = new Output({
+ format: new Mp4OutputFormat(),
+ target: new BufferTarget(),
+});
+
+const conversion = await Conversion.init({
+ input,
+ output,
+});
+await conversion.execute();
+
+output.target.buffer; // => ArrayBuffer containing the MP4 file
+```
+
+For more ways of using Mediabunny, refer to its [guide](https://mediabunny.dev/guide/introduction).
+
+## Building and development
+
+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:
+
+[Install Emscripten](https://emscripten.org/docs/getting_started/downloads.html) and clone [FFmpeg](https://github.com/FFmpeg/FFmpeg). Then, from the Mediabunny root and with Emscripten sourced in:
+
+```bash
+export FFMPEG_PATH=/path/to/ffmpeg
+export MEDIABUNNY_ROOT=$PWD
+
+# Build FFmpeg
+cd $FFMPEG_PATH
+emmake make distclean
+emconfigure ./configure \
+ --target-os=none \
+ --arch=x86_32 \
+ --enable-cross-compile \
+ --disable-asm \
+ --disable-x86asm \
+ --disable-inline-asm \
+ --disable-programs \
+ --disable-doc \
+ --disable-debug \
+ --disable-all \
+ --disable-everything \
+ --disable-autodetect \
+ --disable-pthreads \
+ --disable-runtime-cpudetect \
+ --enable-avcodec \
+ --enable-encoder=aac \
+ --cc="emcc" \
+ --cxx=em++ \
+ --ar=emar \
+ --ranlib=emranlib \
+ --extra-cflags="-DNDEBUG -Oz -flto -msimd128" \
+ --extra-ldflags="-Oz -flto"
+emmake make
+
+# Compile the bridge between JavaScript and FFmpeg's API
+cd $MEDIABUNNY_ROOT/packages/aac-encoder
+emcc src/bridge.c \
+ $FFMPEG_PATH/libavcodec/libavcodec.a \
+ $FFMPEG_PATH/libavutil/libavutil.a \
+ -I$FFMPEG_PATH \
+ -s MODULARIZE=1 \
+ -s EXPORT_ES6=1 \
+ -s SINGLE_FILE=1 \
+ -s ALLOW_MEMORY_GROWTH=1 \
+ -s ENVIRONMENT=web,worker \
+ -s FILESYSTEM=0 \
+ -s MALLOC=emmalloc \
+ -s SUPPORT_LONGJMP=0 \
+ -s EXPORTED_RUNTIME_METHODS=cwrap,HEAPU8 \
+ -s EXPORTED_FUNCTIONS=_malloc,_free \
+ -msimd128 \
+ -flto \
+ -Oz \
+ -o build/aac.js
+```
+
+This generates `build/aac.js`, which contains both the JavaScript "glue code" as well as the compiled WASM inlined.
+
+### 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.
diff --git a/packages/aac-encoder/api-extractor.json b/packages/aac-encoder/api-extractor.json
new file mode 100644
index 0000000..6908bb7
--- /dev/null
+++ b/packages/aac-encoder/api-extractor.json
@@ -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-aac-encoder.d.ts"
+ },
+ "tsdocMetadata": {
+ "enabled": false
+ },
+ "messages": {
+ "compilerMessageReporting": {
+ "default": {
+ "logLevel": "warning"
+ }
+ },
+ "extractorMessageReporting": {
+ "default": {
+ "logLevel": "warning"
+ }
+ },
+ "tsdocMessageReporting": {
+ "default": {
+ "logLevel": "warning"
+ }
+ }
+ },
+ "newlineKind": "lf"
+}
diff --git a/packages/aac-encoder/build/aac.js b/packages/aac-encoder/build/aac.js
new file mode 100644
index 0000000000000000000000000000000000000000..9bd7d8d5b7ec0bfe1856801aa10329b6d93b77b3
GIT binary patch
literal 589600
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