Поток триггера ios avplayer находится вне буфера
Я хочу переподключиться к серверу, когда потоковый буфер пуст.
Как я могу вызвать метод, когда AVPlayer
или же AVPlayerItem
буфер пуст?
Я знаю, что есть playbackLikelyToKeepUp
, playbackBufferEmpty
а также playbackBufferFull
методы для проверки состояния буфера, но это не обратные вызовы.
Есть ли какие-либо функции обратного вызова или каких-либо наблюдателей, которых я должен добавить?
4 ответа
Вы можете добавить наблюдателя для этих ключей:
[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
}
}
}
Для этого вам нужно перейти в Core Audio и CFReadStream. С CFReadStream вы можете предоставить обратный вызов, который вызывается для определенных потоковых событий, таких как обнаружение завершения, ошибка чтения и т. Д. Оттуда вы можете инициировать переподключение к серверу. Если вы используете HTTP-поток, вы можете добавить заголовок диапазона к HTTP-запросу, чтобы вы могли указать серверу отправлять поток из указанной вами точки (это будет последний байт, полученный вами до + 1).
Попробуйте этот код, он должен решить все ваши кошмары:
#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;
}
/* 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")
}