safarimobile-safariweb-audio-apiaudiocontextwebkitaudiocontext

Safari AudioContext suspended even with onclick creation


I'm having troubles creating an AudioContext with Safari (desktop and mobile). It seems that even with creation upon user interaction, it is still suspended.

My code:

<button onclick="test()">Test</button>
const test = () => {
    window.AudioContext = window.AudioContext || window.webkitAudioContext;
    audioContext = new AudioContext();
    console.log(audioContext.state); // Suspended
}

This should be a minimum working example, right? What's wrong here?


Solution

  • I think Safari is actually behaving correctly (at least partially) in this case. The Web Audio Spec says that ...

    A newly-created AudioContext will always begin in the suspended state, and a state change event will be fired whenever the state changes to a different state.

    https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-onstatechange

    Unfortunately Safari doesn't do the transition to the running state on its own. You have to explicitly ask it to do so.

    audioContext.resume();
    audioContext.onstatechange = () => console.log(audioContext.state);
    

    The statechange event should fire almost immediately. If you execute this inside the click handler.

    The function above would then look like this:

    const test = () => {
        window.AudioContext = window.AudioContext || window.webkitAudioContext;
        audioContext = new AudioContext();
        console.log(audioContext.state); //suspended
        audioContext.resume();
        audioContext.onstatechange = () => console.log(audioContext.state); // running
    }
    

    Interestingly Safari only fires the statechange event if you keep the console.log statement before calling resume().

    However there is another hack that you can try to kick of the AudioContext. Just create a simple GainNode.

    const test = () => {
        window.AudioContext = window.AudioContext || window.webkitAudioContext;
        audioContext = new AudioContext();
        audioContext.createGain();
        console.log(audioContext.state); // running
    }
    

    You can also give standardized-audio-context a try which makes all browsers behave the same in that regard.