From b17cb890cdfa871723f11f82624b65897ac780ee Mon Sep 17 00:00:00 2001 From: Doug Date: Wed, 21 Aug 2024 13:11:52 +0100 Subject: [PATCH] Handle isSupported, isPossible and web view errors. --- .../Screens/CallScreen/CallScreenModels.swift | 8 +++- .../CallScreen/CallScreenViewModel.swift | 23 +++++------- .../Screens/CallScreen/View/CallScreen.swift | 37 +++++++++++++------ 3 files changed, 43 insertions(+), 25 deletions(-) diff --git a/ElementX/Sources/Screens/CallScreen/CallScreenModels.swift b/ElementX/Sources/Screens/CallScreen/CallScreenModels.swift index ea0ca8d7da..fa9a15b5ab 100644 --- a/ElementX/Sources/Screens/CallScreen/CallScreenModels.swift +++ b/ElementX/Sources/Screens/CallScreen/CallScreenModels.swift @@ -36,7 +36,7 @@ struct CallScreenViewState: BindableState { struct Bindings { var javaScriptMessageHandler: ((Any) -> Void)? var javaScriptEvaluator: ((String) async throws -> Any)? - var requestPictureInPictureHandler: (() -> AVPictureInPictureController)? + var requestPictureInPictureHandler: (() async -> Result)? var alertInfo: AlertInfo? } @@ -45,4 +45,10 @@ enum CallScreenViewAction { case urlChanged(URL?) case navigateBack case pictureInPictureWillStop + case endCall +} + +enum CallScreenError: Error { + case webViewError(Error) + case pictureInPictureNotSupported } diff --git a/ElementX/Sources/Screens/CallScreen/CallScreenViewModel.swift b/ElementX/Sources/Screens/CallScreen/CallScreenViewModel.swift index 65b94d725c..c4dc8dd637 100644 --- a/ElementX/Sources/Screens/CallScreen/CallScreenViewModel.swift +++ b/ElementX/Sources/Screens/CallScreen/CallScreenViewModel.swift @@ -121,9 +121,11 @@ class CallScreenViewModel: CallScreenViewModelType, CallScreenViewModelProtocol guard let url else { return } MXLog.info("URL changed to: \(url)") case .navigateBack: - handleBackwardsNavigation() + Task { await handleBackwardsNavigation() } case .pictureInPictureWillStop: actionsSubject.send(.pictureInPictureStopped) + case .endCall: + actionsSubject.send(.dismiss) } } @@ -185,25 +187,20 @@ class CallScreenViewModel: CallScreenViewModelType, CallScreenViewModelProtocol } } - private func handleBackwardsNavigation() { - #if targetEnvironment(simulator) - if UIDevice.current.isPhone { - MXLog.warning("The iPhone simulator doesn't support PiP.") - actionsSubject.send(.dismiss) - return - } - #endif - + private func handleBackwardsNavigation() async { guard state.url != nil, isPictureInPictureEnabled, - AVPictureInPictureController.isPictureInPictureSupported(), let requestPictureInPictureHandler = state.bindings.requestPictureInPictureHandler else { actionsSubject.send(.dismiss) return } - let controller = requestPictureInPictureHandler() - actionsSubject.send(.pictureInPictureStarted(controller)) + switch await requestPictureInPictureHandler() { + case .success(let controller): + actionsSubject.send(.pictureInPictureStarted(controller)) + case .failure: + actionsSubject.send(.dismiss) + } } private func setAudioEnabled(_ enabled: Bool) async { diff --git a/ElementX/Sources/Screens/CallScreen/View/CallScreen.swift b/ElementX/Sources/Screens/CallScreen/View/CallScreen.swift index 73c906d522..c327250d72 100644 --- a/ElementX/Sources/Screens/CallScreen/View/CallScreen.swift +++ b/ElementX/Sources/Screens/CallScreen/View/CallScreen.swift @@ -82,7 +82,7 @@ private struct CallView: UIViewRepresentable { private let certificateValidator: CertificateValidatorHookProtocol private var webView: WKWebView! - private var pictureInPictureController: AVPictureInPictureController! + private var pictureInPictureController: AVPictureInPictureController? private let pictureInPictureViewController: AVPictureInPictureVideoCallViewController /// The view to be shown in the app. This will contain the web view when picture in picture isn't running. @@ -132,9 +132,12 @@ private struct CallView: UIViewRepresentable { webViewWrapper.addMatchedSubview(webView) - pictureInPictureController = .init(contentSource: .init(activeVideoCallSourceView: webViewWrapper, - contentViewController: pictureInPictureViewController)) - pictureInPictureController.delegate = self + if AVPictureInPictureController.isPictureInPictureSupported() { + let pictureInPictureController = AVPictureInPictureController(contentSource: .init(activeVideoCallSourceView: webViewWrapper, + contentViewController: pictureInPictureViewController)) + pictureInPictureController.delegate = self + self.pictureInPictureController = pictureInPictureController + } } func load(_ url: URL) { @@ -205,20 +208,32 @@ private struct CallView: UIViewRepresentable { // MARK: - Picture in Picture - func startPictureInPicture() -> AVPictureInPictureController { - pictureInPictureController.startPictureInPicture() - return pictureInPictureController + func startPictureInPicture() async -> Result { + guard let pictureInPictureController, pictureInPictureController.isPictureInPicturePossible else { + return .failure(.pictureInPictureNotSupported) + } + + do { + // Ideally we will replace this with a controls.isPipPossible call in the future. + _ = try await evaluateJavaScript("controls.enablePip()") + pictureInPictureController.startPictureInPicture() + return .success(pictureInPictureController) + } catch { + MXLog.error("Error starting picture in picture \(error)") + return .failure(.webViewError(error)) + } } func stopPictureInPicture() { - pictureInPictureController.stopPictureInPicture() + pictureInPictureController?.stopPictureInPicture() } - // Note: We handle moving the view via the delegate so it works when you background the app without calling startPictureInPicture nonisolated func pictureInPictureControllerWillStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) { Task { @MainActor in + // We move the view via the delegate so it works when you background the app without calling startPictureInPicture pictureInPictureViewController.view.addMatchedSubview(webView) - _ = try await evaluateJavaScript("controls.enablePip()") + // Similarly this is a redundant call when calling calling startPictureInPicture, but is necessary when backgrounding the app. + _ = try? await evaluateJavaScript("controls.enablePip()") } } @@ -229,7 +244,7 @@ private struct CallView: UIViewRepresentable { nonisolated func pictureInPictureControllerDidStopPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) { Task { @MainActor in webViewWrapper.addMatchedSubview(webView) - _ = try await evaluateJavaScript("controls.disablePip()") + _ = try? await evaluateJavaScript("controls.disablePip()") } } }