TBD
Created
October 25, 2022 10:00
-
-
Save abd3lraouf/17f79744ba2dbfa88c6dd1fce26f1733 to your computer and use it in GitHub Desktop.
exo player custom load control with drip-feeding
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* The default {@link LoadControl} implementation. | |
*/ | |
public final class CustomLoadControl implements LoadControl { | |
/** | |
* 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 = 5_000; | |
/** | |
* The default maximum duration of media that the player will attempt to buffer, in milliseconds. | |
*/ | |
public static final int DEFAULT_MAX_BUFFER_MS = 10_000; | |
/** | |
* 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 = 50; | |
/** | |
* 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 = 50; | |
/** | |
* To increase buffer time and size. | |
* Added by Sri | |
*/ | |
public static int VIDEO_BUFFER_SCALE_UP_FACTOR = 4; | |
/** | |
* Priority for media loading. | |
*/ | |
public static final int LOADING_PRIORITY = 0; | |
private EventListener bufferedDurationListener; | |
private Handler eventHandler; | |
private static final int ABOVE_HIGH_WATERMARK = 0; | |
private static final int BETWEEN_WATERMARKS = 1; | |
private static final int BELOW_LOW_WATERMARK = 2; | |
private final DefaultAllocator allocator; | |
private final long minBufferUs; | |
private final long maxBufferUs; | |
private final long bufferForPlaybackUs; | |
private final long bufferForPlaybackAfterRebufferUs; | |
private final PriorityTaskManager priorityTaskManager; | |
private int targetBufferSize; | |
private boolean isBuffering; | |
/** | |
* Constructs a new instance, using the {@code DEFAULT_*} constants defined in this class. | |
*/ | |
public CustomLoadControl() { | |
this(new DefaultAllocator(true, C.DEFAULT_BUFFER_SEGMENT_SIZE)); | |
} | |
/** | |
* Constructs a new instance, using the {@code DEFAULT_*} constants defined in this class. | |
*/ | |
public CustomLoadControl(EventListener listener, Handler handler) { | |
this(new DefaultAllocator(true, C.DEFAULT_BUFFER_SEGMENT_SIZE)); | |
bufferedDurationListener = listener; | |
eventHandler = handler; | |
} | |
/** | |
* Constructs a new instance, using the {@code DEFAULT_*} constants defined in this class. | |
* | |
* @param allocator The {@link DefaultAllocator} used by the loader. | |
*/ | |
public CustomLoadControl(DefaultAllocator allocator) { | |
this(allocator, DEFAULT_MIN_BUFFER_MS, DEFAULT_MAX_BUFFER_MS, DEFAULT_BUFFER_FOR_PLAYBACK_MS, | |
DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS); | |
} | |
/** | |
* Constructs a new instance. | |
* | |
* @param allocator The {@link DefaultAllocator} used by the loader. | |
* @param minBufferMs The minimum duration of media that the player will attempt to ensure is | |
* buffered at all times, in milliseconds. | |
* @param maxBufferMs The maximum duration of media that the player will attempt buffer, in | |
* milliseconds. | |
* @param bufferForPlaybackMs The duration of media that must be buffered for playback to start or | |
* resume following a user action such as a seek, in milliseconds. | |
* @param bufferForPlaybackAfterRebufferMs 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 CustomLoadControl(DefaultAllocator allocator, int minBufferMs, int maxBufferMs, | |
long bufferForPlaybackMs, long bufferForPlaybackAfterRebufferMs) { | |
this(allocator, minBufferMs, maxBufferMs, bufferForPlaybackMs, bufferForPlaybackAfterRebufferMs, | |
null); | |
} | |
/** | |
* Constructs a new instance. | |
* | |
* @param allocator The {@link DefaultAllocator} used by the loader. | |
* @param minBufferMs The minimum duration of media that the player will attempt to ensure is | |
* buffered at all times, in milliseconds. | |
* @param maxBufferMs The maximum duration of media that the player will attempt buffer, in | |
* milliseconds. | |
* @param bufferForPlaybackMs The duration of media that must be buffered for playback to start or | |
* resume following a user action such as a seek, in milliseconds. | |
* @param bufferForPlaybackAfterRebufferMs 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. | |
* @param priorityTaskManager If not null, registers itself as a task with priority | |
* {@link #LOADING_PRIORITY} during loading periods, and unregisters itself during draining | |
* periods. | |
*/ | |
public CustomLoadControl(DefaultAllocator allocator, int minBufferMs, int maxBufferMs, | |
long bufferForPlaybackMs, long bufferForPlaybackAfterRebufferMs, | |
PriorityTaskManager priorityTaskManager) { | |
this.allocator = allocator; | |
minBufferUs = VIDEO_BUFFER_SCALE_UP_FACTOR/*Added by Sri to control buffer size */ * minBufferMs * 1000L; | |
maxBufferUs = VIDEO_BUFFER_SCALE_UP_FACTOR/*Added by Sri to control buffer size */ * maxBufferMs * 1000L; | |
bufferForPlaybackUs = bufferForPlaybackMs * 1000L; | |
bufferForPlaybackAfterRebufferUs = bufferForPlaybackAfterRebufferMs * 1000L; | |
this.priorityTaskManager = priorityTaskManager; | |
} | |
@Override | |
public void onPrepared() { | |
reset(false); | |
} | |
@Override | |
public void onTracksSelected(Renderer[] renderers, TrackGroupArray trackGroups, ExoTrackSelection[] trackSelections) { | |
targetBufferSize = 0; | |
for (int i = 0; i < renderers.length; i++) { | |
if (trackSelections[i] != null) { | |
targetBufferSize += getDefaultBufferSize(renderers[i].getTrackType()); | |
if(renderers[i].getTrackType() == C.TRACK_TYPE_VIDEO) | |
targetBufferSize *= VIDEO_BUFFER_SCALE_UP_FACTOR; /*Added by Sri to control buffer size */ | |
} | |
} | |
allocator.setTargetBufferSize(targetBufferSize); | |
} | |
@Override | |
public void onStopped() { | |
reset(true); | |
} | |
@Override | |
public void onReleased() { | |
reset(true); | |
} | |
@Override | |
public Allocator getAllocator() { | |
return allocator; | |
} | |
@Override | |
public long getBackBufferDurationUs() { | |
return 0; | |
} | |
@Override | |
public boolean retainBackBufferFromKeyframe() { | |
return false; | |
} | |
@Override | |
public boolean shouldContinueLoading(long playbackPositionUs, long bufferedDurationUs, float playbackSpeed) { | |
int bufferTimeState = getBufferTimeState(bufferedDurationUs); | |
boolean targetBufferSizeReached = allocator.getTotalBytesAllocated() >= targetBufferSize; | |
boolean wasBuffering = isBuffering; | |
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); | |
if (priorityTaskManager != null && isBuffering != wasBuffering) { | |
if (isBuffering) { | |
priorityTaskManager.add(LOADING_PRIORITY); | |
} else { | |
priorityTaskManager.remove(LOADING_PRIORITY); | |
} | |
} | |
if (null != bufferedDurationListener && null != eventHandler) | |
eventHandler.post(new Runnable() { | |
@Override | |
public void run() { | |
bufferedDurationListener.onBufferedDurationSample(bufferedDurationUs); | |
} | |
}); | |
// Log.e("DLC","current buff Dur: "+bufferedDurationUs+",max buff:" + maxBufferUs +" shouldContinueLoading: "+isBuffering); | |
return isBuffering; | |
} | |
@Override | |
public boolean shouldStartPlayback(long bufferedDurationUs, float playbackSpeed, boolean rebuffering, long targetLiveOffsetUs) { | |
long minBufferDurationUs = rebuffering ? bufferForPlaybackAfterRebufferUs : bufferForPlaybackUs; | |
return minBufferDurationUs <= 0 || bufferedDurationUs >= minBufferDurationUs; | |
} | |
private int getBufferTimeState(long bufferedDurationUs) { | |
return bufferedDurationUs > maxBufferUs ? ABOVE_HIGH_WATERMARK | |
: (bufferedDurationUs < minBufferUs ? BELOW_LOW_WATERMARK : BETWEEN_WATERMARKS); | |
} | |
private void reset(boolean resetAllocator) { | |
targetBufferSize = 0; | |
if (priorityTaskManager != null && isBuffering) { | |
priorityTaskManager.remove(LOADING_PRIORITY); | |
} | |
isBuffering = false; | |
if (resetAllocator) { | |
allocator.reset(); | |
} | |
} | |
private static int getDefaultBufferSize(@C.TrackType int trackType) { | |
switch (trackType) { | |
case C.TRACK_TYPE_DEFAULT: | |
return DEFAULT_MUXED_BUFFER_SIZE; | |
case C.TRACK_TYPE_AUDIO: | |
return DEFAULT_AUDIO_BUFFER_SIZE; | |
case C.TRACK_TYPE_VIDEO: | |
return DEFAULT_VIDEO_BUFFER_SIZE; | |
case C.TRACK_TYPE_TEXT: | |
return DEFAULT_TEXT_BUFFER_SIZE; | |
case C.TRACK_TYPE_METADATA: | |
return DEFAULT_METADATA_BUFFER_SIZE; | |
case C.TRACK_TYPE_CAMERA_MOTION: | |
return DEFAULT_CAMERA_MOTION_BUFFER_SIZE; | |
case C.TRACK_TYPE_IMAGE: | |
return DEFAULT_IMAGE_BUFFER_SIZE; | |
case C.TRACK_TYPE_NONE: | |
return 0; | |
case C.TRACK_TYPE_UNKNOWN: | |
default: | |
throw new IllegalArgumentException(); | |
} | |
} | |
public interface EventListener { | |
void onBufferedDurationSample(long bufferedDurationUs); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment