30

First a little context about the application...
- There's a lot of heavy UI operation's involving video players (mostly scrolling)
- The videos are dynamic and change based on our current page .
- so the video's have to be dynamic and keep changing and also the UI needs to be responsive

I was initially using a MPMoviePlayerController but then due to certain requirements i had to fall back on the AVPlayer
I made my own wrapper for the AVPlayer .
To change the content in the videoPlayer this is what the method looks like in the AVPlayer-wrapper Class

/**We need to change the whole playerItem each time we wish to change a video url */
-(void)initializePlayerWithUrl:(NSURL *)url
{
    AVPlayerItem *tempItem = [AVPlayerItem playerItemWithURL:url];

    [tempItem addObserver:self forKeyPath:@"status"
                  options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
                  context:nil];
    [tempItem addObserver:self forKeyPath:@"playbackBufferEmpty"
                  options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
                  context:nil];

    //Not sure if this should be stopped or paused under the ideal circumstances
    //These will be changed to custom enums later
    [self setPlaybackState:MPMoviePlaybackStateStopped];
    [self setLoadState:MPMovieLoadStateUnknown];
    [self.videoPlayer replaceCurrentItemWithPlayerItem:tempItem];

    //This is required only if we wish to pause the video immediately as we change the url
    //[self.videoPlayer pause];
}

Now ofcourse everything was working fine ......except ..

[self.videoPlayer replaceCurrentItemWithPlayerItem:tempItem];

Seems to be blocking the UI for a fraction of a second and during scrolling these is making the UI really unresponsive and ugly also this operation cannot be performed in the background

Is there any fix or workaround for this .. ?

Aatish Molasi
  • 2,138
  • 3
  • 20
  • 43

2 Answers2

24

The solution I found was to ensure that the underlying AVAsset is ready to return basic info, such as its duration, before feeding it to the AVPlayer. AVAsset has a method loadValuesAsynchronouslyForKeys: which is handy for this:

AVAsset *asset = [AVAsset assetWithURL:self.mediaURL];
[asset loadValuesAsynchronouslyForKeys:@[@"duration"] completionHandler:^{
    AVPlayerItem *newItem = [[AVPlayerItem alloc] initWithAsset:asset];
    [self.avPlayer replaceCurrentItemWithPlayerItem:newItem];
}];

In my case the URL is a network resource, and replaceCurrentItemWithPlayerItem: will actually block for several seconds waiting for this information to download otherwise.

Glenn Schmidt
  • 623
  • 8
  • 9
  • 1
    As per Apple Docs, these methods of _AVPlayer_ and _AVPlayerItem_ need to called on main thread. – TheCommonEngineer Jan 04 '16 at 05:34
  • Even after getting the information('duration') it is blocking the ui for few seconds before starting the streaming when in slow network – souvickcse Oct 31 '17 at 09:41
  • 1
    Everybody's talking about having to reimplement your own AVPlayer in order to have smooth scrolling with AVPlayers in an UICollectionView, but this is the solution - just preload the asset with loadValuesAsynchronously before assigning it! – Jan Erik Schlorf Mar 06 '19 at 13:22
  • I think observing the "playable" key makes more sense than the "duration". – Mohamed Salah Jul 19 '20 at 19:28
2

We had the same problem when building Ultravisual. I can't exactly remember how we solved it, but IIRC it involved doing as much of the item setup as possible on a background thread, and waiting until the new item reports that it is 'ready to play' before calling replaceCurrentItemWithPlayerItem.

Sadly this involves a voodoo dance with somewhat inconsistent asynchronous KVO, which is no fun.

damian
  • 3,604
  • 1
  • 27
  • 46