问题描述
我一直在尝试找出如何使用AVPlayerLooper
循环播放多个视频,但是它们的templateItem
接受的类型为AVPlayerItem
而不是[AVPlayerItem]
.我目前正在使用AVQueuePlayer
来显示视频,但我需要循环浏览.
I've been trying to figure out how to loop over multiple videos with AVPlayerLooper
, but their templateItem
takes an argument of type AVPlayerItem
and not [AVPlayerItem]
. I'm currently using an AVQueuePlayer
to display the video but I need to loop through it.
到目前为止,这是我的代码:
Here's my code so far:
class MyVC: UIViewController {
@IBOutlet weak var playerView: UIView!
lazy var backgroundVideoPlayer = AVQueuePlayer()
// View Controller related code (viewDidLoad, etc.) is taken out for brevity.
private func loadBackgroundVideosRandomly() -> [AVPlayerItem] {
let mainBundle = Bundle.main
let movieURLs = [mainBundle.url(forResource: "Boop Burj Al Arab", withExtension: "mov"),
mainBundle.url(forResource: "Boop Dubai", withExtension: "mov"),
mainBundle.url(forResource: "Boop Dubai Clock", withExtension: "mov"),
mainBundle.url(forResource: "Boop Dubai Lake", withExtension: "mov")].shuffled()
let items = movieURLs.map { AVPlayerItem(url: $0!) }
return items
}
private func playBackgroundVideos() {
let playerLayer = AVPlayerLayer(player: backgroundVideoPlayer)
playerLayer.videoGravity = .resizeAspectFill
playerLayer.frame = playerView.bounds
playerView.layer.addSublayer(playerLayer)
// Configure the player.
backgroundVideoPlayer.seek(to: kCMTimeZero)
backgroundVideoPlayer.actionAtItemEnd = .advance
}
}
推荐答案
因此,我通过观看WWDC 2016上的一些演讲来描述一个跑步机模式并查看示例代码,从而找到了解决方案.
So I have figured out a solution by watching some of the WWDC 2016 talks where they describe a treadmill pattern and looking at sample code.
基本上,您要加载要播放的视频,然后使用Key Value Observing
响应视频播放的时间,然后将播放的视频添加回堆栈的末尾.
Essentially, you load up the videos you want to play and then using Key Value Observing
you respond to when a video has been played and then add that played video back to the end of the stack.
首先创建一个协议:
protocol BackgroundLooper {
/// Loops the videos specified forever.
///
/// - Parameter urls: The url where the video is located at.
init (urls: [URL])
/// Starts looping the videos in a specified layer.
///
/// - Parameter layer: The layer where the video should be displayed.
func start(in layer: CALayer)
/// Stops the video playback.
func stop()
}
然后创建一个符合协议的BackgroundQueuePlayerLooper
.
Then create a BackgroundQueuePlayerLooper
that conforms to the protocol.
import AVFoundation
/// Repeats a set of videos forever (ideally for use in a background view).
class BackgroundQueuePlayerLooper: NSObject, BackgroundLooper {
// MARK: - Observer contexts
/// The context required for observing.
private struct ObserverContexts {
static var playerStatus = 0
static var playerStatusKey = "status"
static var currentItem = 0
static var currentItemKey = "currentItem"
static var currentItemStatus = 0
static var currentItemStatusKey = "currentItem.status"
static var urlAssetDurationKey = "duration"
static var urlAssetPlayableKey = "playable"
}
// MARK: - Properties
private var player: AVQueuePlayer?
private var playerLayer: AVPlayerLayer?
private var isObserving = false
private let videoURLs: [URL]
// MARK: - Initialization
required init(urls: [URL]) {
self.videoURLs = urls
}
// MARK: - Looper
func start(in layer: CALayer) {
stop()
player = AVQueuePlayer()
player?.externalPlaybackVideoGravity = .resizeAspectFill
playerLayer = AVPlayerLayer(player: player)
playerLayer?.videoGravity = .resizeAspectFill
guard let playerLayer = playerLayer else { fatalError("There was an error creating the player layer!") }
playerLayer.frame = layer.bounds
layer.addSublayer(playerLayer)
let assets = videoURLs.map { AVURLAsset(url: $0) }
assets.forEach { player?.insert(AVPlayerItem(asset: $0), after: nil) }
startObserving()
player?.play()
}
func stop() {
player?.pause()
stopObserving()
player?.removeAllItems()
player = nil
playerLayer?.removeFromSuperlayer()
playerLayer = nil
}
// MARK: - Key value observing
/// Starts observing the player.
private func startObserving() {
guard let player = player else { return }
guard !isObserving else { return }
player.addObserver(self, forKeyPath: ObserverContexts.playerStatusKey, options: .new, context: &ObserverContexts.playerStatus)
player.addObserver(self, forKeyPath: ObserverContexts.currentItemKey, options: .old, context: &ObserverContexts.currentItem)
player.addObserver(self, forKeyPath: ObserverContexts.currentItemStatusKey, options: .new, context: &ObserverContexts.currentItemStatus)
isObserving = true
}
/// Stops observing the player.
private func stopObserving() {
guard let player = player else { return }
guard isObserving else { return }
player.removeObserver(self, forKeyPath: ObserverContexts.playerStatusKey, context: &ObserverContexts.playerStatus)
player.removeObserver(self, forKeyPath: ObserverContexts.currentItemKey, context: &ObserverContexts.currentItem)
player.removeObserver(self, forKeyPath: ObserverContexts.currentItemStatusKey, context: &ObserverContexts.currentItemStatus)
isObserving = false
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if context == &ObserverContexts.playerStatus {
guard let newPlayerStatus = change?[.newKey] as? AVPlayerStatus else { return }
guard newPlayerStatus == .failed else { return }
// End looping since player has failed
stop()
} else if context == &ObserverContexts.currentItem {
guard let player = player else { return }
// Play queue emptied out due to bad player item. End looping.
guard !player.items().isEmpty else { stop(); return }
/*
Append the previous current item to the player's queue. An initial
change from a nil currentItem yields NSNull here. Check to make
sure the class is AVPlayerItem before appending it to the end
of the queue.
*/
guard let itemRemoved = change?[.oldKey] as? AVPlayerItem else { return }
itemRemoved.seek(to: kCMTimeZero, completionHandler: nil)
stopObserving()
player.insert(itemRemoved, after: nil)
startObserving()
} else if context == &ObserverContexts.currentItemStatus {
guard let newPlayerItemStatus = change?[.newKey] as? AVPlayerItemStatus else { return }
guard newPlayerItemStatus == .failed else { return }
// End looping since player item has failed.
stop()
} else {
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
}
}
}
基本上,我们设置AVPlayer
和AVPlayerLayer
对象.然后,KVO会在视频播放完毕时侦听并将其添加到要播放的视频的末尾.
Essentially, we setup the AVPlayer
and AVPlayerLayer
objects. Then KVO listens for when a video has finished playing and adds it to the end of the videos to be played.
这篇关于使用AVPlayerLooper循环播放多个视频的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!