AVPlayer observeValueForKeyPath

2k views Asked by At

I have an audio app that plays stream content. Problem is sometimes, when signal is weak, it stops playing. The network is still reachable, but the buffer seems to have run empty.

I tried implementing an observer to monitor the player' status change, but it's not working, the method just never gets called.

As a particularity, the AVPlayer instance is in the AppDelegate since I have multiple views and the player must keep playing whatever view is displayed. So here is a piece of sample code :

- (void)viewDidLoad
    {
        [super viewDidLoad];
        isPlaying = false;
        playButton.enabled = NO;

        //Add en observer for reachability change
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(reachabilityChanged:) 
                                                     name:kReachabilityChangedNotification
                                                   object:nil];

        //Adding the observer for player's status change
        AppDelegate *delegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
        [delegate.player addObserver:self forKeyPath:@"status" options:0 context:playerStatusContext];

        [self initPlayer];
    }


    //Event raised whenever the current status of the player change
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {

        UIAlertView *alert = [UIAlertView alloc];
        NSString *chaineConcat = @"Entering observer method..."];
        [alert initWithTitle:@"Test" message:chaineConcat delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil];   
        [alert show];

        if (context == playerStatusContext) {
            AppDelegate *delegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
            NSString *statusPlayer = nil; 
            if (object == delegate.player && [keyPath isEqualToString:@"status"]) {
                if (delegate.player.status == AVPlayerStatusReadyToPlay) {
                    statusPlayer = @"Everything's OK";
                } else if (delegate.player.status == AVPlayerStatusFailed) {
                    statusPlayer = @"Houston, we have a problem";
                }
            }
            [self syncUI];

            UIAlertView *alert = [UIAlertView alloc];
            NSString *chaineConcat = [NSString stringWithFormat:@"%@/%@/", @"Player status' is ", statusPlayer];
            [alert initWithTitle:@"Test" message:chaineConcat delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil];   
            [alert show];

        }else {
            [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
        }

        return;
    }

- (void) initPlayer {

    // Load the array with the sample file
    NSString *urlAddress = @"http://MYSTREAMURL";

    AppDelegate *delegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];

    //Create a URL object.
    delegate.urlStream = [NSURL URLWithString:urlAddress];

    //Reinit AVPlayer
    delegate.player = [AVPlayer playerWithURL:delegate.urlStream];

}

Does anyone have an idea about why the event is not raised ? I have 2 alerts in the method but no one is fired, that means it doesn't get into the method. The goal of all this would be to try to implement a way for the player to reinit if this happens.

Thanks !

2

There are 2 answers

0
James Bush On

Here's code that works.

To declare an observable AVPlayer object:

@interface APLViewController ()
{
    AVPlayer *_player;
}

To allocate and initialize an AVPlayer:

- (void)viewDidLoad
{
[super viewDidLoad];

 _player = [[AVPlayer alloc] init];
}

To add the observer:

- (void)viewWillAppear:(BOOL)animated
{
    [self addObserver:self forKeyPath:@"player.currentItem.status" options:NSKeyValueObservingOptionNew context:AVPlayerItemStatusContext];
    [self addTimeObserverToPlayer];

    [super viewWillAppear:animated];
}

To handle the observer:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if (context == AVPlayerItemStatusContext) {
        AVPlayerStatus status = [change[NSKeyValueChangeNewKey] integerValue];
        switch (status) {
            case AVPlayerItemStatusUnknown:
                break;
            case AVPlayerItemStatusReadyToPlay:
                self.playerView.presentationRect = [[_player currentItem] presentationSize];
                break;
            case AVPlayerItemStatusFailed:
                [self stopLoadingAnimationAndHandleError:[[_player currentItem] error]];
                break;
        }
    }
    else {
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }
}

To remove the observer:

- (void)viewWillDisappear:(BOOL)animated
{
    [self removeObserver:self forKeyPath:@"player.currentItem.status" context:AVPlayerItemStatusContext];
    [self removeTimeObserverFromPlayer];

    [super viewWillDisappear:animated];
}

Be sure to put everything where I did or it might not work.

0
Dave Batton On

You're trying to add the observer before you create the object you want to observe. You're just sending a message to a nil object.
Call -initPlayer before calling -addObserver:forKeyPath:options:context: .