mirror of
https://github.com/samsonjs/media.git
synced 2026-03-25 09:25:53 +00:00
Add ability for media period to discard buffered media at the back of the queue
In some occasions, we may want to discard a part of the buffered media to improve playback quality. This CL adds this functionality by allowing the loading media period to re-evaluate its buffer periodically (every 2s) and discard chunks as it needs. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=177958910
This commit is contained in:
parent
6606d73b29
commit
88dea59cd2
20 changed files with 765 additions and 139 deletions
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
### dev-v2 (not yet released) ###
|
||||
|
||||
* Add ability for `SequenceableLoader` to reevaluate its buffer and discard
|
||||
buffered media so that it can be re-buffered in a different quality.
|
||||
* Replace `DefaultTrackSelector.Parameters` copy methods with a builder.
|
||||
* Allow more flexible loading strategy when playing media containing multiple
|
||||
sub-streams, by allowing injection of custom `CompositeSequenceableLoader`
|
||||
|
|
|
|||
|
|
@ -1283,6 +1283,7 @@ import java.io.IOException;
|
|||
|
||||
// Update the loading period if required.
|
||||
maybeUpdateLoadingPeriod();
|
||||
|
||||
if (loadingPeriodHolder == null || loadingPeriodHolder.isFullyBuffered()) {
|
||||
setIsLoading(false);
|
||||
} else if (loadingPeriodHolder != null && !isLoading) {
|
||||
|
|
@ -1386,6 +1387,7 @@ import java.io.IOException;
|
|||
if (loadingPeriodHolder == null) {
|
||||
info = mediaPeriodInfoSequence.getFirstMediaPeriodInfo(playbackInfo);
|
||||
} else {
|
||||
loadingPeriodHolder.reevaluateBuffer(rendererPositionUs);
|
||||
if (loadingPeriodHolder.info.isFinal || !loadingPeriodHolder.isFullyBuffered()
|
||||
|| loadingPeriodHolder.info.durationUs == C.TIME_UNSET) {
|
||||
return;
|
||||
|
|
@ -1440,6 +1442,7 @@ import java.io.IOException;
|
|||
// Stale event.
|
||||
return;
|
||||
}
|
||||
loadingPeriodHolder.reevaluateBuffer(rendererPositionUs);
|
||||
maybeContinueLoading();
|
||||
}
|
||||
|
||||
|
|
@ -1628,13 +1631,18 @@ import java.io.IOException;
|
|||
info = info.copyWithStartPositionUs(newStartPositionUs);
|
||||
}
|
||||
|
||||
public void reevaluateBuffer(long rendererPositionUs) {
|
||||
if (prepared) {
|
||||
mediaPeriod.reevaluateBuffer(toPeriodTime(rendererPositionUs));
|
||||
}
|
||||
}
|
||||
|
||||
public boolean shouldContinueLoading(long rendererPositionUs, float playbackSpeed) {
|
||||
long nextLoadPositionUs = !prepared ? 0 : mediaPeriod.getNextLoadPositionUs();
|
||||
if (nextLoadPositionUs == C.TIME_END_OF_SOURCE) {
|
||||
return false;
|
||||
} else {
|
||||
long loadingPeriodPositionUs = toPeriodTime(rendererPositionUs);
|
||||
long bufferedDurationUs = nextLoadPositionUs - loadingPeriodPositionUs;
|
||||
long bufferedDurationUs = nextLoadPositionUs - toPeriodTime(rendererPositionUs);
|
||||
return loadControl.shouldContinueLoading(bufferedDurationUs, playbackSpeed);
|
||||
}
|
||||
}
|
||||
|
|
@ -1694,7 +1702,6 @@ import java.io.IOException;
|
|||
Assertions.checkState(trackSelections.get(i) == null);
|
||||
}
|
||||
}
|
||||
|
||||
// The track selection has changed.
|
||||
loadControl.onTracksSelected(renderers, trackSelectorResult.groups, trackSelections);
|
||||
return positionUs;
|
||||
|
|
|
|||
|
|
@ -123,6 +123,11 @@ public final class ClippingMediaPeriod implements MediaPeriod, MediaPeriod.Callb
|
|||
mediaPeriod.discardBuffer(positionUs + startUs, toKeyframe);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reevaluateBuffer(long positionUs) {
|
||||
mediaPeriod.reevaluateBuffer(positionUs + startUs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long readDiscontinuity() {
|
||||
if (isPendingInitialDiscontinuity()) {
|
||||
|
|
|
|||
|
|
@ -52,6 +52,13 @@ public class CompositeSequenceableLoader implements SequenceableLoader {
|
|||
return nextLoadPositionUs == Long.MAX_VALUE ? C.TIME_END_OF_SOURCE : nextLoadPositionUs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void reevaluateBuffer(long positionUs) {
|
||||
for (SequenceableLoader loader : loaders) {
|
||||
loader.reevaluateBuffer(positionUs);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean continueLoading(long positionUs) {
|
||||
boolean madeProgress = false;
|
||||
|
|
|
|||
|
|
@ -119,6 +119,11 @@ public final class DeferredMediaPeriod implements MediaPeriod, MediaPeriod.Callb
|
|||
return mediaPeriod.getNextLoadPositionUs();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reevaluateBuffer(long positionUs) {
|
||||
mediaPeriod.reevaluateBuffer(positionUs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean continueLoading(long positionUs) {
|
||||
return mediaPeriod != null && mediaPeriod.continueLoading(positionUs);
|
||||
|
|
|
|||
|
|
@ -288,6 +288,11 @@ import java.util.Arrays;
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reevaluateBuffer(long positionUs) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean continueLoading(long playbackPositionUs) {
|
||||
if (loadingFinished || (prepared && enabledTrackCount == 0)) {
|
||||
|
|
|
|||
|
|
@ -35,27 +35,25 @@ public interface MediaPeriod extends SequenceableLoader {
|
|||
|
||||
/**
|
||||
* Called when preparation completes.
|
||||
* <p>
|
||||
* Called on the playback thread. After invoking this method, the {@link MediaPeriod} can expect
|
||||
* for {@link #selectTracks(TrackSelection[], boolean[], SampleStream[], boolean[], long)} to be
|
||||
* called with the initial track selection.
|
||||
*
|
||||
* <p>Called on the playback thread. After invoking this method, the {@link MediaPeriod} can
|
||||
* expect for {@link #selectTracks(TrackSelection[], boolean[], SampleStream[], boolean[],
|
||||
* long)} to be called with the initial track selection.
|
||||
*
|
||||
* @param mediaPeriod The prepared {@link MediaPeriod}.
|
||||
*/
|
||||
void onPrepared(MediaPeriod mediaPeriod);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares this media period asynchronously.
|
||||
* <p>
|
||||
* {@code callback.onPrepared} is called when preparation completes. If preparation fails,
|
||||
*
|
||||
* <p>{@code callback.onPrepared} is called when preparation completes. If preparation fails,
|
||||
* {@link #maybeThrowPrepareError()} will throw an {@link IOException}.
|
||||
* <p>
|
||||
* If preparation succeeds and results in a source timeline change (e.g. the period duration
|
||||
* becoming known),
|
||||
* {@link MediaSource.Listener#onSourceInfoRefreshed(MediaSource, Timeline, Object)} will be
|
||||
* called before {@code callback.onPrepared}.
|
||||
*
|
||||
* <p>If preparation succeeds and results in a source timeline change (e.g. the period duration
|
||||
* becoming known), {@link MediaSource.Listener#onSourceInfoRefreshed(MediaSource, Timeline,
|
||||
* Object)} will be called before {@code callback.onPrepared}.
|
||||
*
|
||||
* @param callback Callback to receive updates from this period, including being notified when
|
||||
* preparation completes.
|
||||
|
|
@ -66,8 +64,8 @@ public interface MediaPeriod extends SequenceableLoader {
|
|||
/**
|
||||
* Throws an error that's preventing the period from becoming prepared. Does nothing if no such
|
||||
* error exists.
|
||||
* <p>
|
||||
* This method should only be called before the period has completed preparation.
|
||||
*
|
||||
* <p>This method should only be called before the period has completed preparation.
|
||||
*
|
||||
* @throws IOException The underlying error.
|
||||
*/
|
||||
|
|
@ -75,8 +73,8 @@ public interface MediaPeriod extends SequenceableLoader {
|
|||
|
||||
/**
|
||||
* Returns the {@link TrackGroup}s exposed by the period.
|
||||
* <p>
|
||||
* This method should only be called after the period has been prepared.
|
||||
*
|
||||
* <p>This method should only be called after the period has been prepared.
|
||||
*
|
||||
* @return The {@link TrackGroup}s.
|
||||
*/
|
||||
|
|
@ -84,16 +82,16 @@ public interface MediaPeriod extends SequenceableLoader {
|
|||
|
||||
/**
|
||||
* Performs a track selection.
|
||||
* <p>
|
||||
* The call receives track {@code selections} for each renderer, {@code mayRetainStreamFlags}
|
||||
*
|
||||
* <p>The call receives track {@code selections} for each renderer, {@code mayRetainStreamFlags}
|
||||
* indicating whether the existing {@code SampleStream} can be retained for each selection, and
|
||||
* the existing {@code stream}s themselves. The call will update {@code streams} to reflect the
|
||||
* provided selections, clearing, setting and replacing entries as required. If an existing sample
|
||||
* stream is retained but with the requirement that the consuming renderer be reset, then the
|
||||
* corresponding flag in {@code streamResetFlags} will be set to true. This flag will also be set
|
||||
* if a new sample stream is created.
|
||||
* <p>
|
||||
* This method should only be called after the period has been prepared.
|
||||
*
|
||||
* <p>This method should only be called after the period has been prepared.
|
||||
*
|
||||
* @param selections The renderer track selections.
|
||||
* @param mayRetainStreamFlags Flags indicating whether the existing sample stream can be retained
|
||||
|
|
@ -104,16 +102,20 @@ public interface MediaPeriod extends SequenceableLoader {
|
|||
* @param streamResetFlags Will be updated to indicate new sample streams, and sample streams that
|
||||
* have been retained but with the requirement that the consuming renderer be reset.
|
||||
* @param positionUs The current playback position in microseconds. If playback of this period has
|
||||
* not yet started, the value will be the starting position.
|
||||
* not yet started, the value will be the starting position.
|
||||
* @return The actual position at which the tracks were enabled, in microseconds.
|
||||
*/
|
||||
long selectTracks(TrackSelection[] selections, boolean[] mayRetainStreamFlags,
|
||||
SampleStream[] streams, boolean[] streamResetFlags, long positionUs);
|
||||
long selectTracks(
|
||||
TrackSelection[] selections,
|
||||
boolean[] mayRetainStreamFlags,
|
||||
SampleStream[] streams,
|
||||
boolean[] streamResetFlags,
|
||||
long positionUs);
|
||||
|
||||
/**
|
||||
* Discards buffered media up to the specified position.
|
||||
* <p>
|
||||
* This method should only be called after the period has been prepared.
|
||||
*
|
||||
* <p>This method should only be called after the period has been prepared.
|
||||
*
|
||||
* @param positionUs The position in microseconds.
|
||||
* @param toKeyframe If true then for each track discards samples up to the keyframe before or at
|
||||
|
|
@ -123,11 +125,11 @@ public interface MediaPeriod extends SequenceableLoader {
|
|||
|
||||
/**
|
||||
* Attempts to read a discontinuity.
|
||||
* <p>
|
||||
* After this method has returned a value other than {@link C#TIME_UNSET}, all
|
||||
* {@link SampleStream}s provided by the period are guaranteed to start from a key frame.
|
||||
* <p>
|
||||
* This method should only be called after the period has been prepared.
|
||||
*
|
||||
* <p>After this method has returned a value other than {@link C#TIME_UNSET}, all {@link
|
||||
* SampleStream}s provided by the period are guaranteed to start from a key frame.
|
||||
*
|
||||
* <p>This method should only be called after the period has been prepared.
|
||||
*
|
||||
* @return If a discontinuity was read then the playback position in microseconds after the
|
||||
* discontinuity. Else {@link C#TIME_UNSET}.
|
||||
|
|
@ -136,11 +138,11 @@ public interface MediaPeriod extends SequenceableLoader {
|
|||
|
||||
/**
|
||||
* Attempts to seek to the specified position in microseconds.
|
||||
* <p>
|
||||
* After this method has been called, all {@link SampleStream}s provided by the period are
|
||||
*
|
||||
* <p>After this method has been called, all {@link SampleStream}s provided by the period are
|
||||
* guaranteed to start from a key frame.
|
||||
* <p>
|
||||
* This method should only be called when at least one track is selected.
|
||||
*
|
||||
* <p>This method should only be called when at least one track is selected.
|
||||
*
|
||||
* @param positionUs The seek position in microseconds.
|
||||
* @return The actual position to which the period was seeked, in microseconds.
|
||||
|
|
@ -151,8 +153,8 @@ public interface MediaPeriod extends SequenceableLoader {
|
|||
|
||||
/**
|
||||
* Returns an estimate of the position up to which data is buffered for the enabled tracks.
|
||||
* <p>
|
||||
* This method should only be called when at least one track is selected.
|
||||
*
|
||||
* <p>This method should only be called when at least one track is selected.
|
||||
*
|
||||
* @return An estimate of the absolute position in microseconds up to which data is buffered, or
|
||||
* {@link C#TIME_END_OF_SOURCE} if the track is fully buffered.
|
||||
|
|
@ -162,19 +164,19 @@ public interface MediaPeriod extends SequenceableLoader {
|
|||
|
||||
/**
|
||||
* Returns the next load time, or {@link C#TIME_END_OF_SOURCE} if loading has finished.
|
||||
* <p>
|
||||
* This method should only be called after the period has been prepared. It may be called when no
|
||||
* tracks are selected.
|
||||
*
|
||||
* <p>This method should only be called after the period has been prepared. It may be called when
|
||||
* no tracks are selected.
|
||||
*/
|
||||
@Override
|
||||
long getNextLoadPositionUs();
|
||||
|
||||
/**
|
||||
* Attempts to continue loading.
|
||||
* <p>
|
||||
* This method may be called both during and after the period has been prepared.
|
||||
* <p>
|
||||
* A period may call {@link Callback#onContinueLoadingRequested(SequenceableLoader)} on the
|
||||
*
|
||||
* <p>This method may be called both during and after the period has been prepared.
|
||||
*
|
||||
* <p>A period may call {@link Callback#onContinueLoadingRequested(SequenceableLoader)} on the
|
||||
* {@link Callback} passed to {@link #prepare(Callback, long)} to request that this method be
|
||||
* called when the period is permitted to continue loading data. A period may do this both during
|
||||
* and after preparation.
|
||||
|
|
@ -182,10 +184,24 @@ public interface MediaPeriod extends SequenceableLoader {
|
|||
* @param positionUs The current playback position in microseconds. If playback of this period has
|
||||
* not yet started, the value will be the starting position in this period minus the duration
|
||||
* of any media in previous periods still to be played.
|
||||
* @return True if progress was made, meaning that {@link #getNextLoadPositionUs()} will return
|
||||
* a different value than prior to the call. False otherwise.
|
||||
* @return True if progress was made, meaning that {@link #getNextLoadPositionUs()} will return a
|
||||
* different value than prior to the call. False otherwise.
|
||||
*/
|
||||
@Override
|
||||
boolean continueLoading(long positionUs);
|
||||
|
||||
/**
|
||||
* Re-evaluates the buffer given the playback position.
|
||||
*
|
||||
* <p>This method should only be called after the period has been prepared.
|
||||
*
|
||||
* <p>A period may choose to discard buffered media so that it can be re-buffered in a different
|
||||
* quality.
|
||||
*
|
||||
* @param positionUs The current playback position in microseconds. If playback of this period has
|
||||
* not yet started, the value will be the starting position in this period minus the duration
|
||||
* of any media in previous periods still to be played.
|
||||
*/
|
||||
@Override
|
||||
void reevaluateBuffer(long positionUs);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -139,6 +139,11 @@ import java.util.IdentityHashMap;
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reevaluateBuffer(long positionUs) {
|
||||
compositeSequenceableLoader.reevaluateBuffer(positionUs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean continueLoading(long positionUs) {
|
||||
return compositeSequenceableLoader.continueLoading(positionUs);
|
||||
|
|
|
|||
|
|
@ -60,4 +60,15 @@ public interface SequenceableLoader {
|
|||
*/
|
||||
boolean continueLoading(long positionUs);
|
||||
|
||||
/**
|
||||
* Re-evaluates the buffer given the playback position.
|
||||
*
|
||||
* <p>Re-evaluation may discard buffered media so that it can be re-buffered in a different
|
||||
* quality.
|
||||
*
|
||||
* @param positionUs The current playback position in microseconds. If playback of this period has
|
||||
* not yet started, the value will be the starting position in this period minus the duration
|
||||
* of any media in previous periods still to be played.
|
||||
*/
|
||||
void reevaluateBuffer(long positionUs);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -120,6 +120,11 @@ import java.util.Arrays;
|
|||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reevaluateBuffer(long positionUs) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean continueLoading(long positionUs) {
|
||||
if (loadingFinished || loader.isLoading()) {
|
||||
|
|
|
|||
|
|
@ -319,7 +319,9 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
|
|||
IOException error) {
|
||||
long bytesLoaded = loadable.bytesLoaded();
|
||||
boolean isMediaChunk = isMediaChunk(loadable);
|
||||
boolean cancelable = bytesLoaded == 0 || !isMediaChunk || !haveReadFromLastMediaChunk();
|
||||
int lastChunkIndex = mediaChunks.size() - 1;
|
||||
boolean cancelable =
|
||||
bytesLoaded == 0 || !isMediaChunk || !haveReadFromMediaChunk(lastChunkIndex);
|
||||
boolean canceled = false;
|
||||
if (chunkSource.onChunkLoadError(loadable, cancelable, error)) {
|
||||
if (!cancelable) {
|
||||
|
|
@ -327,12 +329,8 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
|
|||
} else {
|
||||
canceled = true;
|
||||
if (isMediaChunk) {
|
||||
BaseMediaChunk removed = mediaChunks.remove(mediaChunks.size() - 1);
|
||||
BaseMediaChunk removed = discardUpstreamMediaChunksFromIndex(lastChunkIndex);
|
||||
Assertions.checkState(removed == loadable);
|
||||
primarySampleQueue.discardUpstreamSamples(removed.getFirstSampleIndex(0));
|
||||
for (int i = 0; i < embeddedSampleQueues.length; i++) {
|
||||
embeddedSampleQueues[i].discardUpstreamSamples(removed.getFirstSampleIndex(i + 1));
|
||||
}
|
||||
if (mediaChunks.isEmpty()) {
|
||||
pendingResetPositionUs = lastSeekPositionUs;
|
||||
}
|
||||
|
|
@ -405,35 +403,29 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
|
|||
}
|
||||
}
|
||||
|
||||
// Internal methods
|
||||
|
||||
// TODO[REFACTOR]: Call maybeDiscardUpstream for DASH and SmoothStreaming.
|
||||
/**
|
||||
* Discards media chunks from the back of the buffer if conditions have changed such that it's
|
||||
* preferable to re-buffer the media at a different quality.
|
||||
*
|
||||
* @param positionUs The current playback position in microseconds.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
private void maybeDiscardUpstream(long positionUs) {
|
||||
@Override
|
||||
public void reevaluateBuffer(long positionUs) {
|
||||
if (loader.isLoading() || isPendingReset()) {
|
||||
return;
|
||||
}
|
||||
int queueSize = chunkSource.getPreferredQueueSize(positionUs, readOnlyMediaChunks);
|
||||
discardUpstreamMediaChunks(Math.max(1, queueSize));
|
||||
discardUpstreamMediaChunks(queueSize);
|
||||
}
|
||||
|
||||
// Internal methods
|
||||
|
||||
private boolean isMediaChunk(Chunk chunk) {
|
||||
return chunk instanceof BaseMediaChunk;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether samples have been read from {@code mediaChunks.getLast()}.
|
||||
*/
|
||||
private boolean haveReadFromLastMediaChunk() {
|
||||
BaseMediaChunk lastChunk = getLastMediaChunk();
|
||||
if (primarySampleQueue.getReadIndex() > lastChunk.getFirstSampleIndex(0)) {
|
||||
/** Returns whether samples have been read from media chunk at given index. */
|
||||
private boolean haveReadFromMediaChunk(int mediaChunkIndex) {
|
||||
BaseMediaChunk mediaChunk = mediaChunks.get(mediaChunkIndex);
|
||||
if (primarySampleQueue.getReadIndex() > mediaChunk.getFirstSampleIndex(0)) {
|
||||
return true;
|
||||
}
|
||||
for (int i = 0; i < embeddedSampleQueues.length; i++) {
|
||||
if (embeddedSampleQueues[i].getReadIndex() > lastChunk.getFirstSampleIndex(i + 1)) {
|
||||
if (embeddedSampleQueues[i].getReadIndex() > mediaChunk.getFirstSampleIndex(i + 1)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -492,27 +484,51 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
|
|||
}
|
||||
|
||||
/**
|
||||
* Discard upstream media chunks until the queue length is equal to the length specified.
|
||||
* Discard upstream media chunks until the queue length is equal to the length specified, but
|
||||
* avoid discarding any chunk whose samples have been read by either primary sample stream or
|
||||
* embedded sample streams.
|
||||
*
|
||||
* @param queueLength The desired length of the queue.
|
||||
* @return Whether chunks were discarded.
|
||||
* @param desiredQueueSize The desired length of the queue. The final queue size after discarding
|
||||
* maybe larger than this if there are chunks after the specified position that have been read
|
||||
* by either primary sample stream or embedded sample streams.
|
||||
*/
|
||||
private boolean discardUpstreamMediaChunks(int queueLength) {
|
||||
if (mediaChunks.size() <= queueLength) {
|
||||
return false;
|
||||
private void discardUpstreamMediaChunks(int desiredQueueSize) {
|
||||
if (mediaChunks.size() <= desiredQueueSize) {
|
||||
return;
|
||||
}
|
||||
|
||||
int firstIndexToRemove = desiredQueueSize;
|
||||
for (int i = firstIndexToRemove; i < mediaChunks.size(); i++) {
|
||||
if (!haveReadFromMediaChunk(i)) {
|
||||
firstIndexToRemove = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (firstIndexToRemove == mediaChunks.size()) {
|
||||
return;
|
||||
}
|
||||
long endTimeUs = getLastMediaChunk().endTimeUs;
|
||||
BaseMediaChunk firstRemovedChunk = mediaChunks.get(queueLength);
|
||||
long startTimeUs = firstRemovedChunk.startTimeUs;
|
||||
Util.removeRange(mediaChunks, /* fromIndex= */ queueLength, /* toIndex= */ mediaChunks.size());
|
||||
BaseMediaChunk firstRemovedChunk = discardUpstreamMediaChunksFromIndex(firstIndexToRemove);
|
||||
loadingFinished = false;
|
||||
eventDispatcher.upstreamDiscarded(primaryTrackType, firstRemovedChunk.startTimeUs, endTimeUs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Discard upstream media chunks from {@code chunkIndex} and corresponding samples from sample
|
||||
* queues.
|
||||
*
|
||||
* @param chunkIndex The index of the first chunk to discard.
|
||||
* @return The chunk at given index.
|
||||
*/
|
||||
private BaseMediaChunk discardUpstreamMediaChunksFromIndex(int chunkIndex) {
|
||||
BaseMediaChunk firstRemovedChunk = mediaChunks.get(chunkIndex);
|
||||
Util.removeRange(mediaChunks, /* fromIndex= */ chunkIndex, /* toIndex= */ mediaChunks.size());
|
||||
primarySampleQueue.discardUpstreamSamples(firstRemovedChunk.getFirstSampleIndex(0));
|
||||
for (int i = 0; i < embeddedSampleQueues.length; i++) {
|
||||
embeddedSampleQueues[i].discardUpstreamSamples(firstRemovedChunk.getFirstSampleIndex(i + 1));
|
||||
}
|
||||
loadingFinished = false;
|
||||
eventDispatcher.upstreamDiscarded(primaryTrackType, startTimeUs, endTimeUs);
|
||||
return true;
|
||||
return firstRemovedChunk;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -15,12 +15,12 @@
|
|||
*/
|
||||
package com.google.android.exoplayer2.trackselection;
|
||||
|
||||
import android.os.SystemClock;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.source.TrackGroup;
|
||||
import com.google.android.exoplayer2.source.chunk.MediaChunk;
|
||||
import com.google.android.exoplayer2.upstream.BandwidthMeter;
|
||||
import com.google.android.exoplayer2.util.Clock;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import java.util.List;
|
||||
|
||||
|
|
@ -42,17 +42,23 @@ public class AdaptiveTrackSelection extends BaseTrackSelection {
|
|||
private final int minDurationToRetainAfterDiscardMs;
|
||||
private final float bandwidthFraction;
|
||||
private final float bufferedFractionToLiveEdgeForQualityIncrease;
|
||||
private final long minTimeBetweenBufferReevaluationMs;
|
||||
private final Clock clock;
|
||||
|
||||
/**
|
||||
* @param bandwidthMeter Provides an estimate of the currently available bandwidth.
|
||||
*/
|
||||
public Factory(BandwidthMeter bandwidthMeter) {
|
||||
this (bandwidthMeter, DEFAULT_MAX_INITIAL_BITRATE,
|
||||
this(
|
||||
bandwidthMeter,
|
||||
DEFAULT_MAX_INITIAL_BITRATE,
|
||||
DEFAULT_MIN_DURATION_FOR_QUALITY_INCREASE_MS,
|
||||
DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS,
|
||||
DEFAULT_MIN_DURATION_TO_RETAIN_AFTER_DISCARD_MS,
|
||||
DEFAULT_BANDWIDTH_FRACTION,
|
||||
DEFAULT_BUFFERED_FRACTION_TO_LIVE_EDGE_FOR_QUALITY_INCREASE);
|
||||
DEFAULT_BUFFERED_FRACTION_TO_LIVE_EDGE_FOR_QUALITY_INCREASE,
|
||||
DEFAULT_MIN_TIME_BETWEEN_BUFFER_REEVALUTATION_MS,
|
||||
Clock.DEFAULT);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -74,37 +80,55 @@ public class AdaptiveTrackSelection extends BaseTrackSelection {
|
|||
public Factory(BandwidthMeter bandwidthMeter, int maxInitialBitrate,
|
||||
int minDurationForQualityIncreaseMs, int maxDurationForQualityDecreaseMs,
|
||||
int minDurationToRetainAfterDiscardMs, float bandwidthFraction) {
|
||||
this (bandwidthMeter, maxInitialBitrate, minDurationForQualityIncreaseMs,
|
||||
maxDurationForQualityDecreaseMs, minDurationToRetainAfterDiscardMs,
|
||||
bandwidthFraction, DEFAULT_BUFFERED_FRACTION_TO_LIVE_EDGE_FOR_QUALITY_INCREASE);
|
||||
this(
|
||||
bandwidthMeter,
|
||||
maxInitialBitrate,
|
||||
minDurationForQualityIncreaseMs,
|
||||
maxDurationForQualityDecreaseMs,
|
||||
minDurationToRetainAfterDiscardMs,
|
||||
bandwidthFraction,
|
||||
DEFAULT_BUFFERED_FRACTION_TO_LIVE_EDGE_FOR_QUALITY_INCREASE,
|
||||
DEFAULT_MIN_TIME_BETWEEN_BUFFER_REEVALUTATION_MS,
|
||||
Clock.DEFAULT);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bandwidthMeter Provides an estimate of the currently available bandwidth.
|
||||
* @param maxInitialBitrate The maximum bitrate in bits per second that should be assumed
|
||||
* when a bandwidth estimate is unavailable.
|
||||
* @param minDurationForQualityIncreaseMs The minimum duration of buffered data required for
|
||||
* the selected track to switch to one of higher quality.
|
||||
* @param maxDurationForQualityDecreaseMs The maximum duration of buffered data required for
|
||||
* the selected track to switch to one of lower quality.
|
||||
* @param maxInitialBitrate The maximum bitrate in bits per second that should be assumed when a
|
||||
* bandwidth estimate is unavailable.
|
||||
* @param minDurationForQualityIncreaseMs The minimum duration of buffered data required for the
|
||||
* selected track to switch to one of higher quality.
|
||||
* @param maxDurationForQualityDecreaseMs The maximum duration of buffered data required for the
|
||||
* selected track to switch to one of lower quality.
|
||||
* @param minDurationToRetainAfterDiscardMs When switching to a track of significantly higher
|
||||
* quality, the selection may indicate that media already buffered at the lower quality can
|
||||
* be discarded to speed up the switch. This is the minimum duration of media that must be
|
||||
* retained at the lower quality.
|
||||
* @param bandwidthFraction The fraction of the available bandwidth that the selection should
|
||||
* consider available for use. Setting to a value less than 1 is recommended to account
|
||||
* for inaccuracies in the bandwidth estimator.
|
||||
* @param bufferedFractionToLiveEdgeForQualityIncrease For live streaming, the fraction of
|
||||
* the duration from current playback position to the live edge that has to be buffered
|
||||
* before the selected track can be switched to one of higher quality. This parameter is
|
||||
* only applied when the playback position is closer to the live edge than
|
||||
* {@code minDurationForQualityIncreaseMs}, which would otherwise prevent switching to a
|
||||
* higher quality from happening.
|
||||
* consider available for use. Setting to a value less than 1 is recommended to account for
|
||||
* inaccuracies in the bandwidth estimator.
|
||||
* @param bufferedFractionToLiveEdgeForQualityIncrease For live streaming, the fraction of the
|
||||
* duration from current playback position to the live edge that has to be buffered before
|
||||
* the selected track can be switched to one of higher quality. This parameter is only
|
||||
* applied when the playback position is closer to the live edge than {@code
|
||||
* minDurationForQualityIncreaseMs}, which would otherwise prevent switching to a higher
|
||||
* quality from happening.
|
||||
* @param minTimeBetweenBufferReevaluationMs The track selection may periodically reevaluate its
|
||||
* buffer and discard some chunks of lower quality to improve the playback quality if
|
||||
* network conditions have changed. This is the minimum duration between 2 consecutive
|
||||
* buffer reevaluation calls.
|
||||
* @param clock A {@link Clock}.
|
||||
*/
|
||||
public Factory(BandwidthMeter bandwidthMeter, int maxInitialBitrate,
|
||||
int minDurationForQualityIncreaseMs, int maxDurationForQualityDecreaseMs,
|
||||
int minDurationToRetainAfterDiscardMs, float bandwidthFraction,
|
||||
float bufferedFractionToLiveEdgeForQualityIncrease) {
|
||||
public Factory(
|
||||
BandwidthMeter bandwidthMeter,
|
||||
int maxInitialBitrate,
|
||||
int minDurationForQualityIncreaseMs,
|
||||
int maxDurationForQualityDecreaseMs,
|
||||
int minDurationToRetainAfterDiscardMs,
|
||||
float bandwidthFraction,
|
||||
float bufferedFractionToLiveEdgeForQualityIncrease,
|
||||
long minTimeBetweenBufferReevaluationMs,
|
||||
Clock clock) {
|
||||
this.bandwidthMeter = bandwidthMeter;
|
||||
this.maxInitialBitrate = maxInitialBitrate;
|
||||
this.minDurationForQualityIncreaseMs = minDurationForQualityIncreaseMs;
|
||||
|
|
@ -113,14 +137,24 @@ public class AdaptiveTrackSelection extends BaseTrackSelection {
|
|||
this.bandwidthFraction = bandwidthFraction;
|
||||
this.bufferedFractionToLiveEdgeForQualityIncrease =
|
||||
bufferedFractionToLiveEdgeForQualityIncrease;
|
||||
this.minTimeBetweenBufferReevaluationMs = minTimeBetweenBufferReevaluationMs;
|
||||
this.clock = clock;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AdaptiveTrackSelection createTrackSelection(TrackGroup group, int... tracks) {
|
||||
return new AdaptiveTrackSelection(group, tracks, bandwidthMeter, maxInitialBitrate,
|
||||
minDurationForQualityIncreaseMs, maxDurationForQualityDecreaseMs,
|
||||
minDurationToRetainAfterDiscardMs, bandwidthFraction,
|
||||
bufferedFractionToLiveEdgeForQualityIncrease);
|
||||
return new AdaptiveTrackSelection(
|
||||
group,
|
||||
tracks,
|
||||
bandwidthMeter,
|
||||
maxInitialBitrate,
|
||||
minDurationForQualityIncreaseMs,
|
||||
maxDurationForQualityDecreaseMs,
|
||||
minDurationToRetainAfterDiscardMs,
|
||||
bandwidthFraction,
|
||||
bufferedFractionToLiveEdgeForQualityIncrease,
|
||||
minTimeBetweenBufferReevaluationMs,
|
||||
clock);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -131,6 +165,7 @@ public class AdaptiveTrackSelection extends BaseTrackSelection {
|
|||
public static final int DEFAULT_MIN_DURATION_TO_RETAIN_AFTER_DISCARD_MS = 25000;
|
||||
public static final float DEFAULT_BANDWIDTH_FRACTION = 0.75f;
|
||||
public static final float DEFAULT_BUFFERED_FRACTION_TO_LIVE_EDGE_FOR_QUALITY_INCREASE = 0.75f;
|
||||
public static final long DEFAULT_MIN_TIME_BETWEEN_BUFFER_REEVALUTATION_MS = 2000;
|
||||
|
||||
private final BandwidthMeter bandwidthMeter;
|
||||
private final int maxInitialBitrate;
|
||||
|
|
@ -139,10 +174,13 @@ public class AdaptiveTrackSelection extends BaseTrackSelection {
|
|||
private final long minDurationToRetainAfterDiscardUs;
|
||||
private final float bandwidthFraction;
|
||||
private final float bufferedFractionToLiveEdgeForQualityIncrease;
|
||||
private final long minTimeBetweenBufferReevaluationMs;
|
||||
private final Clock clock;
|
||||
|
||||
private float playbackSpeed;
|
||||
private int selectedIndex;
|
||||
private int reason;
|
||||
private long lastBufferEvaluationMs;
|
||||
|
||||
/**
|
||||
* @param group The {@link TrackGroup}.
|
||||
|
|
@ -152,12 +190,18 @@ public class AdaptiveTrackSelection extends BaseTrackSelection {
|
|||
*/
|
||||
public AdaptiveTrackSelection(TrackGroup group, int[] tracks,
|
||||
BandwidthMeter bandwidthMeter) {
|
||||
this (group, tracks, bandwidthMeter, DEFAULT_MAX_INITIAL_BITRATE,
|
||||
this(
|
||||
group,
|
||||
tracks,
|
||||
bandwidthMeter,
|
||||
DEFAULT_MAX_INITIAL_BITRATE,
|
||||
DEFAULT_MIN_DURATION_FOR_QUALITY_INCREASE_MS,
|
||||
DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS,
|
||||
DEFAULT_MIN_DURATION_TO_RETAIN_AFTER_DISCARD_MS,
|
||||
DEFAULT_BANDWIDTH_FRACTION,
|
||||
DEFAULT_BUFFERED_FRACTION_TO_LIVE_EDGE_FOR_QUALITY_INCREASE);
|
||||
DEFAULT_BUFFERED_FRACTION_TO_LIVE_EDGE_FOR_QUALITY_INCREASE,
|
||||
DEFAULT_MIN_TIME_BETWEEN_BUFFER_REEVALUTATION_MS,
|
||||
Clock.DEFAULT);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -172,23 +216,35 @@ public class AdaptiveTrackSelection extends BaseTrackSelection {
|
|||
* @param maxDurationForQualityDecreaseMs The maximum duration of buffered data required for the
|
||||
* selected track to switch to one of lower quality.
|
||||
* @param minDurationToRetainAfterDiscardMs When switching to a track of significantly higher
|
||||
* quality, the selection may indicate that media already buffered at the lower quality can
|
||||
* be discarded to speed up the switch. This is the minimum duration of media that must be
|
||||
* quality, the selection may indicate that media already buffered at the lower quality can be
|
||||
* discarded to speed up the switch. This is the minimum duration of media that must be
|
||||
* retained at the lower quality.
|
||||
* @param bandwidthFraction The fraction of the available bandwidth that the selection should
|
||||
* consider available for use. Setting to a value less than 1 is recommended to account
|
||||
* for inaccuracies in the bandwidth estimator.
|
||||
* @param bufferedFractionToLiveEdgeForQualityIncrease For live streaming, the fraction of
|
||||
* the duration from current playback position to the live edge that has to be buffered
|
||||
* before the selected track can be switched to one of higher quality. This parameter is
|
||||
* only applied when the playback position is closer to the live edge than
|
||||
* {@code minDurationForQualityIncreaseMs}, which would otherwise prevent switching to a
|
||||
* higher quality from happening.
|
||||
* consider available for use. Setting to a value less than 1 is recommended to account for
|
||||
* inaccuracies in the bandwidth estimator.
|
||||
* @param bufferedFractionToLiveEdgeForQualityIncrease For live streaming, the fraction of the
|
||||
* duration from current playback position to the live edge that has to be buffered before the
|
||||
* selected track can be switched to one of higher quality. This parameter is only applied
|
||||
* when the playback position is closer to the live edge than {@code
|
||||
* minDurationForQualityIncreaseMs}, which would otherwise prevent switching to a higher
|
||||
* quality from happening.
|
||||
* @param minTimeBetweenBufferReevaluationMs The track selection may periodically reevaluate its
|
||||
* buffer and discard some chunks of lower quality to improve the playback quality if network
|
||||
* condition has changed. This is the minimum duration between 2 consecutive buffer
|
||||
* reevaluation calls.
|
||||
*/
|
||||
public AdaptiveTrackSelection(TrackGroup group, int[] tracks, BandwidthMeter bandwidthMeter,
|
||||
int maxInitialBitrate, long minDurationForQualityIncreaseMs,
|
||||
long maxDurationForQualityDecreaseMs, long minDurationToRetainAfterDiscardMs,
|
||||
float bandwidthFraction, float bufferedFractionToLiveEdgeForQualityIncrease) {
|
||||
public AdaptiveTrackSelection(
|
||||
TrackGroup group,
|
||||
int[] tracks,
|
||||
BandwidthMeter bandwidthMeter,
|
||||
int maxInitialBitrate,
|
||||
long minDurationForQualityIncreaseMs,
|
||||
long maxDurationForQualityDecreaseMs,
|
||||
long minDurationToRetainAfterDiscardMs,
|
||||
float bandwidthFraction,
|
||||
float bufferedFractionToLiveEdgeForQualityIncrease,
|
||||
long minTimeBetweenBufferReevaluationMs,
|
||||
Clock clock) {
|
||||
super(group, tracks);
|
||||
this.bandwidthMeter = bandwidthMeter;
|
||||
this.maxInitialBitrate = maxInitialBitrate;
|
||||
|
|
@ -198,9 +254,17 @@ public class AdaptiveTrackSelection extends BaseTrackSelection {
|
|||
this.bandwidthFraction = bandwidthFraction;
|
||||
this.bufferedFractionToLiveEdgeForQualityIncrease =
|
||||
bufferedFractionToLiveEdgeForQualityIncrease;
|
||||
this.minTimeBetweenBufferReevaluationMs = minTimeBetweenBufferReevaluationMs;
|
||||
this.clock = clock;
|
||||
playbackSpeed = 1f;
|
||||
selectedIndex = determineIdealSelectedIndex(Long.MIN_VALUE);
|
||||
reason = C.SELECTION_REASON_INITIAL;
|
||||
lastBufferEvaluationMs = C.TIME_UNSET;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enable() {
|
||||
lastBufferEvaluationMs = C.TIME_UNSET;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -211,7 +275,7 @@ public class AdaptiveTrackSelection extends BaseTrackSelection {
|
|||
@Override
|
||||
public void updateSelectedTrack(long playbackPositionUs, long bufferedDurationUs,
|
||||
long availableDurationUs) {
|
||||
long nowMs = SystemClock.elapsedRealtime();
|
||||
long nowMs = clock.elapsedRealtime();
|
||||
// Stash the current selection, then make a new one.
|
||||
int currentSelectedIndex = selectedIndex;
|
||||
selectedIndex = determineIdealSelectedIndex(nowMs);
|
||||
|
|
@ -258,17 +322,25 @@ public class AdaptiveTrackSelection extends BaseTrackSelection {
|
|||
|
||||
@Override
|
||||
public int evaluateQueueSize(long playbackPositionUs, List<? extends MediaChunk> queue) {
|
||||
long nowMs = clock.elapsedRealtime();
|
||||
if (lastBufferEvaluationMs != C.TIME_UNSET
|
||||
&& nowMs - lastBufferEvaluationMs < minTimeBetweenBufferReevaluationMs) {
|
||||
return queue.size();
|
||||
}
|
||||
lastBufferEvaluationMs = nowMs;
|
||||
if (queue.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int queueSize = queue.size();
|
||||
long mediaBufferedDurationUs = queue.get(queueSize - 1).endTimeUs - playbackPositionUs;
|
||||
long playoutBufferedDurationUs =
|
||||
Util.getPlayoutDurationForMediaDuration(mediaBufferedDurationUs, playbackSpeed);
|
||||
if (playoutBufferedDurationUs < minDurationToRetainAfterDiscardUs) {
|
||||
MediaChunk lastChunk = queue.get(queueSize - 1);
|
||||
long playoutBufferedDurationBeforeLastChunkUs =
|
||||
Util.getPlayoutDurationForMediaDuration(
|
||||
lastChunk.startTimeUs - playbackPositionUs, playbackSpeed);
|
||||
if (playoutBufferedDurationBeforeLastChunkUs < minDurationToRetainAfterDiscardUs) {
|
||||
return queueSize;
|
||||
}
|
||||
int idealSelectedIndex = determineIdealSelectedIndex(SystemClock.elapsedRealtime());
|
||||
int idealSelectedIndex = determineIdealSelectedIndex(nowMs);
|
||||
Format idealFormat = getFormat(idealSelectedIndex);
|
||||
// If the chunks contain video, discard from the first SD chunk beyond
|
||||
// minDurationToRetainAfterDiscardUs whose resolution and bitrate are both lower than the ideal
|
||||
|
|
@ -293,8 +365,8 @@ public class AdaptiveTrackSelection extends BaseTrackSelection {
|
|||
/**
|
||||
* Computes the ideal selected index ignoring buffer health.
|
||||
*
|
||||
* @param nowMs The current time in the timebase of {@link SystemClock#elapsedRealtime()}, or
|
||||
* {@link Long#MIN_VALUE} to ignore blacklisting.
|
||||
* @param nowMs The current time in the timebase of {@link Clock#elapsedRealtime()}, or {@link
|
||||
* Long#MIN_VALUE} to ignore blacklisting.
|
||||
*/
|
||||
private int determineIdealSelectedIndex(long nowMs) {
|
||||
long bitrateEstimate = bandwidthMeter.getBitrateEstimate();
|
||||
|
|
|
|||
|
|
@ -265,6 +265,11 @@ public final class CompositeSequenceableLoaderTest {
|
|||
return loaded;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reevaluateBuffer(long positionUs) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
private void setNextChunkDurationUs(int nextChunkDurationUs) {
|
||||
this.nextChunkDurationUs = nextChunkDurationUs;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,428 @@
|
|||
/*
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer2.trackselection;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.mockito.MockitoAnnotations.initMocks;
|
||||
|
||||
import android.net.Uri;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.source.TrackGroup;
|
||||
import com.google.android.exoplayer2.source.chunk.MediaChunk;
|
||||
import com.google.android.exoplayer2.testutil.FakeClock;
|
||||
import com.google.android.exoplayer2.upstream.BandwidthMeter;
|
||||
import com.google.android.exoplayer2.upstream.DataSource;
|
||||
import com.google.android.exoplayer2.upstream.DataSpec;
|
||||
import com.google.android.exoplayer2.upstream.DefaultHttpDataSource;
|
||||
import com.google.android.exoplayer2.util.MimeTypes;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
/** Unit test for {@link AdaptiveTrackSelection}. */
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(sdk = Config.TARGET_SDK, manifest = Config.NONE)
|
||||
public final class AdaptiveTrackSelectionTest {
|
||||
|
||||
@Mock private BandwidthMeter mockBandwidthMeter;
|
||||
private FakeClock fakeClock;
|
||||
|
||||
private AdaptiveTrackSelection adaptiveTrackSelection;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
initMocks(this);
|
||||
fakeClock = new FakeClock(0);
|
||||
when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(BandwidthMeter.NO_ESTIMATE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSelectInitialIndexUseMaxInitialBitrateIfNoBandwidthEstimate() {
|
||||
Format format1 = videoFormat(/* bitrate= */ 500, /* width= */ 320, /* height= */ 240);
|
||||
Format format2 = videoFormat(/* bitrate= */ 1000, /* width= */ 640, /* height= */ 480);
|
||||
Format format3 = videoFormat(/* bitrate= */ 2000, /* width= */ 960, /* height= */ 720);
|
||||
TrackGroup trackGroup = new TrackGroup(format1, format2, format3);
|
||||
|
||||
adaptiveTrackSelection = adaptiveTrackSelection(trackGroup, /* initialBitrate= */ 1000);
|
||||
|
||||
assertThat(adaptiveTrackSelection.getSelectedFormat()).isEqualTo(format2);
|
||||
assertThat(adaptiveTrackSelection.getSelectionReason()).isEqualTo(C.SELECTION_REASON_INITIAL);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSelectInitialIndexUseBandwidthEstimateIfAvailable() {
|
||||
Format format1 = videoFormat(/* bitrate= */ 500, /* width= */ 320, /* height= */ 240);
|
||||
Format format2 = videoFormat(/* bitrate= */ 1000, /* width= */ 640, /* height= */ 480);
|
||||
Format format3 = videoFormat(/* bitrate= */ 2000, /* width= */ 960, /* height= */ 720);
|
||||
TrackGroup trackGroup = new TrackGroup(format1, format2, format3);
|
||||
|
||||
when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(500L);
|
||||
|
||||
adaptiveTrackSelection = adaptiveTrackSelection(trackGroup, /* initialBitrate= */ 1000);
|
||||
|
||||
assertThat(adaptiveTrackSelection.getSelectedFormat()).isEqualTo(format1);
|
||||
assertThat(adaptiveTrackSelection.getSelectionReason()).isEqualTo(C.SELECTION_REASON_INITIAL);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateSelectedTrackDoNotSwitchUpIfNotBufferedEnough() {
|
||||
Format format1 = videoFormat(/* bitrate= */ 500, /* width= */ 320, /* height= */ 240);
|
||||
Format format2 = videoFormat(/* bitrate= */ 1000, /* width= */ 640, /* height= */ 480);
|
||||
Format format3 = videoFormat(/* bitrate= */ 2000, /* width= */ 960, /* height= */ 720);
|
||||
TrackGroup trackGroup = new TrackGroup(format1, format2, format3);
|
||||
|
||||
// initially bandwidth meter does not have any estimation. The second measurement onward returns
|
||||
// 2000L, which prompts the track selection to switch up if possible.
|
||||
when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(BandwidthMeter.NO_ESTIMATE, 2000L);
|
||||
|
||||
adaptiveTrackSelection =
|
||||
adaptiveTrackSelectionWithMinDurationForQualityIncreaseMs(
|
||||
trackGroup, /* initialBitrate= */ 1000, /* minDurationForQualityIncreaseMs= */ 10_000);
|
||||
|
||||
adaptiveTrackSelection.updateSelectedTrack(
|
||||
/* playbackPositionUs= */ 0,
|
||||
/* bufferedDurationUs= */ 9_999_000,
|
||||
/* availableDurationUs= */ C.TIME_UNSET);
|
||||
|
||||
// When bandwidth estimation is updated to 2000L, we can switch up to use a higher bitrate
|
||||
// format. However, since we only buffered 9_999_000 us, which is smaller than
|
||||
// minDurationForQualityIncreaseMs, we should defer switch up.
|
||||
assertThat(adaptiveTrackSelection.getSelectedFormat()).isEqualTo(format2);
|
||||
assertThat(adaptiveTrackSelection.getSelectionReason()).isEqualTo(C.SELECTION_REASON_INITIAL);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateSelectedTrackSwitchUpIfBufferedEnough() {
|
||||
Format format1 = videoFormat(/* bitrate= */ 500, /* width= */ 320, /* height= */ 240);
|
||||
Format format2 = videoFormat(/* bitrate= */ 1000, /* width= */ 640, /* height= */ 480);
|
||||
Format format3 = videoFormat(/* bitrate= */ 2000, /* width= */ 960, /* height= */ 720);
|
||||
TrackGroup trackGroup = new TrackGroup(format1, format2, format3);
|
||||
|
||||
// initially bandwidth meter does not have any estimation. The second measurement onward returns
|
||||
// 2000L, which prompts the track selection to switch up if possible.
|
||||
when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(BandwidthMeter.NO_ESTIMATE, 2000L);
|
||||
|
||||
adaptiveTrackSelection =
|
||||
adaptiveTrackSelectionWithMinDurationForQualityIncreaseMs(
|
||||
trackGroup, /* initialBitrate= */ 1000, /* minDurationForQualityIncreaseMs= */ 10_000);
|
||||
|
||||
adaptiveTrackSelection.updateSelectedTrack(
|
||||
/* playbackPositionUs= */ 0,
|
||||
/* bufferedDurationUs= */ 10_000_000,
|
||||
/* availableDurationUs= */ C.TIME_UNSET);
|
||||
|
||||
// When bandwidth estimation is updated to 2000L, we can switch up to use a higher bitrate
|
||||
// format. When we have buffered enough (10_000_000 us, which is equal to
|
||||
// minDurationForQualityIncreaseMs), we should switch up now.
|
||||
assertThat(adaptiveTrackSelection.getSelectedFormat()).isEqualTo(format3);
|
||||
assertThat(adaptiveTrackSelection.getSelectionReason()).isEqualTo(C.SELECTION_REASON_ADAPTIVE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateSelectedTrackDoNotSwitchDownIfBufferedEnough() {
|
||||
Format format1 = videoFormat(/* bitrate= */ 500, /* width= */ 320, /* height= */ 240);
|
||||
Format format2 = videoFormat(/* bitrate= */ 1000, /* width= */ 640, /* height= */ 480);
|
||||
Format format3 = videoFormat(/* bitrate= */ 2000, /* width= */ 960, /* height= */ 720);
|
||||
TrackGroup trackGroup = new TrackGroup(format1, format2, format3);
|
||||
|
||||
// initially bandwidth meter does not have any estimation. The second measurement onward returns
|
||||
// 500L, which prompts the track selection to switch down if necessary.
|
||||
when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(BandwidthMeter.NO_ESTIMATE, 500L);
|
||||
|
||||
adaptiveTrackSelection =
|
||||
adaptiveTrackSelectionWithMaxDurationForQualityDecreaseMs(
|
||||
trackGroup, /* initialBitrate= */ 1000, /* maxDurationForQualityDecreaseMs= */ 25_000);
|
||||
|
||||
adaptiveTrackSelection.updateSelectedTrack(
|
||||
/* playbackPositionUs= */ 0,
|
||||
/* bufferedDurationUs= */ 25_000_000,
|
||||
/* availableDurationUs= */ C.TIME_UNSET);
|
||||
|
||||
// When bandwidth estimation is updated to 500L, we should switch down to use a lower bitrate
|
||||
// format. However, since we have enough buffer at higher quality (25_000_000 us, which is equal
|
||||
// to maxDurationForQualityDecreaseMs), we should defer switch down.
|
||||
assertThat(adaptiveTrackSelection.getSelectedFormat()).isEqualTo(format2);
|
||||
assertThat(adaptiveTrackSelection.getSelectionReason()).isEqualTo(C.SELECTION_REASON_INITIAL);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateSelectedTrackSwitchDownIfNotBufferedEnough() {
|
||||
Format format1 = videoFormat(/* bitrate= */ 500, /* width= */ 320, /* height= */ 240);
|
||||
Format format2 = videoFormat(/* bitrate= */ 1000, /* width= */ 640, /* height= */ 480);
|
||||
Format format3 = videoFormat(/* bitrate= */ 2000, /* width= */ 960, /* height= */ 720);
|
||||
TrackGroup trackGroup = new TrackGroup(format1, format2, format3);
|
||||
|
||||
// initially bandwidth meter does not have any estimation. The second measurement onward returns
|
||||
// 500L, which prompts the track selection to switch down if necessary.
|
||||
when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(BandwidthMeter.NO_ESTIMATE, 500L);
|
||||
|
||||
adaptiveTrackSelection =
|
||||
adaptiveTrackSelectionWithMaxDurationForQualityDecreaseMs(
|
||||
trackGroup, /* initialBitrate= */ 1000, /* maxDurationForQualityDecreaseMs= */ 25_000);
|
||||
|
||||
adaptiveTrackSelection.updateSelectedTrack(
|
||||
/* playbackPositionUs= */ 0,
|
||||
/* bufferedDurationUs= */ 24_999_000,
|
||||
/* availableDurationUs= */ C.TIME_UNSET);
|
||||
|
||||
// When bandwidth estimation is updated to 500L, we should switch down to use a lower bitrate
|
||||
// format. When we don't have enough buffer at higher quality (24_999_000 us is smaller than
|
||||
// maxDurationForQualityDecreaseMs), we should switch down now.
|
||||
assertThat(adaptiveTrackSelection.getSelectedFormat()).isEqualTo(format1);
|
||||
assertThat(adaptiveTrackSelection.getSelectionReason()).isEqualTo(C.SELECTION_REASON_ADAPTIVE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEvaluateQueueSizeReturnQueueSizeIfBandwidthIsNotImproved() {
|
||||
Format format1 = videoFormat(/* bitrate= */ 500, /* width= */ 320, /* height= */ 240);
|
||||
Format format2 = videoFormat(/* bitrate= */ 1000, /* width= */ 640, /* height= */ 480);
|
||||
Format format3 = videoFormat(/* bitrate= */ 2000, /* width= */ 960, /* height= */ 720);
|
||||
TrackGroup trackGroup = new TrackGroup(format1, format2, format3);
|
||||
|
||||
FakeMediaChunk chunk1 =
|
||||
new FakeMediaChunk(format1, /* startTimeUs= */ 0, /* endTimeUs= */ 10_000_000);
|
||||
FakeMediaChunk chunk2 =
|
||||
new FakeMediaChunk(format1, /* startTimeUs= */ 10_000_000, /* endTimeUs= */ 20_000_000);
|
||||
FakeMediaChunk chunk3 =
|
||||
new FakeMediaChunk(format1, /* startTimeUs= */ 20_000_000, /* endTimeUs= */ 30_000_000);
|
||||
List<FakeMediaChunk> queue = new ArrayList<>();
|
||||
queue.add(chunk1);
|
||||
queue.add(chunk2);
|
||||
queue.add(chunk3);
|
||||
|
||||
when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(500L);
|
||||
adaptiveTrackSelection = adaptiveTrackSelection(trackGroup, /* initialBitrate= */ 1000);
|
||||
|
||||
int size = adaptiveTrackSelection.evaluateQueueSize(0, queue);
|
||||
assertThat(size).isEqualTo(3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEvaluateQueueSizeDoNotReevaluateUntilAfterMinTimeBetweenBufferReevaluation() {
|
||||
Format format1 = videoFormat(/* bitrate= */ 500, /* width= */ 320, /* height= */ 240);
|
||||
Format format2 = videoFormat(/* bitrate= */ 1000, /* width= */ 640, /* height= */ 480);
|
||||
Format format3 = videoFormat(/* bitrate= */ 2000, /* width= */ 960, /* height= */ 720);
|
||||
TrackGroup trackGroup = new TrackGroup(format1, format2, format3);
|
||||
|
||||
FakeMediaChunk chunk1 =
|
||||
new FakeMediaChunk(format1, /* startTimeUs= */ 0, /* endTimeUs= */ 10_000_000);
|
||||
FakeMediaChunk chunk2 =
|
||||
new FakeMediaChunk(format1, /* startTimeUs= */ 10_000_000, /* endTimeUs= */ 20_000_000);
|
||||
FakeMediaChunk chunk3 =
|
||||
new FakeMediaChunk(format1, /* startTimeUs= */ 20_000_000, /* endTimeUs= */ 30_000_000);
|
||||
List<FakeMediaChunk> queue = new ArrayList<>();
|
||||
queue.add(chunk1);
|
||||
queue.add(chunk2);
|
||||
queue.add(chunk3);
|
||||
|
||||
when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(500L);
|
||||
adaptiveTrackSelection =
|
||||
adaptiveTrackSelectionWithMinTimeBetweenBufferReevaluationMs(
|
||||
trackGroup,
|
||||
/* initialBitrate= */ 1000,
|
||||
/* durationToRetainAfterDiscardMs= */ 15_000,
|
||||
/* minTimeBetweenBufferReevaluationMs= */ 2000);
|
||||
|
||||
int initialQueueSize = adaptiveTrackSelection.evaluateQueueSize(0, queue);
|
||||
|
||||
fakeClock.advanceTime(1999);
|
||||
when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(1000L);
|
||||
|
||||
// When bandwidth estimation is updated, we can discard chunks at the end of the queue now.
|
||||
// However, since min duration between buffer reevaluation = 2000, we will not reevaluate
|
||||
// queue size if time now is only 1999 ms after last buffer reevaluation.
|
||||
int newSize = adaptiveTrackSelection.evaluateQueueSize(0, queue);
|
||||
assertThat(newSize).isEqualTo(initialQueueSize);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEvaluateQueueSizeRetainMoreThanMinimumDurationAfterDiscard() {
|
||||
Format format1 = videoFormat(/* bitrate= */ 500, /* width= */ 320, /* height= */ 240);
|
||||
Format format2 = videoFormat(/* bitrate= */ 1000, /* width= */ 640, /* height= */ 480);
|
||||
Format format3 = videoFormat(/* bitrate= */ 2000, /* width= */ 960, /* height= */ 720);
|
||||
TrackGroup trackGroup = new TrackGroup(format1, format2, format3);
|
||||
|
||||
FakeMediaChunk chunk1 =
|
||||
new FakeMediaChunk(format1, /* startTimeUs= */ 0, /* endTimeUs= */ 10_000_000);
|
||||
FakeMediaChunk chunk2 =
|
||||
new FakeMediaChunk(format1, /* startTimeUs= */ 10_000_000, /* endTimeUs= */ 20_000_000);
|
||||
FakeMediaChunk chunk3 =
|
||||
new FakeMediaChunk(format1, /* startTimeUs= */ 20_000_000, /* endTimeUs= */ 30_000_000);
|
||||
List<FakeMediaChunk> queue = new ArrayList<>();
|
||||
queue.add(chunk1);
|
||||
queue.add(chunk2);
|
||||
queue.add(chunk3);
|
||||
|
||||
when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(500L);
|
||||
adaptiveTrackSelection =
|
||||
adaptiveTrackSelectionWithMinTimeBetweenBufferReevaluationMs(
|
||||
trackGroup,
|
||||
/* initialBitrate= */ 1000,
|
||||
/* durationToRetainAfterDiscardMs= */ 15_000,
|
||||
/* minTimeBetweenBufferReevaluationMs= */ 2000);
|
||||
|
||||
int initialQueueSize = adaptiveTrackSelection.evaluateQueueSize(0, queue);
|
||||
assertThat(initialQueueSize).isEqualTo(3);
|
||||
|
||||
fakeClock.advanceTime(2000);
|
||||
when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(1000L);
|
||||
|
||||
// When bandwidth estimation is updated and time has advanced enough, we can discard chunks at
|
||||
// the end of the queue now.
|
||||
// However, since duration to retain after discard = 15 000 ms, we need to retain at least the
|
||||
// first 2 chunks
|
||||
int newSize = adaptiveTrackSelection.evaluateQueueSize(0, queue);
|
||||
assertThat(newSize).isEqualTo(2);
|
||||
}
|
||||
|
||||
private AdaptiveTrackSelection adaptiveTrackSelection(TrackGroup trackGroup, int initialBitrate) {
|
||||
return new AdaptiveTrackSelection(
|
||||
trackGroup,
|
||||
selectedAllTracksInGroup(trackGroup),
|
||||
mockBandwidthMeter,
|
||||
initialBitrate,
|
||||
AdaptiveTrackSelection.DEFAULT_MIN_DURATION_FOR_QUALITY_INCREASE_MS,
|
||||
AdaptiveTrackSelection.DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS,
|
||||
AdaptiveTrackSelection.DEFAULT_MIN_DURATION_TO_RETAIN_AFTER_DISCARD_MS,
|
||||
/* bandwidthFraction= */ 1.0f,
|
||||
AdaptiveTrackSelection.DEFAULT_BUFFERED_FRACTION_TO_LIVE_EDGE_FOR_QUALITY_INCREASE,
|
||||
AdaptiveTrackSelection.DEFAULT_MIN_TIME_BETWEEN_BUFFER_REEVALUTATION_MS,
|
||||
fakeClock);
|
||||
}
|
||||
|
||||
private AdaptiveTrackSelection adaptiveTrackSelectionWithMinDurationForQualityIncreaseMs(
|
||||
TrackGroup trackGroup, int initialBitrate, long minDurationForQualityIncreaseMs) {
|
||||
return new AdaptiveTrackSelection(
|
||||
trackGroup,
|
||||
selectedAllTracksInGroup(trackGroup),
|
||||
mockBandwidthMeter,
|
||||
initialBitrate,
|
||||
minDurationForQualityIncreaseMs,
|
||||
AdaptiveTrackSelection.DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS,
|
||||
AdaptiveTrackSelection.DEFAULT_MIN_DURATION_TO_RETAIN_AFTER_DISCARD_MS,
|
||||
/* bandwidthFraction= */ 1.0f,
|
||||
AdaptiveTrackSelection.DEFAULT_BUFFERED_FRACTION_TO_LIVE_EDGE_FOR_QUALITY_INCREASE,
|
||||
AdaptiveTrackSelection.DEFAULT_MIN_TIME_BETWEEN_BUFFER_REEVALUTATION_MS,
|
||||
fakeClock);
|
||||
}
|
||||
|
||||
private AdaptiveTrackSelection adaptiveTrackSelectionWithMaxDurationForQualityDecreaseMs(
|
||||
TrackGroup trackGroup, int initialBitrate, long maxDurationForQualityDecreaseMs) {
|
||||
return new AdaptiveTrackSelection(
|
||||
trackGroup,
|
||||
selectedAllTracksInGroup(trackGroup),
|
||||
mockBandwidthMeter,
|
||||
initialBitrate,
|
||||
AdaptiveTrackSelection.DEFAULT_MIN_DURATION_FOR_QUALITY_INCREASE_MS,
|
||||
maxDurationForQualityDecreaseMs,
|
||||
AdaptiveTrackSelection.DEFAULT_MIN_DURATION_TO_RETAIN_AFTER_DISCARD_MS,
|
||||
/* bandwidthFraction= */ 1.0f,
|
||||
AdaptiveTrackSelection.DEFAULT_BUFFERED_FRACTION_TO_LIVE_EDGE_FOR_QUALITY_INCREASE,
|
||||
AdaptiveTrackSelection.DEFAULT_MIN_TIME_BETWEEN_BUFFER_REEVALUTATION_MS,
|
||||
fakeClock);
|
||||
}
|
||||
|
||||
private AdaptiveTrackSelection adaptiveTrackSelectionWithMinTimeBetweenBufferReevaluationMs(
|
||||
TrackGroup trackGroup,
|
||||
int initialBitrate,
|
||||
long durationToRetainAfterDiscardMs,
|
||||
long minTimeBetweenBufferReevaluationMs) {
|
||||
return new AdaptiveTrackSelection(
|
||||
trackGroup,
|
||||
selectedAllTracksInGroup(trackGroup),
|
||||
mockBandwidthMeter,
|
||||
initialBitrate,
|
||||
AdaptiveTrackSelection.DEFAULT_MIN_DURATION_FOR_QUALITY_INCREASE_MS,
|
||||
AdaptiveTrackSelection.DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS,
|
||||
durationToRetainAfterDiscardMs,
|
||||
/* bandwidth fraction= */ 1.0f,
|
||||
AdaptiveTrackSelection.DEFAULT_BUFFERED_FRACTION_TO_LIVE_EDGE_FOR_QUALITY_INCREASE,
|
||||
minTimeBetweenBufferReevaluationMs,
|
||||
fakeClock);
|
||||
}
|
||||
|
||||
private int[] selectedAllTracksInGroup(TrackGroup trackGroup) {
|
||||
int[] listIndices = new int[trackGroup.length];
|
||||
for (int i = 0; i < trackGroup.length; i++) {
|
||||
listIndices[i] = i;
|
||||
}
|
||||
return listIndices;
|
||||
}
|
||||
|
||||
private static Format videoFormat(int bitrate, int width, int height) {
|
||||
return Format.createVideoSampleFormat(
|
||||
/* id= */ null,
|
||||
/* sampleMimeType= */ MimeTypes.VIDEO_H264,
|
||||
/* codecs= */ null,
|
||||
/* bitrate= */ bitrate,
|
||||
/* maxInputSize= */ Format.NO_VALUE,
|
||||
/* width= */ width,
|
||||
/* height= */ height,
|
||||
/* frameRate= */ Format.NO_VALUE,
|
||||
/* initializationData= */ null,
|
||||
/* drmInitData= */ null);
|
||||
}
|
||||
|
||||
private static final class FakeMediaChunk extends MediaChunk {
|
||||
|
||||
private static final DataSource DATA_SOURCE = new DefaultHttpDataSource("TEST_AGENT", null);
|
||||
|
||||
public FakeMediaChunk(Format trackFormat, long startTimeUs, long endTimeUs) {
|
||||
super(
|
||||
DATA_SOURCE,
|
||||
new DataSpec(Uri.EMPTY),
|
||||
trackFormat,
|
||||
C.SELECTION_REASON_ADAPTIVE,
|
||||
null,
|
||||
startTimeUs,
|
||||
endTimeUs,
|
||||
0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelLoad() {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLoadCanceled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load() throws IOException, InterruptedException {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLoadCompleted() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long bytesLoaded() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -270,6 +270,11 @@ import java.util.Map;
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reevaluateBuffer(long positionUs) {
|
||||
compositeSequenceableLoader.reevaluateBuffer(positionUs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean continueLoading(long positionUs) {
|
||||
return compositeSequenceableLoader.continueLoading(positionUs);
|
||||
|
|
|
|||
|
|
@ -195,6 +195,11 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reevaluateBuffer(long positionUs) {
|
||||
compositeSequenceableLoader.reevaluateBuffer(positionUs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean continueLoading(long positionUs) {
|
||||
return compositeSequenceableLoader.continueLoading(positionUs);
|
||||
|
|
|
|||
|
|
@ -524,6 +524,11 @@ import java.util.Arrays;
|
|||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reevaluateBuffer(long positionUs) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
// Loader.Callback implementation.
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -149,6 +149,11 @@ import java.util.ArrayList;
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reevaluateBuffer(long positionUs) {
|
||||
compositeSequenceableLoader.reevaluateBuffer(positionUs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean continueLoading(long positionUs) {
|
||||
return compositeSequenceableLoader.continueLoading(positionUs);
|
||||
|
|
|
|||
|
|
@ -203,8 +203,20 @@ public class SsManifest {
|
|||
long timescale, String name, int maxWidth, int maxHeight, int displayWidth,
|
||||
int displayHeight, String language, Format[] formats, List<Long> chunkStartTimes,
|
||||
long lastChunkDuration) {
|
||||
this (baseUri, chunkTemplate, type, subType, timescale, name, maxWidth, maxHeight,
|
||||
displayWidth, displayHeight, language, formats, chunkStartTimes,
|
||||
this(
|
||||
baseUri,
|
||||
chunkTemplate,
|
||||
type,
|
||||
subType,
|
||||
timescale,
|
||||
name,
|
||||
maxWidth,
|
||||
maxHeight,
|
||||
displayWidth,
|
||||
displayHeight,
|
||||
language,
|
||||
formats,
|
||||
chunkStartTimes,
|
||||
Util.scaleLargeTimestamps(chunkStartTimes, C.MICROS_PER_SECOND, timescale),
|
||||
Util.scaleLargeTimestamp(lastChunkDuration, C.MICROS_PER_SECOND, timescale));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -151,6 +151,11 @@ public class FakeMediaPeriod implements MediaPeriod {
|
|||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reevaluateBuffer(long positionUs) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
public long readDiscontinuity() {
|
||||
Assert.assertTrue(prepared);
|
||||
|
|
|
|||
Loading…
Reference in a new issue