Reputation: 359
When i start picture in picture from my custom video player, pip starts for a moment and then fails. I found the following error log in the debug console:
PGPictureInPictureProxy (0x12710a280) _updateAutoPIPSettingsAndNotifyRemoteObjectWithReason:] - Acquiring remote object proxy for connection <NSXPCConnection: 0x2825a32a0> connection to service with pid 63 named com.apple.pegasus failed with error: Error Domain=NSCocoaErrorDomain Code=4099 "The connection to service with pid 63 named com.apple.pegasus was invalidated from this process." UserInfo={NSDebugDescription=The connection to service with pid 63 named com.apple.pegasus was invalidated from this process.}
Here's how i manage pip in code:-
CustomVideoPlayer's code to setup pip vc:
private func setupPIPIfEligible() {
if AVPictureInPictureController.isPictureInPictureSupported() {
// Create a new controller, passing the reference to the AVPlayerLayer.
pipVC = nil
if let layer = playerLayer {
pipVC = AVPictureInPictureController(playerLayer: layer)
pipVC?.delegate = self
}
}}
CustomVideoPlayer's code to toggle pip on press of a button:
private func togglePIP() {
if pipVC?.isPictureInPictureActive ?? false {
pipVC?.stopPictureInPicture()
} else {
pipVC?.startPictureInPicture()
}
}
The custom video player VC forwards the pip delegate methods to its delegate as:
extension CustomVideoPlayerViewController: AVPictureInPictureControllerDelegate {
func pictureInPictureControllerWillStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
videoPlayerDelegate?.playerViewControllerWillStartPictureInPicture(self)
}
func pictureInPictureControllerWillStopPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
videoPlayerDelegate?.playerViewControllerWillStopPictureInPicture(self)
}
func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: @escaping (Bool) -> Void) {
videoPlayerDelegate?.playerViewController(self, restoreUserInterfaceForPictureInPictureStopWithCompletionHandler: completionHandler)
}}
CustomVideoPlayerViewController's delegate handles the pip methods:
extension TabBarViewController: CustomVideoPlayerDelegate {
func playerViewController(_ playerViewController: UHVideoPlayerViewController, restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: @escaping (Bool) -> Void) {
shouldReleaseVideoPlayer = false
let currentviewController = navigationController?.visibleViewController
if currentviewController != playerViewController, currentviewController != nil {
if !(currentviewController?.isKind(of: CustomVideoPlayerViewController.self) ?? false) {
currentviewController?.present(playerViewController, animated: false) {
}
}
}
}
func playerViewControllerWillStartPictureInPicture(_ playerViewController: UHVideoPlayerViewController) {
playerViewController.dismiss(animated: true, completion: nil)
}
func playerViewControllerWillStopPictureInPicture(_ playerViewController: UHVideoPlayerViewController) {
if shouldReleaseVideoPlayer {
currentVideoPlayer = nil
}
shouldReleaseVideoPlayer = true
}}
var currentVideoPlayer: CustomVideoPlayerViewController?
is a strong reference i keep for the video player.
I've already checked: no memory leaks/early release of video player reference.
What am i doing wrong here?
Upvotes: 4
Views: 3928
Reputation: 1844
Do not forget that you need to keep your AVPLayerLayer
presented on screen while you play video in PiP mode if you are not using AVPlayerViewController
but rather some custom view with its own layer as AVPLayerLayer
. If you remove your custom view from screen, the PiP video will also end. You also need to setup AVAudioSession
for playing playback longform audio, add this code to your AppDelegate
class :
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
setupAudioSession()
return true
}
private func setupAudioSession() {
let audioSession = AVAudioSession.sharedInstance()
do {
try audioSession.setCategory(.playback)
try audioSession.setActive(true, options: [])
} catch {
print("Setting category to AVAudioSessionCategoryPlayback failed.")
}
}
EDIT:
The point of PiP still remains if user goes to background, the video will keep playing in PiP window on top of other apps. Well, if you want to present PiP video through all your app screens you have basically only two options (that I know of). Either use AVPlayerViewController which will keep playing video regardless on which screen you are currently in, or present video in your app's keywindow and if the user navigates to other screen you can schrink size of your AVPlayerlayer and either present video there (as YouTube does), or trigger PiP but remain small piece of your AVPlayerlayer somewhere at the bottom of your app. There is also third option (but I haven't tried that one) and it is incorporating your custom video viewcontroller as a child of some other presented viewcontrollers. However I have a feeling it might be too much of pain to use it that way. Hopefully these answers are helpful to you. Wish you good luck!.
Upvotes: 4