我一直试图弄清楚如何使用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
}
}发布于 2017-08-10 02:52:02
因此,我通过观看一些WWDC 2016演讲找到了一个解决方案,他们描述了一种跑步机模式,并查看了示例代码。
本质上,您加载想要播放的视频,然后使用在播放视频时响应的Key Value Observing,然后将播放的视频添加回堆栈的末尾。
首先创建一个协议:
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。
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监听视频何时播放完毕,并将其添加到要播放的视频的末尾。
发布于 2017-08-09 14:55:59
AVPlayerLooper将玩家作为第一个参数,所以你可以这样做:
let myPlayer = AVQueuePlayer([AVPlayerItem])
AVPlayerLooper(player: myPlayer, templateItem: oneoftheitems)发布于 2020-05-27 04:32:20
对于任何仍在寻找答案的人来说,Ray Wenderlich可以帮助他们:
https://www.raywenderlich.com/5191-video-streaming-tutorial-for-ios-getting-started#toc-anchor-009
基本上,观察队列中何时还有一个playerItem,然后重新插入所有playerItems。
https://stackoverflow.com/questions/45582140
复制相似问题