Using AVPlayerLooper to loop through multiple videos

前端 未结 3 1430
闹比i
闹比i 2020-12-12 00:36

I\'ve been trying to figure out how to loop over multiple videos with AVPlayerLooper, but their templateItem takes an argument of type AVPlay

相关标签:
3条回答
  • 2020-12-12 01:05

    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.

    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.

    First create a 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()
    }
    

    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)
            }
        }
    }
    

    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.

    0 讨论(0)
  • 2020-12-12 01:07

    AVPlayerLooper takes a player as the first argument, so you can go like:

    let myPlayer = AVQueuePlayer([AVPlayerItem])
    
    AVPlayerLooper(player: myPlayer, templateItem: oneoftheitems)
    
    0 讨论(0)
  • 2020-12-12 01:23

    For anyone that is still looking for an answer, Ray Wenderlich to the rescue:

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

    Basically, observe for when there is one more playerItem in the queue, then reinsert all playerItems.

    0 讨论(0)
提交回复
热议问题