【问题标题】:ios avplayer trigger streaming is out of bufferios avplayer 触发流媒体缓冲区不足
【发布时间】:2011-10-16 09:16:15
【问题描述】:

我想在流缓冲区为空时重新连接到服务器。

AVPlayerAVPlayerItem 缓冲区为空时如何触发方法?

我知道有 playbackLikelyToKeepUpplaybackBufferEmptyplaybackBufferFull 方法来检查缓冲区状态,但这些不是回调。

是否有任何回调函数,或者我应该添加任何观察者?

【问题讨论】:

    标签: ios audio-streaming avplayer


    【解决方案1】:
    /* Swift 3.0, Add Observers */    
    func setupPlayerObservers(){
        player.addObserver(self, forKeyPath: "currentItem.loadedTimeRanges", options: .new, context: nil)
        player.addObserver(self, forKeyPath: "rate", options: [.new], context: nil)
        player.currentItem?.addObserver(self, forKeyPath: "status", options: [.old, .new], context: nil)
        player.currentItem?.addObserver(self, forKeyPath: "playbackBufferEmpty", options: [.old, .new], context: nil)
        player.currentItem?.addObserver(self, forKeyPath: "playbackLikelyToKeepUp", options: [.old, .new], context: nil)
    
    }    
    
    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    
        //this is when the player is ready and rendering frames
        if keyPath == "currentItem.loadedTimeRanges" {
    
            if !hasLoaded {
                activityIndicatorView.stopAnimating()
                Mixpanel.mainInstance().track(event: "Play", properties: ["success": true, "type": "clip"])
            }
            hasLoaded = true
        }
    
        if keyPath == "rate" {
            if player.rate == 0.0 {
                playPauseButton.setImage(UIImage(named: "PlayButton"), for: UIControlState())
            } else {
                playPauseButton.setImage(UIImage(named: "PauseButton"), for: UIControlState())
            }
    
        }
    
        if keyPath == "status" {
            // Do something here if you get a failed || error status
        }
    
        if keyPath == "playbackBufferEmpty" {
            let time = Int(CMTimeGetSeconds(player.currentTime()))
    
            bufferingCount += 1
            if bufferingCount % 4 == 0 {
                Mixpanel.mainInstance().track(event: "VideoBuffered", properties: ["numTimes": bufferingCount, "type": "clip", "currentTime": time])
            }
            activityIndicatorView.isHidden = false
            activityIndicatorView.startAnimating()
        }
    
    
        if keyPath == "playbackLikelyToKeepUp" {
            activityIndicatorView.isHidden = true
            activityIndicatorView.stopAnimating()
        }
    }
    /* Remove observers in deinit */
    deinit {
        player.removeTimeObserver(timeObserver)
        player.removeObserver(self, forKeyPath: "currentItem.loadedTimeRanges")
        player.removeObserver(self, forKeyPath: "rate")
        player.currentItem?.removeObserver(self, forKeyPath: "playbackBufferEmpty")
        player.currentItem?.removeObserver(self, forKeyPath: "playbackLikelyToKeepUp")
        player.currentItem?.removeObserver(self, forKeyPath: "status")
    }
    

    【讨论】:

      【解决方案2】:

      试试这段代码,它应该可以解决你所有的噩梦:

      #import <AVFoundation/AVFoundation.h>
      
      @interface CustomAVPlayerItem : AVPlayerItem
      {
          BOOL bufferEmptyVideoWasStopped;
      }
      
      -(void)manuallyRegisterEvents;
      
      @end
      
      =========== in the .m file
      
      #import "CustomAVPlayerItem.h"
      #import "AppDelegate.h"
      
      @implementation CustomAVPlayerItem
      
      -(void)manuallyRegisterEvents
      {
          //NSLog(@"manuallyRegisterEvents %lu", (unsigned long)self.hash);
      
          [self addObserver:self forKeyPath:@"playbackBufferEmpty" options:NSKeyValueObservingOptionNew context:NULL];
          [self addObserver:self forKeyPath:@"playbackLikelyToKeepUp" options:NSKeyValueObservingOptionNew context:NULL];
      
          bufferEmptyVideoWasStopped=NO;
      }
      
      - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
      {
          if (object == self && [keyPath isEqualToString:@"playbackBufferEmpty"])
          {
              if (self.playbackBufferEmpty)
              {
                  //NSLog(@"AVPLAYER playbackBufferEmpty");
                  bufferEmptyVideoWasStopped=YES;
                  [[NSNotificationCenter defaultCenter] postNotificationName:NOTIFICATION_VIDEO_PLAYBACKBUFFER_EMPTY object:self];
              }
          }
          else if (object == self && [keyPath isEqualToString:@"playbackLikelyToKeepUp"])
          {
              if (self.playbackLikelyToKeepUp)
              {
                  //NSLog(@"AVPLAYER playbackLikelyToKeepUp");
      
                  if(bufferEmptyVideoWasStopped)
                  {
                      bufferEmptyVideoWasStopped=NO;
      
                      [[NSNotificationCenter defaultCenter] postNotificationName:NOTIFICATION_VIDEO_PLAYBACKBUFFER_FULL object:self];
                  }
                  else
                      [[NSNotificationCenter defaultCenter] postNotificationName:NOTIFICATION_VIDEO_PLAYBACKBUFFER_KEEP_UP object:self];
      
              }
          }
      }
      
      -(void)dealloc
      {
          //NSLog(@"dealloc CustomAVPlayerItem %lu", (unsigned long)self.hash);
      
          [self removeObserver:self forKeyPath:@"playbackBufferEmpty"];
          [self removeObserver:self forKeyPath:@"playbackLikelyToKeepUp"];
      }
      
      @end
      
      ===== and now where you want to use it
      
      -(void)startVideoWithSound
      {
          if([CustomLog jsonFieldAvailable:[videoObject objectForKey:@"video_url"]])
          {
              CustomAVPlayerItem *playerItem=[[CustomAVPlayerItem alloc] initWithURL:[[OfflineManager new] getCachedURLForVideoURL:[videoObject objectForKey:@"video_url"]]];
      
              AVPlayer *player=[AVPlayer playerWithPlayerItem:playerItem];
              [playerItem manuallyRegisterEvents];
              [player play];
      
              player.muted=NO;
      
              [viewPlayer setPlayer:player];
      
              viewPlayer.hidden=NO;
      
              [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(notifBufferEmpty:) name:NOTIFICATION_VIDEO_PLAYBACKBUFFER_EMPTY object:nil];
              [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(notifBufferFull:) name:NOTIFICATION_VIDEO_PLAYBACKBUFFER_FULL object:nil];
              [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(notifBufferKeepUp:) name:NOTIFICATION_VIDEO_PLAYBACKBUFFER_KEEP_UP object:nil];
          }
      }
      
      - (void)notifBufferEmpty:(NSNotification *)notification
      {
          //NSLog(@"notifBufferEmpty");
      
          [[viewPlayer player] play]; // resume it
          viewLoading.hidden=NO;
      }
      
      - (void)notifBufferFull:(NSNotification *)notification
      {
          //NSLog(@"notifBufferFull");
          viewLoading.hidden=YES;
      }
      
      - (void)notifBufferKeepUp:(NSNotification *)notification
      {
          //NSLog(@"notifBufferKeepUp");
          viewLoading.hidden=YES;
      }
      

      【讨论】:

        【解决方案3】:

        您可以为这些键添加观察者:

        [playerItem addObserver:self forKeyPath:@"playbackBufferEmpty" options:NSKeyValueObservingOptionNew context:nil];
        [playerItem addObserver:self forKeyPath:@"playbackLikelyToKeepUp" options:NSKeyValueObservingOptionNew context:nil];
        

        当您的缓冲区为空时,第一个会警告您,当您的缓冲区可以再次使用时,第二个会警告您。

        然后要处理密钥更改,您可以使用以下代码:

        - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
                                change:(NSDictionary *)change context:(void *)context {
            if (!player)
            {
                return;
            }
        
            else if (object == playerItem && [keyPath isEqualToString:@"playbackBufferEmpty"])
            {
                if (playerItem.playbackBufferEmpty) {
                    //Your code here
                }
            }
        
            else if (object == playerItem && [keyPath isEqualToString:@"playbackLikelyToKeepUp"])
            {
                if (playerItem.playbackLikelyToKeepUp)
                {
                    //Your code here
                }
            }
        }
        

        【讨论】:

        • 谢谢!你知道让 AVPlayer 或 AVPlayerItem 重新连接的好方法吗,还是我要创建一个新的播放器或项目?
        • 在我的情况下,我只需要将播放消息发送到我的 AVPlayer 对象。
        • AVPlayer 在缓冲期间是否会在某个时候彻底放弃?因为我注意到有时连接速度很慢,并且一段时间后似乎没有尝试重新连接,在这种情况下手动停止+重新启动可以解决问题。以编程方式检测到这一点会很好。
        • RE 上面:当我注意到这种情况发生时,它通常与“playbackBufferEmpty”键值观察器没有被命中。
        【解决方案4】:

        您需要下拉到 Core Audio 和 CFReadStream 来执行此操作。使用 CFReadStream,您可以提供在某些流事件(如遇到结束、读取错误等)时调用的回调。从那里您可以触发重新连接到服务器。如果您使用的是 HTTP 流,则可以将范围标头添加到 HTTP 请求中,以便告诉服务器从您指定的点发送流(这将是您在 + 1 之前收到的最后一个字节)。

        【讨论】:

        • 你不想这样做。相信我。
        猜你喜欢
        • 1970-01-01
        • 2012-05-17
        • 2023-03-09
        • 1970-01-01
        • 2013-05-18
        • 1970-01-01
        • 2012-06-10
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多