ios avplayer 触发流媒体缓冲区不足
Posted
技术标签:
【中文标题】ios avplayer 触发流媒体缓冲区不足【英文标题】:ios avplayer trigger streaming is out of buffer 【发布时间】:2011-10-16 09:16:15 【问题描述】:我想在流缓冲区为空时重新连接到服务器。
AVPlayer
或AVPlayerItem
缓冲区为空时如何触发方法?
我知道有 playbackLikelyToKeepUp
、playbackBufferEmpty
和 playbackBufferFull
方法来检查缓冲区状态,但这些不是回调。
是否有任何回调函数,或者我应该添加任何观察者?
【问题讨论】:
【参考方案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 触发流媒体缓冲区不足的主要内容,如果未能解决你的问题,请参考以下文章