iOS Audio playback does not resume when minimizing and bringing the web browser back into focus

Hi everyone,
I’m encountering an issue on iOS where audio playback stops and doesn’t automatically resume after the user:

  1. Minimizes the web browser and then brings it back into focus, or
  2. Locks and unlocks their device while the browser is open.

What I’ve tried:
Calling this.app.soundManager.context.resume() when the page regains visibility, however this consistently results in the following error after repeated use:

Attempted to resume the AudioContext on SoundManager.resume(), but it was rejected. InvalidStateError: Failed to start the audio device.

I’ve prepared a test scene to demonstrate the issue:
https://playcanvas.com/project/1447932/overview/audio-test

In the demo:

  • Background music starts when the user clicks “Start” .
  • Buttons to manually trigger SFX, call context.resume() , and context.suspend() .
  • Three experimental scripts that attempt to auto-resume audio (all currently disabled due to errors).
    Feel free to fork the project and enable them for testing.
    Of these, the “Suspend & Resume” approach seemed most reliable, though it feels hacky and might break other things…

Steps to reproduce:

  1. Open the project on an iOS device and click “Start” (BGM should play).
  2. Minimize the browser or lock the device.
  3. Return to the browser - audio does not resume.
  4. Press the “ctx.resume()” button - BGM resumes.
  5. Minimize or lock the device again.
  6. Return once more - the console now logs the InvalidStateError noted above.

Interesting Notes:

  • When the InvalidStateError occurs, it appears that manually calling soundManager.context.suspend() followed by soundManager.context.resume() causes audio to play again.

If anyone has experienced this or knows a workaround, I’d greatly appreciate your advice!

Thanks in advance.

Hmm, not sure. Couple of things, though:

  1. context.resume() returns a promise, so you would have to do something like
ctx.resume().then(() => {
    // play audio
})
// or use await in async
  1. A context state on a phone also has “interrupted” state, which you probably need to handle:
    Web Audio API 1.1

  2. If a context is in closed state, then .resume() promise will reject.

1 Like