问题
The ask here is to play all kinds of web sounds regardless of the hardware silent switch , both muted and not muted devices must keep playing the sound in HTML pages while the app is foregrounded.
The solution for deprecated UIWebView
is quite easy
let localWebView = UIWebView(frame: .zero)
localWebView.allowsInlineMediaPlayback = true
localWebView.mediaPlaybackRequiresUserAction = false
How the same behavior can be achieved for WKWebView
?
回答1:
Since I have a solution to this nontrivial problem, I'd like to share it:
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)
let configuration = WKWebViewConfiguration()
configuration.allowsInlineMediaPlayback = true
configuration.mediaTypesRequiringUserActionForPlayback = []
wkWebView = WKWebView(frame: .zero, configuration: configuration)
}
@objc func willResignActive() {
disableIgnoreSilentSwitch(wkWebView)
}
@objc func didBecomeActive() {
//Always creates new js Audio object to ensure the audio session behaves correctly
forceIgnoreSilentHardwareSwitch(wkWebView, initialSetup: false)
}
And most importantly in 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)
}
Interestingly a related Safari problem is mentioned here: IOS WebAudio only works on headphones where @Spencer Evans workaround looks very similar to mine.
However when I tried to apply his shorter base64 silence sound it didn't work for WKWebView
, so I'm providing my own minimal silence sound tested on iOS12.
Why it works?
Playing an <audio>
or <video>
element (which in the workaround happens to be non audible silence) changes WKWebView
audio session category from AVAudioSessionCategoryAmbient
to AVAudioSessionCategoryPlayback
. This will be valid until next load request resets it.
It's all great till the app is backgrounded. But upon subsequent foregrounding things will break in 2 possible ways:
- user needs to tap for the sounds to reappear
- rarely no user input will help and the WKWebView lands in semi frozen state
To counter that^ the hack is reverted with disableIgnoreSilentSwitch(wkWebView)
and later reenabled with forceIgnoreSilentHardwareSwitch(wkWebView, initialSetup: false)
Since WKWebView
core runs in an external process it cannot be accessed the way UIWebView
shared (with our app) AVAudioSession
can be.
Verified for iOS 11.4 - 13.3
来源:https://stackoverflow.com/questions/56460362/how-to-force-wkwebview-to-ignore-hardware-silent-switch-on-ios