Files
2026-05-27 23:47:53 +01:00

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).
  • AbortSignal and timeoutMs integrated — both raise the canonical ABORTED / TIMEOUT codes on the next flushAsync awaited.

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): FfmpegStreamingDecoder
  • FfmpegStreamingDecoder — extends StreamingDecoder with flushAsync(): Promise<readonly DecodeResult[]>.
  • 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.