7.5 KiB
OpenSpec Change: enterprise-audio-codec-ffmpeg-native
- Change ID: enterprise-audio-codec-ffmpeg-native
- Package:
@suaveplan/audio-codec-ffmpeg-native - Category: server
- Target path:
packages/media/audio/audio-codec-ffmpeg-native/ - Status: PROPOSED
- Date: 2026-05-15
- Design: ./design.md
- Spec delta: ./specs/audio-codec-ffmpeg-native/spec.md
Summary
Deliver @suaveplan/audio-codec-ffmpeg-native as a server-tier concrete codec plugin that
implements the AudioCodec contract from @suaveplan/audio-codecs by shelling out to the
system ffmpeg binary via the fluent-ffmpeg CLI wrapper. The package bundles a static
binary via @ffmpeg-installer/ffmpeg (declared as optionalDependencies so consumers may
override) and streams audio through ffmpeg's stdin/stdout to support decode, encode, and
true chunked streaming for wav, mp3, ogg, opus, flac, aac/m4a, webm/matroska, and wma —
without ever buffering the full file in memory. Concurrency is bounded by an in-package
semaphore, every spawn carries an AbortSignal that escalates SIGTERM → SIGKILL, and
the package exposes a one-line register(registry) helper plus @suaveplan/streams-based
TransformStream adapters for pipeline composition.
Current-state audit (genesis)
The package does not exist on genesis yet — this is a fresh build. No prior source, no
test results, no coverage data. The package is genesis-original (no upstream parity
checklist required); the AudioCodec contract it plugs into is defined by the
in-flight enterprise-audio-codecs change.
Available workspace deps on genesis
| Package | Role |
|---|---|
@suaveplan/audio-codecs (workspace:^) |
AudioCodec, CodecRegistry, DecodeResult, StreamingDecoder, StreamingEncoder contracts |
@suaveplan/audio-core (workspace:^) |
AudioPage, AudioFormat aliases |
@suaveplan/streams (workspace:^) |
TransformStream, ReadableStream, WritableStream adapters with backpressure |
@suaveplan/error (workspace:^) |
SuaveplanError base class, defineCodes, structured error registry |
@suaveplan/types (workspace:^) |
Clock, LoggerLike injectables |
fluent-ffmpeg (catalog:audio) |
CLI wrapper used to compose ffmpeg invocations |
@ffmpeg-installer/ffmpeg (catalog:audio, optionalDependencies) |
Bundled static ffmpeg binary, default binaryPath |
zod (catalog:forms) |
Public options validation |
@opentelemetry/api (catalog:observability) |
Spans, counters, histograms, gauges |
tslib (catalog:build) |
TS runtime helpers |
Dev deps: vitest, @vitest/coverage-istanbul, fast-check, mitata (all catalog:testing),
@suaveplan/testing (workspace:^). Note: catalog entries for fluent-ffmpeg and
@ffmpeg-installer/ffmpeg must be added to root package.json catalog.audio if not
already present — this is tracked in tasks.md step 1.
Public API
Core types
import type {
AudioCodec,
CodecRegistry,
DecodeResult,
EncodeOpts,
StreamingDecoder,
StreamingEncoder,
} from "@suaveplan/audio-codecs";
import type { Clock, LoggerLike } from "@suaveplan/types";
export interface FfmpegNativeCodecOptions {
binaryPath?: string;
binaryProvider?: () => string;
concurrencyLimit?: number;
timeoutMs?: number;
logger?: LoggerLike;
clock?: Clock;
}
export class FfmpegNativeCodec implements AudioCodec {
readonly name: string;
readonly extensions: readonly string[];
readonly mimeTypes: readonly string[];
constructor(options?: FfmpegNativeCodecOptions);
canDecode(bytes: Uint8Array): boolean;
decode(bytes: Uint8Array, opts?: { signal?: AbortSignal }): Promise<DecodeResult>;
encode(
pcm: Float32Array[],
opts: EncodeOpts & { signal?: AbortSignal },
): Promise<Uint8Array>;
createDecoder(opts?: { format: string; signal?: AbortSignal }): StreamingDecoder;
createEncoder(opts: EncodeOpts & { signal?: AbortSignal }): StreamingEncoder;
}
export function register(registry: CodecRegistry, options?: FfmpegNativeCodecOptions): void;
Error codes
export type AudioCodecFfmpegNativeErrorCode =
| "AUDIO_CODEC_FFMPEG_NATIVE.BINARY_MISSING"
| "AUDIO_CODEC_FFMPEG_NATIVE.SPAWN_FAILED"
| "AUDIO_CODEC_FFMPEG_NATIVE.DECODE_FAILED"
| "AUDIO_CODEC_FFMPEG_NATIVE.ENCODE_FAILED"
| "AUDIO_CODEC_FFMPEG_NATIVE.TIMEOUT"
| "AUDIO_CODEC_FFMPEG_NATIVE.ABORTED"
| "AUDIO_CODEC_FFMPEG_NATIVE.UNSUPPORTED_CODEC"
| "AUDIO_CODEC_FFMPEG_NATIVE.INVALID_PCM";
export class AudioCodecFfmpegNativeError extends SuaveplanError<AudioCodecFfmpegNativeErrorCode> {}
export const AUDIO_CODEC_FFMPEG_NATIVE_ERROR_METADATA: Readonly<
Record<AudioCodecFfmpegNativeErrorCode, CodeMetadata>
>;
export function registerAudioCodecFfmpegNativeErrorCodes(): void;
Zod schemas (sibling *.schemas.ts)
export const FfmpegNativeCodecOptionsSchema: z.ZodType<FfmpegNativeCodecOptions>;
export const EncodeOptsSchema: z.ZodType<EncodeOpts>;
Module layout
Five-file pattern per feature module (feature.ts, feature.test.ts, feature.types.ts,
feature.md, index.ts); barrels use explicit named re-exports (rule 10 / §6a). Every
test file imports runner primitives from @suaveplan/testing/runner (rule 11 / §7).
packages/media/audio/audio-codec-ffmpeg-native/
package.json
tsconfig.json
vitest.config.ts
README.md
src/
index.ts
error-codes.ts
error-codes.md
error-codes.test.ts
constants/
index.ts
codec/
codec.ts
codec.md
codec.test.ts
codec.types.ts
codec.schemas.ts
index.ts
binary/
binary.ts
binary.md
binary.test.ts
binary.types.ts
index.ts
runner/
runner.ts
runner.md
runner.test.ts
runner.types.ts
index.ts
semaphore/
semaphore.ts
semaphore.md
semaphore.test.ts
semaphore.types.ts
index.ts
decode/
decode.ts
decode.md
decode.test.ts
decode.types.ts
index.ts
encode/
encode.ts
encode.md
encode.test.ts
encode.types.ts
index.ts
stream/
stream-decoder.ts
stream-decoder.md
stream-decoder.test.ts
stream-decoder.types.ts
stream-encoder.ts
stream-encoder.md
stream-encoder.test.ts
stream-encoder.types.ts
index.ts
abort/
abort.ts
abort.md
abort.test.ts
abort.types.ts
index.ts
otel/
otel.ts
otel.md
otel.test.ts
otel.types.ts
index.ts
register/
register.ts
register.md
register.test.ts
register.types.ts
index.ts
__benchmarks__/
codec.bench.ts
tests/
purity.test.ts
integration.test.ts
exports.test.ts
Impact
Affected dependents
Run bunx turbo ls --filter=...@suaveplan/audio-codec-ffmpeg-native to enumerate.
Package does not exist yet:
- No dependents — fresh package. No downstream consumers exist on genesis at proposal
time; future consumers (
@suaveplan/audio-codecsdefault-registry server bundle, application-sideregister(registry, ...)call sites) will pick this up by direct workspace dep when published.
Breaking changes: none — fresh build.
Prerequisites: @suaveplan/audio-codecs, @suaveplan/audio-core, @suaveplan/streams,
@suaveplan/error, @suaveplan/types published (or workspace-resolvable) on genesis;
catalog entries for fluent-ffmpeg and @ffmpeg-installer/ffmpeg declared in the root
package.json catalog.audio block.