如何检查我的 AVPlayer 是否正在缓冲?

How can I check if my AVPlayer is buffering?

我想检测我的 AVPlayer 是否正在缓冲当前位置,以便我可以显示加载程序或其他内容。但我似乎无法在 AVPlayer 的文档中找到任何内容。

您可以观察 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)

然后

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

接受的答案对我不起作用,我使用下面的代码来有效地显示加载器。

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

Swift 4 个观察值:

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

我们完成观察后需要移除观察者。

要删除这三个观察者,只需将 playbackBufferEmptyObserverplaybackLikelyToKeepUpKeyPathObserverplaybackBufferFullObserver 设置为 nil

无需手动删除它们(这是 observe<Value>(_ keyPath:, options:, changeHandler:) 方法所特有的。

#在 Swift 4 中更新并且运行良好

因为我已经接受了答案,但在 swift 4 中对我没有用,所以经过某些研究后我发现 this thinks from apple doc。有两种方法可以确定 AVPlayer 状态 即,

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

使用方法是这样的

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

并且不要忘记杀死时间观察者以防止内存泄漏。杀死实例的方法,根据您的需要添加此方法,但我在 viewWillDisappear 方法中使用了它。

       if let observer = self.observer{

            self.avPlayer?.removeTimeObserver(observer)
            observer = nil
        }

更新为 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)
            }
        }
    })

嗯,公认的解决方案对我不起作用,周期性观察者解决方案似乎很笨拙。

这是我的建议,请在 AVPlayer 上观察 timeControlerStatus

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

这是一个简单的方法,适用于 Swift 5.

这将在您的播放器停止时添加 loadingIndicator

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
}

这将在缓冲区为空时显示加载程序指示器:

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

这将在玩家准备好游戏时隐藏加载程序:

if self.playerItem?.status == AVPlayerItem.Status.readyToPlay{
    if let isPlaybackLikelyToKeepUp = self.player?.currentItem?.isPlaybackLikelyToKeepUp {
        if isPlaybackLikelyToKeepUp{
            self.loadingIndicator.isHidden = true
            self.playPauseButton.isHidden = false
        }
    }
}

对我来说,上面接受的答案没有用,但是这个方法 does.You 可以使用 timeControlStatus 但它只在 iOS 10.

以上可用

根据苹果官方文档

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

将此观察者添加到播放器。

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

然后,观察

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

method.Use 上述方法中的代码下方

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

这已在上面的 iOS 11 和 swift 4 上进行了测试,并且可以正常工作。

启发的Xamarin解决方案
// 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));
}

请注意

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

在Swift5.3

变量:

private var playerItemBufferEmptyObserver: NSKeyValueObservation?
private var playerItemBufferKeepUpObserver: NSKeyValueObservation?
private var playerItemBufferFullObserver: NSKeyValueObservation?

添加观察者

playerItemBufferEmptyObserver = player.currentItem?.observe(\AVPlayerItem.isPlaybackBufferEmpty, options: [.new]) { [weak self] (_, _) in
    guard let self = self else { return }
    self.showLoadingIndicator(over: self)
}
    
playerItemBufferKeepUpObserver = player.currentItem?.observe(\AVPlayerItem.isPlaybackLikelyToKeepUp, options: [.new]) { [weak self] (_, _) in
    guard let self = self else { return }
    self.dismissLoadingIndicator()
}
    
playerItemBufferFullObserver = player.currentItem?.observe(\AVPlayerItem.isPlaybackBufferFull, options: [.new]) { [weak self] (_, _) in
    guard let self = self else { return }
    self.dismissLoadingIndicator()
}

删除观察者

playerItemBufferEmptyObserver?.invalidate()
playerItemBufferEmptyObserver = nil
    
playerItemBufferKeepUpObserver?.invalidate()
playerItemBufferKeepUpObserver = nil
    
playerItemBufferFullObserver?.invalidate()
playerItemBufferFullObserver = nil

使用 Combine,您可以轻松订阅发布者,了解 AVPlayerItem 是否正在缓冲,如下所示:

// Subscribe to this and update your `View` appropriately
@Published var isBuffering = false
private var observation: AnyCancellable?

observation = avPlayer?.currentItem?.publisher(for: \.isPlaybackBufferEmpty).sink(receiveValue: { [weak self] isBuffering in
  self?.isBuffering = isBuffering
})

我们可以直接Observe Playback State使用state observer方法一旦有任何播放状态变化就会通知它,这是一个非常简单的方法并且已经用[=16测试过=]swift 5 和 iOS 13.0+

var player: AVPlayer!

player.currentItem!.addObserver(self, forKeyPath: "playbackLikelyToKeepUp", options: .new, context: nil)

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

    if (player.currentItem?.isPlaybackLikelyToKeepUp ?? false) {
        // End Buffering
    } else {
        // Buffering is in progress
    }
}

Apple Doc Reference