Files
suaveplan/packages/media/audio/audio-codec-ffmpeg-wasm/src/streaming/streaming-decoder.md
2026-05-27 23:47:53 +01:00

2.5 KiB

streaming-decoder.ts

Purpose

This module implements the StreamingDecoder contract from @suaveplan/audio-codecs over ffmpeg.wasm. The implementation is buffered emulation — ffmpeg.wasm is whole-file in / whole-file out and cannot produce decoded pages incrementally, so this class accumulates chunks in memory and runs the decode pipeline once during flush.

Features

  • write(chunk) buffers without touching the worker; returns [].
  • flushAsync() runs the decode pipeline and caches the result.
  • flush() (synchronous, per the contract) returns the cached result populated by a prior flushAsync() call.
  • Idempotent flush / flushAsync — subsequent calls return cached.
  • close() clears buffered chunks; idempotent.

Usage: Basic

import { FfmpegWasmStreamingDecoder } from "@suaveplan/audio-codec-ffmpeg-wasm/streaming/streaming-decoder";

const decoder = new FfmpegWasmStreamingDecoder({ host, format: "wav" });
for (const chunk of chunks) decoder.write(chunk);
const result = await decoder.flushAsync();
decoder.close();

Usage: Advanced

const decoder = new FfmpegWasmStreamingDecoder({
    host,
    format: "mp3",
    sampleRate: 44_100,
    channels: 2,
    timeoutMs: 30_000,
    signal: controller.signal,
});
let consumed = 0;
for await (const chunk of source) {
    decoder.write(chunk);
    consumed += chunk.byteLength;
}
const decoded = await decoder.flushAsync();
// Subsequent synchronous flush() calls return the cached result so
// CodecRegistry.decodeStream can pull frames in lock-step.
const sync = decoder.flush();

API Reference

  • class FfmpegWasmStreamingDecoder implements StreamingDecoder.
    • new FfmpegWasmStreamingDecoder(opts: StreamingDecoderOptions).
    • decoder.write(chunk): readonly DecodeResult[].
    • decoder.flush(): readonly DecodeResult[].
    • decoder.flushAsync(): Promise<DecodeResult | null>.
    • decoder.close(): void.
  • interface StreamingDecoderOptions.

Implementation Notes

The StreamingDecoder interface in @suaveplan/audio-codecs is fully synchronous. ffmpeg.wasm cannot satisfy that synchronously, so the implementation exposes an additional flushAsync() method that callers must await before they invoke flush() to retrieve the result. The CodecRegistry.decodeStream consumer in audio-codecs calls flush() synchronously, so a wrapping ReadableStream pull body must await flushAsync() first. Inside an async pull body the wait is natural. Progress events fire only during the flushAsync() call, never during write().