在iOS开发中,如果您想实现自定义画中画(PiP)功能,就需要用到AVPictureInPictureController这个类了,配合AVPlayerLayer、AVPlayer实现视频播放及画中画(PiP)
实现代码
- 配置Audio Playback Behavior,需要设置App 的AVAudioSession的Category为playback模式
do {
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .moviePlayback, options: [])
try AVAudioSession.sharedInstance().setActive(true)
} catch {
print("无法设置音频会话类别: \(error)")
}
- 功能实现
import UIKit
import AVKit
class WSPlayerViewController: UIViewController, AVPictureInPictureControllerDelegate {
var playerLayer: AVPlayerLayer?
var pipVC: AVPictureInPictureController?
var player: AVPlayer?
init(player:AVPlayer) {
self.player = player
// 初始化播放图层
self.playerLayer = AVPlayerLayer(player: player)
self.playerLayer!.frame = .zero
self.playerLayer!.videoGravity = .resizeAspect
self.view.layer.addSublayer(playerLayer!)
// 判断是否支持画中画功能
if AVPictureInPictureController.isPictureInPictureSupported() {
let _pipVC = AVPictureInPictureController(playerLayer: self.playerLayer!)
_pipVC?.delegate = self
if #available(iOS 14.2, *) {
_pipVC?.canStartPictureInPictureAutomaticallyFromInline = true
}
if #available(iOS 14.0, *) {
_pipVC?.requiresLinearPlayback = true
}
//去除播放相关按钮,需要时可选
//_pipVC?.setValue(1, forKey: "controlsStyle")
//在 iOS 16 及以上版本支持点击画中画直接回到 App,需要时可选
//_pipVC?.setValue(2, forKey: "controlsStyle")
self.pipVC = _pipVC
} else {
print("当前设备不支持PiP")
}
}
func startPictureInPicture() {
if self.pipVC?.isPictureInPictureActive == false && self.pipVC?.isPictureInPicturePossible == true {
self.pipVC?.startPictureInPicture()
}
}
func stopPictureInPicture() {
if self.pipVC?.isPictureInPictureActive == true {
self.pipVC?.stopPictureInPicture()
}
}
}
- AVPictureInPictureControllerDelegate代理方法
// 即将开启画中画
func pictureInPictureControllerWillStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
print("PIP:即将开启画中画")
}
// 已经开启画中画
func pictureInPictureControllerDidStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
print("PIP:已经开启画中画")
}
// 开启画中画失败
func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, failedToStartPictureInPictureWithError error: Error) {
print("PIP:开启画中画失败:\(error)")
}
// 即将关闭画中画
func pictureInPictureControllerWillStopPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
print("PIP:即将关闭画中画")
}
// 已经关闭画中画
func pictureInPictureControllerDidStopPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
print("PIP:已经关闭画中画")
}
// 关闭画中画且恢复播放界面"
func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: @escaping (Bool) -> Void) {
print("PIP:关闭画中画且恢复播放界面")
completionHandler(true)
}
扩展功能
-
添加自定义View
在创建AVPictureInPictureController
之后,系统会创建一个PGHostedWindow
,windowLevel 为 -10000000。所以可以通过UIApplication
获取到这个window
,将自定义的UIView
添加到这个window
即可。func pictureInPictureControllerWillStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) { print("PIP:即将开启画中画") if let window = UIApplication.shared.windows.first { let _customView = UIView() let _button = UIButton(type: .custom) _button.setTitle("按钮", for: .normal) _button.setTitleColor(.white, for: .normal) _button.addTarget(self, action: #selector(test), for: .touchUpInside) _customView.addSubview(_button) _button.snp.makeConstraints { make in make.width.equalTo(80) make.height.equalTo(40) make.center.equalToSuperview() } window.addSubview(_customView) // use autoLayout _customView.snp.makeConstraints { (make) -> Void in make.edges.equalToSuperview() } } }
或
- 设置contentSource,iOS15以上
将自定义 View 的 layerClass 改写成 AVSampleBufferDisplayerLayer
@implementation PIPCustomView
+ (Class)layerClass {
return [AVSampleBufferDisplayLayer class];
}
@end
再通过initWithSampleBufferDisplayLayer
创建AVPictureInPictureControllerContentSource
let _customView = PIPCustomView()
let _layer = AVSampleBufferDisplayLayer(layer: _customView.layer)
let _source = AVPictureInPictureControllerContentSource(sampleBufferDisplayLayer: _layer, playbackDelegate: _customView)
let _pipVC = AVPictureInPictureController(contentSource: _source)
注意:在自定义的 UIView 添加一个 UIButton,iOS 14、15 可正常执行点击回调方法,iOS 16 及以上则没有任何反应
- 设置PiP播放窗样式
可以通过pipVC.setValue(1, forKey: "controlsStyle")设置
0 = automatic: 系统会根据上下文自动选择适当的控制样式;
1 = minimal: 显示最少的控件,仅包含关闭和恢复全屏按钮;
2 = default: 使用系统的默认控件显示方式;
-
启动画中画的方式
- APP在前台直接调用
startPictureInPicture
即可启动 - App 切换到后台时自动开启,将
canStartPictureInPictureAutomaticallyFromInline
设置为 true 后,可在App切换到后台时自动开启。但需要注意的是该属性只在iOS14.2及以上可用,并且视频必须是在播放中的。
- APP在前台直接调用