An iOS 9 Multitasking Picture in Picture Tutorial

PreviousTable of ContentsNext
iOS 9 Video Playback using AVPlayer and AVPlayerViewControllerPlaying Audio on iOS 9 using AVAudioPlayer


Learn SwiftUI and take your iOS Development to the Next Level
SwiftUI Essentials – iOS 16 Edition book is now available in Print ($39.99) and eBook ($29.99) editions. Learn more...

Buy Print Preview Book


The topic of multitasking in iOS 9 on iPad devices was covered in some detail in the earlier chapter titled A Guide to Multitasking in iOS 9. An area of multitasking that was mentioned briefly in that chapter was that of Picture in Picture support. This chapter will provide a more detailed introduction to Picture in Picture multitasking before extending the AVPlayerDemo project created in the previous chapter to include Picture in Picture support.

An Overview of Picture in Picture Multitasking

Picture in Picture (PiP) multitasking allows the video playback within an app running on recent iPad models to appear within a floating, movable and resizable window. Figure 93-1, for example shows the upper portion of an iPad screen. In the figure, the PiP window is displayed in the top left hand corner of the display while the user is interacting with a Safari browser session. Once displayed, the location of the PiP window can be moved by the user to any other corner of the display.


Ios 9 pip window.png

Figure 93-1


Video playback within a PiP window may only be initiated via a user action. When the user presses the Home button on the iPad device while full screen video playback is in progress, the video will be transferred to a PiP window. Alternatively, the video playback can be moved to a PiP window using a button that appears in the video player control bar as highlighted in Figure 93-2


Ios 9 pip button.png

Figure 93-2


A set of controls is also available from within the PiP window to pause and resume playback, return to full screen viewing or exit the playback entirely.

Adding Picture in Picture Support to the AVPlayerDemo App

The remainder of this chapter will work through the steps to integrate PiP support into the AVPlayerDemo app created in the previous chapter. The first step is to enable PiP support within the project capabilities. If the project is not already loaded, start Xcode and open the AVPlayerDemo project. In the Project Navigator panel, select the AVPlayerDemo project target at the top of the panel and click on the Capabilities tab in the main panel.

Scroll down the list of capabilities until the Background Mode entry comes into view. Switch Background Mode support to the On position and enable the checkbox next to Audio, AirPlay and Picture in Picture as outlined in Figure 93-3:


Xcode 7 enable pip.png

Figure 93-3

Learn SwiftUI and take your iOS Development to the Next Level
SwiftUI Essentials – iOS 16 Edition book is now available in Print ($39.99) and eBook ($29.99) editions. Learn more...

Buy Print Preview Book


Adding the Navigation Controller

In order to better demonstrate the implementation of PiP support within an iOS app, the storyboard for the AVPlayerDemo will be adapted to include a navigation controller so that the user can navigate back to the main view controller scene from the AVKit Player scene. Within the Project Navigator panel, select the View Controller scene so that it highlights in blue and choose the Xcode Editor -> Embed In -> Navigation Controller menu option.

Setting the Audio Session Category

The next step is to configure the app to use an appropriate audio session category. For the purposes of this example this will be set to AVAudioSessionCategoryPlayback, the code for which should now be added to the didFinishLaunchingWithOptions method located in the AppDelegate.swift file as follows:

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
    let audioSession = AVAudioSession.sharedInstance()
    do {
        try audioSession.setCategory(AVAudioSessionCategoryPlayback)
    } catch {
        print("Unable to set audio session category")
    }
    return true
}

With these steps completed, launch the app on a physical iPad or iOS simulator session (keeping in mind that PiP support is not available on the first generation iPad Mini or any full size iPad models older than the first iPad Air). Once loaded, tap the Play Movie button and start video playback. Tap the PiP button in the video playback control bar, or use the device Home button and note that video playback is transferred to the PiP window:


Ios pip window visible.png

Figure 93-4


With the PiP window visible and the three control buttons displayed, tap the button to toggle back to full screen playback mode. PiP support is now implemented with the exception of one important step. To experience the need for this additional step, run the app once again, begin movie playback and switch playback to PiP mode. Remaining within the AVPlayerDemo app, tap the Back button located in the navigation bar to return to the original view controller scene containing the Play Movie button. Tap the PiP button to return the movie playback to full screen at which point the movie playback window will disappear leaving just the initial view controller visible. The reason for this is that a delegate method needs to be implemented to present the full size video playback view controller in this situation.

Implementing the Delegate

The AVPlayerViewController class can have assigned to it a delegate object on which methods will be called at various points during the playback of a video. This delegate object must implement the AVPlayerViewControllerDelegate protocol. For the purposes of this example, the ViewController class will be designated as the delegate object for the class. Select the ViewController.swift file and modify it to declare that it now implements the AVPlayerViewControllerDelegate protocol:

