アプリのログインページでビデオがループ再生されています。私はそれを機能させるためにこのYoutubeチュートリアルに従いました ビューコントローラーのループビデオ
問題は、アプリがバックグラウンドに移行したとき、すぐに戻ってこない場合、戻ってきたときにビデオがフリーズすることです。
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)
}
Apple Docs によると、ビデオが再生され、アプリがバックグラウンドに送信されると、プレーヤーは自動的に一時停止されます。
彼らが言うことは、アプリがバックグラウンドに行くときにAVPlayerLayer
(nilに設定)を削除し、フォアグラウンドになるとそれを再初期化することです。
そして、これを処理するために彼らが言う最良の方法は、applicationDidEnterBackground
とapplicationDidBecomeActive
です。
NSNotificationを使用して、バックグラウンドイベントとフォアグラウンドイベントをリッスンし、プレーヤーを一時停止してplayerLayerをnil(両方ともバックグラウンドイベント用)に設定する関数を設定してから、playerLayerを再初期化し、フォアグラウンドイベント用にプレーヤーを再生しました。これらは私が使用した通知です.UIApplicationWillEnterForeground
と.UIApplicationDidEnterBackground
どういうわけか、ホームボタンを長押しすると、ホームボタンをもう一度押して元の場所に戻ると、「何ができますか」というポップアップ画面が表示されることがわかりました。アプリのビデオはフリーズし、上記の2つの通知を使用してもそれを防ぐことはできません。これを防ぐために私が見つけた唯一の方法は、また通知.UIApplicationWillResignActive
および.UIApplicationDidBecomeActive
を使用することです。上記の通知に加えてこれらを追加しない場合、ビデオはホームボタンを長押しして元に戻すとフリーズします。すべての凍結シナリオを防ぐために私が見つけた最善の方法は、4つの通知すべてを使用することです。
上記のコードとは異なる2つのことは、playerクラスとplayerLayerクラス変数を暗黙的にアンラップされたオプションではなくオプションとして設定することでした。また、AVPlayerクラスに拡張機能を追加して、iOS9以下で再生されているかどうかを確認しました。 。 iOS 10以降には、組み込みのメソッド.timeControlStatus
AVPlayerタイマーステータス があります。
上記の私のコード:
var player: AVPlayer?
var playerLayer: AVPlayerLayer?
AVPlayerに拡張機能を追加して AVPlayerの状態を確認してください iOS 9以下の場合:
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
拡張機能を追加することを忘れないでください
受け入れられた答えは私にはうまくいきませんでした。私の「ウェルカム」ビデオは、特定の機会にランダムに一時停止しました。
これが何をしたかです:
Background:アプリが「resignsActive」または「background」に入ったときに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)
}
オブザーバーを追加
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()
}
このコードを試してください
Swift 4.で私のために働く簡単な解決策を見つけました。オーバーライドされた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は、VideoViewにサブレイヤーとして追加されるAVLayerです。冗長にするために、ビデオセットアップのコードを追加して、見た目が大きく異なっていてもすべてが理解できるようにしました。
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の削除は、レンダリングスナップショットエラーを防ぐため、つまり「フリーズ」を克服するための基本でした。