Skip to content

Commit

Permalink
Add load cancelation support to DASH and SS
Browse files Browse the repository at this point in the history
Issue: #7244 added this feature to HLS. This change is the exact copy
in ChunkSampleStream to add the same support to the other adaptive
formats.

Note that ChunkSampleStream doesn't support slicing, so we can't cancel
a read-from chunk, and we need to prevent reading into an already
canceled chunk load so that the chunk can be automatically discarded
after the cancelation.

Issue: #2848
PiperOrigin-RevId: 324179972
  • Loading branch information
tonihei authored and ojw28 committed Aug 1, 2020
1 parent 32d1a78 commit d625af6
Show file tree
Hide file tree
Showing 7 changed files with 125 additions and 17 deletions.
2 changes: 2 additions & 0 deletions RELEASENOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,8 @@
decoders.
* DASH:
* Enable support for embedded CEA-708.
* Add support for load cancelation when discarding upstream
([#2848](https://github.com/google/ExoPlayer/issues/2848)).
* HLS:
* Add support for upstream discard including cancelation of ongoing load
([#6322](https://github.com/google/ExoPlayer/issues/6322)).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer2.source.chunk;

import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import static java.lang.Math.max;
import static java.lang.Math.min;

Expand Down Expand Up @@ -85,11 +86,13 @@ public interface ReleaseCallback<T extends ChunkSource> {
private final SampleQueue[] embeddedSampleQueues;
private final BaseMediaChunkOutput chunkOutput;

@Nullable private Chunk loadingChunk;
private @MonotonicNonNull Format primaryDownstreamTrackFormat;
@Nullable private ReleaseCallback<T> releaseCallback;
private long pendingResetPositionUs;
private long lastSeekPositionUs;
private int nextNotifyPrimaryFormatMediaChunkIndex;
@Nullable private BaseMediaChunk canceledMediaChunk;

/* package */ boolean loadingFinished;

Expand Down Expand Up @@ -144,7 +147,7 @@ public ChunkSampleStream(
primarySampleQueue =
new SampleQueue(
allocator,
/* playbackLooper= */ Assertions.checkNotNull(Looper.myLooper()),
/* playbackLooper= */ checkNotNull(Looper.myLooper()),
drmSessionManager,
drmEventDispatcher);
trackTypes[0] = primaryTrackType;
Expand All @@ -154,7 +157,7 @@ public ChunkSampleStream(
SampleQueue sampleQueue =
new SampleQueue(
allocator,
/* playbackLooper= */ Assertions.checkNotNull(Looper.myLooper()),
/* playbackLooper= */ checkNotNull(Looper.myLooper()),
DrmSessionManager.getDummyDrmSessionManager(),
drmEventDispatcher);
embeddedSampleQueues[i] = sampleQueue;
Expand Down Expand Up @@ -315,10 +318,7 @@ public void seekToUs(long positionUs) {
loader.cancelLoading();
} else {
loader.clearFatalError();
primarySampleQueue.reset();
for (SampleQueue embeddedSampleQueue : embeddedSampleQueues) {
embeddedSampleQueue.reset();
}
resetSampleQueues();
}
}
}
Expand Down Expand Up @@ -386,6 +386,13 @@ public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer,
if (isPendingReset()) {
return C.RESULT_NOTHING_READ;
}
if (canceledMediaChunk != null
&& canceledMediaChunk.getFirstSampleIndex(/* trackIndex= */ 0)
<= primarySampleQueue.getReadIndex()) {
// Don't read into chunk that's going to be discarded.
// TODO: Support splicing to allow this. See [internal b/161130873].
return C.RESULT_NOTHING_READ;
}
maybeNotifyPrimaryTrackFormatChanged();

return primarySampleQueue.read(formatHolder, buffer, formatRequired, loadingFinished);
Expand All @@ -397,6 +404,14 @@ public int skipData(long positionUs) {
return 0;
}
int skipCount = primarySampleQueue.getSkipCount(positionUs, loadingFinished);
if (canceledMediaChunk != null) {
// Don't skip into chunk that's going to be discarded.
// TODO: Support splicing to allow this. See [internal b/161130873].
int maxSkipCount =
canceledMediaChunk.getFirstSampleIndex(/* trackIndex= */ 0)
- primarySampleQueue.getReadIndex();
skipCount = min(skipCount, maxSkipCount);
}
primarySampleQueue.skip(skipCount);
maybeNotifyPrimaryTrackFormatChanged();
return skipCount;
Expand All @@ -406,6 +421,7 @@ public int skipData(long positionUs) {

@Override
public void onLoadCompleted(Chunk loadable, long elapsedRealtimeMs, long loadDurationMs) {
loadingChunk = null;
chunkSource.onChunkLoadCompleted(loadable);
LoadEventInfo loadEventInfo =
new LoadEventInfo(
Expand All @@ -432,6 +448,8 @@ public void onLoadCompleted(Chunk loadable, long elapsedRealtimeMs, long loadDur
@Override
public void onLoadCanceled(
Chunk loadable, long elapsedRealtimeMs, long loadDurationMs, boolean released) {
loadingChunk = null;
canceledMediaChunk = null;
LoadEventInfo loadEventInfo =
new LoadEventInfo(
loadable.loadTaskId,
Expand All @@ -452,9 +470,14 @@ public void onLoadCanceled(
loadable.startTimeUs,
loadable.endTimeUs);
if (!released) {
primarySampleQueue.reset();
for (SampleQueue embeddedSampleQueue : embeddedSampleQueues) {
embeddedSampleQueue.reset();
if (isPendingReset()) {
resetSampleQueues();
} else if (isMediaChunk(loadable)) {
// TODO: Support splicing to keep data from canceled chunk. See [internal b/161130873].
discardUpstreamMediaChunksFromIndex(mediaChunks.size() - 1);
if (mediaChunks.isEmpty()) {
pendingResetPositionUs = lastSeekPositionUs;
}
}
callback.onContinueLoadingRequested(this);
}
Expand Down Expand Up @@ -535,6 +558,7 @@ public LoadErrorAction onLoadError(
error,
canceled);
if (canceled) {
loadingChunk = null;
loadErrorHandlingPolicy.onLoadTaskConcluded(loadable.loadTaskId);
callback.onContinueLoadingRequested(this);
}
Expand Down Expand Up @@ -574,6 +598,7 @@ public boolean continueLoading(long positionUs) {
return false;
}

loadingChunk = loadable;
if (isMediaChunk(loadable)) {
BaseMediaChunk mediaChunk = (BaseMediaChunk) loadable;
if (pendingReset) {
Expand Down Expand Up @@ -625,19 +650,41 @@ public long getNextLoadPositionUs() {

@Override
public void reevaluateBuffer(long positionUs) {
if (loader.isLoading() || loader.hasFatalError() || isPendingReset()) {
if (loader.hasFatalError() || isPendingReset()) {
return;
}

int currentQueueSize = mediaChunks.size();
int preferredQueueSize = chunkSource.getPreferredQueueSize(positionUs, readOnlyMediaChunks);
if (currentQueueSize <= preferredQueueSize) {
if (loader.isLoading()) {
Chunk loadingChunk = checkNotNull(this.loadingChunk);
if (isMediaChunk(loadingChunk)
&& haveReadFromMediaChunk(/* mediaChunkIndex= */ mediaChunks.size() - 1)) {
// Can't cancel anymore because the renderers have read from this chunk.
return;
}
if (chunkSource.shouldCancelLoad(positionUs, loadingChunk, readOnlyMediaChunks)) {
loader.cancelLoading();
if (isMediaChunk(loadingChunk)) {
canceledMediaChunk = (BaseMediaChunk) loadingChunk;
}
}
return;
}

int preferredQueueSize = chunkSource.getPreferredQueueSize(positionUs, readOnlyMediaChunks);
if (preferredQueueSize < mediaChunks.size()) {
discardUpstream(preferredQueueSize);
}
}

private void discardUpstream(int preferredQueueSize) {
Assertions.checkState(!loader.isLoading());

int currentQueueSize = mediaChunks.size();
int newQueueSize = C.LENGTH_UNSET;
for (int i = preferredQueueSize; i < currentQueueSize; i++) {
if (!haveReadFromMediaChunk(i)) {
// TODO: Sparse tracks (e.g. ESMG) may prevent discarding in almost all cases because it
// means that most chunks have been read from already. See [internal b/161126666].
newQueueSize = i;
break;
}
Expand All @@ -656,12 +703,17 @@ public void reevaluateBuffer(long positionUs) {
primaryTrackType, firstRemovedChunk.startTimeUs, endTimeUs);
}

// Internal methods

private boolean isMediaChunk(Chunk chunk) {
return chunk instanceof BaseMediaChunk;
}

private void resetSampleQueues() {
primarySampleQueue.reset();
for (SampleQueue embeddedSampleQueue : embeddedSampleQueues) {
embeddedSampleQueue.reset();
}
}

/** Returns whether samples have been read from media chunk at given index. */
private boolean haveReadFromMediaChunk(int mediaChunkIndex) {
BaseMediaChunk mediaChunk = mediaChunks.get(mediaChunkIndex);
Expand Down Expand Up @@ -788,9 +840,19 @@ public int skipData(long positionUs) {
if (isPendingReset()) {
return 0;
}
maybeNotifyDownstreamFormat();
int skipCount = sampleQueue.getSkipCount(positionUs, loadingFinished);
if (canceledMediaChunk != null) {
// Don't skip into chunk that's going to be discarded.
// TODO: Support splicing to allow this. See [internal b/161130873].
int maxSkipCount =
canceledMediaChunk.getFirstSampleIndex(/* trackIndex= */ 1 + index)
- sampleQueue.getReadIndex();
skipCount = min(skipCount, maxSkipCount);
}
sampleQueue.skip(skipCount);
if (skipCount > 0) {
maybeNotifyDownstreamFormat();
}
return skipCount;
}

Expand All @@ -805,6 +867,13 @@ public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer,
if (isPendingReset()) {
return C.RESULT_NOTHING_READ;
}
if (canceledMediaChunk != null
&& canceledMediaChunk.getFirstSampleIndex(/* trackIndex= */ 1 + index)
<= sampleQueue.getReadIndex()) {
// Don't read into chunk that's going to be discarded.
// TODO: Support splicing to allow this. See [internal b/161130873].
return C.RESULT_NOTHING_READ;
}
maybeNotifyDownstreamFormat();
return sampleQueue.read(formatHolder, buffer, formatRequired, loadingFinished);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,23 @@ public interface ChunkSource {
*
* <p>Will only be called if no {@link MediaChunk} in the queue is currently loading.
*
* @param playbackPositionUs The current playback position.
* @param playbackPositionUs The current playback position, in microseconds.
* @param queue The queue of buffered {@link MediaChunk}s.
* @return The preferred queue size.
*/
int getPreferredQueueSize(long playbackPositionUs, List<? extends MediaChunk> 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.
*/
boolean shouldCancelLoad(
long playbackPositionUs, Chunk loadingChunk, List<? extends MediaChunk> queue);

/**
* Returns the next chunk to load.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,15 @@ public int getPreferredQueueSize(long playbackPositionUs, List<? extends MediaCh
return trackSelection.evaluateQueueSize(playbackPositionUs, queue);
}

@Override
public boolean shouldCancelLoad(
long playbackPositionUs, Chunk loadingChunk, List<? extends MediaChunk> queue) {
if (fatalError != null) {
return false;
}
return trackSelection.shouldCancelChunkLoad(playbackPositionUs, loadingChunk, queue);
}

@Override
public void getNextChunk(
long playbackPositionUs,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1133,6 +1133,8 @@ private boolean canDiscardUpstreamMediaChunksFromIndex(int mediaChunkIndex) {
int discardFromIndex = mediaChunk.getFirstSampleIndex(/* sampleQueueIndex= */ i);
if (sampleQueues[i].getReadIndex() > discardFromIndex) {
// Discarding not possible because we already read from the chunk.
// TODO: Sparse tracks (e.g. ID3) may prevent discarding in almost all cases because it
// means that most chunks have been read from already. See [internal b/161126666].
return false;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,15 @@ public int getPreferredQueueSize(long playbackPositionUs, List<? extends MediaCh
return trackSelection.evaluateQueueSize(playbackPositionUs, queue);
}

@Override
public boolean shouldCancelLoad(
long playbackPositionUs, Chunk loadingChunk, List<? extends MediaChunk> queue) {
if (fatalError != null) {
return false;
}
return trackSelection.shouldCancelChunkLoad(playbackPositionUs, loadingChunk, queue);
}

@Override
public final void getNextChunk(
long playbackPositionUs,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,12 @@ public int getPreferredQueueSize(long playbackPositionUs, List<? extends MediaCh
return trackSelection.evaluateQueueSize(playbackPositionUs, queue);
}

@Override
public boolean shouldCancelLoad(
long playbackPositionUs, Chunk loadingChunk, List<? extends MediaChunk> queue) {
return trackSelection.shouldCancelChunkLoad(playbackPositionUs, loadingChunk, queue);
}

@Override
public void getNextChunk(
long playbackPositionUs,
Expand Down

0 comments on commit d625af6

Please sign in to comment.