first commit
This commit is contained in:
141
docs/media/audio/audio-codecs/src/registry/registry.md
Normal file
141
docs/media/audio/audio-codecs/src/registry/registry.md
Normal file
@@ -0,0 +1,141 @@
|
||||
# `registry/registry`
|
||||
|
||||
In-memory plugin registry that owns audio-codec lookup and dispatch for the
|
||||
whole package.
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
`CodecRegistry` is the single entry point through which `decodeSource`,
|
||||
`encodeTo`, `progressiveDecode`, and the streaming transform factories
|
||||
resolve a codec from raw bytes, a format hint, an extension, or a MIME type
|
||||
and then dispatch decode/encode operations to it. Without a single registry,
|
||||
every public entry point would re-implement the lookup + dispatch + error
|
||||
mapping triad — duplicating wiring and risking divergence.
|
||||
|
||||
The registry also owns the package's OpenTelemetry instrumentation surface
|
||||
for decode/encode calls (`codec.decode` / `codec.encode` spans, the
|
||||
`audio_codec_*` counter and histogram pair). Codecs themselves stay free of
|
||||
OTel concerns; the registry observes the boundary.
|
||||
|
||||
## Features
|
||||
|
||||
- Three lookup indexes — by canonical `name`, by file extension, by MIME
|
||||
type. `get(key)` checks all three so callers do not need to know which
|
||||
shape they hold.
|
||||
- Eager initial registration via `new CodecRegistry({ codecs: [...] })` plus
|
||||
imperative `register(codec)` / `unregister(name)`.
|
||||
- `detect(bytes)` walks every registered codec's `canDecode` in
|
||||
registration order and returns the first match's `name` (or `undefined`).
|
||||
- `decode(bytes, opts?)` resolves the codec, awaits its `decode`, and builds
|
||||
an `AudioDocument` from the resulting `Float32Array[]` PCM. Non-API codec
|
||||
errors are wrapped in `AudioCodecsError("AUDIO_CODECS.DECODE_FAILED")`
|
||||
with the original on `cause`; `AudioCodecsError` instances pass through.
|
||||
- `encode(doc, format, opts?)` materialises the document's PCM, dispatches
|
||||
to the codec registered for `format`, and returns the encoded bytes.
|
||||
Unknown format → `AUDIO_CODECS.NOT_SUPPORTED`. Non-API encode error →
|
||||
`AUDIO_CODECS.ENCODE_FAILED` with `cause`.
|
||||
- `decodeStream(source, format)` returns a `ReadableStream<AudioPage>` whose
|
||||
pull loop drives the codec's `createDecoder()` streaming factory.
|
||||
Codecs without that factory throw `AUDIO_CODECS.STREAM_ERROR`.
|
||||
- `encodeStream(doc, format)` returns a `ReadableStream<Uint8Array>` whose
|
||||
pull loop feeds the document's pages (sorted by `startSample`) into the
|
||||
codec's `createEncoder()`.
|
||||
- `list()` returns every registered codec for introspection.
|
||||
- Zod-validated codec registration via `AudioCodecSchema` — a malformed
|
||||
codec object is rejected at `register()` with `INVALID_SOURCE`.
|
||||
- OTel spans, counter, and duration histogram on every successful decode
|
||||
and encode; spans carry the resolved `format` as an attribute.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { CodecRegistry } from "@suaveplan/audio-codecs/registry/registry";
|
||||
import { WavCodec } from "@suaveplan/audio-codecs/codecs/wav";
|
||||
|
||||
const registry = new CodecRegistry({ codecs: [WavCodec] });
|
||||
|
||||
// decode WAV bytes into an AudioDocument
|
||||
const doc = await registry.decode(wavBytes);
|
||||
|
||||
// re-encode back to WAV at 24-bit PCM
|
||||
const wav = await registry.encode(doc, "wav", { bitDepth: 24, format: "pcm" });
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
Hot-swap a codec and pipeline a streaming decode into a custom transform:
|
||||
|
||||
```ts
|
||||
import { CodecRegistry } from "@suaveplan/audio-codecs/registry/registry";
|
||||
import { Mp3Codec, WavCodec, FlacCodec } from "@suaveplan/audio-codecs";
|
||||
|
||||
const registry = new CodecRegistry({ codecs: [WavCodec, Mp3Codec] });
|
||||
registry.register(FlacCodec); // additive — now decodes FLAC too
|
||||
|
||||
const sourceStream: ReadableStream<Uint8Array> = await fetchAudio();
|
||||
const pageStream = registry.decodeStream(sourceStream, "mp3");
|
||||
|
||||
// pipe through a downstream normaliser (defined elsewhere)
|
||||
const normalised = pageStream.pipeThrough(makeNormaliser());
|
||||
|
||||
for await (const page of normalised) {
|
||||
consumePage(page);
|
||||
}
|
||||
|
||||
// detect-then-dispatch for unknown bytes
|
||||
const fmt = registry.detect(unknownBytes);
|
||||
if (fmt) {
|
||||
const doc = await registry.decode(unknownBytes, { format: fmt });
|
||||
useDoc(doc);
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `class CodecRegistry`
|
||||
- `constructor(options?: CodecRegistryOptions)` — `options.codecs` are
|
||||
each `register`-ed eagerly.
|
||||
- `register(codec: AudioCodec): void` — throws
|
||||
`AudioCodecsError("AUDIO_CODECS.INVALID_SOURCE")` if `codec` fails
|
||||
`AudioCodecSchema` validation. Replaces any prior codec sharing the
|
||||
same `name`; extension and MIME indexes additively expand.
|
||||
- `unregister(name: string): void` — no-op for unknown names; clears
|
||||
matching extension and MIME entries.
|
||||
- `get(nameOrExtOrMime: string): AudioCodec | undefined`
|
||||
- `list(): readonly AudioCodec[]`
|
||||
- `detect(bytes: Uint8Array): string | undefined`
|
||||
- `decode(bytes, opts?): Promise<AudioDocument>` — `opts.format` is a hint
|
||||
that wins over `canDecode` detection when supplied; `opts.metadata` is
|
||||
forwarded onto the resulting `AudioDocument`.
|
||||
- `encode(doc, format, opts?): Promise<Uint8Array>`
|
||||
- `decodeStream(source, format): ReadableStream<AudioPage>`
|
||||
- `encodeStream(doc, format): ReadableStream<Uint8Array>`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- The three lookup indexes are kept independently — `unregister` walks both
|
||||
extension and MIME maps and deletes only entries that still point at the
|
||||
removed codec, so registering two codecs that share an extension and
|
||||
unregistering one leaves the other intact.
|
||||
- `detect` iteration order is the order codecs were registered. Callers
|
||||
that need a different priority (e.g. AIFF before WAV when both
|
||||
canDecode an ambiguous prefix) should re-`register` the higher-priority
|
||||
codec last so its `canDecode` runs first.
|
||||
- The `#instrumented` helper centralises span lifetime, counter, and
|
||||
histogram observations. It catches any thrown error, sets the span
|
||||
status to ERROR, records the exception, then re-throws — so the
|
||||
outer-most caller still observes the original (or wrapped) error.
|
||||
- `pcmFromDocument` walks the document's pages in their stored order, not
|
||||
by `startSample`. For documents produced by `buildDocument` this is the
|
||||
same; for hand-constructed documents the caller is responsible for
|
||||
ordered insertion. `encodeStream` does sort by `startSample` explicitly
|
||||
to handle reordering at the streaming boundary.
|
||||
- The constants `TRACER`, `METER`, `DECODE_COUNTER`, `ENCODE_COUNTER`,
|
||||
`DECODE_HIST`, `ENCODE_HIST` are acquired at module evaluation time but
|
||||
the underlying OTel SDK is a no-op when no exporter is registered, so
|
||||
importing the registry has no observable runtime cost in unconfigured
|
||||
environments.
|
||||
Reference in New Issue
Block a user