ios avplayer 触发流媒体缓冲区不足

Posted

技术标签:

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

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

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

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

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

【问题讨论】:

【参考方案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 之前收到的最后一个字节)。

【讨论】:

你不想这样做。相信我。

以上是关于ios avplayer 触发流媒体缓冲区不足的主要内容,如果未能解决你的问题,请参考以下文章

AVPlayer 播放、暂停和缓冲问题

iOS音频播放

AVPlayer 在后台视图控制器中缓冲视频会导致内存问题

AVPlayer 不在后台转发流媒体轨道

如何在 iOS 中保存流媒体视频?

IOS同步音乐流媒体