Audio
AudioManager in @g2w/core follows the same pattern as the asset system: an injected backend loads sounds, requests are cached and deduplicated, and failed loads are retryable. Core never interprets audio data — everything browser-specific lives behind the AudioBackendcontract.
Load and play
play(key, options?) loads the sound (usually a cache hit), starts one playback, and returns a SoundPlayback handle. Invalid options — a non-finite or negative volume — throwAudioError before any backend call:
import { AudioManager } from "@g2w/core";
import { WebAudioBackend } from "@g2w/audio-web";
const audio = new AudioManager({
backend: new WebAudioBackend(),
});
const laser = await audio.load("laser.wav");
const shot = await audio.play("laser.wav", { volume: 0.5 });
// a playback is a handle you may keep and stop early
shot.stop(); // idempotent: stopping twice is a no-op
// or stop everything the manager started
audio.stopAll();Repeated and concurrent load(key) requests share one load and one Sound value. If the backend cannot start playback — for example a browser autoplay policy holds the context suspended — the rejection propagates unchanged; it is never silently swallowed.
Looping music, scene-scoped
const music = await audio.play("theme.ogg", {
loop: true, volume: 0.4,
});
// leaving the gameplay scene:
exit() { music.stop(); },Ownership rules
The manager owns playback lifetimes, never the sounds themselves.Sound values are caller/backend-owned — decoded audio may be shared freely, even across managers. dispose() stops all active playbacks, clears the cache, and calls backend.dispose() once. It is terminal and idempotent: load and play after disposal throw AudioError.
Wire disposal into the game
const audio = new AudioManager({
backend: new WebAudioBackend(),
});
// after game.init(): audio joins the LIFO finalizer chain,
// releasing playbacks + AudioContext before the renderer
game.addFinalizer(() => audio.dispose());Next steps
See Scenes for where to trigger sounds from game state, orarchitecture notes for the backend contract in full.