Home Implementing Picture-in-Picture in Swift for iOS
Post
Cancel

Implementing Picture-in-Picture in Swift for iOS

In today’s fast-paced world, users crave flexibility and the ability to multitask effortlessly. Imagine a scenario where your iOS app allows users to continue watching their favorite videos while browsing through other content or checking their emails. Enter Picture-in-Picture (PiP), a powerful functionality that empowers users to maintain an uninterrupted video experience while engaging in various activities on their devices. In this article, we’ll explore how to integrate Picture-in-Picture into your iOS app using Swift, unlocking a world of enhanced user experience and seamless multitasking. Get ready to captivate your users and provide them with the ultimate convenience they deserve. Let’s dive in!

First Things First

To implement PiP functionality in our app, we’ll need to set up and enable our app to access background mode capabilities. Don’t worry it’s pretty simple, heres how:

Step 1: Add Background Mode Capability to your app

The first step is to add the Background Mode capability and then turn on the Audio, AirPlay, and Picture in Picture flag in xcode GUI under the Signing & Capabilities section. Here is what it would look like when you’re done

Adding Background Mode to your xcode Project

Adding `Background Mode` to your xcode Project

Toggle on the Audio, AirPlay, and Picture in Picture flag in xcode

Toggle on the `Audio, AirPlay, and Picture in Picture` flag in xcode

Step 2: Request for background mode playback access

The second step is to setup the AVAudioSession.sharedInstance() category before the VideoPlayer is instantiated. An ideal place for this in a UIKit app would be in the appDelegate like below, or in your WindowGroup for SwiftUI apps. Really doesn’t matter, the point is so long as we set the AVAudioSession.sharedInstance() category to playback before we init our video player we will be fine.

1
2
3
4
5
6
7
8
9
10
11
12
13
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        requestPIPBackgroundMode()
        return true
    }

    private func requestPIPBackgroundMode() {
        let session = AVAudioSession.sharedInstance()
        do {
            try session.setCategory(.playback, mode: .moviePlayback)
        } catch let error {
            print(error.localizedDescription)
        }
    }

How Do We Do It

To implement PiP functionality in our app, we’ll leverage the power of AVKit and AVFoundation frameworks. AVKit provides a comprehensive set of tools and components specifically designed for media playback, while AVFoundation offers the underlying functionality for working with audiovisual media. With both frameworks, we can create an AVPlayerViewController instance and associate it with the desired video content that we want to enable PiP for or we can even create our own custom video player with the help of the AVPlayer API.

Setting Up PiP in UIKit

The AVPlayerViewController class not only simplifies the process of playing videos but also provides built-in support for PiP functionality. With just a few lines of code, we can present the AVPlayerViewController to our users, allowing them to start watching videos seamlessly within our app. Remember to ensure that you have properly imported the AVKit and AVFoundation frameworks into the file else xcode will yell at you.

1
2
3
4
5
6
7
8
// In UIKIT

let playerViewController = AVPlayerViewController()
let videoURL = URL(string: "your_video_url_here")
let player = AVPlayer(url: videoURL!)
playerViewController.player = player
playerViewController.canStartPictureInPictureAutomaticallyFromInline = true
present(playerViewController, animated: true)

Setting the canStartPictureInPictureAutomaticallyFromInline option to true we are basically telling our video player to automatically start PiP when the app transitions to background mode.

Once the AVPlayerViewController is presented, we can enable PiP functionality by setting the appropriate delegate and implementing the necessary delegate methods. The AVPlayerViewControllerDelegate protocol provides us with hooks into the PiP lifecycle events, enabling us to respond to PiP-related actions and customize the behavior of our app accordingly.

1
2
3
4
5
6
7
8
// PiP LifeCycle Dlegates
func playerViewControllerWillStartPictureInPicture(_ playerViewController: AVPlayerViewController)
        
func playerViewControllerDidStartPictureInPicture(_ playerViewController: AVPlayerViewController) 
        
func playerViewControllerWillStopPictureInPicture(_ playerViewController: AVPlayerViewController)        
        
func playerViewControllerDidStopPictureInPicture(_ playerViewController: AVPlayerViewController)

Ok here’s what your full code should look like when you are done.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import UIKit
import AVKit

class ViewController: UIViewController, AVPlayerViewControllerDelegate {
    
    var playerViewController: AVPlayerViewController()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupPlayer()
    }
    
    func setupPlayer() {
        let videoURL = URL(string: "your_video_url_here")
        let player = AVPlayer(url: videoURL!)
        playerViewController.delegate = self
        playerViewController.player = player
        playerViewController.canStartPictureInPictureAutomaticallyFromInline = true
        playerViewController.player?.play()
        present(playerViewController, animated: true)
    }
    
    // AVPlayerViewControllerDelegate methods
    func playerViewControllerWillStartPictureInPicture(_ playerViewController: AVPlayerViewController) {
        // Perform any necessary actions when PiP starts
    }
    
    func playerViewControllerDidStartPictureInPicture(_ playerViewController: AVPlayerViewController) {
        // Perform any necessary actions when PiP has started
    }
    
    func playerViewControllerWillStopPictureInPicture(_ playerViewController: AVPlayerViewController) {
        // Perform any necessary actions when PiP is about to stop
    }
    
    func playerViewControllerDidStopPictureInPicture(_ playerViewController: AVPlayerViewController) {
        // Perform any necessary actions when PiP has stopped
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        
        // Stop the video playback when the view disappears
        playerViewController.player?.pause()
    }
}

Setting Up PiP in SwiftUI

Although SwiftUI provides us with a neat media player API called VideoPlayer, unfortunately it doesn’t provide a PiP support straight out of the box like the UIKit equivalent does. So with anything else when SwiftUI falls short we have to dip back down to UIKit to do it the old imperative way but thanks to the help our homies UIViewRepresentable and UIViewControllerRepresentable it’s not as difficult to accomplish. Let’s see how we can leverage a UIKit native PiP support to add the functionality to a SwiftUI app.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import SwiftUI
import AVKit
struct AVVideoPlayer: UIViewControllerRepresentable {
    
    func makeUIViewController(context: Context) -> AVPlayerViewController {
        let playerViewController = AVPlayerViewController()
        let videoUrlString = "https://player.vimeo.com/external/291648067.sd.mp4?s=7f9ee1f8ec1e5376027e4a6d1d05d5738b2fbb29&profile_id=164&oauth2_token_id=57447761"
        let videoURL = URL(string: videoUrlString)
        let player = AVPlayer(url: videoURL!)
        playerViewController.player = player
        playerViewController.canStartPictureInPictureAutomaticallyFromInline = true
        playerViewController.player?.play()
        return playerViewController
    }
    
    func updateUIViewController(_ uiViewController: AVPlayerViewController, context: Context) { }
}


struct VideoPlayerContainerView: View {
    var body: some View {
        VStack {
            AVVideoPlayer()
                .frame(width: UIScreen.main.bounds.width, height:  UIScreen.main.bounds.width)
            
            Spacer()
        }
        .ignoresSafeArea()
    }
}

struct VideoPlayerContainerViewProvider_Previews: PreviewProvider {
    static var previews: some View {
        VideoPlayerContainerView()
    }
}

What’s Going on

The AVVideoPlayer is a UIViewControllerRepresentable that allows us to create and embed a AVPlayerViewController into our SwiftUI view called VideoPlayerContainerView. It’s really as simple as that.

That’s it! By following these steps, you can integrate Picture-in-Picture functionality into your iOS app. I’m sure your Users will enjoy the flexibility and convenience of multitasking while enjoying their favorite videos.

This post is licensed under CC BY 4.0 by the author.