PiP for the Win
November 23, 2023 at 6:00AMWas tasked recently to enable PiP (Picture In Picture) mode in an iOS application. Thanks to an awesome wwdc session video I was able to get things working smoothly. Gotta say I really like how Apple has implemented it. The frameworks makes certain behavioral assumptions and does a good job of transitioning between video display modes.
AppDelegate+AVPlayer
import Foundation
import AVKit
// MARK: - AVPlayerViewControllerDelegate
extension AppDelegate: AVPlayerViewControllerDelegate {
func playerViewController(_ playerViewController: AVPlayerViewController, restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: @escaping (Bool) -> Void) {
navigationController?.present(playerViewController, animated: true) {
completionHandler(true)
}
}
func playerViewController(_ playerViewController: AVPlayerViewController, willEndFullScreenPresentationWithAnimationCoordinator coordinator: UIViewControllerTransitionCoordinator) {
guard playerViewController.player?.currentItem?.status != .failed else {
playerViewController.dismiss(animated: true)
self.presentPlaybackError()
return
}
}
func playerViewController(_ playerViewController: AVPlayerViewController, failedToStartPictureInPictureWithError error: Error) {
presentPlaybackError()
}
func presentPlaybackError() {
///PRESENT FAILURE DIALOG HERE
}
/// Takes the given URL and plays it using **AVPlayer** in FullScreen with the PiP option when the system determines compatibility.
func presetFullScreenVideo(_ videoUrl: URL) {
let avPlayer = AVPlayer(url: videoUrl)
avPlayer.allowsExternalPlayback = true
let playerController = AVPlayerViewController.init()
playerController.delegate = self
playerController.allowsPictureInPicturePlayback = true
playerController.entersFullScreenWhenPlaybackBegins = true
playerController.canStartPictureInPictureAutomaticallyFromInline = true
playerController.exitsFullScreenWhenPlaybackEnds = true
playerController.player = avPlayer
avPlayer.playImmediately(atRate: 1.0)
navigationController?.present(playerController, animated: true, completion: {
/// Allow the OS to attempt to display the PiP button
do{
try AVAudioSession.sharedInstance().setActive(true)
try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback)
}
catch{}
guard playerController.player?.currentItem?.status != .failed else {
playerController.dismiss(animated: true)
self.presentPlaybackError()
return
}
})
}
}
Happy Coding ;-)