import UIKit
import AVKit
import AVFoundation

class ViewController: UIViewController, AVPlayerViewControllerDelegate {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }
.
.
}

Learn SwiftUI and take your iOS Development to the Next Level
SwiftUI Essentials – iOS 16 Edition book is now available in Print ($39.99) and eBook ($29.99) editions. Learn more...

Buy Print Preview Book

Having declared that the class implements the protocol, the prepareForSegue method now needs to be modified to assign the current instance of the class as the delegate for the player view controller instance:

override func prepareForSegue(segue: UIStoryboardSegue,
                sender: AnyObject?) {
    let destination = segue.destinationViewController as?
                            AVPlayerViewController
    let url = NSURL(string:
            "http://www.ebookfrenzy.com/ios_book/movie/movie.mov")

    destination!.delegate = self
           
    if let movieURL = url {
        destination!.player = AVPlayer(URL: movieURL)
    }
}

When the user moves back from the PiP window to full screen playback mode, the restoreUserInterfaceForPictureInPictureStopWithCompletionHandler method is called on the delegate object. This method is passed a reference to the player view controller and a completion handler. It is the responsibility of this method to make sure that the player view controller is presented to the user before calling the completion handler with a Boolean value indicating the success or otherwise of the operation. Remaining within the ViewController.swift file, implement this method as follows:

func playerViewController(playerViewController: AVPlayerViewController,
        restoreUserInterfaceForPictureInPictureStopWithCompletionHandler
        completionHandler: (Bool) -> Void) {

    let currentViewController = navigationController?.visibleViewController

    if currentViewController != playerViewController {
        if let topViewController = 
               navigationController?.topViewController {
                    
            topViewController.presentViewController(playerViewController, 
                  animated: true, completion: {()
                completionHandler(true)
            })
        }
    }
}

The delegate method is passed a reference to the player view controller instance and a completion handler. The code added to the method begins by identifying the currently visible view controller and checking if the current view controller matches the playerViewController instance. If the current view controller is not the player view controller, the navigation controller is used to obtain a reference to the top view controller which, in turn, is used to display the player view controller. The completion handler is then called and passed a true value.

If, on the other hand, the current view controller is the playerViewController, no action needs to be taken since the system will automatically display the player controller to show the movie in full screen. In fact, attempting to present the player view controller when it is already the current view controller will cause the app to crash with an error which reads as follows:

Learn SwiftUI and take your iOS Development to the Next Level
SwiftUI Essentials – iOS 16 Edition book is now available in Print ($39.99) and eBook ($29.99) editions. Learn more...

Buy Print Preview Book

Application tried to present modally an active view controller <AVPlayerViewController>

Run the app once again and verify that the full screen playback view now appears when the full screen button is tapped in the PiP window after navigating back to the main view controller scene.

Opting Out of Picture in Picture Support

The default behavior for the AVPlayerViewController class is to support PiP. To disable PiP support for an AVPlayerViewController instance, set the allowsPictureInPicturePlayback property on the object to false. For example:

myPlayerViewController.allowsPictureInPicturePlayback = false

With PiP support disabled, the PiP button will no longer appear in the playback control panel of the full screen view and pressing the device Home button during video playback will no longer place the video into a PiP window.

Additional Delegate Methods

A number of other methods are supported by the AVPlayerViewControllerDelegate protocol which, if implemented in the designated delegate, will be called at various points in the life cycle of the video playback:

  • pictureInPictureControllerWillStartPictureInPicture – Called before the transition to Picture in Picture mode is about to start.
  • pictureInPictureControllerDidStartPictureInPicture - Called when the Picture in Picture playback has started. This method is passed a reference to the playback view controller object.
  • pictureInPictureControllerWillStopPictureInPicture - Called when the Picture in Picture playback is about to stop. This method is passed a reference to the playback view controller object.
  • pictureInPictureControllerDidStopPictureInPicture - Called when the Picture in Picture playback has stopped. This method is passed a reference to the playback view controller object.

Summary

Picture in Picture support leverages the multitasking capabilities of iOS 9 to allow video playback to be placed within a floating, movable and resizable window where it continues to play as the user performs other tasks on the device. This chapter has highlighted the steps involved in implementing PiP support within an iOS app including enabling entitlements, configuring the audio session and implementing delegate methods.


Learn SwiftUI and take your iOS Development to the Next Level
SwiftUI Essentials – iOS 16 Edition book is now available in Print ($39.99) and eBook ($29.99) editions. Learn more...

Buy Print Preview Book



PreviousTable of ContentsNext
iOS 9 Video Playback using AVPlayer and AVPlayerViewControllerPlaying Audio on iOS 9 using AVAudioPlayer