garysimpson.dev
Mobile development with swift and flutter

PiP for the Win

November 23, 2023 at 6:00AM

Was 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 ;-)