Unlock JavaScript Web Audio in Safari and Chrome

Update: This is now necessary for Chrome, too. You might see this message in the console:

The AudioContext was not allowed to start. It must be resumed (or created) after a user gesture on the page.

It can be tricky to deal with Web Audio because browser vendors have measures in place to protect users from undesired sound playback. In short, user interaction is required to unlock the AudioContext.

Sanity check: make sure your device is unmuted - I know at least Safari iOS will prevent Web Audio playback if the ringer is set to vibrate.

Here's some small boilerplate code to add the necessary hooks. This is written in ES6 JavaScript, and will work in Safari (iOS and macOS) and Chrome:

function unlockAudioContext(audioCtx) {
  if (audioCtx.state !== 'suspended') return;
  const b = document.body;
  const events = ['touchstart','touchend', 'mousedown','keydown'];
  events.forEach(e => b.addEventListener(e, unlock, false));
  function unlock() { audioCtx.resume().then(clean); }
  function clean() { events.forEach(e => b.removeEventListener(e, unlock)); }
}

Call the function immediately after creating the audio context, like this:

const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
unlockAudioContext(audioCtx);

Here's something very similar in ES5:

function unlockAudioContext(audioCtx) {
  if (audioCtx.state === 'suspended') {
    var events = ['touchstart', 'touchend', 'mousedown', 'keydown'];
    var unlock = function unlock() {
      events.forEach(function (event) {
        document.body.removeEventListener(event, unlock)
      });
      audioCtx.resume();
    };

    events.forEach(function (event) {
      document.body.addEventListener(event, unlock, false)
    });
  }
}

7 ResponsesLeave a Reply

  1. Digbalay Bose

     /  May 15, 2019 Quote

    Thanks for the code Matt ! The code worked for safari from macbooks but when I tried it from safari in iphone and ipad, i could not hear any sound.

  2. James Moore

     /  July 6, 2019 Quote

    Thanks. Finally something that worked for me. The only problem I have now is that I have to click the button twice or second click outside the button to hear the audio. After 4 times or so, it just stops working and I have to refresh the browser to get it to work again. Very frustrating!

  3. baraa

     /  May 25, 2021 Quote

    Hello How can i add this function in an angular 8 application
    thank you in advance

  4. Great little article. It got me past the gesture-unlocking problem I had on Safari. Thanks!

    To James Moore or whoever else comes along, I've read that Safari allows just 4 open AudioContext instances for a web page. Maybe that matches the "after 4 times or so, it just stops working". Reusing a single AudioContext instance seems like a good practice in any case.

  5. Dave

     /  March 4, 2022 Quote

    The key insight that helped me was discovering that upon user interaction that AudioContext.resume() needed to be called. The comments here also helped me realize a single AudioContext could be unlocked once and reused for as many different audio streams as needed.

    Thank you very much for sharing this!

  6. I believe in the ES6 formula, "context.state" should be "audioCtx.state"

  7. Eric: I believe in the ES6 formula, "context.state" should be "audioCtx.state"

    Thanks! Fixed.

Leave a Reply