3.2 KiB
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<DecodeResult[]>for safe end-of-stream semantics (mandatory whenever the caller wants the trailing decoded frames). AbortSignalandtimeoutMsintegrated — both raise the canonicalABORTED/TIMEOUTcodes on the nextflushAsyncawaited.
Usage: Basic
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
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): FfmpegStreamingDecoderFfmpegStreamingDecoder— extendsStreamingDecoderwithflushAsync(): Promise<readonly DecodeResult[]>.StreamingDecoderOptions—binaryProvider,args,channels,sampleRate,format, optionalsignal,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.