diff --git a/src/audio/emscripten/SDL_emscriptenaudio.c b/src/audio/emscripten/SDL_emscriptenaudio.c index 341c9dfcd..9623d4aaa 100644 --- a/src/audio/emscripten/SDL_emscriptenaudio.c +++ b/src/audio/emscripten/SDL_emscriptenaudio.c @@ -183,6 +183,12 @@ static void EMSCRIPTENAUDIO_CloseDevice(_THIS) SDL2.audio.scriptProcessorNode.disconnect(); SDL2.audio.scriptProcessorNode = undefined; } + if (SDL2.audio.silenceTimer !== undefined) { + clearInterval(SDL2.audio.silenceTimer); + } + if (SDL2.audio.silenceBuffer !== undefined) { + SDL2.audio.silenceBuffer = undefined + } SDL2.audio = undefined; } if ((SDL2.audioContext !== undefined) && (SDL2.audio === undefined) && (SDL2.capture === undefined)) { @@ -227,7 +233,9 @@ static int EMSCRIPTENAUDIO_OpenDevice(_THIS, const char *devname) SDL2.audioContext = new webkitAudioContext(); } if (SDL2.audioContext) { - autoResumeAudioContext(SDL2.audioContext); + if ((typeof navigator.userActivation) === 'undefined') { // Firefox doesn't have this (as of August 2023), use autoResumeAudioContext instead. + autoResumeAudioContext(SDL2.audioContext); + } } } return SDL2.audioContext === undefined ? -1 : 0; @@ -298,6 +306,7 @@ static int EMSCRIPTENAUDIO_OpenDevice(_THIS, const char *devname) if (SDL2.capture.silenceTimer !== undefined) { clearTimeout(SDL2.capture.silenceTimer); SDL2.capture.silenceTimer = undefined; + SDL2.capture.silenceBuffer = undefined } SDL2.capture.mediaStreamNode = SDL2.audioContext.createMediaStreamSource(stream); SDL2.capture.scriptProcessorNode = SDL2.audioContext.createScriptProcessor($1, $0, 1); @@ -339,10 +348,37 @@ static int EMSCRIPTENAUDIO_OpenDevice(_THIS, const char *devname) SDL2.audio.scriptProcessorNode = SDL2.audioContext['createScriptProcessor']($1, 0, $0); SDL2.audio.scriptProcessorNode['onaudioprocess'] = function (e) { if ((SDL2 === undefined) || (SDL2.audio === undefined)) { return; } + // if we're actually running the node, we don't need the fake callback anymore, so kill it. + if (SDL2.audio.silenceTimer !== undefined) { + clearInterval(SDL2.audio.silenceTimer); + SDL2.audio.silenceTimer = undefined; + SDL2.audio.silenceBuffer = undefined; + } SDL2.audio.currentOutputBuffer = e['outputBuffer']; dynCall('vi', $2, [$3]); }; + SDL2.audio.scriptProcessorNode['connect'](SDL2.audioContext['destination']); + + if (SDL2.audioContext.state === 'suspended') { // uhoh, autoplay is blocked. + SDL2.audio.silenceBuffer = SDL2.audioContext.createBuffer($0, $1, SDL2.audioContext.sampleRate); + SDL2.audio.silenceBuffer.getChannelData(0).fill(0.0); + var silence_callback = function() { + if ((typeof navigator.userActivation) !== 'undefined') { // Almost everything modern except Firefox (as of August 2023) + if (navigator.userActivation.hasBeenActive) { + SDL2.audioContext.resume(); + } + } + + // the buffer that gets filled here just gets ignored, so the app can make progress + // and/or avoid flooding audio queues until we can actually play audio. + SDL2.audio.currentOutputBuffer = SDL2.audio.silenceBuffer; + dynCall('vi', $2, [$3]); + SDL2.audio.currentOutputBuffer = undefined; + }; + + SDL2.audio.silenceTimer = setInterval(silence_callback, ($1 / SDL2.audioContext.sampleRate) * 1000); + } }, this->spec.channels, this->spec.samples, HandleAudioProcess, this); } /* *INDENT-ON* */ /* clang-format on */