ExoPlayer Hacks: Part 2 – Improved Buffering

This is 2nd post in ExoPlayer Hacks series, if you are wondering what was there in Part1, you can catch it up here.

What about Buffering?

OK there is nothing wrong with way Buffering is handled in Exo, but when I went through DefaultLoadControl component it seems that buffering policy is constrained. I will explain how part in detail later. When asked why, one of the Exo developers responded as below

“There are arguments that mobile carriers prefer this kind of traffic pattern over their networks (i.e. bursts rather than drip-feeding). Which is an important consideration given ExoPlayer is used by some very popular services. It may also be more battery efficient.”

though default policy seem to work in general, it might not be sufficient for all scenarios.

In my case I have to increase the buffer to 2-3 minutes on our TV platform with only 2 renditions available which means rendition switch wont happen too often.

First, let’s understand current Buffer policy

Default Buffer Policy

/**
 * The default minimum duration of media that the player will attempt to ensure is buffered at all
 * times, in milliseconds.
 */
public static final int DEFAULT_MIN_BUFFER_MS = 15000;
/**
 * The default maximum duration of media that the player will attempt to buffer, in milliseconds.
 */
public static final int DEFAULT_MAX_BUFFER_MS = 30000;
/**
 * The default duration of media that must be buffered for playback to start or resume following a
 * user action such as a seek, in milliseconds.
 */
public static final int DEFAULT_BUFFER_FOR_PLAYBACK_MS = 2500;
/**
 * The default duration of media that must be buffered for playback to resume after a rebuffer,
 * in milliseconds. A rebuffer is defined to be caused by buffer depletion rather than a user
 * action.
 */
public static final int DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS  = 5000;

Out of above 4 constants, 2 of them control the load timing and the other two control when the playback starts by having sufficient buffering. Please refer to the figure below.

The maxBufferUs & minBufferUs are about the load timing but bufferForPlaybackAfterRebufferUs & bufferForPlaybackUs are about when the playback starts by having sufficient buffering.

b8cfffe2-f02f-11e6-8781-3e3ba43aa81d

Refer to shouldContinueLoading(), a typical loading pattern includes 3 stages.

@Override
public boolean shouldContinueLoading(long bufferedDurationUs) {
  int bufferTimeState = getBufferTimeState(bufferedDurationUs);
  boolean targetBufferSizeReached = allocator.getTotalBytesAllocated() >= targetBufferSize;
  boolean wasBuffering = isBuffering;
  isBuffering = bufferTimeState == BELOW_LOW_WATERMARK
      || (bufferTimeState == BETWEEN_WATERMARKS && isBuffering && !targetBufferSizeReached);
  if (priorityTaskManager != null && isBuffering != wasBuffering) {
    if (isBuffering) {
      priorityTaskManager.add(C.PRIORITY_PLAYBACK);
    } else {
      priorityTaskManager.remove(C.PRIORITY_PLAYBACK);
    }
  }
  return isBuffering;
}

At the first stage we continuing loading until maxBufferUs is reached as below.

812da840-f030-11e6-9aee-f0f224144a37

Since then isBuffering = false and bufferTimeState == BETWEEN_WATERMARKS; so shouldContinueLoading() returns false as below.

d758d3a2-f030-11e6-9b14-ff4f6c7857df

Finally, when bufferTimeState == BELOW_LOW_WATERMARK again we recover the download, as the figure below.

ffc588ee-f030-11e6-9bd9-fffb11aa52b3

As per the flow described, you could see within the period of [t1, t3] actually we DO NOT load anymore. Therefore you may meet the condition, for example to have merely ~ 15 seconds buffering.

Besides, there is a limit upon buffering size. In short, typically the buffering data will be from 15 to 30 second if your connection speed is good enough.

How to enlarge the buffer?

Remove the upper limit by applying ‘Drip – Feeding’ method.

isBuffering = bufferTimeState == BELOW_LOW_WATERMARK
    || (bufferTimeState == BETWEEN_WATERMARKS
        /*
         * commented below line to achieve drip-feeding method for better caching. once you are below maxBufferUs, do fetch immediately.
         * Added by Sri
         */
        /* && isBuffering */
        && !targetBufferSizeReached);

Also enlarge maxBufferUs & minBufferUs

/**
 * To increase buffer time and size.
 */
public static int VIDEO_BUFFER_SCALE_UP_FACTOR = 4;
....
minBufferUs = VIDEO_BUFFER_SCALE_UP_FACTOR * minBufferMs * 1000L;
maxBufferUs = VIDEO_BUFFER_SCALE_UP_FACTOR * maxBufferMs * 1000L;
...

checkout the CustomLoadControl for code reference.

You can add below log in shouldContinueLoading() to verify the improvement before and after.

Log.e("CustomLoadControl","current buff Dur: "+bufferedDurationUs+",max buff:" + maxBufferUs +" shouldContinueLoading: "+isBuffering);

Cool. yeah!!

Hope you enjoyed both of these articles. If you are ExoPlayer lover, do try these hacks (github demo) and leave me your feedback.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s