How can I check if my AVPlayer is buffering?

僤鯓⒐⒋嵵緔 提交于 2019-11-26 15:59:23

问题


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 AVPlayer.


回答1:


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



回答2:


The accepted answer didn't work for me, I used the code below to show the loader efficiently.

Swift 3

//properties 
var observer:Any!
var player:AVPlayer!


self.observer = self.player.addPeriodicTimeObserver(forInterval: CMTimeMake(1, 600), queue: DispatchQueue.main) {
    [weak self] time in

    if self?.player.currentItem?.status == AVPlayerItemStatus.readyToPlay {

        if let isPlaybackLikelyToKeepUp = self?.player.currentItem?.isPlaybackLikelyToKeepUp {
            //do what ever you want with isPlaybackLikelyToKeepUp value, for example, show or hide a activity indicator.
        }
    }
}



回答3:


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.




回答4:


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



回答5:


Swift 4 observations:

var playerItem: AVPlayerItem?
var playbackLikelyToKeepUpKeyPathObserver: NSKeyValueObservation?
var playbackBufferEmptyObserver: NSKeyValueObservation?
var playbackBufferFullObserver: NSKeyValueObservation?

private func observeBuffering() {
    let playbackBufferEmptyKeyPath = \AVPlayerItem.playbackBufferEmpty
    playbackBufferEmptyObserver = playerItem?.observe(playbackBufferEmptyKeyPath, options: [.new]) { [weak self] (_, _) in
        // show buffering
    }

    let playbackLikelyToKeepUpKeyPath = \AVPlayerItem.playbackLikelyToKeepUp
    playbackLikelyToKeepUpKeyPathObserver = playerItem?.observe(playbackLikelyToKeepUpKeyPath, options: [.new]) { [weak self] (_, _) in
        // hide buffering
    }

    let playbackBufferFullKeyPath = \AVPlayerItem.playbackBufferFull
    playbackBufferFullObserver = playerItem?.observe(playbackBufferFullKeyPath, options: [.new]) { [weak self] (_, _) in
        // hide buffering
    }
}

Observers need to be removed after we are done observing.

To remove these three observers just set playbackBufferEmptyObserver, playbackLikelyToKeepUpKeyPathObserver and playbackBufferFullObserver to nil.

No need to remove them manually (this is specific for observe<Value>(_ keyPath:, options:, changeHandler:) method.




回答6:


Updated for Swift 4.2

    var player : AVPlayer? = nil

    let videoUrl = URL(string: "https://wolverine.raywenderlich.com/content/ios/tutorials/video_streaming/foxVillage.mp4")
    self.player = AVPlayer(url: videoUrl!)
    self.player?.addPeriodicTimeObserver(forInterval: CMTimeMake(value: 1, timescale: 600), queue: DispatchQueue.main, using: { time in

        if self.player?.currentItem?.status == AVPlayerItem.Status.readyToPlay {

            if let isPlaybackLikelyToKeepUp = self.player?.currentItem?.isPlaybackLikelyToKeepUp {
                //do what ever you want with isPlaybackLikelyToKeepUp value, for example, show or hide a activity indicator.

                //MBProgressHUD.hide(for: self.view, animated: true)
            }
        }
    })



回答7:


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

Here's my suggestion, observer 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)
    }
}



回答8:


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



回答9:


Solution for Xamarin inspired by Marco's answer

// KVO registrations
private void Initialize()
{
    playbackBufferEmptyObserver?.Dispose();
    playbackBufferEmptyObserver = (NSObject)playerItem.AddObserver("playbackBufferEmpty",
        NSKeyValueObservingOptions.New,
        AVPlayerItem_BufferUpdated);

    playbackLikelyToKeepUpObserver?.Dispose();
    playbackLikelyToKeepUpObserver = (NSObject)playerItem.AddObserver("playbackLikelyToKeepUp",
        NSKeyValueObservingOptions.New,
        AVPlayerItem_BufferUpdated);

    playbackBufferFullObserver?.Dispose();
    playbackBufferFullObserver = (NSObject)playerItem.AddObserver("playbackBufferFull",
        NSKeyValueObservingOptions.New,
        AVPlayerItem_BufferUpdated);
}

private void AVPlayerItem_BufferUpdated(NSObservedChange e)
{
    ReportVideoBuffering();
}

private void ReportVideoBuffering()
{
    // currentPlayerItem is the current AVPlayerItem of AVPlayer
    var isBuffering = !currentPlayerItem.PlaybackLikelyToKeepUp;
    // NOTE don't make "buffering" as one of your PlayerState.
    // Treat it as a separate property instead. Learned this the hard way.
    Buffering?.Invoke(this, new BufferingEventArgs(isBuffering));
}


来源:https://stackoverflow.com/questions/38867190/how-can-i-check-if-my-avplayer-is-buffering

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!