How can I check if my AVPlayer is buffering?

后端 未结 10 1059
后悔当初
后悔当初 2020-12-01 06:02

I want to detect if my AVPlayer is buffering for the current location, so that I can show a loader or something. But I can\'t seem to find anything in the documentation for

相关标签:
10条回答
  • 2020-12-01 06:47

    Please note that

    Use a weak reference to self in the callback block to prevent creating a retain cycle.

    func playRemote(url: URL) {
                showSpinner()
                let playerItem = AVPlayerItem(url: url)
                avPlayer = AVPlayer(playerItem: playerItem)
                avPlayer.rate = 1.0
                avPlayer.play()
                self.avPlayer.addPeriodicTimeObserver(forInterval: CMTimeMake(value: 1,
         timescale: 600), queue: DispatchQueue.main, using: { [weak self] time in
                    if self?.avPlayer.currentItem?.status == AVPlayerItem.Status.readyToPlay {
                        if let isPlaybackLikelyToKeepUp = self?.avPlayer.currentItem?.isPlaybackLikelyToKeepUp { 
                            self?.removeSpinner()
                        }
                    }
                })
            }
    }
    
    0 讨论(0)
  • 2020-12-01 06:48

    You can observe the values of your player.currentItem:

    playerItem.addObserver(self, forKeyPath: "playbackBufferEmpty", options: .New, context: nil)
    playerItem.addObserver(self, forKeyPath: "playbackLikelyToKeepUp", options: .New, context: nil)
    playerItem.addObserver(self, forKeyPath: "playbackBufferFull", options: .New, context: nil)
    

    then

    override public func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
        if object is AVPlayerItem {
            switch keyPath {
                case "playbackBufferEmpty":
                   // Show loader
    
                case "playbackLikelyToKeepUp":
                    // Hide loader
    
                case "playbackBufferFull":
                    // Hide loader
            }
        }
    }
    
    0 讨论(0)
  • 2020-12-01 06:50

    Hmm, the accepted solution didn't work for me and the periodic observer solutions seem heavy handed.

    Here's my suggestion, observe timeControlerStatus on AVPlayer.

    // Add observer
    player.addObserver(self,
                       forKeyPath: #keyPath(AVPlayer.timeControlStatus),
                       options: [.new],
                       context: &playerItemContext)
    
    // At some point you'll need to remove yourself as an observer otherwise
    // your app will crash 
    self.player?.removeObserver(self, forKeyPath: #keyPath(AVPlayer.timeControlStatus))
    
    // handle keypath callback
    if keyPath == #keyPath(AVPlayer.timeControlStatus) {
        guard let player = self.player else { return }
        if let isPlaybackLikelyToKeepUp = player.currentItem?.isPlaybackLikelyToKeepUp,
            player.timeControlStatus != .playing && !isPlaybackLikelyToKeepUp {
            self.playerControls?.loadingStatusChanged(true)
        } else {
            self.playerControls?.loadingStatusChanged(false)
        }
    }
    
    0 讨论(0)
  • 2020-12-01 06:50

    Here is a simple method, that works with Swift 5.

    This will add the loadingIndicator when your player is stalled

    NotificationCenter.default.addObserver(self, selector:
    #selector(playerStalled(_:)), name: NSNotification.Name.AVPlayerItemPlaybackStalled, object: self.player?.currentItem)
    
    @objc func playerStalled(_ notification: Notification){
        self.loadingIndicator.isHidden = false
        self.playPauseButton.isHidden = true
    }
    

    This will show loader Indicator when buffer is empty:

    if let isPlayBackBufferEmpty = self.player?.currentItem?.isPlaybackBufferEmpty{
        if isPlayBackBufferEmpty{
            self.loadingIndicator.isHidden = false
            self.playPauseButton.isHidden = true
        }
    }
    

    This will hide the loader when player is ready to play:

    if self.playerItem?.status == AVPlayerItem.Status.readyToPlay{
        if let isPlaybackLikelyToKeepUp = self.player?.currentItem?.isPlaybackLikelyToKeepUp {
            if isPlaybackLikelyToKeepUp{
                self.loadingIndicator.isHidden = true
                self.playPauseButton.isHidden = false
            }
        }
    }
    
    0 讨论(0)
  • 2020-12-01 06:51

    #Updated in Swift 4 and worked fine

    As through i have gone with accepted answer but didn't work in swift 4 for me so after certain research i have found this thinks from apple doc. There are two way to determine AVPlayer states that are,

    1. addPeriodicTimeObserverForInterval:queue:usingBlock: and
    2. addBoundaryTimeObserverForTimes:queue:usingBlock:

    and using ways is like this

    var observer:Any?
    var avplayer : AVPlayer?
    
    func preriodicTimeObsever(){
    
            if let observer = self.observer{
                //removing time obse
                avplayer?.removeTimeObserver(observer)
                observer = nil
            }
    
            let intervel : CMTime = CMTimeMake(1, 10)
            observer = avplayer?.addPeriodicTimeObserver(forInterval: intervel, queue: DispatchQueue.main) { [weak self] time in
    
                guard let `self` = self else { return }
    
                let sliderValue : Float64 = CMTimeGetSeconds(time)
               //this is the slider value update if you are using UISlider.
    
                let playbackLikelyToKeepUp = self.avPlayer?.currentItem?.isPlaybackLikelyToKeepUp
                if playbackLikelyToKeepUp == false{
    
                   //Here start the activity indicator inorder to show buffering
                }else{
                    //stop the activity indicator 
                }
            }
        }
    

    And Don't forget to kill time observer to save from memory leak. method for killing instance, add this method according to your need but i have used it in viewWillDisappear method.

           if let observer = self.observer{
    
                self.avPlayer?.removeTimeObserver(observer)
                observer = nil
            }
    
    0 讨论(0)
  • 2020-12-01 06:55

    For me above accepted answer didn't worked but this method does.You can use timeControlStatus but it is available only above iOS 10.

    According to apple's official documentation

    A status that indicates whether playback is currently in progress, paused indefinitely, or suspended while waiting for appropriate network conditions

    Add this observer to the player.

    player.addObserver(self, forKeyPath: “timeControlStatus”, options: [.old, .new], context: nil)
    

    Then,Observe the changes in

    func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?)
    

    method.Use below code inside above method

    override public func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        if keyPath == "timeControlStatus", let change = change, let newValue = change[NSKeyValueChangeKey.newKey] as? Int, let oldValue = change[NSKeyValueChangeKey.oldKey] as? Int {
            let oldStatus = AVPlayer.TimeControlStatus(rawValue: oldValue)
            let newStatus = AVPlayer.TimeControlStatus(rawValue: newValue)
            if newStatus != oldStatus {
                DispatchQueue.main.async {[weak self] in
                    if newStatus == .playing || newStatus == .paused {
                        self?.loaderView.isHidden = true
                    } else {
                        self?.loaderView.isHidden = false
                    }
                }
            }
        }
    }
    

    This is tested on iOS 11 above with swift 4 and It is working.

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