SimpleX-Chat/apps/ios/Shared/Views/Call/WebRTCView.swift
Evgeny Poberezkin fa844c48e9
ios: SimpleXChat framework to be shared by app/NSE (#714)
* ios: SimpleXChat framework to be shared by app/NSE

* remove bridging headers from pp/NSE

* embed & sign
2022-05-31 07:55:13 +01:00

183 lines
6.8 KiB
Swift

//
// WebRTCView.swift
// SimpleX (iOS)
//
// Created by Ian Davies on 29/04/2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
import SwiftUI
import WebKit
import SimpleXChat
class WebRTCCoordinator: NSObject, WKNavigationDelegate, WKScriptMessageHandler, WKUIDelegate {
var rtcWebView: Binding<WKWebView?>
var webViewMsg: Binding<WVAPIMessage?>
internal init(rtcWebView: Binding<WKWebView?>, webViewMsg: Binding<WVAPIMessage?>) {
self.rtcWebView = rtcWebView
self.webViewMsg = webViewMsg
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
webView.allowsBackForwardNavigationGestures = false
self.rtcWebView.wrappedValue = webView
ChatModel.shared.callWebView = webView
}
func webView(_ webView: WKWebView, decideMediaCapturePermissionsFor origin : WKSecurityOrigin, initiatedBy frame: WKFrameInfo, type: WKMediaCaptureType) async -> WKPermissionDecision {
print("webView", #function)
return .grant
}
// receive message from WKWebView
func userContentController(
_ userContentController: WKUserContentController,
didReceive message: WKScriptMessage
) {
logger.debug("WebRTCCoordinator.userContentController")
switch message.name {
case "webrtc":
if let msgStr = message.body as? String,
let msg: WVAPIMessage = decodeJSON(msgStr) {
// this is the binding that communicates messages from webview to swift view
webViewMsg.wrappedValue = msg
if case .invalid = msg.resp {
logger.error("WebRTCCoordinator.userContentController: invalid message \(String(describing: message.body))")
}
} else {
logger.error("WebRTCCoordinator.userContentController: message parsing error \(String(describing: message.body))")
}
case "logger":
if let msgStr = message.body as? String {
logger.error("WebRTCCoordinator console.log: \(msgStr)")
} else {
logger.error("WebRTCCoordinator console.log: \(String(describing: message.body))")
}
default:
logger.error("WebRTCCoordinator.userContentController: invalid message.name \(message.name)")
}
}
}
struct WebRTCView: UIViewRepresentable {
@State private var coordinator: WebRTCCoordinator?
@Binding var rtcWebView: WKWebView?
@Binding var webViewMsg: WVAPIMessage?
func makeCoordinator() -> WebRTCCoordinator {
WebRTCCoordinator(rtcWebView: $rtcWebView, webViewMsg: $webViewMsg)
}
func makeUIView(context: Context) -> WKWebView {
let wkCoordinator = makeCoordinator()
DispatchQueue.main.async { coordinator = wkCoordinator }
let wkController = WKUserContentController()
let cfg = WKWebViewConfiguration()
cfg.userContentController = wkController
cfg.mediaTypesRequiringUserActionForPlayback = []
cfg.allowsInlineMediaPlayback = true
let addScript = { (handler: String, source: String) in
let script = WKUserScript(source: source, injectionTime: .atDocumentEnd, forMainFrameOnly: false)
wkController.addUserScript(script)
wkController.add(wkCoordinator, name: handler)
}
addScript("webrtc", "sendMessageToNative = (msg) => webkit.messageHandlers.webrtc.postMessage(JSON.stringify(msg))")
addScript("logger", "console.log = (arg) => webkit.messageHandlers.logger.postMessage(JSON.stringify(arg))")
let wkWebView = WKWebView(frame: .zero, configuration: cfg)
wkWebView.navigationDelegate = wkCoordinator
guard let path: String = Bundle.main.path(forResource: "call", ofType: "html", inDirectory: "www") else {
logger.error("WebRTCView.makeUIView call.html not found")
return wkWebView
}
let localHTMLUrl = URL(fileURLWithPath: path, isDirectory: false)
wkWebView.loadFileURL(localHTMLUrl, allowingReadAccessTo: localHTMLUrl)
return wkWebView
}
func updateUIView(_ webView: WKWebView, context: Context) {
logger.debug("WebRTCView.updateUIView")
}
}
func sendCallCommand(_ webView: WKWebView, _ command: WCallCommand) {
logger.debug("sendCallCommand: \(command.cmdType)")
let apiCmd = encodeJSON(WVAPICall(command: command))
let js = "processCommand(\(apiCmd))"
webView.evaluateJavaScript(js)
}
//struct CallViewDebug: View {
// @State private var commandStr = ""
// @State private var rtcWebView: WKWebView? = nil
// @State private var webViewMsg: WVAPIMessage? = nil
// @FocusState private var keyboardVisible: Bool
//
// var body: some View {
// VStack(spacing: 30) {
// WebRTCView(rtcWebView: $rtcWebView, webViewMsg: $webViewMsg).frame(maxHeight: 260)
// .onChange(of: webViewMsg) { _ in
// if let resp = webViewMsg {
// commandStr = encodeJSON(resp)
// }
// }
// TextEditor(text: $commandStr)
// .focused($keyboardVisible)
// .disableAutocorrection(true)
// .textInputAutocapitalization(.never)
// .padding(.horizontal, 5)
// .padding(.top, 2)
// .frame(height: 112)
// .overlay(
// RoundedRectangle(cornerRadius: 10)
// .strokeBorder(.secondary, lineWidth: 0.3, antialiased: true)
// )
// HStack(spacing: 20) {
// Button("Copy") {
// UIPasteboard.general.string = commandStr
// }
// Button("Paste") {
// commandStr = UIPasteboard.general.string ?? ""
// }
// Button("Clear") {
// commandStr = ""
// }
// Button("Send") {
// if let wv = rtcWebView,
// let command: WCallCommand = decodeJSON(commandStr) {
// sendCallCommand(wv, command)
// }
// }
// }
// HStack(spacing: 20) {
// Button("Capabilities") {
// if let wv = rtcWebView {
// sendCallCommand(wv, .capabilities(useWorker: true))
// }
// }
// Button("Start") {
// if let wv = rtcWebView {
// sendCallCommand(wv, .start(media: .video))
// }
// }
// Button("Accept") {
//
// }
// Button("Answer") {
//
// }
// Button("ICE") {
//
// }
// Button("End") {
//
// }
// }
// }
// }
//}