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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/38867190/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-31 09:53:00  来源:igfitidea点击:

How can I check if my AVPlayer is buffering?

iosavplayerbuffering

提问by vrwim

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.

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

回答by Marco Santarossa

You can observe the values of your player.currentItem:

你可以观察你的值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
        }
    }
}

回答by aytek

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

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

Swift 3

斯威夫特 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.
        }
    }
}

回答by Mohit Kumar

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

对我来说,上面接受的答案不起作用,但这种方法有效。您可以使用 timeControlStatus,但它仅适用于 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.

这已在上面的 iOS 11 上使用 swift 4 进行了测试,并且正在运行。

回答by Amrit Tiwari

#Updated in Swift 4 and worked fine

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

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

正如我已经接受的答案一样,但没有在swift 4 中为我工作,所以经过某些研究后,我发现这是来自苹果文档的想法。有两种方法可以确定AVPlayer 状态

  1. addPeriodicTimeObserverForInterval:queue:usingBlock: and
  2. addBoundaryTimeObserverForTimes:queue:usingBlock:
  1. addPeriodicTimeObserverForInterval:queue:usingBlock: 和
  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.

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

       if let observer = self.observer{

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

回答by Roman Barzyczak

Swift 4 observations:

斯威夫特 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
    }
}

Observers need to be removed after we are done observing.

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

To remove these three observers just set playbackBufferEmptyObserver, playbackLikelyToKeepUpKeyPathObserverand playbackBufferFullObserverto nil.

删除这三个观察者刚刚设置playbackBufferEmptyObserverplaybackLikelyToKeepUpKeyPathObserverplaybackBufferFullObservernil

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

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

回答by xuzepei

Updated for Swift 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)
            }
        }
    })

回答by SamB

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

嗯,公认的解决方案对我不起作用,定期观察者解决方案似乎很严厉。

Here's my suggestion, observer timeControlerStatuson AVPlayer.

这里是我的建议,观测timeControlerStatusAVPlayer

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

回答by Asis

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

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

This will add the loadingIndicator when your player is stalled

这将在您的播放器停止时添加 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
}

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

回答by mr5

Solution for Xamarin inspired by Marco's answer

Marco 回答启发的 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));
}

回答by Badr

Please note that

请注意

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

在回调块中使用对 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()
            }
        }
    })
}

}

}