我在应用的登录页面上循环播放视频。我按照这个Youtube教程使其工作loop video in view controller
问题是当应用程序进入后台时,如果我不立即回来,当我回来时,视频会被冻结。
根据应该发生的Apple Docs。
我尝试使用NotificationCenter的Notification.Name.UIApplicationWillResignActive
,但这不起作用。
如果应用从后台返回,如何让视频继续播放?
var player: AVPlayer!
var playerLayer: AVPlayerLayer!
override func viewDidLoad() {
super.viewDidLoad()
configurePlayer()
}
@objc fileprivate func configurePlayer(){
let url = Bundle.main.url(forResource: "myVideo", withExtension: ".mov")
player = AVPlayer.init(url: url!)
playerLayer = AVPlayerLayer(player: player!)
playerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill
playerLayer.frame = view.layer.frame
player.actionAtItemEnd = AVPlayerActionAtItemEnd.none
player.play()
view.layer.insertSublayer(playerLayer, at: 0)
NotificationCenter.default.addObserver(self, selector: #selector(playerItemReachedEnd), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: player.currentItem)
NotificationCenter.default.addObserver(self, selector: #selector(playerItemReachedEnd), name: Notification.Name.UIApplicationWillResignActive, object: player.currentItem)
}
@objc fileprivate func playerItemReachedEnd(){
player.seek(to: kCMTimeZero)
}
答案 0 :(得分:8)
根据Apple Docs播放视频并将应用程序发送到后台时,播放器会自动暂停:
他们要做的是在应用程序进入后台时删除AVPlayerLayer
(设置为nil),然后在前台进行重新初始化时使用
他们说处理此问题的最佳方式是applicationDidEnterBackground
和applicationDidBecomeActive
:
我使用NSNotification来监听背景和前景事件,并设置暂停播放器和放大器的功能。将playerLayer设置为nil(均为背景事件),然后重新初始化playerLayer&为前景事件播放了播放器。这些是我使用的通知.UIApplicationWillEnterForeground
和.UIApplicationDidEnterBackground
我发现的是,出于某种原因,如果您长按主页按钮,弹出的屏幕显示“我可以帮助您”,如果您再次按“主页”按钮回到你的应用程序后,视频将被冻结,并使用上面的2个通知不会阻止它。我发现阻止此操作的唯一方法是还使用通知.UIApplicationWillResignActive
和.UIApplicationDidBecomeActive
。如果您不在上述通知中添加这些内容,那么您的视频将在长按按住主页按钮后冻结。我发现阻止所有冻结方案的最佳方法是使用所有4个通知。
我必须做的两件事与上面的代码不同的是将player和playerLayer类变量设置为可选项而不是隐式展开的选项,我还在AVPlayer类中添加了一个扩展,以检查它是否在iOS中播放9或以下。在iOS 10及更高版本中,有一种内置方法.timeControlStatus
AVPlayer timer status
上面的代码:
var player: AVPlayer?
var playerLayer: AVPlayerLayer?
在iOS 9或更低版本中将AVPlayer的扩展名添加到check the state of the AVPlayer:
import AVFoundation
extension AVPlayer{
var isPlaying: Bool{
return rate != 0 && error == nil
}
}
以下是完整的代码:
var player: AVPlayer?
var playerLayer: AVPlayerLayer? //must be optional because it will get set to nil on background event
override func viewDidLoad() {
super.viewDidLoad()
// background event
NotificationCenter.default.addObserver(self, selector: #selector(setPlayerLayerToNil), name: .UIApplicationDidEnterBackground, object: nil)
// foreground event
NotificationCenter.default.addObserver(self, selector: #selector(reinitializePlayerLayer), name: .UIApplicationWillEnterForeground, object: nil)
// add these 2 notifications to prevent freeze on long Home button press and back
NotificationCenter.default.addObserver(self, selector: #selector(setPlayerLayerToNil), name: Notification.Name.UIApplicationWillResignActive, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(reinitializePlayerLayer), name: Notification.Name.UIApplicationDidBecomeActive, object: nil)
configurePlayer()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// this is also for the long Home button press
if let player = player{
if #available(iOS 10.0, *) {
if player.timeControlStatus == .paused{
player.play()
}
} else {
if player.isPlaying == false{
player.play()
}
}
}
}
@objc fileprivate func configurePlayer(){
let url = Bundle.main.url(forResource: "myVideo", withExtension: ".mov")
player = AVPlayer.init(url: url!)
playerLayer = AVPlayerLayer(player: player!)
playerLayer?.videoGravity = AVLayerVideoGravityResizeAspectFill
playerLayer?.frame = view.layer.frame
player?.actionAtItemEnd = AVPlayerActionAtItemEnd.none
player?.play()
view.layer.insertSublayer(playerLayer!, at: 0)
NotificationCenter.default.addObserver(self, selector: #selector(playerItemReachedEnd), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: player.currentItem)
}
@objc fileprivate func playerItemReachedEnd(){
// this works like a rewind button. It starts the player over from the beginning
player?.seek(to: kCMTimeZero)
}
// background event
@objc fileprivate func setPlayerLayerToNil(){
// first pause the player before setting the playerLayer to nil. The pause works similar to a stop button
player?.pause()
playerLayer = nil
}
// foreground event
@objc fileprivate func reinitializePlayerLayer(){
if let player = player{
playerLayer = AVPlayerLayer(player: player)
if #available(iOS 10.0, *) {
if player.timeControlStatus == .paused{
player.play()
}
} else {
// if app is running on iOS 9 or lower
if player.isPlaying == false{
player.play()
}
}
}
}
不要忘记向AVPlayer添加isPlaying
扩展
答案 1 :(得分:3)
接受的答案对我不起作用。我的“欢迎”视频在某些情况下会随机暂停。
这是做什么的:
背景:由于当应用程序“ resignsActive”或进入“背景”时,player和playerLayer对象不会被破坏(可以通过观察它们各自的通知来观察它们的状态来进行验证)叫)
我推测将这些对象中的任何一个都设置为nil,然后在进入背景或前景时将它们重新初始化是不必要的。
我只会在播放器对象进入前景时再次播放它。
var player: AVPlayer?
var playerLayer: AVPlayerLayer?
在ViewDidLoad中,我配置播放器对象。
override func viewDidLoad() {
configurePlayer()
}
configurePlayer()函数在下面定义
private func configurePlayer() {
guard let URL = Bundle.main.url(forResource: "welcome", withExtension: ".mp4") else { return }
player = AVPlayer.init(url: URL)
playerLayer = AVPlayerLayer(player: player)
playerLayer?.videoGravity = AVLayerVideoGravity.resizeAspectFill
playerLayer?.frame = view.layer.frame
player?.actionAtItemEnd = AVPlayerActionAtItemEnd.none
playItem()
setupPlayNotificationItems()
}
这是辅助函数的实现
private func setupPlayNotificationItems() {
NotificationCenter.default.addObserver(self,
selector: #selector(restartPlayerItem),
name: NSNotification.Name.AVPlayerItemDidPlayToEndTime,
object: player?.currentItem)
NotificationCenter.default.addObserver(self,
selector: #selector(playItem),
name: .UIApplicationWillEnterForeground,
object: nil)
}
@objc private func playItem() {
// If you please, you can also restart the video here
restartPlayerItem()
player?.play()
if let playerlayer = playerLayer {
view.layer.insertSublayer(playerlayer, at: 0)
}
}
@objc func restartPlayerItem() {
player?.seek(to: kCMTimeZero)
}
答案 2 :(得分:2)
添加观察者
func addPlayerNotifications() {
NotificationCenter.default.addObserver(self, selector: #selector(playerItemDidPlayToEnd), name: .AVPlayerItemDidPlayToEndTime, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(applicationWillEnterForeground), name: .UIApplicationWillEnterForeground, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(applicationDidEnterBackground), name: .UIApplicationDidEnterBackground, object: nil)
}
删除观察者
func removePlayerNotifations() {
NotificationCenter.default.removeObserver(self, name: .AVPlayerItemDidPlayToEndTime, object: nil)
NotificationCenter.default.removeObserver(self, name: .UIApplicationWillEnterForeground, object: nil)
NotificationCenter.default.removeObserver(self, name: .UIApplicationDidEnterBackground, object: nil)
}
方法
// Player end.
@objc func playerItemDidPlayToEnd(_ notification: Notification) {
// Your Code.
player.seek(to: kCMTimeZero)
}
//App enter in forground.
@objc func applicationWillEnterForeground(_ notification: Notification) {
player.play()
}
//App enter in forground.
@objc func applicationDidEnterBackground(_ notification: Notification) {
player.pause()
}
试试此代码
答案 3 :(得分:0)
我在 Swift 4.3 中找到了一个适用于我的简单解决方案。我刚刚创建了一个观察器,用于观察应用程序何时在覆盖的ViewDidLoad中进入背景,何时进入前景。
NotificationCenter.default.addObserver(self, selector:#selector(VideoViewController.shutItDown), name: UIApplication.didEnterBackgroundNotification, object: UIApplication.shared)
NotificationCenter.default.addObserver(self, selector:#selector(VideoViewController.refresh), name: UIApplication.willEnterForegroundNotification, object: nil)
然后,我在类中具有以下由观察者调用的方法:
@objc func refresh() {
setupVideo()
}
@objc func shutItDown() {
self.newLayer.removeFromSuperlayer()
}
其中newLayer是我的AVLayer,已作为子图层添加到我的VideoView中。只是为了更加冗长,我为我的视频设置添加了代码,以确保即使您的外观看起来完全不同,所有内容都可以理解。
private func setupVideo() {
self.path = URL(fileURLWithPath: Bundle.main.path(forResource: "coined", ofType: "mov")!)
self.player = AVPlayer(url: self.path)
self.newLayer = AVPlayerLayer(player: self.player)
self.newLayer.frame = self.videoView.frame
self.videoView.layer.addSublayer(newLayer)
self.newLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill
self.player.play()
self.videoView.bringSubviewToFront(continueButton)
self.videoView.bringSubviewToFront(settingsButton)
}
此方法的“缺点”是,每次从后台转到前景时,视频都会重新启动。在我的情况下,这是可以接受的,但在您的情况下可能不可接受。这是因为当您进入背景时,AVLayer被删除,并且每次您进入前景时,都会在videoView上放置一个新的AVLayer。为了防止呈现快照错误(即克服“冻结”),必须删除旧的AVLayer。
答案 4 :(得分:0)
Tsonono答案很好用,我只是用它来修复冻结的视频。
一方面,不摆脱他在谈论的缺点(每次进入前景时,视频都会重新启动),只需使用这两种方法(shutdowndown方法中的暂停播放器和refresh方法中的播放器)调用播放器本身:< / p>
@objc func refresh() {
self.player?.play()
@objc func shutItDown() {
self.player?.pause()
}