Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AV Foundation: Difference between currentItem being ready to play, and -[AVPlayer readyForDisplay] property?

I'm running into a weird situation with my video player, the core code of which hasn't changed much from what worked in an earlier app I made. Here's the problem: I'm inserting a "_loadingLayer" (a CATextLayer that says the video is loading), and then observing the AVPlayer's currentItem's status property to figure out when to remove the "_loadingLayer" and replace it with my actual "_playerLayer". Here's my KVO code for that:

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {

if ((object == _playerLayer) && (_playerLayer.player.currentItem.status == AVPlayerItemStatusReadyToPlay)) {

    [CATransaction setAnimationDuration:1.8];

    _loadingLayer.opaque = NO;

    if (_playerLayer.readyForDisplay) {

        NSLog(@"Should be ready now.");

    }

    [self addPlayerLayerToLayerTree];

}

}

My problem is that the video is starting, but only the audio is playing -- the layer stays black. When I inserted the NSLog statement above, I found out why: Apparently although the currentItem's status is "AVPlayerItemStatusReadyToPlay", the player layer isn't actually readyForDisplay. This makes no sense to me -- it seems counterintuitive. Can someone please give me some guidance on this?

I was able to verify that _playerLayer is being added to the layer tree by setting its background color to red.

One other weird thing that I think might be related.... I've been seeing these messages in the debugger console:

PSsetwindowlevel, error setting window level (1000) CGSSetIgnoresCycle: error 1000 setting or clearing window tags

Thanks in advance. This is a crosspost from the Apple Dev Forums.

like image 412
stiggs Avatar asked Sep 29 '12 17:09

stiggs


1 Answers

We had a similar problem and traced it to what I believe is a bug in iOS 5.1 (and maybe earlier versions). It is fixed in iOS 6.0. Since I couldn't find a solution to this anywhere, I'm writing a long writeup for future people that have this problem.

If the AVPlayerItem reports a status of AVPlayerStatusReadyToPlay before the AVPlayerLayer has been obtained then the AVPlayer will never report that it is readyForDisplay.

So when you do:

self.player = [AVPlayer playerWithPlayerItem:self.playerItem];

make sure that it's followed with:

self.playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player]; 

and that you don't have much if any code in between the two.

I built a test rig to make it work 100% of the time or fail 100% of the time. Note that it can be tricky to see what's going on in your actual app since you will have different load times on the video and that will affect how quickly the playerItem reports AVPlayerStatusReadyToPlay.

If you want to test in your app, put this into a simple view. The below will not work (i.e. you'll hear audio but not see video) on iOS 5.1. If you switch loadPlayerLayer to instead be invoked at the end of loadPlayer, it will always work.

A follow on for future readers: A couple of player events can switch up this order and make you think it's working. They're red herrings though since they're inadvertently reversing the load order such that playerLayer is grabbed before AVStatusReadyToPlay. The events are: seeking the video, going to the home screen and then reactivating the app, the player switching to a different video/audio track inside an HLS video. These actions trigger AVStatusReadyToPlay again and thus make the playerLayer happen before AVStatusReadyToPlay.

Here's the test harness that uses Apple's test HLS video:

-(void)loadPlayer
{
  NSLog(@"loadPlayer invoked");

  NSURL *url = [NSURL URLWithString:@"https://devimages.apple.com.edgekey.net/resources/http-streaming/examples/bipbop_4x3/bipbop_4x3_variant.m3u8"];
  self.playerItem = [AVPlayerItem playerItemWithURL:url];
  [self.playerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:&kPlayerContext];
  self.player = [AVPlayer playerWithPlayerItem:self.playerItem];

}

-(void)loadPlayerLayer
{
  NSLog(@"starting player layer");
  self.playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player]; 
  [self.playerLayer addObserver:self forKeyPath:@"readyForDisplay" options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:&kPlayerLayerContext];
  [self.playerLayer setFrame:[[self view] bounds]];
  [[[self view] layer] addSublayer:self.playerLayer];
}

-(void)observeValueForKeyPath:(NSString*)path ofObject:(id)object change:(NSDictionary*)change context:(void*) context
{
  if(context == &kPlayerContext){
    if([self.player status] == AVPlayerStatusReadyToPlay){
      NSLog(@"Player is ready to play");
      //Robert: Never works if after AVPlayerItem reports AVPlayerStatusReadyToPlay
      if(!self.startedPlayerLayer){
        self.startedPlayerLayer = YES;
        [self loadPlayerLayer];
      }
    }
  }

  if(context == &kPlayerLayerContext){
    if([self.playerLayer isReadyForDisplay] == YES){
      NSLog(@"PlayerLayer says it's ready to display now");
      [self playTheVideoIfReady];
    }
  }
}
like image 96
rhh Avatar answered Sep 28 '22 08:09

rhh