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 priorflushAsync()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().