Search code examples
javascriptswiftwkwebviewweb-audio-api

How to play audio in WKWebView?


I'm making an iOS app to wrap my javascript game. On mobile safari it works fine because after I play a sound in ontouchstart, then I'm allowed to play any audio whenever I want, and I can set their volume too.

Problem 1: In a WKWebView I can only play the specific sounds that I played in ontouchstart, not the rest. So I'm playing every single audio in my game on the first tap. It sounds really bad. Otherwise in the javascript on audio.play() I get 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.

Problem 2: I also can't lower the volume of sounds in a WKWebView. If I set myAudio.volume=.5 it's instantly 1 again. Which means the user has to actually hear them, in order for them to get to readyState=4

Any good solution or hack? Right now I'm playing every single sound on the first tap, and everything is full volume.

<html>
    <body style='bakcground:#DDD;height:333px'>
        <div id=debug style='font-size:20px' onclick="playSound(myAudio)">
            Debug<br />
            Tap here to play the sound.<br />
        </div>
        <script>
            var context = new webkitAudioContext()
            var myAudio = new Audio("sound/upgrade.wav")
            myAudio.addEventListener('canplaythrough', function(){log('canplaythrough1')}, false)
            var myAudio2 = new Audio("sound/dragon.wav")
            myAudio2.addEventListener('canplaythrough', function(){log('canplaythrough2')}, false)

            function playSound(sound)
            {
                log("playSound() readyState="+sound.readyState)
                try{
                    sound.play()
                    context.resume().then(() => {
                                          log("Context resumed")
                                          sound.play()
                                          })
                    }
                catch(e)
                {
                    log(e)
                }
            }

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

            window.ontouchstart = function()
            {
                log("ontouchstart()")
                playSound(myAudio)
                setTimeout(function() {
                           playSound(myAudio2, 1111)
                           })
            }
        </script>
    </body>
</html>

And ViewController.swift

import UIKit
import WebKit

class ViewController: UIViewController {
    private var myWebView3 : WKWebView!
    override func viewDidLoad() {
        super.viewDidLoad()

        self.myWebView3 = WKWebView(frame: .zero)

        self.myWebView3.configuration.preferences.setValue(true, forKey: "allowFileAccessFromFileURLs")
        self.myWebView3.configuration.mediaTypesRequiringUserActionForPlayback = []
        self.myWebView3.configuration.allowsInlineMediaPlayback = true

        let url = Bundle.main.url(forResource: "index6", withExtension: "html", subdirectory: "/")!
        self.myWebView3.loadFileURL(url, allowingReadAccessTo: url)
        let request = URLRequest(url: url)
        self.myWebView3.load(request)

        self.view.addSubview(self.myWebView3)
    }

    override func viewDidLayoutSubviews() {
        self.myWebView3.frame = self.view.bounds
    }

}

I'm using Xcode 10.1 iPhone SE 12.1.1 I have also tried on an iPad with iOS 10 and get the same error. I have also tried context.decodeAudioData() / context.createBufferSource() and get the same error.


Solution

  • Re: problem 1, you need to create the configuration object before you set the web view's configuration. The line

    self.myWebView3.configuration.mediaTypesRequiringUserActionForPlayback = []
    

    will fail silently. Just create a new WKWebViewConfiguration object, set its properties, and then create the web view:

    webView = WKWebView(frame: .zero, configuration: webConfiguration)