我一直在尝试找出如何使用 AVPlayerLooper
循环播放多个视频,但它们的 templateItem
采用 AVPlayerItem
类型的参数而不是[AVPlayerItem]
。我目前正在使用 AVQueuePlayer
来显示视频,但我需要循环播放它。
这是我到目前为止的代码:
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 的一些演讲,其中描述了运行机模式并查看了示例代码,找到了解决方案。
本质上,您加载要播放的视频,然后使用键值观察
在视频播放时做出响应,然后将播放的视频添加回堆栈的末尾。
首先创建一个协议(protocol):
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()
}
然后创建一个符合协议(protocol)的BackgroundQueuePlayerLooper
。
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 监听视频何时播放完毕并将其添加到要播放的视频的末尾。
关于ios - 使用AVPlayerLooper循环播放多个视频,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45582140/