Audio mixer coordination — don't fight other audio
Source: SDH-GameThemeMusic — AudioLoader integration Category: Pattern — audio / Steam Deck
Audio mixer coordination — on a shared audio device (Steam Deck, phone, any desktop), multiple apps want to play sound. Your plugin shouldn’t blast theme music over a Discord call or stack with the game’s own music. Duck, pause, defer — through whatever coordination primitive the platform offers.
What it is
Section titled “What it is”Before playing audio, check for:
- Other plugin audio (e.g. AudioLoader also wants to play something)
- System audio state (game paused? game playing its own theme?)
- User preference (per-game override? “silence mode”?)
During playback:
- Duck (lower volume) when another source starts
- Pause on user actions (focus lost, voice chat opened)
- Resume when the conflict resolves
After stopping:
- Release resources (audio session, OS audio device lock)
Why it exists
Section titled “Why it exists”The problem: Your plugin’s “theme music auto-play on game page” is delightful when the device is quiet. It’s jarring when:
- User is on a Discord call; music blasts over the voice channel
- Another plugin plays a different track simultaneously → cacophony
- Game has its own opening theme → double tracks
- User stopped the music manually; it restarts on the next page load
The fix: coordinate. The specifics vary per platform; the pattern is always “check before playing, adapt during playback, clean up after”.
SDH-GameThemeMusic specifics (AudioLoader integration)
Section titled “SDH-GameThemeMusic specifics (AudioLoader integration)”Decky’s AudioLoader plugin is the shared audio mixer. It exposes a JS API that other plugins register with:
// On plugin loadif (window.AudioLoader) { AudioLoader.registerMixerSource({ id: 'game-theme-music', priority: 'low', // lower than voice chat, higher than silence canDuck: true, onDuckRequest: (level) => { setVolume(level); // another source wants to play }, });}
// Before playingasync function play(url: string) { if (AudioLoader?.isExclusiveActive()) { return; // something higher priority is playing }
audioElement.src = url; audioElement.volume = AudioLoader?.getDuckedLevel?.() ?? 1.0; audioElement.play();}AudioLoader exposes:
- Mixer registration
- Ducking levels (0.0-1.0) that react to other sources
- “Exclusive mode” flags (for voice chat)
- Fade in/out utilities
Plugins that don’t integrate just… play over everything.
Cross-platform equivalents
Section titled “Cross-platform equivalents”- macOS/iOS —
AVAudioSessionwith categories (.playback,.ambient);interruptionNotificationfor calls - Android —
AudioManager.requestAudioFocus()/AbandonAudioFocus()with duck/pause callbacks - Windows — Windows.Media.Playback namespace; ISystemMediaTransportControls
- Web browsers — no formal mixer; HTMLMediaElement + your own coordination
How it’s used
Section titled “How it’s used”- SDH-GameThemeMusic — registers with AudioLoader, ducks on voice chat, pauses when the user manually pauses game audio
- Pattern generalizes to any always-on-audio app (ambient music, notification sounds, game audio)
Gotchas
Section titled “Gotchas”- Don’t start audio at 100% volume. Fade in over 500ms — users opening a page shouldn’t be startled.
- Save the user’s mute. If they muted the theme once, don’t auto-play again on the next game page. Per-game preference in plugin storage.
- Respect system mute. iOS silent switch, Android DND mode, macOS “Do Not Disturb”. Don’t override; the user has spoken.
- Page transitions. When the user navigates away, fade out (400ms) before stopping. Abrupt cuts are jarring.
- Memory on long playback. Audio elements hold buffers; 30+ minute tracks can leak memory if not disposed.
audio.src = ''; audio.load();releases the buffer. - Duplicate events. If the same page loads twice (Steam’s UI sometimes does this), don’t start two audio elements. Singleton pattern.
- Test in a voice call. Best QA is joining a Discord call and loading the plugin. If the music interrupts the call, you have bugs.
- Volume normalization. YouTube tracks vary wildly in volume. Apply a compressor or ReplayGain; consistent volume across games is table-stakes UX.
- Handoff to OS media controls. On Android/iOS, populate the lock-screen media widget. Users expect to pause from there.
See also
Section titled “See also”- projects/sdh-gamethememusic
- patterns/decky-plugin-architecture — where this runs
- patterns/youtube-audio-extraction-fallbacks — how the audio got there