Search code examples
iosobjective-cavplayeravplayeritem

How to know when AVPlayerItem is buffered to the end of a song


I'm trying to determine the best way to tell if an AVPlayerItem is buffered to the end of the stream. Not just that the buffer is full, but that the buffer contains everything needed to play the rest of the item without additional buffering. AVPlayerItem offers a is isPlaybackBufferFull call, but that doesn't tell me if any additional buffering needs to happen before the item is finished playing.

My current plan is to combine that with preferredForwardBufferDuration to check if the item will ever need to buffer more, but is this the best way?

For instance:

- (void)observeValueForKeyPath:(NSString*)aKeyPath ofObject:(id)aObject change:(NSDictionary*)aChange context:(void*)aContext
{
    if( [aKeyPath isEqualToString:@"playbackBufferFull"] )
    {
        CMTime theBufferTime = CMTimeMakeWithSeconds( self.currentItem.preferredForwardBufferDuration, 1 );
        CMTime theEndBufferTime = CMTimeAdd( self.currentItem.currentTime, theBufferTime );
        if( CMTimeCompare( theEndBufferTime, self.currentItem.duration ) >= 0 )
        {
            // Buffered to the end
        }
    }
}

Solution

  • I found a pretty good solution to this problem that can be seen below. The proposed solution written in the question didn't really work well because the preferredForwardBufferDuration is set to 0 by default which pretty much makes the solution not viable.

    The following the code works pretty well. I call it every second on a timer.

    auto theLoadedRanges = self.currentItem.loadedTimeRanges;
    
    CMTime theTotalBufferedDuration = kCMTimeZero;
    for( NSValue* theRangeValue in theLoadedRanges )
    {
        auto theRange = [theRangeValue CMTimeRangeValue];
        theTotalBufferedDuration = CMTimeAdd( theTotalBufferedDuration, theRange.duration );
    }
    
    auto theDuration = CMTimeGetSeconds( self.currentItem.duration );
    if( theDuration > 0 )
    {
        float thePercent = CMTimeGetSeconds( theTotalBufferedDuration ) / theDuration;
        if( thePercent >= 0.99f )
        {
            // Fully buffered
        }
    }