Resume AudioContext on user gesture for web audio

Browsers (Chrome, Safari, mobile Firefox) create every AudioContext
suspended and refuse to start it without a user gesture. Bevy -> rodio ->
cpal build their context at startup, so it stayed suspended and no sound
played except on lenient desktop Firefox.

Wrap the AudioContext constructor in the web shell to track created
contexts and resume them on the first pointer/key/touch gesture.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
main
Elijah Voigt 1 day ago
parent 7aa159e7b8
commit a5825af471

@ -2,6 +2,41 @@
<html lang="en"> <html lang="en">
<body style="margin: 0px;"> <body style="margin: 0px;">
<script>
// --- Web Audio autoplay unlock -----------------------------------------
// Browsers (Chrome, Safari, mobile Firefox) create every AudioContext in a
// "suspended" state and refuse to start it until the user interacts with
// the page. Bevy -> rodio -> cpal build their AudioContext at startup, long
// before any gesture, so it stays suspended and no sound is ever produced
// ("AudioContext was prevented from starting automatically" in the console).
//
// We wrap the AudioContext constructor to remember every context that gets
// created, then resume them all on the first real user gesture. This must
// run BEFORE the wasm module loads so the wrapper is in place when cpal
// constructs its context.
(function () {
const Native = window.AudioContext || window.webkitAudioContext;
if (!Native) return;
const live = new Set();
function Wrapped(...args) {
const ctx = new Native(...args);
live.add(ctx);
return ctx;
}
Wrapped.prototype = Native.prototype;
window.AudioContext = Wrapped;
window.webkitAudioContext = Wrapped;
function resumeAll() {
for (const ctx of live) {
if (ctx.state === 'suspended') ctx.resume().catch(() => {});
}
}
for (const ev of ['pointerdown', 'mousedown', 'keydown', 'touchstart']) {
window.addEventListener(ev, resumeAll, { capture: true, passive: true });
}
})();
</script>
<script type="module"> <script type="module">
import init from './bin.js' import init from './bin.js'

Loading…
Cancel
Save