Commit 88dea59c by hoangtc Committed by Oliver Woodman

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
parent 6606d73b
Showing with 213 additions and 87 deletions
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
### dev-v2 (not yet released) ### ### 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. * Replace `DefaultTrackSelector.Parameters` copy methods with a builder.
* Allow more flexible loading strategy when playing media containing multiple * Allow more flexible loading strategy when playing media containing multiple
sub-streams, by allowing injection of custom `CompositeSequenceableLoader` sub-streams, by allowing injection of custom `CompositeSequenceableLoader`
......
...@@ -1283,6 +1283,7 @@ import java.io.IOException; ...@@ -1283,6 +1283,7 @@ import java.io.IOException;
// Update the loading period if required. // Update the loading period if required.
maybeUpdateLoadingPeriod(); maybeUpdateLoadingPeriod();
if (loadingPeriodHolder == null || loadingPeriodHolder.isFullyBuffered()) { if (loadingPeriodHolder == null || loadingPeriodHolder.isFullyBuffered()) {
setIsLoading(false); setIsLoading(false);
} else if (loadingPeriodHolder != null && !isLoading) { } else if (loadingPeriodHolder != null && !isLoading) {
...@@ -1386,6 +1387,7 @@ import java.io.IOException; ...@@ -1386,6 +1387,7 @@ import java.io.IOException;
if (loadingPeriodHolder == null) { if (loadingPeriodHolder == null) {
info = mediaPeriodInfoSequence.getFirstMediaPeriodInfo(playbackInfo); info = mediaPeriodInfoSequence.getFirstMediaPeriodInfo(playbackInfo);
} else { } else {
loadingPeriodHolder.reevaluateBuffer(rendererPositionUs);
if (loadingPeriodHolder.info.isFinal || !loadingPeriodHolder.isFullyBuffered() if (loadingPeriodHolder.info.isFinal || !loadingPeriodHolder.isFullyBuffered()
|| loadingPeriodHolder.info.durationUs == C.TIME_UNSET) { || loadingPeriodHolder.info.durationUs == C.TIME_UNSET) {
return; return;
...@@ -1440,6 +1442,7 @@ import java.io.IOException; ...@@ -1440,6 +1442,7 @@ import java.io.IOException;
// Stale event. // Stale event.
return; return;
} }
loadingPeriodHolder.reevaluateBuffer(rendererPositionUs);
maybeContinueLoading(); maybeContinueLoading();
} }
...@@ -1628,13 +1631,18 @@ import java.io.IOException; ...@@ -1628,13 +1631,18 @@ import java.io.IOException;
info = info.copyWithStartPositionUs(newStartPositionUs); info = info.copyWithStartPositionUs(newStartPositionUs);
} }
public void reevaluateBuffer(long rendererPositionUs) {
if (prepared) {
mediaPeriod.reevaluateBuffer(toPeriodTime(rendererPositionUs));
}
}
public boolean shouldContinueLoading(long rendererPositionUs, float playbackSpeed) { public boolean shouldContinueLoading(long rendererPositionUs, float playbackSpeed) {
long nextLoadPositionUs = !prepared ? 0 : mediaPeriod.getNextLoadPositionUs(); long nextLoadPositionUs = !prepared ? 0 : mediaPeriod.getNextLoadPositionUs();
if (nextLoadPositionUs == C.TIME_END_OF_SOURCE) { if (nextLoadPositionUs == C.TIME_END_OF_SOURCE) {
return false; return false;
} else { } else {
long loadingPeriodPositionUs = toPeriodTime(rendererPositionUs); long bufferedDurationUs = nextLoadPositionUs - toPeriodTime(rendererPositionUs);
long bufferedDurationUs = nextLoadPositionUs - loadingPeriodPositionUs;
return loadControl.shouldContinueLoading(bufferedDurationUs, playbackSpeed); return loadControl.shouldContinueLoading(bufferedDurationUs, playbackSpeed);
} }
} }
...@@ -1694,7 +1702,6 @@ import java.io.IOException; ...@@ -1694,7 +1702,6 @@ import java.io.IOException;
Assertions.checkState(trackSelections.get(i) == null); Assertions.checkState(trackSelections.get(i) == null);
} }
} }
// The track selection has changed. // The track selection has changed.
loadControl.onTracksSelected(renderers, trackSelectorResult.groups, trackSelections); loadControl.onTracksSelected(renderers, trackSelectorResult.groups, trackSelections);
return positionUs; return positionUs;
......
...@@ -124,6 +124,11 @@ public final class ClippingMediaPeriod implements MediaPeriod, MediaPeriod.Callb ...@@ -124,6 +124,11 @@ public final class ClippingMediaPeriod implements MediaPeriod, MediaPeriod.Callb
} }
@Override @Override
public void reevaluateBuffer(long positionUs) {
mediaPeriod.reevaluateBuffer(positionUs + startUs);
}
@Override
public long readDiscontinuity() { public long readDiscontinuity() {
if (isPendingInitialDiscontinuity()) { if (isPendingInitialDiscontinuity()) {
long initialDiscontinuityUs = pendingInitialDiscontinuityPositionUs; long initialDiscontinuityUs = pendingInitialDiscontinuityPositionUs;
......
...@@ -53,6 +53,13 @@ public class CompositeSequenceableLoader implements SequenceableLoader { ...@@ -53,6 +53,13 @@ public class CompositeSequenceableLoader implements SequenceableLoader {
} }
@Override @Override
public final void reevaluateBuffer(long positionUs) {
for (SequenceableLoader loader : loaders) {
loader.reevaluateBuffer(positionUs);
}
}
@Override
public boolean continueLoading(long positionUs) { public boolean continueLoading(long positionUs) {
boolean madeProgress = false; boolean madeProgress = false;
boolean madeProgressThisIteration; boolean madeProgressThisIteration;
......
...@@ -120,6 +120,11 @@ public final class DeferredMediaPeriod implements MediaPeriod, MediaPeriod.Callb ...@@ -120,6 +120,11 @@ public final class DeferredMediaPeriod implements MediaPeriod, MediaPeriod.Callb
} }
@Override @Override
public void reevaluateBuffer(long positionUs) {
mediaPeriod.reevaluateBuffer(positionUs);
}
@Override
public boolean continueLoading(long positionUs) { public boolean continueLoading(long positionUs) {
return mediaPeriod != null && mediaPeriod.continueLoading(positionUs); return mediaPeriod != null && mediaPeriod.continueLoading(positionUs);
} }
......
...@@ -289,6 +289,11 @@ import java.util.Arrays; ...@@ -289,6 +289,11 @@ import java.util.Arrays;
} }
@Override @Override
public void reevaluateBuffer(long positionUs) {
// Do nothing.
}
@Override
public boolean continueLoading(long playbackPositionUs) { public boolean continueLoading(long playbackPositionUs) {
if (loadingFinished || (prepared && enabledTrackCount == 0)) { if (loadingFinished || (prepared && enabledTrackCount == 0)) {
return false; return false;
......
...@@ -35,27 +35,25 @@ public interface MediaPeriod extends SequenceableLoader { ...@@ -35,27 +35,25 @@ public interface MediaPeriod extends SequenceableLoader {
/** /**
* Called when preparation completes. * Called when preparation completes.
* <p> *
* Called on the playback thread. After invoking this method, the {@link MediaPeriod} can expect * <p>Called on the playback thread. After invoking this method, the {@link MediaPeriod} can
* for {@link #selectTracks(TrackSelection[], boolean[], SampleStream[], boolean[], long)} to be * expect for {@link #selectTracks(TrackSelection[], boolean[], SampleStream[], boolean[],
* called with the initial track selection. * long)} to be called with the initial track selection.
* *
* @param mediaPeriod The prepared {@link MediaPeriod}. * @param mediaPeriod The prepared {@link MediaPeriod}.
*/ */
void onPrepared(MediaPeriod mediaPeriod); void onPrepared(MediaPeriod mediaPeriod);
} }
/** /**
* Prepares this media period asynchronously. * 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}. * {@link #maybeThrowPrepareError()} will throw an {@link IOException}.
* <p> *
* If preparation succeeds and results in a source timeline change (e.g. the period duration * <p>If preparation succeeds and results in a source timeline change (e.g. the period duration
* becoming known), * becoming known), {@link MediaSource.Listener#onSourceInfoRefreshed(MediaSource, Timeline,
* {@link MediaSource.Listener#onSourceInfoRefreshed(MediaSource, Timeline, Object)} will be * Object)} will be called before {@code callback.onPrepared}.
* called before {@code callback.onPrepared}.
* *
* @param callback Callback to receive updates from this period, including being notified when * @param callback Callback to receive updates from this period, including being notified when
* preparation completes. * preparation completes.
...@@ -66,8 +64,8 @@ public interface MediaPeriod extends SequenceableLoader { ...@@ -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 * Throws an error that's preventing the period from becoming prepared. Does nothing if no such
* error exists. * 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. * @throws IOException The underlying error.
*/ */
...@@ -75,8 +73,8 @@ public interface MediaPeriod extends SequenceableLoader { ...@@ -75,8 +73,8 @@ public interface MediaPeriod extends SequenceableLoader {
/** /**
* Returns the {@link TrackGroup}s exposed by the period. * 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. * @return The {@link TrackGroup}s.
*/ */
...@@ -84,16 +82,16 @@ public interface MediaPeriod extends SequenceableLoader { ...@@ -84,16 +82,16 @@ public interface MediaPeriod extends SequenceableLoader {
/** /**
* Performs a track selection. * 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 * 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 * 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 * 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 * 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 * corresponding flag in {@code streamResetFlags} will be set to true. This flag will also be set
* if a new sample stream is created. * 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 selections The renderer track selections.
* @param mayRetainStreamFlags Flags indicating whether the existing sample stream can be retained * @param mayRetainStreamFlags Flags indicating whether the existing sample stream can be retained
...@@ -104,16 +102,20 @@ public interface MediaPeriod extends SequenceableLoader { ...@@ -104,16 +102,20 @@ public interface MediaPeriod extends SequenceableLoader {
* @param streamResetFlags Will be updated to indicate new sample streams, and sample streams that * @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. * 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 * @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. * @return The actual position at which the tracks were enabled, in microseconds.
*/ */
long selectTracks(TrackSelection[] selections, boolean[] mayRetainStreamFlags, long selectTracks(
SampleStream[] streams, boolean[] streamResetFlags, long positionUs); TrackSelection[] selections,
boolean[] mayRetainStreamFlags,
SampleStream[] streams,
boolean[] streamResetFlags,
long positionUs);
/** /**
* Discards buffered media up to the specified position. * 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 positionUs The position in microseconds.
* @param toKeyframe If true then for each track discards samples up to the keyframe before or at * @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 { ...@@ -123,11 +125,11 @@ public interface MediaPeriod extends SequenceableLoader {
/** /**
* Attempts to read a discontinuity. * Attempts to read a discontinuity.
* <p> *
* After this method has returned a value other than {@link C#TIME_UNSET}, all * <p>After this method has returned a value other than {@link C#TIME_UNSET}, all {@link
* {@link SampleStream}s provided by the period are guaranteed to start from a key frame. * 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>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 * @return If a discontinuity was read then the playback position in microseconds after the
* discontinuity. Else {@link C#TIME_UNSET}. * discontinuity. Else {@link C#TIME_UNSET}.
...@@ -136,11 +138,11 @@ public interface MediaPeriod extends SequenceableLoader { ...@@ -136,11 +138,11 @@ public interface MediaPeriod extends SequenceableLoader {
/** /**
* Attempts to seek to the specified position in microseconds. * 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. * 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. * @param positionUs The seek position in microseconds.
* @return The actual position to which the period was seeked, in microseconds. * @return The actual position to which the period was seeked, in microseconds.
...@@ -151,8 +153,8 @@ public interface MediaPeriod extends SequenceableLoader { ...@@ -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. * 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 * @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. * {@link C#TIME_END_OF_SOURCE} if the track is fully buffered.
...@@ -162,19 +164,19 @@ public interface MediaPeriod extends SequenceableLoader { ...@@ -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. * 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 * <p>This method should only be called after the period has been prepared. It may be called when
* tracks are selected. * no tracks are selected.
*/ */
@Override @Override
long getNextLoadPositionUs(); long getNextLoadPositionUs();
/** /**
* Attempts to continue loading. * Attempts to continue loading.
* <p> *
* This method may be called both during and after the period has been prepared. * <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>A period may call {@link Callback#onContinueLoadingRequested(SequenceableLoader)} on the
* {@link Callback} passed to {@link #prepare(Callback, long)} to request that this method be * {@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 * called when the period is permitted to continue loading data. A period may do this both during
* and after preparation. * and after preparation.
...@@ -182,10 +184,24 @@ public interface MediaPeriod extends SequenceableLoader { ...@@ -182,10 +184,24 @@ public interface MediaPeriod extends SequenceableLoader {
* @param positionUs The current playback position in microseconds. If playback of this period has * @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 * 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. * of any media in previous periods still to be played.
* @return True if progress was made, meaning that {@link #getNextLoadPositionUs()} will return * @return True if progress was made, meaning that {@link #getNextLoadPositionUs()} will return a
* a different value than prior to the call. False otherwise. * different value than prior to the call. False otherwise.
*/ */
@Override @Override
boolean continueLoading(long positionUs); 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);
} }
...@@ -140,6 +140,11 @@ import java.util.IdentityHashMap; ...@@ -140,6 +140,11 @@ import java.util.IdentityHashMap;
} }
@Override @Override
public void reevaluateBuffer(long positionUs) {
compositeSequenceableLoader.reevaluateBuffer(positionUs);
}
@Override
public boolean continueLoading(long positionUs) { public boolean continueLoading(long positionUs) {
return compositeSequenceableLoader.continueLoading(positionUs); return compositeSequenceableLoader.continueLoading(positionUs);
} }
......
...@@ -60,4 +60,15 @@ public interface SequenceableLoader { ...@@ -60,4 +60,15 @@ public interface SequenceableLoader {
*/ */
boolean continueLoading(long positionUs); 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);
} }
...@@ -121,6 +121,11 @@ import java.util.Arrays; ...@@ -121,6 +121,11 @@ import java.util.Arrays;
} }
@Override @Override
public void reevaluateBuffer(long positionUs) {
// Do nothing.
}
@Override
public boolean continueLoading(long positionUs) { public boolean continueLoading(long positionUs) {
if (loadingFinished || loader.isLoading()) { if (loadingFinished || loader.isLoading()) {
return false; return false;
......
...@@ -319,7 +319,9 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S ...@@ -319,7 +319,9 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
IOException error) { IOException error) {
long bytesLoaded = loadable.bytesLoaded(); long bytesLoaded = loadable.bytesLoaded();
boolean isMediaChunk = isMediaChunk(loadable); 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; boolean canceled = false;
if (chunkSource.onChunkLoadError(loadable, cancelable, error)) { if (chunkSource.onChunkLoadError(loadable, cancelable, error)) {
if (!cancelable) { if (!cancelable) {
...@@ -327,12 +329,8 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S ...@@ -327,12 +329,8 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
} else { } else {
canceled = true; canceled = true;
if (isMediaChunk) { if (isMediaChunk) {
BaseMediaChunk removed = mediaChunks.remove(mediaChunks.size() - 1); BaseMediaChunk removed = discardUpstreamMediaChunksFromIndex(lastChunkIndex);
Assertions.checkState(removed == loadable); 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()) { if (mediaChunks.isEmpty()) {
pendingResetPositionUs = lastSeekPositionUs; pendingResetPositionUs = lastSeekPositionUs;
} }
...@@ -405,35 +403,29 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S ...@@ -405,35 +403,29 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
} }
} }
// Internal methods @Override
public void reevaluateBuffer(long positionUs) {
// TODO[REFACTOR]: Call maybeDiscardUpstream for DASH and SmoothStreaming. if (loader.isLoading() || isPendingReset()) {
/** return;
* 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) {
int queueSize = chunkSource.getPreferredQueueSize(positionUs, readOnlyMediaChunks); int queueSize = chunkSource.getPreferredQueueSize(positionUs, readOnlyMediaChunks);
discardUpstreamMediaChunks(Math.max(1, queueSize)); discardUpstreamMediaChunks(queueSize);
} }
// Internal methods
private boolean isMediaChunk(Chunk chunk) { private boolean isMediaChunk(Chunk chunk) {
return chunk instanceof BaseMediaChunk; return chunk instanceof BaseMediaChunk;
} }
/** /** Returns whether samples have been read from media chunk at given index. */
* Returns whether samples have been read from {@code mediaChunks.getLast()}. private boolean haveReadFromMediaChunk(int mediaChunkIndex) {
*/ BaseMediaChunk mediaChunk = mediaChunks.get(mediaChunkIndex);
private boolean haveReadFromLastMediaChunk() { if (primarySampleQueue.getReadIndex() > mediaChunk.getFirstSampleIndex(0)) {
BaseMediaChunk lastChunk = getLastMediaChunk();
if (primarySampleQueue.getReadIndex() > lastChunk.getFirstSampleIndex(0)) {
return true; return true;
} }
for (int i = 0; i < embeddedSampleQueues.length; i++) { 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; return true;
} }
} }
...@@ -492,27 +484,51 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S ...@@ -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. * @param desiredQueueSize The desired length of the queue. The final queue size after discarding
* @return Whether chunks were discarded. * 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) { private void discardUpstreamMediaChunks(int desiredQueueSize) {
if (mediaChunks.size() <= queueLength) { if (mediaChunks.size() <= desiredQueueSize) {
return false; 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; long endTimeUs = getLastMediaChunk().endTimeUs;
BaseMediaChunk firstRemovedChunk = mediaChunks.get(queueLength); BaseMediaChunk firstRemovedChunk = discardUpstreamMediaChunksFromIndex(firstIndexToRemove);
long startTimeUs = firstRemovedChunk.startTimeUs; loadingFinished = false;
Util.removeRange(mediaChunks, /* fromIndex= */ queueLength, /* toIndex= */ mediaChunks.size()); 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)); primarySampleQueue.discardUpstreamSamples(firstRemovedChunk.getFirstSampleIndex(0));
for (int i = 0; i < embeddedSampleQueues.length; i++) { for (int i = 0; i < embeddedSampleQueues.length; i++) {
embeddedSampleQueues[i].discardUpstreamSamples(firstRemovedChunk.getFirstSampleIndex(i + 1)); embeddedSampleQueues[i].discardUpstreamSamples(firstRemovedChunk.getFirstSampleIndex(i + 1));
} }
loadingFinished = false; return firstRemovedChunk;
eventDispatcher.upstreamDiscarded(primaryTrackType, startTimeUs, endTimeUs);
return true;
} }
/** /**
......
...@@ -265,6 +265,11 @@ public final class CompositeSequenceableLoaderTest { ...@@ -265,6 +265,11 @@ public final class CompositeSequenceableLoaderTest {
return loaded; return loaded;
} }
@Override
public void reevaluateBuffer(long positionUs) {
// Do nothing.
}
private void setNextChunkDurationUs(int nextChunkDurationUs) { private void setNextChunkDurationUs(int nextChunkDurationUs) {
this.nextChunkDurationUs = nextChunkDurationUs; this.nextChunkDurationUs = nextChunkDurationUs;
} }
......
...@@ -271,6 +271,11 @@ import java.util.Map; ...@@ -271,6 +271,11 @@ import java.util.Map;
} }
@Override @Override
public void reevaluateBuffer(long positionUs) {
compositeSequenceableLoader.reevaluateBuffer(positionUs);
}
@Override
public boolean continueLoading(long positionUs) { public boolean continueLoading(long positionUs) {
return compositeSequenceableLoader.continueLoading(positionUs); return compositeSequenceableLoader.continueLoading(positionUs);
} }
......
...@@ -196,6 +196,11 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper ...@@ -196,6 +196,11 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
} }
@Override @Override
public void reevaluateBuffer(long positionUs) {
compositeSequenceableLoader.reevaluateBuffer(positionUs);
}
@Override
public boolean continueLoading(long positionUs) { public boolean continueLoading(long positionUs) {
return compositeSequenceableLoader.continueLoading(positionUs); return compositeSequenceableLoader.continueLoading(positionUs);
} }
......
...@@ -524,6 +524,11 @@ import java.util.Arrays; ...@@ -524,6 +524,11 @@ import java.util.Arrays;
return true; return true;
} }
@Override
public void reevaluateBuffer(long positionUs) {
// Do nothing.
}
// Loader.Callback implementation. // Loader.Callback implementation.
@Override @Override
......
...@@ -150,6 +150,11 @@ import java.util.ArrayList; ...@@ -150,6 +150,11 @@ import java.util.ArrayList;
} }
@Override @Override
public void reevaluateBuffer(long positionUs) {
compositeSequenceableLoader.reevaluateBuffer(positionUs);
}
@Override
public boolean continueLoading(long positionUs) { public boolean continueLoading(long positionUs) {
return compositeSequenceableLoader.continueLoading(positionUs); return compositeSequenceableLoader.continueLoading(positionUs);
} }
......
...@@ -203,8 +203,20 @@ public class SsManifest { ...@@ -203,8 +203,20 @@ public class SsManifest {
long timescale, String name, int maxWidth, int maxHeight, int displayWidth, long timescale, String name, int maxWidth, int maxHeight, int displayWidth,
int displayHeight, String language, Format[] formats, List<Long> chunkStartTimes, int displayHeight, String language, Format[] formats, List<Long> chunkStartTimes,
long lastChunkDuration) { long lastChunkDuration) {
this (baseUri, chunkTemplate, type, subType, timescale, name, maxWidth, maxHeight, this(
displayWidth, displayHeight, language, formats, chunkStartTimes, baseUri,
chunkTemplate,
type,
subType,
timescale,
name,
maxWidth,
maxHeight,
displayWidth,
displayHeight,
language,
formats,
chunkStartTimes,
Util.scaleLargeTimestamps(chunkStartTimes, C.MICROS_PER_SECOND, timescale), Util.scaleLargeTimestamps(chunkStartTimes, C.MICROS_PER_SECOND, timescale),
Util.scaleLargeTimestamp(lastChunkDuration, C.MICROS_PER_SECOND, timescale)); Util.scaleLargeTimestamp(lastChunkDuration, C.MICROS_PER_SECOND, timescale));
} }
......
...@@ -152,6 +152,11 @@ public class FakeMediaPeriod implements MediaPeriod { ...@@ -152,6 +152,11 @@ public class FakeMediaPeriod implements MediaPeriod {
} }
@Override @Override
public void reevaluateBuffer(long positionUs) {
// Do nothing.
}
@Override
public long readDiscontinuity() { public long readDiscontinuity() {
Assert.assertTrue(prepared); Assert.assertTrue(prepared);
long positionDiscontinuityUs = this.discontinuityPositionUs; long positionDiscontinuityUs = this.discontinuityPositionUs;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment