概述
本文中Demo下载地址。
如果你想自定义播放器实现画中画,就可以采用 AVKit 框架中的AVPictureInPictureController
类。 如果你想基础的实现画中画可以参考AVPlayerViewController实现画中画 。
使用AVPictureInPictureController实现画中画步骤
-
创建一个项目,添加Background modes;选择Target > Signing & Capabilities项,点击“+ Capability”添加Background modes,把“Audio,AirPlay,and Picture in picture”打上对勾。如下图:
配置Audio Playback Behavior,需要设置App 的AVAudioSession的Category为playback模式,示例代码如下:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
//需要设置App 的AVAudioSession的Category为playback模式
let audioSession = AVAudioSession.sharedInstance()
do {
try audioSession.setCategory(.playback)
} catch {
print("Setting category to AVAudioSessionCategoryPlayback failed.")
}
return true
}
- 创建一个新的AVPictureInPictureController对象,向它传递一个对显示视频内容的AVPlayerLayer的引用。需要对AVPictureInPictureController对象必须强引用才能使PiP正常功能。设置AVPictureInPictureController示例代码如下:
var playerLayer : AVPlayerLayer = AVPlayerLayer()
var player : AVPlayer?
var pictureInPictureController : AVPictureInPictureController?
//设置PictureInPicture
func setupPictureInPicture() {
guard let videoURL = Bundle.main.url(forResource: "v1", withExtension: "MP4") else {
return
}
//设置AVPlayerLayer
playerLayer.frame = self.view.frame
playerLayer.videoGravity = .resizeAspect
self.view.layer.addSublayer(playerLayer)
//设置AVPlayer
let asset = AVURLAsset(url: videoURL)
let playerItem = AVPlayerItem(asset: asset)
player = AVPlayer(playerItem: playerItem)
if let player = player {
playerLayer.player = player
}
// Ensure PiP is supported by current device. isPictureInPictureSupported()判断设备是否支持画中画
if AVPictureInPictureController.isPictureInPictureSupported() {
// Create a new controller, passing the reference to the AVPlayerLayer.
pictureInPictureController = AVPictureInPictureController(playerLayer: playerLayer)
pictureInPictureController?.delegate = self //设置代理
} else {
print("当前设备不支持PiP")
}
player?.play()
}
- 使用AVPictureInPictureController的pictureInPictureButtonStartImage和pictureInPictureButtonStopImage,我们可以创建一个Button,然后把Button的image设置为pictureInPictureButtonStartImage或者pictureInPictureButtonStopImage。示例代码如下:
func addPipButton() {
//设置开启或者关闭pictureInPicture
pipButton = UIButton(type: .system)
//系统默认的pictureInPictureButtonStartImage
let startImage = AVPictureInPictureController.pictureInPictureButtonStartImage
pipButton.setImage(startImage, for: .normal)
pipButton.frame = CGRect(x: 10, y: 20, width: 40, height: 40)
pipButton.addTarget(self, action: #selector(clickPipButton(_:)), for: .touchUpInside)
self.view.addSubview(pipButton)
}
@objc func clickPipButton(_ sender: Any) {
//判断Pip是否在Active状态
guard let isActive = pictureInPictureController?.isPictureInPictureActive else { return }
if (isActive) {
//停止画中画
pictureInPictureController?.stopPictureInPicture()
let startImage = AVPictureInPictureController.pictureInPictureButtonStartImage
pipButton.setImage(startImage, for: .normal)
} else {
//启动画中画
pictureInPictureController?.startPictureInPicture()
//系统默认的pictureInPictureButtonStopImage
let stopImage = AVPictureInPictureController.pictureInPictureButtonStopImage
pipButton.setImage(stopImage, for: .normal)
}
}
-
然后 build and run你的项目即可实现一个自定义的画中画Demo。
效果图如下:
AVPictureInPictureController的一些常用方法:
//开始启动画中画
open func startPictureInPicture()
//停止画中画
open func stopPictureInPicture()
//当前是否可以使用画中画
open var isPictureInPicturePossible: Bool { get }
//当前是否处于画中画状态
open var isPictureInPictureActive: Bool { get }
//当前画中画是否被挂起
open var isPictureInPictureSuspended: Bool { get }
//这可用于临时强制播放强制内容(如法律术语或广告)
@available(iOS 14.0, *)
open var requiresLinearPlayback: Bool
AVPictureInPictureControllerDelegate的代理方法
extension ViewController : AVPictureInPictureControllerDelegate {
func pictureInPictureControllerWillStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
print("将要开始PictureInPicture的代理方法")
}
func pictureInPictureControllerDidStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
print("已经开始PictureInPicture的代理方法")
}
func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, failedToStartPictureInPictureWithError error: Error) {
print("启动PictureInPicture失败的代理方法")
}
func pictureInPictureControllerWillStopPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
print("将要停止PictureInPicture的代理方法")
}
func pictureInPictureControllerDidStopPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
print("已经停止PictureInPicture的代理方法")
}
func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: @escaping (Bool) -> Void) {
//此方法执行在pictureInPictureControllerWillStopPictureInPicture代理方法之后,在pictureInPictureControllerDidStopPictureInPicture执行之前。 但是点击“X”移除画中画时,不执行此方法。
print("PictureInPicture停止之前恢复用户界面")
}
}