I'm designing an app that allows the user to view a certain video in a SwiftUI view. I want to support PiP, and AVPictureInPictureController.isPictureInPictureSupported() always returns true, while pipController.isPictureInPicturePossible sometimes returns true, but not usually while the video is actually playing. It's possible I misunderstand how to use AVPictureInPictureController.
My app initializes a view, called a FullScreenVideoView, with the following code:
init(player: KHKVideoPlayer, aspectRatio: CGFloat, presentingViewController pvc: UIViewController?) {
self.aspectRatio = aspectRatio
self.pvc = pvc
self.model = FullScreenVideoViewerModel(player: player)
self.playerController = KHKPlayerController(player: player.avPlayer, cornerRadius: 0)
if AVPictureInPictureController.isPictureInPictureSupported() {
self.pipController = AVPictureInPictureController(playerLayer: AVPlayerLayer(player: player.avPlayer))!
}
self.isMuted = player.avPlayer.isMuted
}
Then, inside the view body, it displays the video by calling the playerController defined in the initializer:
playerController
.frame(height: aspectRatio * geometry.size.width)
.onTapGesture {
if self.model.player.avPlayer.isPlaying {
self.model.pause()
} else {
self.model.play()
}
}
The playerController is a wrapper on an AVPlayerViewController:
struct KHKPlayerController: UIViewControllerRepresentable {
typealias UIViewControllerType = AVPlayerViewController
var player: AVPlayer
var cornerRadius: CGFloat?
init(player: AVPlayer) {
self.player = player
}
init(player: AVPlayer, cornerRadius: CGFloat) {
self.player = player
self.cornerRadius = cornerRadius
}
func makeUIViewController(context: Context) -> AVPlayerViewController {
let playerVC = AVPlayerViewController()
playerVC.showsPlaybackControls = false
playerVC.player = player
playerVC.view.layer.masksToBounds = true
playerVC.view.layer.cornerRadius = cornerRadius ?? 8
return playerVC
}
func updateUIViewController(_ uiViewController: AVPlayerViewController, context: Context) {
}
}
A button is supposed to start PiP:
RectangleButton(action: {
print("pip is possible: \(pipController?.isPictureInPicturePossible ?? false)")
pipController?.startPictureInPicture()
}, imageSystemName: "pip.enter", foregroundColor: UI.Constants.primaryColor, backgroundColor: UI.Constants.tertiaryColor, width: 35, height: 25, cornerRadius: 3)
Any help would be much appreciated. I can't find any helpful information on starting PiP in SwiftUI.
Note- the correct background capability is enabled. Also, in my AppDelegate didFinishLaunchingWithOptions method, I have this code that seems to be required for PiP:
do {
try audioSession.setCategory(AVAudioSession.Category.playback)
} catch {
print("Audio session failed")
}
Your code actually mostly works - at least inside my SwiftUI-code I pretty much have the same steps as you described.
However, I do a bit more on the playerLayer and maybe this causes the pipController to actually work.
Here is what I do with the playerLayer inside my View-definition, I add it as a separate controls layer:
I am not 100% sure if this extra sublayer is really causing the pipController to improve.
Also, I am not using
KHKVideoPlayer
but rather plainAVPlayer(url: url)
.And I am not using
AVPlayerViewController
at all since doing all in SwiftUI.And of course, I also add the Background-mode to make pip possible...