I have a mobile-optimized web app that makes use of getUserMedia
to access webcam and mic resources.
I'm wrapping this app in a WKWebView
as I want to offer a native app experience. I'm aware that iOS doesn't allow camera access via browsers - however is there any way to gain permissions to the webcam/mic with native code (alongside the wrapper) and feed this to the web app - perhaps by somehow pointing getUserMedia
to a local stream source?
Yes, take a look at cordova-plugin-iosrtc and cordova-plugin-wkwebview-engine. The idea behind the plugin is as follows:
1. Create a JavaScript file (WebRTC.js) that defines the various WebRTC classes and functions, and passes the calls to the WKWebView, for example:
(function() {
if (!window.navigator) window.navigator = {};
window.navigator.getUserMedia = function() {
webkit.messageHandlers.callbackHandler.postMessage(arguments);
}
})();
2. In the WKWebView, inject the script at the document start:
let contentController = WKUserContentController();
contentController.add(self, name: "callbackHandler")
let script = try! String(contentsOf: Bundle.main.url(forResource: "WebRTC", withExtension: "js")!, encoding: String.Encoding.utf8)
contentController.addUserScript(WKUserScript(source: script, injectionTime: WKUserScriptInjectionTime.atDocumentStart, forMainFrameOnly: true))
let config = WKWebViewConfiguration()
config.userContentController = contentController
webView = WKWebView(frame: CGRect.zero, configuration: config)
3. Listen for messages sent from the JavaScript:
class ViewController: UIViewController, WKUIDelegate, WKNavigationDelegate, WKScriptMessageHandler {
var webView: WKWebView!
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
if message.name == "callbackHandler" {
print(message.body)
// make native calls to the WebRTC framework here
}
}
}
4. If success or failure callbacks need to be performed back in JavaScript-land, evaluate the function call directly within the WKWebView:
webView.evaluateJavaScript("callback({id: \(id), status: 'success', args: ...})", completionHandler: nil)
These callbacks need to be stored in a hash in the JavaScript before calling postMessage
, then the hash key must be sent to the WKWebView. This is the commandId
in the plugins.
int exec_id = 0;
function exec(success, failure, ...) {
// store the callbacks for later
if (typeof success == 'function' || typeof failure == 'function') {
exec_id++;
exec_callbacks[exec_id] = { success: success, failure: failure };
var commandId = exec_id;
}
webkit.messageHandlers.callbackHandler.postMessage({id: commandId, args: ...})
}
// the native code calls this directly with the same commandId, so the callbacks can be performed and released
function callback(opts) {
if (opts.status == "success") {
if (typeof exec_callbacks[opts.id].success == 'function') exec_callbacks[opts.id].success(opts.args);
} else {
if (typeof exec_callbacks[opts.id].failure == 'function') exec_callbacks[opts.id].failure(opts.args);
}
// some WebRTC functions invoke the callbacks multiple times
// the native Cordova plugin uses setKeepCallbackAs(true)
if (!opts.keepalive) delete exec_callbacks[opts.id];
}
5. Of course add the NSCameraUsageDescription
and NSMicrophoneUsageDescription
permissions to the Info.plist
for your project.
Keep in mind this is a non-trivial task, but that's the general idea behind bridging JavaScript, WKWebView, and native framework code with asynchronous callbacks.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With