Picture in Picture Across All Platforms

Learn how to implement Picture in Picture for default and custom video players across all app platforms. By Jordan Osterberg.

3.6 (7) · 2 Reviews

Download materials
Save for later
Share
You are currently viewing page 2 of 2 of this article. Click here to view the first page.

Handling PiP controller failure and closing

When the user closes PiP using the close button, or PiP mode fails, you’ll need to remove the custom player controller from the set of active controllers.

Inside pictureInPictureController(_:failedToStartPictureInPictureWithError:), add the following code:

activeCustomPlayerViewControllers.remove(self)

This removes the custom controller from the set of active controllers when PiP was unable to start.

Next, inside pictureInPictureControllerDidStopPictureInPicture(_:), write the same line:

activeCustomPlayerViewControllers.remove(self)

This does the same job as above, but is called when the user has closed the PiP window.

Now, build and run. Play a video and enter PiP mode.

The custom controller dismisses itself when Picture in Picture starts

Starting PiP now dismisses the custom player controller, and closing the PiP window works. But if you tap the button to return to standard full-screen playback from PiP, continuing to play the same video, nothing happens. You’ll deal with that now.

Restoring the Player Controller

Right now, when you start playing a video in PiP mode, you can close the window altogether but you can’t go back to full screen. This is true for both the default AVPlayerViewController and the custom player controller. To get unstuck, you need to add player controller restoration.

Inside CustomPlayerViewController.swift‘s pictureInPictureController(_:restoreUserInterfaceForPictureInPictureStopWithCompletionHandler:), insert the following code:

delegate?.playerViewController(
  self,
  restoreUserInterfaceForPictureInPictureStopWithCompletionHandler: 
    completionHandler)

CustomPlayerViewController has a delegate that mirrors many of the methods contained in AVPlayerViewControllerDelegate. The method you are calling here is the equivalent to the one the standard player would call when the user requests to go back to standard playback from PiP.

Now open CategoryListViewController.swift. At the bottom of the file, you’ll see an extension of the class, which has one method: restore(playerViewController:completionHandler:).

For both types of player controllers, the delegate extensions call this method when the user taps Restore within the PiP window.

Inside the method, add the following code:

// 1
if let presentedViewController = presentedViewController {
  // 2
  presentedViewController.dismiss(animated: false) { [weak self] in
    // 3
    self?.present(playerViewController, animated: false) {
      completionHandler(true)
    }
  }
} else {
  // 4
  present(playerViewController, animated: false) {
    completionHandler(true)
  }
}

Here’s what’s happening in the code above:

  1. Check if any other view controller is already being presented. Maybe your user was watching two videos at once, how productive of them!
  2. If there is a presented controller, dismiss it without animation, since the user wants to get their video back to normal as soon as possible and isn’t interested in any view controller animations.
  3. Once the dismissal is done, present the original player controller, again without animation, then call the completion block so the system knows to hand playback back to the original player layer.
  4. If there was no presented controller, simply present the original controller again and call the completion block.

Build and run.

Picture in Picture restoration working correctly using the custom view controller

The GIF above shows both code paths in action:

  1. Entering PiP and then restoring continues the PiP video in full screen.
  2. Entering PiP, starting a second video, then restoring PiP replaces the full screen video with the PiP content.

To test PiP using AVPlayerViewController instead of the custom player controller, modify customPlayer in the last line of CategoryListViewController‘s collectionView(_:didSelectItemAt:) by changing it to false:

presentPlayerController(with: player, customPlayer: false)

This will present the system player controller instead of yours, and you can see that the same player restoration behavior also works.

Where to Go From Here?

Download the completed project by clicking the Download Materials button at the top or bottom of this tutorial.

Congratulations on finishing the tutorial! Remember, while this tutorial focuses on iOS’s PiP implementation, the majority of the content applies to other Apple platforms like tvOS or macOS as well.

To learn more about PiP, check out Master Picture in Picture on tvOS from WWDC 2020.

You can also learn more about AVKit, which powers video playback on Apple platforms.

We hope you enjoyed this tutorial. If you have any questions or comments, please join the forum discussion below!