1. 让你的App支持画中画
画中画指可以让视频在小窗中播放,可以一边看视频一边刷知乎

你可以使用AVPlayerViewController或者AVPictureInPictureController来实现画中画播放。 其中AVPictureInPictureController支持你自定义一些播放控件
在支持画中画播放之前,确保你按照iOS音视频播放指南(一) 第三部分(3.音频设置 )完成相应的设置。
AVPlayerViewController
支持画中画播放最简单的方式就是使用 AVPlayerViewController 。如果你的设备支持画中画播放,使用AVPlayerViewController点击按钮就能看到效果。支持画中画播放的设备在视频播放的时候直接返回桌面,视频也会以画中画形式继续播放。

如果你返回桌面的时候视频不会以画中画形式继续播放,检测一下设置-通用-画中画。查看是否开启

但是当你想还原画中画视频的时候,AVKit在默认情况下会停止视频的播放(系统不知道如何处理你App的用户界面),我们需要自己实现代理来完成视频的恢复。
letcontroller=AVPlayerViewController()
controller.delegate=self
extensionViewController:AVPlayerViewControllerDelegate{
//在这里处理App的恢复逻辑
funcplayerViewController(_playerViewController:AVPlayerViewController,restoreUserInterfaceForPictureInPictureStopWithCompletionHandlercompletionHandler:@escaping(Bool)->Void){
//重新present playerViewController
present(playerViewController,animated:true){
//通知系统我们已经完成了视频的界面恢复
completionHandler(true)
}
}
}
\2. 使用AVPictureInPictureController
当你自定义的播放器需要支持画中画的时候,你需要用到AVPictureInPictureController ,它管理着AVPlayerLayer。
funcsetupPictureInPicture(){
// 判断设备是否支持画中画
ifAVPictureInPictureController.isPictureInPictureSupported(){
//创建AVPlayerLayer
playerLayer=AVPlayerLayer(player:AVPlayer(url:url))
playerLayer.frame=CGRect(x:100,y:100,width:100,height:100)
view.layer.addSublayer(playerLayer)
playerLayer.player?.play()
// 创建AVPictureInPictureController
pictureInPictureController=AVPictureInPictureController(playerLayer:playerLayer)
pictureInPictureController.delegate=self
}else{
// 不支持画中画
startButton.isEnabled=false
stopButton.isEnabled=false
}
}
@objcfunctogglePictureInPictureMode(_sender:UIButton){
ifpictureInPictureController.isPictureInPictureActive{
//停止
pictureInPictureController.stopPictureInPicture()
}else{
//开始
pictureInPictureController.startPictureInPicture()
}
}
funcpictureInPictureController(_pictureInPictureController:AVPictureInPictureController,restoreUserInterfaceForPictureInPictureStopWithCompletionHandlercompletionHandler:@escaping(Bool)->Void){
//在这里进行用户视频播放界面的恢复逻辑
print("restore")
completionHandler(true)
}
funcpictureInPictureControllerWillStartPictureInPicture(_pictureInPictureController:AVPictureInPictureController){
//在界面上显示placeholder,隐藏播放控件等操作
print("will start")
}
funcpictureInPictureControllerWillStopPictureInPicture(_pictureInPictureController:AVPictureInPictureController){
//在界面上隐藏placeholder,恢复播放控件等操作
print("will stop")
}
其中AVPictureInPictureController 创建使用到了AVPlayerLayer,但是实际播放的时候PIP不使用AVPlayerLayer进行显示。当PIP开始使用的时候,系统会自动停止向AVPlayerLayer输出视频帧。
你一定要让用户通过操作(点击按钮等)来开始画中画显示。 不能直接在代码中直接startPictureInPicture ,这样的话你的APP上架审核会被拒绝。
2. 音频控制
后台播放请参考 iOS音视频播放指南(一)第三部分 (3.音频设置 )
当你完成后台播放设置以后,如果你播放的是音频文件,你退到后台的时候系统会自动继续播放。但是当你播放的是视频文件,默认情况下进入后台系统会自动停止播放。 如果你想在退到后台继续播放声音,需要在进入后台时断开AVPlayer的连接,进入前台重新连接。
funcsceneWillEnterForeground(_scene:UIScene){
guardletvc=(scene.delegateas?SceneDelegate)?.window?.rootViewControlleras?ViewControllerelse{
return
}
ifletplayerLayer=vc.playerLayer,letplayer=vc.player{
// 进入前台重新连接player
playerLayer.player=player
}
}
funcsceneDidEnterBackground(_scene:UIScene){
guardletvc=(scene.delegateas?SceneDelegate)?.window?.rootViewControlleras?ViewControllerelse{
return
}
ifletplayerLayer=vc.playerLayer{
// 进入后台断开与player的连接
playerLayer.player=nil
}
}
如果你的App支持后台音频播放,你可能还需要支持远程控制(耳机控制等)以及在锁屏界面的控制。这里我们使用到MediaPlayer 框架中的MPRemoteCommandCenter和MPNowPlayingInfoCenter 这两个类。
MPRemoteCommandCenter用于处理远程控制
importMediaPlayer
funcsetupRemoteTransportControls(){
// 获取 MPRemoteCommandCenter
letcommandCenter=MPRemoteCommandCenter.shared()
// 播放控制
commandCenter.playCommand.addTarget{[unownedself]eventin
ifself.player.rate==0.0{
self.player.play()
return.success
}
return.commandFailed
}
// 停止控制
commandCenter.pauseCommand.addTarget{[unownedself]eventin
ifself.player.rate==1.0{
self.player.pause()
return.success
}
return.commandFailed
}
}
MPNowPlayingInfoCenter用于锁屏界面的显示。其中我们需要注意的是AVPlayerViewController会自动刷新锁屏界面显示内容,这里我们关闭自动刷新。
funcsetupNowPlaying(){
// 由我们自己控制锁屏界面的显示,如果设置为false可能会使耳机控制失效
playerViewController.updatesNowPlayingInfoCenter=false
// 设置显示内容
varnowPlayingInfo=[String:Any]()
nowPlayingInfo[MPMediaItemPropertyTitle]="My Movie"
ifletimage=UIImage(named:"lockscreen"){
nowPlayingInfo[MPMediaItemPropertyArtwork]=
MPMediaItemArtwork(boundsSize:image.size){sizein
returnimage
}
}
nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime]=playerItem.currentTime().seconds
nowPlayingInfo[MPMediaItemPropertyPlaybackDuration]=playerItem.asset.duration.seconds
nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate]=player.rate
// 提交给MPNowPlayingInfoCenter
MPNowPlayingInfoCenter.default().nowPlayingInfo=nowPlayingInfo
}
我们可以在屏幕上看到我们提供显示的内容

