如何强制WKWebView忽略iOS上的硬件静音开关?

时间:2019-06-05 12:18:57

标签: ios swift wkwebview

这里的要求是播放各种Web声音,而与硬件静音开关无关,无论处于静音状态还是未处于静音状态的设备必须在应用程序处于前台时继续播放 HTML页面中的声音。 已弃用 UIWebView的解决方案很简单

let localWebView = UIWebView(frame: .zero)
localWebView.allowsInlineMediaPlayback = true
localWebView.mediaPlaybackRequiresUserAction = false

如何WKWebView实现相同的行为?

1 个答案:

答案 0 :(得分:1)

由于我有解决这个非凡问题的方法,因此我想与大家分享:

override func viewDidLoad() {
    super.viewDidLoad()
    NotificationCenter.default.addObserver(self, selector: #selector(didBecomeActive),
                                               name: NSNotification.Name.UIApplicationDidBecomeActive, object: nil)
    NotificationCenter.default.addObserver(self, selector: #selector(willResignActive),
                                               name: NSNotification.Name.UIApplicationWillResignActive, object: nil)

}

@objc func willResignActive() {
    if let wkWebView = wkWebView {
       disableIgnoreSilentSwitch(wkWebView)
    }
}

@objc func didBecomeActive() {
if let wkWebView = wkWebView {
    //Always creates new js Audio object to ensure the audio session behaves correctly
    forceIgnoreSilentHardwareSwitch(wkWebView, initialSetup: false)
    }
}   

let configuration = WKWebViewConfiguration()
configuration.allowsInlineMediaPlayback = true
configuration.mediaTypesRequiringUserActionForPlayback = []
let localWebView = WKWebView(frame: .zero, configuration: configuration)

最重要的是在WKNavigationDelegate中:

private func disableIgnoreSilentSwitch(_ webView: WKWebView) {
    //Nullifying the js Audio object src is critical to restore the audio sound session to consistent state for app background/foreground cycle
    let jsInject = "document.getElementById('wkwebviewAudio').src=null;"
    webView.evaluateJavaScript(jsInject, completionHandler: nil)
}

private func forceIgnoreSilentHardwareSwitch(_ webView: WKWebView, initialSetup: Bool) {
    //after some trial and error this seems to be minimal silence sound that still plays
    let silenceMono56kbps100msBase64Mp3 = "data:audio/mp3;base64,//tAxAAAAAAAAAAAAAAAAAAAAAAASW5mbwAAAA8AAAAFAAAESAAzMzMzMzMzMzMzMzMzMzMzMzMzZmZmZmZmZmZmZmZmZmZmZmZmZmaZmZmZmZmZmZmZmZmZmZmZmZmZmczMzMzMzMzMzMzMzMzMzMzMzMzM//////////////////////////8AAAA5TEFNRTMuMTAwAZYAAAAAAAAAABQ4JAMGQgAAOAAABEhNIZS0AAAAAAD/+0DEAAPH3Yz0AAR8CPqyIEABp6AxjG/4x/XiInE4lfQDFwIIRE+uBgZoW4RL0OLMDFn6E5v+/u5ehf76bu7/6bu5+gAiIQGAABQIUJ0QolFghEn/9PhZQpcUTpXMjo0OGzRCZXyKxoIQzB2KhCtGobpT9TRVj/3Pmfp+f8X7Pu1B04sTnc3s0XhOlXoGVCMNo9X//9/r6a10TZEY5DsxqvO7mO5qFvpFCmKIjhpSItGsUYcRO//7QsQRgEiljQIAgLFJAbIhNBCa+JmorCbOi5q9nVd2dKnusTMQg4MFUlD6DQ4OFijwGAijRMfLbHG4nLVTjydyPlJTj8pfPflf9/5GD950A5e+jsrmNZSjSirjs1R7hnkia8vr//l/7Nb+crvr9Ok5ZJOylUKRxf/P9Zn0j2P4pJYXyKkeuy5wUYtdmOu6uobEtFqhIJViLEKIjGxchGev/L3Y0O3bwrIOszTBAZ7Ih28EUaSOZf/7QsQfg8fpjQIADN0JHbGgQBAZ8T//y//t/7d/2+f5m7MdCeo/9tdkMtGLbt1tqnabRroO1Qfvh20yEbei8nfDXP7btW7f9/uO9tbe5IvHQbLlxpf3DkAk0ojYcv///5/u3/7PTfGjPEPUvt5D6f+/3Lea4lz4tc4TnM/mFPrmalWbboeNiNyeyr+vufttZuvrVrt/WYv3T74JFo8qEDiJqJrmDTs///v99xDku2xG02jjunrICP/7QsQtA8kpkQAAgNMA/7FgQAGnobgfghgqA+uXwWQ3XFmGimSbe2X3ksY//KzK1a2k6cnNWOPJnPWUsYbKqkh8RJzrVf///P///////4vyhLKHLrCb5nIrYIUss4cthigL1lQ1wwNAc6C1pf1TIKRSkt+a//z+yLVcwlXKSqeSuCVQFLng2h4AFAFgTkH+Z/8jTX/zr//zsJV/5f//5UX/0ZNCNCCaf5lTCTRkaEdhNP//n/KUjf/7QsQ5AEhdiwAAjN7I6jGddBCO+WGTQ1mXrYatSAgaykxBTUUzLjEwMKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqg=="
    //Plays 100ms silence once the web page has loaded through HTML5 Audio element (through Javascript)
    //which as a side effect will switch WKWebView AudioSession to AVAudioSessionCategoryPlayback

    var jsInject: String
    if initialSetup {
        jsInject = "var s=new Audio('\(silenceMono56kbps100msBase64Mp3)');s.id='wkwebviewAudio';s.controls=false;s.loop=true;s.play();document.body.appendChild(s)"
    } else {
        jsInject = "var s=document.getElementById('wkwebviewAudio');s.src=null;s.parentNode.removeChild(s);s=null;s=new Audio('\(silenceMono56kbps100msBase64Mp3)');s.id='wkwebviewAudio';s.controls=false;s.loop=true;s.play();document.body.appendChild(s)"
    }
    webView.evaluateJavaScript(jsInject, completionHandler: nil)
}

func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
    //As a result the WKWebView ignores the silent switch for games
    forceIgnoreSilentHardwareSwitch(webView, initialSetup: true)
}

有趣的是,这里提到了一个与Safari有关的问题:IOS WebAudio only works on headphones,其中@Spencer Evans的解决方法与我的非常相似。

但是,当我尝试应用他的较短的base64静音声音时,它不适用于WKWebView,因此,我提供了自己在iOS12上经过测试的最小静音声音。

为什么起作用?

播放<audio><video>元素(在变通方法中碰巧是不可听见的静音)将WKWebView音频会话类别从AVAudioSessionCategoryAmbient更改为AVAudioSessionCategoryPlayback 。这将一直有效,直到下一个加载请求将其重置。

在应用程序后台运行之前,一切都很好。但是在随后的前景中,事情将以两种可能的方式破裂:

  • 用户需要点击才能使声音重新出现
  • 几乎没有用户输入会帮助您,并且WKWebView处于半冻结状态

计数器,认为^黑客通过disableIgnoreSilentSwitch(wkWebView)还原了,随后又通过forceIgnoreSilentHardwareSwitch(wkWebView, initialSetup: false)重新启用了

由于WKWebView内核在外部进程中运行,因此无法访问UIWebView与我们的应用共享AVAudioSession的方式。

已验证(适用于iOS 11.4-13 Beta)