потоковая передача триггера 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") }