diff --git a/Classes/Swift/ProviderDelegate.swift b/Classes/Swift/ProviderDelegate.swift index a99b0add0..6998bfefd 100644 --- a/Classes/Swift/ProviderDelegate.swift +++ b/Classes/Swift/ProviderDelegate.swift @@ -193,7 +193,13 @@ extension ProviderDelegate: CXProviderDelegate { if (UIApplication.shared.applicationState != .active) { CallManager.instance().backgroundContextCall = call CallManager.instance().backgroundContextCameraIsEnabled = call?.params?.videoEnabled == true || call?.callLog?.wasConference() == true - call?.cameraEnabled = false // Disable camera while app is not on foreground + if #available(iOS 16.0, *) { + if (call?.cameraEnabled == true) { + call?.cameraEnabled = AVCaptureSession().isMultitaskingCameraAccessSupported + } + } else { + call?.cameraEnabled = false // Disable camera while app is not on foreground + } } CallManager.instance().callkitAudioSessionActivated = false CallManager.instance().lc?.configureAudioSession() diff --git a/Classes/Swift/Voip/Views/CompositeViewControllers/SingleCallView.swift b/Classes/Swift/Voip/Views/CompositeViewControllers/SingleCallView.swift index 4ac1a624e..9e9c80627 100644 --- a/Classes/Swift/Voip/Views/CompositeViewControllers/SingleCallView.swift +++ b/Classes/Swift/Voip/Views/CompositeViewControllers/SingleCallView.swift @@ -20,6 +20,7 @@ import UIKit import linphonesw +import AVKit @objc class SingleCallView: AbstractCallView, UICompositeViewDelegate { @@ -28,6 +29,9 @@ import linphonesw var callPausedByLocalView : PausedCallOrConferenceView? = nil var currentCallView : ActiveCallView? = nil + private var pipController: AVPictureInPictureController! + private var pipRemoteVideoView = SampleBufferVideoCallView() + static let compositeDescription = UICompositeViewDescription(SingleCallView.self, statusBar: StatusBarView.self, tabBar: nil, sideMenu: nil, fullscreen: false, isLeftFragment: false,fragmentWith: nil) static func compositeViewDescription() -> UICompositeViewDescription! { return compositeDescription } func compositeViewDescription() -> UICompositeViewDescription! { return type(of: self).compositeDescription } @@ -98,6 +102,11 @@ import linphonesw view.onClick { ControlsViewModel.shared.audioRoutesSelected.value = false } + + // picture in picture init + if #available(iOS 15.0, *) { + DispatchQueue.main.async { self.configurationPiPViewController() } + } } @@ -111,4 +120,112 @@ import linphonesw self.currentCallView?.layoutRotatableElements() } + // Picture in picture on video call + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + if (CallsViewModel.shared.currentCallData.value??.call.state == .StreamsRunning && pipController.isPictureInPicturePossible) { + pipController.startPictureInPicture() + } + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + if pipController.isPictureInPictureActive { + pipController.stopPictureInPicture() + } + } + } + +// Picture in picture on video call +@available(iOS 15.0, *) +extension SingleCallView : AVPictureInPictureControllerDelegate { + + func configurationPiPViewController() { + let pipVideoCallController = PictureInPictureVideoCallViewController() + pipRemoteVideoView = pipVideoCallController.pipRemoteVideoView + let pipContentSource = AVPictureInPictureController.ContentSource( + activeVideoCallSourceView: currentCallView!.remoteVideo, + contentViewController: pipVideoCallController) + pipController = AVPictureInPictureController(contentSource: pipContentSource) + pipController.delegate = self + + ControlsViewModel.shared.isVideoEnabled.readCurrentAndObserve{ (video) in + pipVideoCallController.matchVideoDimension() + self.pipController.canStartPictureInPictureAutomaticallyFromInline = video == true + } + + CallsViewModel.shared.currentCallData.observe(onChange: { callData in + if (callData??.call.state != .StreamsRunning && self.pipController.isPictureInPictureActive) { + self.pipController.stopPictureInPicture() + } + }) + } + + + func pictureInPictureControllerWillStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) { + Core.get().nativeVideoWindow = pipRemoteVideoView + } + + func pictureInPictureControllerDidStopPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) { + Core.get().nativeVideoWindow = currentCallView?.remoteVideo + } + + func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, failedToStartPictureInPictureWithError error: Error) { + Core.get().nativeVideoWindow = currentCallView?.remoteVideo + Log.e("Start Picture in Picture video call error : \(error)") + DispatchQueue.main.async { self.configurationPiPViewController() } + } + + func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: @escaping (Bool) -> Void) { + if (CallsViewModel.shared.currentCallData.value??.call.state == .StreamsRunning && PhoneMainView.instance().currentView != self.compositeViewDescription()) { + PhoneMainView.instance().changeCurrentView(self.compositeViewDescription()) + Core.get().nativeVideoWindow = pipRemoteVideoView // let the video on the pip view during the stop animation + } + pictureInPictureController.contentSource?.activeVideoCallContentViewController.view.layer.cornerRadius = ActiveCallView.center_view_corner_radius + completionHandler(true) + } +} + +@available(iOS 15.0, *) +class PictureInPictureVideoCallViewController : AVPictureInPictureVideoCallViewController { + + var pipRemoteVideoView = SampleBufferVideoCallView() + + override func viewDidLoad() { + super.viewDidLoad() + + view.backgroundColor = .black + view.clipsToBounds = true + view.addSubview(pipRemoteVideoView) + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + view.layer.cornerRadius = 0 + } + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + matchVideoDimension() + } + + func matchVideoDimension() { + let videoDefinition = CallsViewModel.shared.currentCallData.value??.call.currentParams?.receivedVideoDefinition + if (videoDefinition != nil) { + self.preferredContentSize = CGSize(width: Double(videoDefinition!.width), height: Double(videoDefinition!.height)) + pipRemoteVideoView.frame = view.bounds + } + } +} + +class SampleBufferVideoCallView: UIView { + override class var layerClass: AnyClass { + AVSampleBufferDisplayLayer.self + } + + var sampleBufferDisplayLayer: AVSampleBufferDisplayLayer { + layer as! AVSampleBufferDisplayLayer + } +} +