10

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
    }
}
Claus Jørgensen
  • 25,882
  • 9
  • 87
  • 150

2 Answers2

11

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!)

Claus Jørgensen
  • 25,882
  • 9
  • 87
  • 150
  • 1
    Any tips on how to check the frame size for the avplayerlayer? Still doesn't work. – ScottyBlades Mar 26 '21 at 18:06
  • 1
    That’s very interesting insights. I already noticed that I need to set `playerController.videoGravity = .resizeAspectFill` in order for it to work (my video is a portrait iPhone screen recording, so I assume it’s related to the aspect ratio bug). – DeveloBär May 16 '22 at 10:17
11

Starting iOS 14.2, Apple has exposed an api to start PIP when app goes into background:

if #available(iOS 14.2, *) {
   pictureInPictureController.canStartPictureInPictureAutomaticallyFromInline = true
}

Additionally, it is worth noting that Apple has forbidden to start picture-in-picture without user manually tapping the button. It will result in app rejection. Best bet is to use Apple's API mentioned above to avoid rejection.

atulkhatri
  • 10,896
  • 3
  • 53
  • 89
  • 1
    DAMN!!! Thanks a lot! I have been trying to find out how to get this working correctly all week!! – vanlooverenkoen Aug 19 '22 at 11:03
  • This solved an issue for me. Also, I have to clarify that using `canStartPictureInPictureAutomaticallyFromInline` should not result in your app rejection since you do not actually start picture-in-picture without the user manually tapping the button as Apple requires — the iPadOS does, while the user can disable this behavior in system settings. You are actually expected to use this property, since Apple presented it in the [What's new in AVKit][1] video at WWDC 21 session. [1]: https://developer.apple.com/wwdc21/1029 – kikiwora Apr 27 '23 at 08:36