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

时间:2016-08-10 07:45:59

标签: ios avplayer buffering

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

12 个答案:

答案 0 :(得分:31)

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

答案 1 :(得分:16)

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

  

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

答案 2 :(得分:5)

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

答案 3 :(得分:4)

斯威夫特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:)方法特有的。

答案 4 :(得分:2)

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

答案 5 :(得分:1)

对我来说,上面接受的答案无效,但是此方法有效。您可以使用timeControlStatus,但仅在iOS 10以上版本可用。

根据Apple的官方文档

  

一种状态,指示当前是否正在播放,   无限期暂停,或在等待适当时暂停   网络条件

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

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

然后,观察其中的变化

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

方法。在上述方法中使用以下代码

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进行了测试,并且可以正常工作。

答案 6 :(得分:1)

请注意

在回调块中使用对self的弱引用以防止创建保留周期。

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

答案 7 :(得分:1)

在 Swift 5.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

答案 8 :(得分:0)

嗯,被接受的解决方案对我不起作用,并且周期性的观察者解决方案似乎举手之劳。

这是我的建议,timeControlerStatus的旁观者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)
    }
}

答案 9 :(得分:0)

这是一种简单的方法,可与 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
        }
    }
}

答案 10 :(得分:0)

Marco's answer启发的Xamarin解决方案

"""data= data clustered retrieved by function as you want"""
"""model = result from the data with got by KMeans"""
"""cluster = clusters formed by the model"""
from sklearn.cluster import KMeans

data = clusteredData()
model = KMeans(n_clusters=5, init='random', max_iter=100, n_init=1, verbose=1)
cluster = model.fit_predict(scale(data))

dictionary = {}
for index in range(len(data)): 
    if cluster[index] in dictionary:
        value = []
        value = dictionary[cluster[index]]
        value.append(data[index])
        dictionary[cluster[index]] = value
    else:
        dictionary[cluster[index]]=data[index]

答案 11 :(得分:0)

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