4.4 KiB
SpritePlayer
(no submodules)
Purpose
SpritePlayer is the public orchestrator for @suaveplan/audio-sprite-player. It accepts a validated AudioSprite (a binary blob plus a JSON manifest produced by @suaveplan/audio-sprite-packer) and exposes fire-and-forget playback primitives — play, playSequence, playRandom, stopAll — on top of an injectable SpritePlaybackAdapter that engine-tier packages implement.
The class deliberately owns no audio output of its own. Every actual decode, gain ramp, pitch scheduling, and mixer routing concern is reached through the adapter. The orchestrator's job is manifest validation, entry lookup, option defaulting, voice management with stealing, sequence and random scheduling, abort propagation, telemetry, and a typed event surface.
Features
- Strict Zod validation of
SpritePlayerOptions, the loaded manifest, and everyPlayOptions/SequenceOptionsbag at the public boundary. - Voice manager with oldest-first stealing capped at
maxConcurrentVoices(default 32, hard ceiling 256). - Sequence playback with
durationMs + gapMsscheduling and rollback of started voices on failure. - Uniform random playback with optional filter predicate and
Rnginjection for determinism in tests. - Pre-aborted and mid-flight
AbortSignalpaths acrossload,play,playSequence, andplayRandom. - Typed event bus (
play,stop,complete,voice-stolen,error) and OTel span/counter/gauge/histogram on every invocation.
Usage: Basic Example
import {
createSpritePlayer,
NullSpritePlaybackAdapter,
} from "@suaveplan/audio-sprite-player";
const adapter = new NullSpritePlaybackAdapter();
const player = createSpritePlayer({ adapter });
await player.load(sprite);
const handle = await player.play("hit");
await handle.stop();
await player.dispose();
Usage: Advanced Example
import { createSpritePlayer } from "@suaveplan/audio-sprite-player";
import { BrowserSpritePlaybackAdapter } from "@suaveplan/audio-engine-browser";
const adapter = new BrowserSpritePlaybackAdapter({ audioContext });
const player = createSpritePlayer({
adapter,
maxConcurrentVoices: 16,
clock: () => audioContext.currentTime * 1000,
rng: seededRng(42),
});
player.events.on("voice-stolen", (e) => {
metrics.spritePlayerVoiceStolen.add(1, { entry: e.entryName });
});
await player.load(sprite);
const ctrl = new AbortController();
await player.playSequence(["step", "step", "land"], {
gapMs: 50,
fadeInMs: 10,
fadeOutMs: 20,
signal: ctrl.signal,
});
API Reference
class SpritePlayer
constructor(options: SpritePlayerOptions)— validates options; throwsINVALID_OPTIONSon failure.load(sprite, signal?)— validates manifest, initialises adapter. ThrowsINVALID_MANIFEST/ADAPTER_INIT_FAILED/ABORTED.play(entryName, opts?)— looks up entry, allocates voice (with stealing), delegates to adapter. ThrowsNOT_LOADED/UNKNOWN_ENTRY/INVALID_OPTIONS/PLAYBACK_FAILED/VOICE_LIMIT_EXCEEDED/ABORTED.playSequence(entryNames, opts?)— see spec §playSequence.playRandom(filterFn?, opts?)— uniform random pick from filtered entries.stopAll(fadeOutMs?)—Promise.allover every active voice.list()/has(name)— read accessors for loaded entries.dispose()— idempotent teardown.readonly events: SpritePlayerEvents— typed event subscriber.readonly telemetry: SpritePlayerTelemetry— OTel instruments.
createSpritePlayer(options)
Functional alias for new SpritePlayer(options).
Implementation Notes
- The player never imports a runtime symbol from
@suaveplan/audio-sprite-packer; the producer is consumed as a types-only dependency. - Auto-completion is scheduled via
setTimeout(resolved.durationMs)so the universal layer can firecompleteevents without knowing the adapter's actual end-of-voice signal — adapter implementations are free to stop the voice earlier and the player will skip its own completion emission via theisPlaying()guard. playRandomusesrng.nextUint32() % entries.lengthso tests can inject a deterministicRng.- The wrapper handle returned by
playis the same handle registered in theVoiceManager, so a stolen handle'sstop()is the same call path as a user-drivenstop()— the only difference is which event the player emits.