first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,94 @@
# NullSpritePlaybackAdapter
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`NullSpritePlaybackAdapter` is the recording adapter that conforms to the `SpritePlaybackAdapter` contract without producing any audio. It is the fixture every test in this package uses, and it is exported so downstream engine adapters can use it as the contract-conformance baseline before plugging in real audio output.
Every method on the adapter appends a typed entry to a public read-only `calls` array. Every `play` returns a `SpritePlayHandle` whose `stop`, `setVolume`, and `isPlaying` invocations are also recorded. The timestamps come from an injectable `Clock`, so tests using `FakeClock` (or any deterministic clock) produce identical traces on every run.
## Features
- Implements the full `SpritePlaybackAdapter` interface: `init`, `play`, `dispose`.
- Records every method call with `{ kind, at, payload }` where `at` is the clock reading and `payload` carries the relevant arguments.
- Records every handle lifecycle event (`stop` with `fadeOutMs`, `setVolume` with value).
- Honours `AbortSignal`: throws an `aborted` error when invoked with an already-aborted signal.
- Pure — no platform globals, no side effects beyond the in-memory `calls` array.
## Usage: Basic Example
```ts
import { NullSpritePlaybackAdapter, createSpritePlayer } from "@suaveplan/audio-sprite-player";
const adapter = new NullSpritePlaybackAdapter();
const player = createSpritePlayer({ adapter });
await player.load(sprite);
await player.play("hit");
console.log(adapter.calls.map((c) => c.kind));
// → ["init", "play"]
```
## Usage: Advanced Example
```ts
import {
createNullSpritePlaybackAdapter,
createSpritePlayer,
} from "@suaveplan/audio-sprite-player";
import { FakeClock } from "@suaveplan/testing";
const fakeClock = new FakeClock({ startTime: 1_000_000 });
const adapter = createNullSpritePlaybackAdapter({
clock: () => fakeClock.now(),
});
const player = createSpritePlayer({
adapter,
clock: () => fakeClock.now(),
idGenerator: (() => {
let i = 0;
return () => `id-${++i}`;
})(),
});
await player.load(sprite);
await player.play("hit");
fakeClock.advance(100);
await player.play("hit");
const playRecords = adapter.calls.filter((c) => c.kind === "play");
console.log(playRecords.length); // 2
```
## API Reference
### `new NullSpritePlaybackAdapter(options?)`
`options.clock?: Clock` — defaults to `() => Date.now()`.
### `calls: ReadonlyArray<NullCall>`
Append-only log. `NullCall` is `{ kind: "init" | "play" | "dispose" | "stop" | "setVolume"; at: number; payload?: unknown }`.
### `init(sprite, signal?)`
Records `{ kind: "init" }`. Throws if `signal.aborted`.
### `play(entry, opts, signal?)`
Records `{ kind: "play", payload: { entryName, id, opts } }`. Returns a handle. Throws if `signal.aborted`.
### `dispose()`
Records `{ kind: "dispose" }`.
### `createNullSpritePlaybackAdapter(options?)`
Functional alias for `new NullSpritePlaybackAdapter(options)`.
## Implementation Notes
- The adapter is exported from the package root and from the `./adapter/null/null` subpath so consumers can import it without pulling the rest of the surface.
- It is a valid implementation of `SpritePlaybackAdapter` from the type level, so the `adapter contract compiles from type-only imports` smoke test in `null.test.ts` confirms a downstream engine could ship an adapter using nothing but `import type` from this package.
- The handle returned by `play` carries the same `id` recorded in the `play` call's payload, so consumers can correlate handle lifecycle calls back to their originating `play` deterministically.