6

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?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
ffritz
  • 2,180
  • 1
  • 28
  • 64

1 Answers1

7

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.

chrisguttandin
  • 7,025
  • 15
  • 21
  • Thanks for your reply. Well this doesn't seem to work either, the state change event is only called after clicking the button twice. I tried your library and get an encoding error. Well, I think I'm just gonna skip Safari. – ffritz Jun 26 '19 at 10:52
  • I was curious why it doesn't work for you and it seems like Safari needs that log statement to fire the `statechange` event. I updated the answer above and also added another option. – chrisguttandin Jun 26 '19 at 11:05
  • Getting an EncodingError when using standardized-audio-context with the code above sounds very strange to me. That should definitely not happen. Do you mind filing an issue on GitHub for that? – chrisguttandin Jun 26 '19 at 11:07
  • I will file an issue sure. I'm closer to getting this working I think, the problem I believe is `audioContext.decodeAudioData()`, which I believe safari simply isn't executing. -- UPDATE: It works! My arrayBuffer was Opus encoded. Who knew Safari doesn't support it. – ffritz Jun 26 '19 at 11:18
  • The EncodingError was then probably also related to my audio buffer being in Opus, and thus Safari throwing an EncodingError with your lib. The native solution you provided works on Safari Desktop, not iPhone though. – ffritz Jun 26 '19 at 11:21
  • Yes the EncodingError is in that case expected. Safari can't decode Opus files and thus throwing the error is the correct behavior. Do both options not work on the iPhone (resuming and creating a GainNode)? – chrisguttandin Jun 26 '19 at 11:40
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/195575/discussion-between-ffritz-and-chrisguttandin). – ffritz Jun 26 '19 at 11:58