Skip to content

Audio

YAGE wraps @pixi/sound for audio playback. The @yagejs/audio package provides channel-based mixing, a SoundComponent for entity-bound audio, and a sound() asset factory for preloading.

import { AudioPlugin } from "@yagejs/audio";
engine.use(new AudioPlugin({
channels: {
sfx: { volume: 1.0 },
music: { volume: 0.7 },
},
}));

Use the sound() factory to create preloadable asset handles:

import { sound } from "@yagejs/audio";
const CoinSfx = sound("assets/coin.wav");
const BgMusic = sound("assets/music.mp3");

Add them to your scene’s preload array so they load before onEnter:

class GameScene extends Scene {
readonly preload = [CoinSfx, BgMusic];
}

Resolve the AudioManager from the DI container and call play:

import { AudioManagerKey } from "@yagejs/audio";
const audio = this.use(AudioManagerKey);
// Play a sound effect
audio.play(CoinSfx.path, { channel: "sfx" });
// Play background music (looping)
audio.play(BgMusic.path, { channel: "music", loop: true, volume: 0.5 });

Play options:

audio.play(alias, {
channel: "sfx", // which channel (default: "sfx")
volume: 1, // instance volume 0–1 (default: 1)
loop: false, // loop playback (default: false)
speed: 1, // playback rate (default: 1)
});

Additional play methods:

// Only play if not already playing via playOnce
audio.playOnce(BgMusic.path, { channel: "music", loop: true });
// Pick a random sound from an array
audio.playRandom(["step1.wav", "step2.wav", "step3.wav"], { channel: "sfx" });

play returns a SoundHandle for controlling playback:

const handle = audio.play(BgMusic.path, { channel: "music", loop: true });
handle.playing; // boolean
handle.volume = 0.5; // adjust volume
handle.speed = 1.5; // adjust speed
handle.paused = true; // pause/resume
handle.muted = true; // mute/unmute
handle.stop(); // stop playback

You can also stop via the manager:

audio.stop(handle);

Channels group sounds for independent volume control. The default channels are "sfx" and "music", but you can define any channels in the config.

// Volume control
audio.setChannelVolume("music", 0.3);
audio.getChannelVolume("music"); // 0.3
// Mute/unmute a channel
audio.muteChannel("sfx");
audio.unmuteChannel("sfx");
// Pause/resume a channel
audio.pauseChannel("music");
audio.resumeChannel("music");
// Stop all sounds in a channel
audio.stopChannel("sfx");
audio.muteAll();
audio.unmuteAll();
audio.stopAll();

Web browsers suspend the AudioContext when a page loads and require a user gesture (click, tap, or keypress) before any audio can play. @pixi/sound attaches a one-shot gesture listener that resumes the context on the first interaction, so calling audio.play(...) after a click just works.

What this means in practice: any audio you schedule on page load is silent until the user first interacts with the page. That is expected browser behavior, not a YAGE bug — the classic symptom is “my title music doesn’t play until I click somewhere.” To handle the gap cleanly, use onUnlock:

const audio = this.use(AudioManagerKey);
audio.onUnlock(() => {
audio.play("music/title", { channel: "music", loop: true });
});

onUnlock fires synchronously if the context is already running, or once on the next gesture that resumes it. It returns a disposer, so you can cancel a pending registration:

const dispose = audio.onUnlock(() => startMusic());
// …later
dispose();
// or: audio.offUnlock(startMusic);

Expose the pending state to the UI if you want a “tap to enable audio” hint:

if (!audio.isUnlocked()) {
// render a prompt; it's safe to dismiss on first click —
// @pixi/sound will have resumed the context by then
}

isUnlocked() reflects AudioContext.state === "running" and is never flipped by the mute/pause-on-blur flags below — it is strictly the browser-capability check.

Most web games go silent when the user backgrounds the tab. AudioManager suspends the underlying AudioContext when the window loses focus and resumes it on return:

engine.use(new AudioPlugin({
autoMuteOnBlur: true, // default: true
}));
// Or toggle at runtime:
audio.autoMuteOnBlur = false;

autoMuteOnBlur is delegated to @pixi/sound’s built-in WebAudioContext.autoPause, which listens for window.blur/focus events and pauses/resumes the entire AudioContext. Per-channel mutes and volumes are untouched. Toggling the flag while the window is currently unfocused takes effect immediately (the manager reconciles paused for you); otherwise the new policy applies on the next blur event.

Pausing scenes on tab blur is a scene-lifecycle concern, not an audio one — see SceneManager.autoPauseOnBlur.

SoundComponent binds a sound to an entity. The sound is automatically stopped when the entity is destroyed.

import { SoundComponent } from "@yagejs/audio";
entity.add(new SoundComponent({
alias: BgMusic.path,
channel: "music",
loop: true,
volume: 0.8,
playOnAdd: true, // start playing when the component is added
}));

Control playback from the component:

const sc = entity.get(SoundComponent);
const handle = sc.play();
sc.stop();
sc.handle; // current SoundHandle or null

This is useful for ambient sounds attached to world entities — the sound lifecycle is tied to the entity lifecycle.