# `stream-decoder` ## Purpose `createStreamingDecoder` returns a `FfmpegStreamingDecoder` — the `@suaveplan/audio-codecs` `StreamingDecoder` plus an additive Promise-returning `flushAsync()` companion. It is backed by a single long-running ffmpeg child process per decoder instance, lazily spawned on the first `write` so that a decoder constructed but never fed never spends a PID. `write` and `flush` honour the underlying interface's synchronous contract by returning whatever pages have already been buffered out of stdout; `flushAsync` is the safe end-of-stream method that ends stdin, awaits the child to exit, and yields any trailing pages. ## Features - Single persistent ffmpeg subprocess per decoder. - Lazy spawn — the child is only created once the first chunk arrives. - Sync `write(chunk): DecodeResult[]` returning all pages buffered to date. - Sync `flush(): DecodeResult[]` for callers that pump synchronously. - Additive `flushAsync(): Promise` for safe end-of-stream semantics (mandatory whenever the caller wants the trailing decoded frames). - `AbortSignal` and `timeoutMs` integrated — both raise the canonical `ABORTED` / `TIMEOUT` codes on the next `flushAsync` awaited. ## Usage: Basic ```ts import { FfmpegNativeCodec } from "@suaveplan/audio-codec-ffmpeg-native"; const decoder = new FfmpegNativeCodec().createDecoder({ format: "ogg" }); for (const chunk of oggChunks) decoder.write(chunk); const pages = await decoder.flushAsync(); decoder.close(); ``` ## Usage: Advanced ```ts import { FfmpegNativeCodec } from "@suaveplan/audio-codec-ffmpeg-native"; const codec = new FfmpegNativeCodec({ channels: 2, sampleRate: 48_000 }); const controller = new AbortController(); const decoder = codec.createDecoder({ signal: controller.signal }); try { for await (const chunk of network()) decoder.write(chunk); const tail = await decoder.flushAsync(); process.stdout.write(`decoded ${tail.length} trailing pages\n`); } finally { decoder.close(); } ``` ## API Reference - `createStreamingDecoder(opts: StreamingDecoderOptions): FfmpegStreamingDecoder` - `FfmpegStreamingDecoder` — extends `StreamingDecoder` with `flushAsync(): Promise`. - `StreamingDecoderOptions` — `binaryProvider`, `args`, `channels`, `sampleRate`, `format`, optional `signal`, `timeoutMs`, `spawn`. ## Implementation Notes The `@suaveplan/audio-codecs` `StreamingDecoder` contract is synchronous — `write(chunk): readonly DecodeResult[]`. Since the underlying ffmpeg subprocess is fundamentally asynchronous, the synchronous `write` returns only the pages already buffered from earlier `stdout data` events; the new chunk is piped into stdin in the same call but its decoded output arrives in subsequent ticks. This is why `flushAsync` exists and why the package docs guide callers to prefer it. The base `flush` is still useful for callers driving the decoder from an event loop tick that already drained stdout (e.g. a `WritableStream` pipe). Frame alignment matters: ffmpeg's `-f f32le` output is a stream of 4-byte-per-sample, channels-interleaved frames. The sync drain only de-interleaves a whole number of frames and re-buffers any trailing bytes for the next call, so partial chunks never corrupt the channel split.