iOS Picture in Picture player controls visibility

Hi Mart,

Thanks for posting this. Could you please give the below code a try on your end? It’s a modified version of our BasicPictureInPicture app after following this article, and it seems to work fine for me both on 3.16.0 and 3.29.0 on iOS16.1. Note that I’m calling self.playerView.enterPiP() instead of self.playerView.enterPictureInPicture()

import UIKit
import Foundation //added by alberto
import BitmovinPlayer


//added by alberto
class CustomPiPView: PlayerView, AVPictureInPictureControllerDelegate {
    var controller: AVPictureInPictureController?
    override init(player: Player, frame: CGRect) {
        super.init(player: player, frame: frame)
        let layer = self.layer as! AVPlayerLayer
        if AVPictureInPictureController.isPictureInPictureSupported() {
            self.controller = AVPictureInPictureController(playerLayer: layer)
            self.controller?.delegate = self
            //added by alberto
            if #available(iOS 14.0, *) {
                self.controller?.requiresLinearPlayback = true
                self.controller?.setValue(1, forKey: "controlsStyle")
            } else {
                // Fallback on earlier versions
            }
        }
    }
    func enterPiP() {
        self.controller?.startPictureInPicture()
    }
    func exitPiP() {
        self.controller?.stopPictureInPicture()
    }
}




class ViewController: UIViewController {
    let streamUrl = URL(string: "https://bitmovin-a.akamaihd.net/content/MI201109210084_1/m3u8s/f08e80da-bf1d-4e3d-8899-f0f6155f6efa.m3u8")!
    let posterUrl = URL(string: "https://bitmovin-a.akamaihd.net/content/MI201109210084_1/poster.jpg")!
    
    //var userInterfaceType: UserInterfaceType = .bitmovin
    var userInterfaceType: UserInterfaceType = .system //added by alberto
    
    var player: Player!
    //var playerView: PlayerView!
    var playerView: CustomPiPView! //added by alberto
    
    deinit {
        player?.destroy()
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        self.view.backgroundColor = .black
        
        let config = createPlayerConfig()
        let player = PlayerFactory.create(playerConfig: config)
        let playerView = createPlayerView(player: player)
        let sourceConfig = createSourceConfig(
            url: streamUrl,
            posterUrl: posterUrl,
            type: .hls
        )
        let source = SourceFactory.create(from: sourceConfig)
        
        player.add(listener: self)
        playerView.add(listener: self)
        
        self.player = player
        //self.playerView = playerView
        self.playerView = createPlayerView(player: player) //added by alberto
        
        view.addSubview(self.playerView)
        
        configureAudioSession()
        
        self.player.load(source: source)
        
        //added by alberto
        DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
            print("entering pip via 5sec automation")
            //self.playerView.enterPictureInPicture()
            self.playerView.enterPiP() 
        }
    }
    
    private func configureAudioSession() {
        // You need to set a category for audio session to '.playback'
        // to be able to use PiP functionality, otherwise PiP won't work
        let audioSession = AVAudioSession.sharedInstance()
        try? audioSession.setCategory(.playback)
    }
    
    private func createPlayerConfig() -> PlayerConfig {
        let config = PlayerConfig()
        config.playbackConfig.isBackgroundPlaybackEnabled = true
        config.playbackConfig.isPictureInPictureEnabled = true
        config.playbackConfig.isAutoplayEnabled = true //added by alberto
        config.styleConfig.isUiEnabled = false //added by alberto
        config.styleConfig.userInterfaceType = userInterfaceType
        
        return config
    }
    
    //added by alberto
    private func createPlayerView(player: Player) -> CustomPiPView {
        let playerView = CustomPiPView(player: player, frame: .zero)
        playerView.autoresizingMask = [.flexibleHeight, .flexibleWidth]
        playerView.frame = view.bounds
        
        return playerView
    }
    
    private func createSourceConfig(
        url: URL,
        posterUrl: URL,
        type: SourceType
    ) -> SourceConfig {
        let sourceConfig = SourceConfig(url: streamUrl, type: .hls)
        sourceConfig.posterSource = posterUrl
        return sourceConfig
    }
}