【问题标题】:Using AVPlayerLooper to loop through multiple videos使用 AVPlayerLooper 循环播放多个视频
【发布时间】:2018-01-16 20:08:42
【问题描述】:

我一直试图弄清楚如何使用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
   }
}

【问题讨论】:

    标签: ios swift avfoundation repeat avqueueplayer


    【解决方案1】:

    所以我通过观看 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)
            }
        }
    }
    

    本质上,我们设置了AVPlayerAVPlayerLayer 对象。然后 KVO 会监听视频何时播放完毕并将其添加到要播放的视频的末尾。

    【讨论】:

    • 值得强调的是,在这 3 个 KVO 注册中,有两个在寻找新值,但 player.currentItem 实际上是不同的,因为它寻找
    【解决方案2】:

    AVPlayerLooper 将播放器作为第一个参数,所以你可以这样:

    let myPlayer = AVQueuePlayer([AVPlayerItem])
    
    AVPlayerLooper(player: myPlayer, templateItem: oneoftheitems)
    

    【讨论】:

    • 啊,但它会循环播放所有视频吗?我希望它播放所有视频,然后重新开始播放视频(而不仅仅是再次播放其中一个视频)。
    • 好的,所以我有一个不同的解决方案对我有用。我会发布它:)
    • 根据我使用 AvPlayerLooper 的经验,在循环开始时会出现轻微故障,而不是使用 NSNotification + seek 来完成
    • 你知道,这很奇怪,因为对于我的用例,我总是会结结巴巴。即使为正在播放的部分制作中间格式,也找不到让它干净循环的方法。我实际上最终使用developer.apple.com/library/archive/samplecode/avloopplayer/… 修改了一堆,我认为代码在下一个 ios 版本(我认为是 10)中最终成为 AVPlayerLooper。 (即使基本概念相同,Apple 代码也明显比我制作的代码更精致)
    【解决方案3】:

    对于仍在寻找答案的任何人,Ray Wenderlich 来救援:

    https://www.raywenderlich.com/5191-video-streaming-tutorial-for-ios-getting-started#toc-anchor-009

    基本上,观察队列中何时还有一个 playerItem,然后重新插入所有 playerItem。

    【讨论】:

      猜你喜欢
      • 2018-08-15
      • 1970-01-01
      • 2019-05-04
      • 1970-01-01
      • 1970-01-01
      • 2015-04-03
      • 1970-01-01
      • 2017-03-31
      • 1970-01-01
      相关资源
      最近更新 更多