потоковая передача триггера iOS avplayer вышла из буфера


Я хочу снова подключиться к серверу, когда буфер потоковой передачи пуст.

Как я могу запустить метод, когда буфер AVPlayer или AVPlayerItem пуст?

Я знаю, что есть playbackLikelyToKeepUp, playbackBufferEmpty и playbackBufferFull методы для проверки состояния буфера, но это не обратные вызовы.

Есть ли какие-либо функции обратного вызова или какие-либо наблюдатели, которые я должен добавить?
4 16

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")
}