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
}
}