Search code examples
iosavkitios14

AVPictureInPictureController doesn't automatically start picture-in-picture when backgrounding the app


When creating a custom video player using the AVPlayer + AVPlayerLayer + AVPictureInPictureController for a iPhone running iOS 14 (beta 7) the video does not automatically enter picture-in-picture-mode when the app enters the background after player.start() is called from a UIButton action.

The issue does not reproduce using the AVPlayerViewController which seems to indicate a problem with the AVPictureInPictureController on iOS 14 in general, but I was wondering if anyone else had run into this problem and know of any workarounds. I've also filed this problem with Apple under rdar://8620271

Sample code.

import UIKit
import AVFoundation
import AVKit

class ViewController: UIViewController {
    private let player = AVPlayer(url: URL(string: "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4")!)
    private var pictureInPictureController: AVPictureInPictureController!
    private var playerView: PlayerView!
    private var playButton: UIButton!
    
    override func viewDidLoad() {
        super.viewDidLoad()

        playerView = PlayerView(frame: CGRect(x: 0, y: 44, width: view.bounds.width, height: 200))
        playerView.backgroundColor = .black
        playerView.playerLayer.player = player
        view.addSubview(playerView)

        playButton = UIButton(frame: CGRect(x: view.bounds.midX - 50, y: playerView.frame.maxY + 20, width: 100, height: 22))
        playButton.setTitleColor(.blue, for: .normal)
        playButton.setTitle("Play", for: .normal)
        playButton.addTarget(self, action: #selector(play), for: .touchUpInside)
        view.addSubview(playButton)

        pictureInPictureController = AVPictureInPictureController(playerLayer: playerView.playerLayer)

        do {
            let audioSession = AVAudioSession.sharedInstance()
            try audioSession.setCategory(.playback)
            try audioSession.setMode(.moviePlayback)
            try audioSession.setActive(true)
        } catch let e {
            print(e.localizedDescription)
        }
    }

    @objc func play() {
        player.play()
    }
}

class PlayerView: UIView {
    override class var layerClass: AnyClass {
        return AVPlayerLayer.self
    }

    var playerLayer: AVPlayerLayer! {
        return layer as? AVPlayerLayer
    }
}

Solution

  • The root cause of the problem ended up being twofold:

    1. AVAudioSession.sharedInstance().setActive(true) must be called before the AVPictureInPictureController is initialised.

    2. The frame size for the AVPlayerLayer must have a aspect ratio no greater than 16/9 (filed as a separate bug, rdar://8689203)

    3. For iPads, the video must be the same width as the device (in any given orientation). No separate rdar, as Apple have acknowledged the other bug already.

    (The 2nd issues is not present in the example above)

    Apple have acknowledged these bugs, and reported back to me that they have been / will be fixed (a rare case of a radar actually resulting in a reply!)