3.1 KiB
provider/audio-provider
Purpose
<AudioProvider> is the single React component that owns the audio
engine bundle for a React subtree. It constructs exactly one
BrowserTimelineAdapter, one TimelineEngine, one Transport, one
ProviderBusRegistry, and one PluginRegistry, publishes the
resolved bundle through AudioReactContext, and tears every one of
them down on unmount.
The provider is React 19 Strict Mode safe: under the development
double-invoke pattern (mount → unmount → re-mount) it never leaks an
engine. The async init pipeline is gated by a cancellation flag and an
AbortController; if the effect tears down before init resolves the
flag suppresses the publish step and any partially-constructed adapter
is force-disposed immediately.
Features
- Strict-mode-safe async init with
AbortSignalcancellation. - Synchronous unmount path that disposes
adapter, thentransport, thenbuses— each at most once. - Zod-validated options (
AudioProviderOptions) — invalid input throwsReactAudioError(REACT_AUDIO.INVALID_OPTIONS)at construct time. - Auto-registers consumer-supplied plugin factories into the package registry.
- Publishes
engineState('pending' | 'ready' | 'error') for theuseEngineStatehook and any consumer that wants to render a loading state.
Usage: Basic
import { AudioProvider } from "@suaveplan/react-audio";
function App() {
return (
<AudioProvider>
<Workspace />
</AudioProvider>
);
}
Usage: Advanced
import { AudioProvider } from "@suaveplan/react-audio";
function App() {
return (
<AudioProvider
options={{
adapter: { sampleRate: 48000, latencyHint: "interactive" },
transport: { bpm: 128 },
plugins: [
{ pluginType: "delay", factory: () => makeDelay() },
],
sampleRateHz: 30,
}}
>
<Workspace />
</AudioProvider>
);
}
API Reference
<AudioProvider options? children?>— renders a context provider.AudioProviderProps—{ options?: AudioProviderOptions, children?: ReactNode }.
Implementation Notes
The async init pipeline lives entirely inside useEffect's async
IIFE so React's render path stays synchronous. A PendingInit value
carries the in-flight adapter, transport, and bus-registry references
so the cleanup callback can dispose anything already constructed if
the effect is torn down mid-init — this is the linchpin of the
Strict Mode guarantee. The AbortController is also passed to
adapter.init({ signal }) so the engine package's own cancellable
work paths short-circuit. State updates (setState, setEngineState,
setVersion) are skipped when cancelled is true so a torn-down
effect cannot mutate React state. The teardown path is unconditional
and synchronous: every dispose is wrapped in nothing but a null
check, no try/catch — disposing a class that hasn't fully initialised
is the responsibility of the owning package's dispose() to handle.