【问题标题】:How can I check if my AVPlayer is buffering?如何检查我的 AVPlayer 是否正在缓冲?
【发布时间】:2016-12-16 11:17:57
【问题描述】:

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

【问题讨论】:

    标签: ios avplayer buffering


    【解决方案1】:

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

    【讨论】:

    • 当AVPlayer在寻找某个位置时,它不起作用。
    • @Marco_Santarossa 我已经使用了上面的代码并且它正在工作。但是在视图控制器弹出后代码崩溃了你能帮我解决这个问题吗?
    • 嘿@ArunK。当您关闭您的视图时,您是否正在移除观察者?还要确保在删除视图之前停止并销毁所有对象,否则可能会出现一些内存泄漏。我需要你的代码示例来了解是否是这种情况
    • "playbackBufferFull" 在我的情况下从未被调用过。你能告诉我原因吗? @马可
    • @FrankCheng 尝试将观察选项设置为 0 而不是 .new
    【解决方案2】:

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

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

    【讨论】:

    • 亲爱的@aytek,你能不能把你的解决方案翻译成Swift 4? :)
    • 亲爱的@ixany,我没有机会安装最新版本的 Xcode。我会尽快添加 Swift 4 版本。感谢您的评论。
    • 我观察到的是,您必须在 AVPlayerItem 实例而不是 AVPlayer 实例上向那些观察者注册,否则它不起作用。事实上,公认的答案就是这样做的。
    【解决方案3】:

    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&lt;Value&gt;(_ keyPath:, options:, changeHandler:) 方法特有的。

    【讨论】:

      【解决方案4】:

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

      【讨论】:

      • Amrit Tiwari,你为什么使用强链接?
      • 在 addPeriodicTimeObserver 中我们必须使用弱引用,所以我将 self 包装到使用的 self 属性中!!!
      【解决方案5】:

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

      【讨论】:

      • 谢谢,但建议在块中使用弱自我。我发布了我的答案。
      【解决方案6】:

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

      这是我的建议,在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)
          }
      }
      

      【讨论】:

        【解决方案7】:

        这是一个简单的方法,适用于 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
                }
            }
        }
        

        【讨论】:

        • 对我来说,上面的通知没有被调用,你能帮忙
        【解决方案8】:

        对我来说,上面接受的答案没有用,但这种方法可以。您可以使用 timeControlStatus,但它仅适用于 iOS 10 以上。

        根据苹果官方文档

        指示当前是否正在播放的状态, 无限期暂停,或在等待适当的时候暂停 网络状况

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

        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 测试的,它正在工作。

        【讨论】:

        • 这是唯一适用于 iOS 13 和 swift 5 的东西
        【解决方案9】:

        Marco's answer启发的 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));
        }
        

        【讨论】:

          【解决方案10】:

          请注意

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

          【讨论】:

            【解决方案11】:

            在 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
            

            【讨论】:

              【解决方案12】:

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

              【讨论】:

                【解决方案13】:

                我们可以直接Observe Playback State使用状态观察器方法,一旦有任何播放状态变化就会通知它,这是一个非常简单的方法,并且已经使用 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

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2011-06-27
                  • 1970-01-01
                  • 1970-01-01
                  • 2010-10-09
                  • 1970-01-01
                  相关资源
                  最近更新 更多