Initial Commit
This commit is contained in:
127
packages/media/audio/audio-codec-ffmpeg-wasm/README.md
Normal file
127
packages/media/audio/audio-codec-ffmpeg-wasm/README.md
Normal file
@@ -0,0 +1,127 @@
|
||||
# @suaveplan/audio-codec-ffmpeg-wasm - Browser-tier ffmpeg.wasm AudioCodec plugin for @suaveplan/audio-codecs. Lazy-loaded @ffmpeg/core wasm inside a Web Worker; decode/encode for WAV/MP3/OGG/Opus/FLAC/AAC/WebM; AbortSignal, timeout watchdog, COI-gated multi-thread, OTel spans+counters+histograms, structured AUDIO_CODEC_FFMPEG_WASM.* error codes.
|
||||
|
||||
Browser-tier `AudioCodec` plugin for
|
||||
[`@suaveplan/audio-codecs`](../audio-codecs) backed by
|
||||
[`@ffmpeg/ffmpeg`](https://github.com/ffmpegwasm/ffmpeg.wasm) running inside
|
||||
a dedicated Web Worker. Decodes and encodes `wav`, `mp3`, `ogg`, `opus`,
|
||||
`flac`, `aac`, and `webm`.
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
## Documentation tree
|
||||
|
||||
- [codec](./src/codec/ffmpeg-wasm-codec.md) — This module assembles the public `FfmpegWasmCodec` class — the browser-tier `AudioCodec` plugin for `@suaveplan/audio-c…
|
||||
- [ffmpeg-wasm-codec](./src/codec/ffmpeg-wasm-codec.md) — This module assembles the public `FfmpegWasmCodec` class — the browser-tier `AudioCodec` plugin for `@suaveplan/audio-c…
|
||||
- [core-loader](./src/core-loader/core-loader.md) — This module owns the lazy resolution of the ffmpeg.wasm core module. It picks the single-thread `@ffmpeg/core` or the m…
|
||||
- [core-loader](./src/core-loader/core-loader.md) — This module owns the lazy resolution of the ffmpeg.wasm core module. It picks the single-thread `@ffmpeg/core` or the m…
|
||||
- [decode](./src/decode/decode-pipeline.md) — This module owns the pure logic of the decode pipeline: format sniffing from magic bytes, the ffmpeg argument list that…
|
||||
- [decode-pipeline](./src/decode/decode-pipeline.md) — This module owns the pure logic of the decode pipeline: format sniffing from magic bytes, the ffmpeg argument list that…
|
||||
- [encode](./src/encode/encode-pipeline.md) — This module owns the pure logic of the encode pipeline: validating the requested format against the supported set *befo…
|
||||
- [encode-pipeline](./src/encode/encode-pipeline.md) — This module owns the pure logic of the encode pipeline: validating the requested format against the supported set *befo…
|
||||
- [error-codes](./src/error-codes/error-codes.md) — This module declares the canonical error-code registry for `@suaveplan/audio-codec-ffmpeg-wasm`. It exposes the nine `A…
|
||||
- [error-codes](./src/error-codes/error-codes.md) — This module declares the canonical error-code registry for `@suaveplan/audio-codec-ffmpeg-wasm`. It exposes the nine `A…
|
||||
- [register](./src/register/register.md) — This module exposes the `register(registry, options?)` install helper — a single function that callers use to plug `Ffm…
|
||||
- [register](./src/register/register.md) — This module exposes the `register(registry, options?)` install helper — a single function that callers use to plug `Ffm…
|
||||
- [streaming-decoder](./src/streaming/streaming-decoder.md) — This module implements the `StreamingDecoder` contract from `@suaveplan/audio-codecs` over ffmpeg.wasm. The implementat…
|
||||
- [streaming-encoder](./src/streaming/streaming-encoder.md) — This module implements the `StreamingEncoder` contract from `@suaveplan/audio-codecs` over ffmpeg.wasm. The implementat…
|
||||
- [worker](./src/worker/worker-host.md) — This module owns the lifecycle of the dedicated Web Worker that runs the ffmpeg.wasm core. It serialises every codec ca…
|
||||
- [worker-host](./src/worker/worker-host.md) — This module owns the lifecycle of the dedicated Web Worker that runs the ffmpeg.wasm core. It serialises every codec ca…
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
bun add @suaveplan/audio-codec-ffmpeg-wasm @suaveplan/audio-codecs
|
||||
# The wasm core packages are optional dependencies — install whichever you
|
||||
# plan to serve:
|
||||
bun add @ffmpeg/ffmpeg @ffmpeg/core
|
||||
# Optional: multi-thread core (~30 MB) for cross-origin-isolated pages
|
||||
bun add @ffmpeg/core-mt
|
||||
```
|
||||
|
||||
## Quick start
|
||||
|
||||
```ts
|
||||
import { CodecRegistry } from "@suaveplan/audio-codecs";
|
||||
import { register } from "@suaveplan/audio-codec-ffmpeg-wasm/register/register";
|
||||
|
||||
const registry = new CodecRegistry();
|
||||
const codec = register(registry, {
|
||||
corePath: "/static/ffmpeg-core.js",
|
||||
});
|
||||
|
||||
const doc = await registry.decode(mp3Bytes);
|
||||
const opus = await registry.encode(doc, "opus");
|
||||
await codec.dispose();
|
||||
```
|
||||
|
||||
## The 30 MB lazy-load disclaimer
|
||||
|
||||
The `@ffmpeg/core` wasm payload is roughly 30 MB. It is **not** loaded
|
||||
when this package is imported, nor when `new FfmpegWasmCodec()` is
|
||||
constructed — it is loaded on the first call to `decode`, `encode`,
|
||||
`createDecoder`, or `createEncoder`. Subsequent calls reuse the loaded
|
||||
core. `codec.dispose()` tears the worker down and releases the core.
|
||||
|
||||
## Multi-thread (cross-origin isolation)
|
||||
|
||||
Pass `enableMultithread: true` to opt into the `@ffmpeg/core-mt` build.
|
||||
This requires the page to be cross-origin-isolated:
|
||||
|
||||
- Serve the page with
|
||||
`Cross-Origin-Opener-Policy: same-origin` and
|
||||
`Cross-Origin-Embedder-Policy: require-corp`.
|
||||
- `globalThis.crossOriginIsolated` must be `true` at runtime.
|
||||
|
||||
If `enableMultithread` is `true` without COI, the codec throws
|
||||
`AUDIO_CODEC_FFMPEG_WASM.CROSS_ORIGIN_ISOLATION_REQUIRED` on first use
|
||||
**before** attempting the dynamic `import()`. If COI is true but the
|
||||
optional `@ffmpeg/core-mt` package is missing, the codec logs a warning
|
||||
and silently falls back to the single-thread core.
|
||||
|
||||
## Streaming honesty note
|
||||
|
||||
The `StreamingDecoder` / `StreamingEncoder` factories from this codec
|
||||
are **buffered emulation** — ffmpeg.wasm is whole-file in / whole-file
|
||||
out, so `write(chunk)` only appends to an internal buffer and never
|
||||
invokes the worker; the actual decode/encode happens once when
|
||||
`flushAsync()` is awaited. Use this for chunked-source UX (progress
|
||||
bars, abortable downloads), not for true low-latency streaming.
|
||||
|
||||
## Public API
|
||||
|
||||
- `class FfmpegWasmCodec implements AudioCodec`
|
||||
- `class FfmpegWasmStreamingDecoder implements StreamingDecoder`
|
||||
- `class FfmpegWasmStreamingEncoder implements StreamingEncoder`
|
||||
- `class WorkerHost` — worker lifecycle abstraction
|
||||
- `loadCore(opts)` — lazy core loader
|
||||
- `register(registry, options?)` — one-line install helper
|
||||
- `AUDIO_CODEC_FFMPEG_WASM_CODES` + `AudioCodecFfmpegWasmError`
|
||||
|
||||
## Error codes
|
||||
|
||||
| Code | Thrown when |
|
||||
|---|---|
|
||||
| `AUDIO_CODEC_FFMPEG_WASM.CORE_LOAD_FAILED` | wasm core fetch / import failed |
|
||||
| `AUDIO_CODEC_FFMPEG_WASM.WORKER_SPAWN_FAILED` | Worker construction or pre-load error |
|
||||
| `AUDIO_CODEC_FFMPEG_WASM.DECODE_FAILED` | ffmpeg non-zero exit on decode |
|
||||
| `AUDIO_CODEC_FFMPEG_WASM.ENCODE_FAILED` | ffmpeg non-zero exit on encode |
|
||||
| `AUDIO_CODEC_FFMPEG_WASM.TIMEOUT` | Per-call watchdog fired |
|
||||
| `AUDIO_CODEC_FFMPEG_WASM.ABORTED` | `AbortSignal` propagated |
|
||||
| `AUDIO_CODEC_FFMPEG_WASM.UNSUPPORTED_CODEC` | Format outside the supported set, or Zod boundary failure |
|
||||
| `AUDIO_CODEC_FFMPEG_WASM.MEMORY_LIMIT_EXCEEDED` | wasm 4 GB linear memory cap hit |
|
||||
| `AUDIO_CODEC_FFMPEG_WASM.CROSS_ORIGIN_ISOLATION_REQUIRED` | `enableMultithread` without COI |
|
||||
|
||||
## Test seams
|
||||
|
||||
For testing without a real worker, supply:
|
||||
|
||||
- `workerFactory: () => Worker` — returns a controllable shim.
|
||||
- `coreLoader: () => Promise<unknown>` — overrides the dynamic
|
||||
`import("@ffmpeg/core")`.
|
||||
- `coreMtLoader: () => Promise<unknown>` — overrides the dynamic
|
||||
`import("@ffmpeg/core-mt")`.
|
||||
|
||||
These are dependency-injection seams, not mocks; production callers
|
||||
omit them and the real `new Worker(coreUrl)` / dynamic `import()` path
|
||||
runs unchanged.
|
||||
|
||||
Reference in New Issue
Block a user