3. 处理中断请求
当用户观看视频的时候,如果有电话打进来,系统会自动暂停视频播放。当通话结束的时候,系统会自动恢复播放。如果你自定义了播放界面,你可能需要在这种情况下更新界面,对AVPlayer的rate属性进行KVO可以很方便的处理业务逻辑。
此外,你也可以通过监听通知来处理业务逻辑
funcsetupInterruptionNotification(){
letnotificationCenter=NotificationCenter.default
notificationCenter.addObserver(self,
selector:#selector(handleInterruption),
name:AVAudioSession.interruptionNotification,
object:nil)
}
@objcfunchandleInterruption(notification:Notification){
guardletuserInfo=notification.userInfo,
lettypeValue=userInfo[AVAudioSessionInterruptionTypeKey]as?UInt,
lettype=AVAudioSession.InterruptionType(rawValue:typeValue)else{
return
}
iftype==.began{
// 中断请求触发,在这里处理你的业务逻辑
}
elseiftype==.ended{
ifletoptionsValue=userInfo[AVAudioSessionInterruptionOptionKey]as?UInt{
letoptions=AVAudioSession.InterruptionOptions(rawValue:optionsValue)
ifoptions.contains(.shouldResume){
// 系统会自动恢复播放 (通话结束)
}else{
// 系统不会自动恢复播放
}
}
}
}
AVAudioSession一个比较重要的功能是处理音频路由变化。 一般情况下,在用户插入耳机的时候,音频会继续播放,在用户拔出耳机的时候,音频停止播放,这一切都由系统为你自动完成。你可能需要在App中对这种情况进行一些业务处理。对AVPlayer的rate属性进行KVO或者使用AVAudioSession.routeChangeNotification进行监听
funcsetupRouteChangeNotification(){
letnotificationCenter=NotificationCenter.default
notificationCenter.addObserver(self,
selector:#selector(handleRouteChange),
name:AVAudioSession.routeChangeNotification,
object:nil)
}
@objcfunchandleRouteChange(notification:Notification){
guardletuserInfo=notification.userInfo,
letreasonValue=userInfo[AVAudioSessionRouteChangeReasonKey]as?UInt,
letreason=AVAudioSession.RouteChangeReason(rawValue:reasonValue)else{
return
}
switchreason{
case.newDeviceAvailable:
//耳机插入、蓝牙连接等情况
letsession=AVAudioSession.sharedInstance()
//获取当前路由信息
foroutputinsession.currentRoute.outputswhereoutput.portType==AVAudioSession.Port.headphones{
//耳机已连接
//headphonesConnected = true
break
}
case.oldDeviceUnavailable:
//耳机拔出、蓝牙断开等情况
//获取先前的路由信息
ifletpreviousRoute=
userInfo[AVAudioSessionRouteChangePreviousRouteKey]as?AVAudioSessionRouteDescription{
foroutputinpreviousRoute.outputswhereoutput.portType==AVAudioSession.Port.headphones{
//耳机已断开连接
//headphonesConnected = false
break
}
}
default:()
}
}
原文https://zhuanlan.zhihu.com/p/335956344
★文末名片可以免费领取音视频开发学习资料,内容包括(FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,srs)以及音视频学习路线图等等。
见下方!↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