first commit
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
# `binary`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
This module owns ffmpeg-binary discovery for `@suaveplan/audio-codec-ffmpeg-native`.
|
||||
It implements a fixed four-step precedence chain — caller-supplied provider,
|
||||
explicit absolute path, dynamically imported `@ffmpeg-installer/ffmpeg`
|
||||
optional dependency, then PATH-based discovery — and memoises successful
|
||||
resolutions by options-object identity so that high-traffic decode/encode
|
||||
pipelines pay the resolution cost exactly once per shared options bundle.
|
||||
|
||||
The resolver throws `AudioCodecFfmpegNativeError("AUDIO_CODEC_FFMPEG_NATIVE.BINARY_MISSING")`
|
||||
when every step fails, which lets the codec surface an actionable error to the
|
||||
caller instead of waiting for a downstream `ENOENT` from `child_process.spawn`.
|
||||
|
||||
## Features
|
||||
|
||||
- Strict precedence: provider → explicit path → installer → PATH lookup.
|
||||
- Promise-returning provider supported (sync providers also accepted).
|
||||
- Dynamic `import("@ffmpeg-installer/ffmpeg")` so the optional dependency is
|
||||
never a hard requirement at module load.
|
||||
- Test-injectable `installerLoader` and `pathLookup` hooks decouple resolution
|
||||
from real filesystem state.
|
||||
- `BINARY_MISSING` error code on full-chain failure with no leaked stack from
|
||||
the underlying optional-dep failure.
|
||||
- WeakMap-keyed memoisation that automatically GCs entries when the caller
|
||||
drops their options reference.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { resolveBinary } from "@suaveplan/audio-codec-ffmpeg-native/binary/binary";
|
||||
|
||||
const { path, source } = await resolveBinary();
|
||||
console.log(`Using ffmpeg at ${path} via ${source}`);
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import { resolveBinary } from "@suaveplan/audio-codec-ffmpeg-native/binary/binary";
|
||||
|
||||
const opts = {
|
||||
binaryProvider: async () => process.env.FFMPEG_PATH ?? "",
|
||||
binaryPath: "/usr/local/bin/ffmpeg",
|
||||
installerLoader: async () => ({ path: "/opt/ffmpeg" }),
|
||||
pathLookup: () => "/usr/bin/ffmpeg",
|
||||
} as const;
|
||||
|
||||
const first = await resolveBinary(opts);
|
||||
const second = await resolveBinary(opts); // memoised — no re-resolution.
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `resolveBinary(opts?): Promise<ResolvedBinary>` — returns `{ path, source }`.
|
||||
- `BinaryResolverOptions` — `binaryProvider`, `binaryPath`, `installerLoader`,
|
||||
`pathLookup` (all optional; first match wins per the precedence chain).
|
||||
- `ResolvedBinary.source` — `"provider" | "explicit" | "installer" | "path"`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The four-step chain is hard-coded — there is no plugin slot — because deviation
|
||||
in precedence would silently change which binary actually runs on production
|
||||
servers. Empty strings are treated as "miss" so callers can write `() => ""`
|
||||
in a provider to opt into the next strategy without throwing.
|
||||
|
||||
The installer is imported via dynamic `import()` because static imports would
|
||||
make `@ffmpeg-installer/ffmpeg` a hard dependency rather than the optional one
|
||||
the spec requires. Any failure inside the installer load (missing module,
|
||||
platform mismatch, exec-bit stripped) is swallowed and the chain advances —
|
||||
the only surfaced error is `BINARY_MISSING` after the entire chain misses.
|
||||
|
||||
Memoisation uses a `WeakMap` keyed by the options object reference; callers
|
||||
who want forced re-resolution pass a fresh options bundle.
|
||||
Reference in New Issue
Block a user