safari ios audio- refusing to play with error

Viewed 1737

The javascript error is: Unhandled Promise Rejection: NotAllowedError: The request is not allowed by the user agent or the platform in the current context, possibly because the user denied permission.

My setup works across other browsers, desktop and mobile.

The way it works is:

  • have a flag first_audio_played = false;
  • add a touch event listener that plays some audio, and sets first_audio_played = true; (then removes the touch listener)
  • all subsequent audio checks if(first_audio_played) some_other_audio.play();

this way, only the first audio played requires direct user input. after that, all audio is free to be triggered by in-game events, timing, etc...

this appears to be the "rule" for audio across most browsers. is the iOS "rule" that every audio needs to be triggered by user input? or is there some other step I'm missing?

2 Answers

For my javascript game, sounds stopped working on iOS recently. They all have readyState=4, but only the sound I played on tap works, the others won't play. Maybe you could play all the sounds on the first tap. But the solution I found that works for now for me is to load all the sounds from ajax arraybuffers and use decodeAudioData(). Then once you've played 1 sound from user tap (on not the body), they all play whenever.

Here is my working code where the second way of doing it is on bottom. When I tap to play sound2, sound1 starts working also.

<html>
    <body>
        <div id=all style='font-size:160%;background:#DDD' onclick="log('clicked');playSound(myAudio)">
            Sound1 should be playing every couple seconds.
            <br />Tap here to play sound1.
        </div>
        <div id=debug style='font-size:120%;' onclick="playSound(myAudio2)">
            Tap here to play the sound2.
        </div>
        <script>
            var url = "http://curtastic.com/drum.wav"
            var url2 = "http://curtastic.com/gold.wav"
            var myAudio, myAudio2
            if(0)
            {
                var playSound = function(sound)
                {
                    log("playSound() readyState="+sound.readyState)
                    log("gold readyState="+myAudio2.readyState)
                    sound.play()
                }

                var loadSound = function(url, callback)
                {
                    var audio = new Audio(url)
                    audio.addEventListener('canplaythrough', function()
                    {
                        log('canplaythrough');
                        if(callback)
                            callback()
                    }, false)

                    audio.load()
                    if(audio.readyState > 3)
                    {
                        log('audio.readyState > 3');
                        if(callback)
                            callback()
                    }
                    return audio
                }

                myAudio = loadSound(url, startInterval)
                myAudio2 = loadSound(url2)
            }
            else
            {
                var playSound = function(sound)
                {
                    log("playSound()")
                    var source = audioContext.createBufferSource()
                    if(source)
                    {
                        source.buffer = sound
                        if(!source.start)
                            source.start = source.noteOn

                        if(source.start)
                        {
                            var gain = audioContext.createGain()
                            source.connect(gain)
                            gain.connect(audioContext.destination)

                            source.start()
                        }
                    }
                }

                var loadSound = function(url, callback)
                {
                    log("start loading sound "+url)

                    var ajax = new XMLHttpRequest()
                    ajax.open("GET", url, true)
                    ajax.responseType = "arraybuffer"
                    ajax.onload = function()
                    {
                        audioContext.decodeAudioData(
                            ajax.response,
                            function(buffer)
                            {
                                log("loaded sound "+url)
                                log(buffer)
                                callback(buffer)
                            },
                            function(error)
                            {
                                log(error)
                            }
                        )

                    }
                    ajax.send()
                }

                var AudioContext = window.AudioContext || window.webkitAudioContext
                var audioContext = new AudioContext()

                loadSound(url, function(r) {myAudio = r; startInterval()})
                loadSound(url2, function(r) {myAudio2 = r})
            }

            function startInterval()
            {
                log("startInterval()")
                setInterval(function()
                {
                    playSound(myAudio)
                }, 2000)
            }

            function log(m)
            {
                console.log(m)
                debug.innerHTML += m+"<br />"
            }
        </script>
    </body>
</html>

You can use either [WKWebViewConfiguration setMediaTypesRequiringUserActionForPlayback:WKAudiovisualMediaTypeNone] or [UIWebView setMediaPlaybackRequiresUserAction:NO] depending on your WebView class (or Swift equivalent).

Related