Files
skills/docs/media/audio/audio-codec-ffmpeg-wasm/README.md
2026-05-29 08:20:56 +01:00

7.2 KiB

@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 backed by @ffmpeg/ffmpeg running inside a dedicated Web Worker. Decodes and encodes wav, mp3, ogg, opus, flac, aac, and webm.

Documentation tree

  • codec — This module assembles the public FfmpegWasmCodec class — the browser-tier AudioCodec plugin for `@suaveplan/audio-c…
    • ffmpeg-wasm-codec — This module assembles the public FfmpegWasmCodec class — the browser-tier AudioCodec plugin for `@suaveplan/audio-c…
  • core-loader — This module owns the lazy resolution of the ffmpeg.wasm core module. It picks the single-thread @ffmpeg/core or the m…
    • core-loader — This module owns the lazy resolution of the ffmpeg.wasm core module. It picks the single-thread @ffmpeg/core or the m…
  • decode — This module owns the pure logic of the decode pipeline: format sniffing from magic bytes, the ffmpeg argument list that…
    • decode-pipeline — This module owns the pure logic of the decode pipeline: format sniffing from magic bytes, the ffmpeg argument list that…
  • encode — This module owns the pure logic of the encode pipeline: validating the requested format against the supported set *befo…
    • encode-pipeline — This module owns the pure logic of the encode pipeline: validating the requested format against the supported set *befo…
  • error-codes — This module declares the canonical error-code registry for @suaveplan/audio-codec-ffmpeg-wasm. It exposes the nine `A…
    • error-codes — This module declares the canonical error-code registry for @suaveplan/audio-codec-ffmpeg-wasm. It exposes the nine `A…
  • register — This module exposes the register(registry, options?) install helper — a single function that callers use to plug `Ffm…
    • register — This module exposes the register(registry, options?) install helper — a single function that callers use to plug `Ffm…
  • streaming-decoder — This module implements the StreamingDecoder contract from @suaveplan/audio-codecs over ffmpeg.wasm. The implementat…
  • streaming-encoder — This module implements the StreamingEncoder contract from @suaveplan/audio-codecs over ffmpeg.wasm. The implementat…
  • worker — This module owns the lifecycle of the dedicated Web Worker that runs the ffmpeg.wasm core. It serialises every codec ca…
    • worker-host — This module owns the lifecycle of the dedicated Web Worker that runs the ffmpeg.wasm core. It serialises every codec ca…

Install

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

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.