tripleblep
tripleblep

Reputation: 540

Cannot resume AudioContext in Safari

I'm running into an issue where calling resume on an AudioContext never resolves when attempting to play audio in Safari. I'm creating an AudioContext on page load, thus it starts in a suspended state.

According to this chromium issue, calling resume will not resolve if blocked by the browser's autoplay policy. I have a click event bound to a <button> that will resume the context if it's suspended. This works in both Firefox and Chrome, and will work in Safari if I change the autoplay settings for the site.

Below is how I would normally resume the context:

await context.resume();

For Safari, I've tried calling resume without waiting for the promise to resolve, and instead register a callback for onstatechange, which is then wrapped in a Promise:

if (window.webkitAudioContext) {
    await new Promise((accept, reject) => {
        this.context.onstatechange = async () => {
            if ((await this.context.state) === "playing") {
                accept();
            } else {
                reject();
            }
         };

        this.context.resume();
    });
}

However, nothing has changed: the Promise never resolves, which means that the context state isn't changing.

Am I missing something?

Upvotes: 8

Views: 5572

Answers (2)

grim_i_am
grim_i_am

Reputation: 3923

iOS will only allow audioContext to be resumed if it is running within the call-stack of a UI Event Handler. Running it within a Promise moves the call to another call-stack.

Also, audioContext.resume() returns a promise, which must be awaited.

Try this:

onPlayHandler() {
   alert("State before: " + this.audioContext.state);
   await this.audioContext.resume();
   alert("State after: " + this.audioContext.state);
}

Upvotes: 8

tripleblep
tripleblep

Reputation: 540

I finally managed to get audio playing on Safari.

The "fix" was rather simple: I had to call resume within the event handler that is bound to the element being used to initiate playback. In my scenario, I was using Redux with React, so a dispatch would be made, and resuming the context would happen at a later time within another component. I'm guessing that Safari didn't see this as a direct response to a user interaction event, so it kept the context in a suspended state.

Upvotes: 7

Related Questions