Split TrackSelection.evalauteQueueSize in discard and cancelation.

The option to cancel ongoing loads as part of the queue size evalation
was added recently. This split out the decision to a new method so that
a TrackSelection implementation can independently cancel loads and
discard upstream data. It also clarifies that evaluateQueueSize will
only be called if there is no ongoing load.

Issue: #2848
PiperOrigin-RevId: 315659735
This commit is contained in:
tonihei 2020-06-10 11:09:30 +01:00 committed by Oliver Woodman
parent 2a9144fa56
commit 95b61eb835
7 changed files with 106 additions and 38 deletions

View File

@ -88,6 +88,9 @@
([#7332](https://github.com/google/ExoPlayer/issues/7332)).
* Add `HttpDataSource.InvalidResponseCodeException#responseBody` field
([#6853](https://github.com/google/ExoPlayer/issues/6853)).
* Add `TrackSelection.shouldCancelMediaChunkLoad` to check whether an
ongoing load should be canceled. Only supported by HLS streams so far.
([#2848](https://github.com/google/ExoPlayer/issues/2848)).
* Video: Pass frame rate hint to `Surface.setFrameRate` on Android R devices.
* Text:
* Parse `<ruby>` and `<rt>` tags in WebVTT subtitles (rendering is coming

View File

@ -239,8 +239,8 @@ public interface MediaPeriod extends SequenceableLoader {
*
* <p>This method is only 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.
* <p>A period may choose to discard buffered media or cancel ongoing loads so that media 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

View File

@ -66,8 +66,8 @@ public interface SequenceableLoader {
/**
* 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.
* <p>Re-evaluation may discard buffered media or cancel ongoing loads so that media 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

View File

@ -38,8 +38,6 @@ public interface ChunkSource {
/**
* If the source is currently having difficulty providing chunks, then this method throws the
* underlying error. Otherwise does nothing.
* <p>
* This method should only be called after the source has been prepared.
*
* @throws IOException The underlying error.
*/
@ -47,10 +45,12 @@ public interface ChunkSource {
/**
* Evaluates whether {@link MediaChunk}s should be removed from the back of the queue.
* <p>
* Removing {@link MediaChunk}s from the back of the queue can be useful if they could be replaced
* with chunks of a significantly higher quality (e.g. because the available bandwidth has
* substantially increased).
*
* <p>Removing {@link MediaChunk}s from the back of the queue can be useful if they could be
* replaced with chunks of a significantly higher quality (e.g. because the available bandwidth
* has substantially increased).
*
* <p>Will only be called if no {@link MediaChunk} in the queue is currently loading.
*
* @param playbackPositionUs The current playback position.
* @param queue The queue of buffered {@link MediaChunk}s.
@ -85,8 +85,6 @@ public interface ChunkSource {
* Called when the {@link ChunkSampleStream} has finished loading a chunk obtained from this
* source.
*
* <p>This method should only be called when the source is enabled.
*
* @param chunk The chunk whose load has been completed.
*/
void onChunkLoadCompleted(Chunk chunk);
@ -95,8 +93,6 @@ public interface ChunkSource {
* Called when the {@link ChunkSampleStream} encounters an error loading a chunk obtained from
* this source.
*
* <p>This method should only be called when the source is enabled.
*
* @param chunk The chunk whose load encountered the error.
* @param cancelable Whether the load can be canceled.
* @param e The error.

View File

@ -19,6 +19,7 @@ import androidx.annotation.Nullable;
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.Chunk;
import com.google.android.exoplayer2.source.chunk.MediaChunk;
import com.google.android.exoplayer2.source.chunk.MediaChunkIterator;
import com.google.android.exoplayer2.upstream.BandwidthMeter;
@ -93,8 +94,8 @@ public interface TrackSelection {
/**
* Enables the track selection. Dynamic changes via {@link #updateSelectedTrack(long, long, long,
* List, MediaChunkIterator[])} or {@link #evaluateQueueSize(long, List)} will only happen after
* this call.
* List, MediaChunkIterator[])}, {@link #evaluateQueueSize(long, List)} or {@link
* #shouldCancelChunkLoad(long, Chunk, List)} will only happen after this call.
*
* <p>This method may not be called when the track selection is already enabled.
*/
@ -102,8 +103,8 @@ public interface TrackSelection {
/**
* Disables this track selection. No further dynamic changes via {@link #updateSelectedTrack(long,
* long, long, List, MediaChunkIterator[])} or {@link #evaluateQueueSize(long, List)} will happen
* after this call.
* long, long, List, MediaChunkIterator[])}, {@link #evaluateQueueSize(long, List)} or {@link
* #shouldCancelChunkLoad(long, Chunk, List)} will happen after this call.
*
* <p>This method may only be called when the track selection is already enabled.
*/
@ -202,7 +203,7 @@ public interface TrackSelection {
/**
* Updates the selected track for sources that load media in discrete {@link MediaChunk}s.
*
* <p>This method may only be called when the selection is enabled.
* <p>This method will only be called when the selection is enabled.
*
* @param playbackPositionUs The current playback position in microseconds. If playback of the
* period to which this track selection belongs has not yet started, the value will be the
@ -231,34 +232,77 @@ public interface TrackSelection {
MediaChunkIterator[] mediaChunkIterators);
/**
* May be called periodically by sources that load media in discrete {@link MediaChunk}s and
* support discarding of buffered chunks in order to re-buffer using a different selected track.
* Returns the number of chunks that should be retained in the queue.
* <p>
* To avoid excessive re-buffering, implementations should normally return the size of the queue.
* An example of a case where a smaller value may be returned is if network conditions have
*
* <p>May be called by sources that load media in discrete {@link MediaChunk MediaChunks} and
* support discarding of buffered chunks.
*
* <p>To avoid excessive re-buffering, implementations should normally return the size of the
* queue. An example of a case where a smaller value may be returned is if network conditions have
* improved dramatically, allowing chunks to be discarded and re-buffered in a track of
* significantly higher quality. Discarding chunks may allow faster switching to a higher quality
* track in this case. This method may only be called when the selection is enabled.
* track in this case.
*
* <p>Note that even if the source supports discarding of buffered chunks, the actual number of
* discarded chunks is not guaranteed. The source will call {@link #updateSelectedTrack(long,
* long, long, List, MediaChunkIterator[])} with the updated queue of chunks before loading a new
* chunk to allow switching to another quality.
*
* <p>This method will only be called when the selection is enabled and none of the {@link
* MediaChunk MediaChunks} in the queue are currently loading.
*
* @param playbackPositionUs The current playback position in microseconds. If playback of the
* period to which this track selection belongs has not yet started, the value will be the
* starting position in the period minus the duration of any media in previous periods still
* to be played.
* @param queue The queue of buffered {@link MediaChunk}s. Must not be modified.
* @param queue The queue of buffered {@link MediaChunk MediaChunks}. Must not be modified.
* @return The number of chunks to retain in the queue.
*/
int evaluateQueueSize(long playbackPositionUs, List<? extends MediaChunk> queue);
/**
* Returns whether an ongoing load of a chunk should be canceled.
*
* <p>May be called by sources that load media in discrete {@link MediaChunk MediaChunks} and
* support canceling the ongoing chunk load. The ongoing chunk load is either the last {@link
* MediaChunk} in the queue or another type of {@link Chunk}, for example, if the source loads
* initialization or encryption data.
*
* <p>To avoid excessive re-buffering, implementations should normally return {@code false}. An
* example where {@code true} might be returned is if a load of a high quality chunk gets stuck
* and canceling this load in favor of a lower quality alternative may avoid a rebuffer.
*
* <p>The source will call {@link #evaluateQueueSize(long, List)} after the cancelation finishes
* to allow discarding of chunks, and {@link #updateSelectedTrack(long, long, long, List,
* MediaChunkIterator[])} before loading a new chunk to allow switching to another quality.
*
* <p>This method will only be called when the selection is enabled.
*
* @param playbackPositionUs The current playback position in microseconds. If playback of the
* period to which this track selection belongs has not yet started, the value will be the
* starting position in the period minus the duration of any media in previous periods still
* to be played.
* @param loadingChunk The currently loading {@link Chunk} that will be canceled if this method
* returns {@code true}.
* @param queue The queue of buffered {@link MediaChunk MediaChunks}, including the {@code
* loadingChunk} if it's a {@link MediaChunk}. Must not be modified.
* @return Whether the ongoing load of {@code loadingChunk} should be canceled.
*/
default boolean shouldCancelChunkLoad(
long playbackPositionUs, Chunk loadingChunk, List<? extends MediaChunk> queue) {
return false;
}
/**
* Attempts to blacklist the track at the specified index in the selection, making it ineligible
* for selection by calls to {@link #updateSelectedTrack(long, long, long, List,
* MediaChunkIterator[])} for the specified period of time. Blacklisting will fail if all other
* tracks are currently blacklisted. If blacklisting the currently selected track, note that it
* will remain selected until the next call to {@link #updateSelectedTrack(long, long, long, List,
* MediaChunkIterator[])}.
* MediaChunkIterator[])} for the specified period of time.
*
* <p>This method may only be called when the selection is enabled.
* <p>Blacklisting will fail if all other tracks are currently blacklisted. If blacklisting the
* currently selected track, note that it will remain selected until the next call to {@link
* #updateSelectedTrack(long, long, long, List, MediaChunkIterator[])}.
*
* <p>This method will only be called when the selection is enabled.
*
* @param index The index of the track in the selection.
* @param blacklistDurationMs The duration of time for which the track should be blacklisted, in

View File

@ -458,6 +458,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
* could be replaced with chunks of a significantly higher quality (e.g. because the available
* bandwidth has substantially increased).
*
* <p>Will only be called if no {@link MediaChunk} in the queue is currently loading.
*
* @param playbackPositionUs The current playback position, in microseconds.
* @param queue The queue of buffered {@link MediaChunk MediaChunks}.
* @return The preferred queue size.
@ -469,6 +471,22 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
return trackSelection.evaluateQueueSize(playbackPositionUs, queue);
}
/**
* Returns whether an ongoing load of a chunk should be canceled.
*
* @param playbackPositionUs The current playback position, in microseconds.
* @param loadingChunk The currently loading {@link Chunk}.
* @param queue The queue of buffered {@link MediaChunk MediaChunks}.
* @return Whether the ongoing load of {@code loadingChunk} should be canceled.
*/
public boolean shouldCancelLoad(
long playbackPositionUs, Chunk loadingChunk, List<? extends MediaChunk> queue) {
if (fatalError != null) {
return false;
}
return trackSelection.shouldCancelChunkLoad(playbackPositionUs, loadingChunk, queue);
}
// Private methods.
/**

View File

@ -133,6 +133,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
private final ArrayList<HlsSampleStream> hlsSampleStreams;
private final Map<String, DrmInitData> overridingDrmInitData;
@Nullable private Chunk loadingChunk;
private HlsSampleQueue[] sampleQueues;
private int[] sampleQueueTrackIds;
private Set<Integer> sampleQueueMappingDoneByType;
@ -674,6 +675,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
if (isMediaChunk(loadable)) {
initMediaChunkLoad((HlsMediaChunk) loadable);
}
loadingChunk = loadable;
long elapsedRealtimeMs =
loader.startLoading(
loadable, this, loadErrorHandlingPolicy.getMinimumLoadableRetryCount(loadable.type));
@ -700,14 +702,16 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
return;
}
int currentQueueSize = mediaChunks.size();
int preferredQueueSize = chunkSource.getPreferredQueueSize(positionUs, readOnlyMediaChunks);
if (currentQueueSize <= preferredQueueSize) {
if (loader.isLoading()) {
Assertions.checkNotNull(loadingChunk);
if (chunkSource.shouldCancelLoad(positionUs, loadingChunk, readOnlyMediaChunks)) {
loader.cancelLoading();
}
return;
}
if (loader.isLoading()) {
loader.cancelLoading();
} else {
int preferredQueueSize = chunkSource.getPreferredQueueSize(positionUs, readOnlyMediaChunks);
if (preferredQueueSize < mediaChunks.size()) {
discardUpstream(preferredQueueSize);
}
}
@ -716,6 +720,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
@Override
public void onLoadCompleted(Chunk loadable, long elapsedRealtimeMs, long loadDurationMs) {
loadingChunk = null;
chunkSource.onChunkLoadCompleted(loadable);
LoadEventInfo loadEventInfo =
new LoadEventInfo(
@ -746,6 +751,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
@Override
public void onLoadCanceled(
Chunk loadable, long elapsedRealtimeMs, long loadDurationMs, boolean released) {
loadingChunk = null;
LoadEventInfo loadEventInfo =
new LoadEventInfo(
loadable.loadTaskId,
@ -841,6 +847,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
error,
wasCanceled);
if (wasCanceled) {
loadingChunk = null;
loadErrorHandlingPolicy.onLoadTaskConcluded(loadable.loadTaskId);
}
@ -885,7 +892,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
if (newQueueSize == C.LENGTH_UNSET) {
return;
}
long endTimeUs = getLastMediaChunk().endTimeUs;
HlsMediaChunk firstRemovedChunk = discardUpstreamMediaChunksFromIndex(newQueueSize);
if (mediaChunks.isEmpty()) {