Commit d625af67 by tonihei Committed by Oliver Woodman

Add load cancelation support to DASH and SS

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
parent 32d1a787
...@@ -200,6 +200,8 @@ ...@@ -200,6 +200,8 @@
decoders. decoders.
* DASH: * DASH:
* Enable support for embedded CEA-708. * Enable support for embedded CEA-708.
* Add support for load cancelation when discarding upstream
([#2848](https://github.com/google/ExoPlayer/issues/2848)).
* HLS: * HLS:
* Add support for upstream discard including cancelation of ongoing load * Add support for upstream discard including cancelation of ongoing load
([#6322](https://github.com/google/ExoPlayer/issues/6322)). ([#6322](https://github.com/google/ExoPlayer/issues/6322)).
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
*/ */
package com.google.android.exoplayer2.source.chunk; 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.max;
import static java.lang.Math.min; import static java.lang.Math.min;
...@@ -85,11 +86,13 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S ...@@ -85,11 +86,13 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
private final SampleQueue[] embeddedSampleQueues; private final SampleQueue[] embeddedSampleQueues;
private final BaseMediaChunkOutput chunkOutput; private final BaseMediaChunkOutput chunkOutput;
@Nullable private Chunk loadingChunk;
private @MonotonicNonNull Format primaryDownstreamTrackFormat; private @MonotonicNonNull Format primaryDownstreamTrackFormat;
@Nullable private ReleaseCallback<T> releaseCallback; @Nullable private ReleaseCallback<T> releaseCallback;
private long pendingResetPositionUs; private long pendingResetPositionUs;
private long lastSeekPositionUs; private long lastSeekPositionUs;
private int nextNotifyPrimaryFormatMediaChunkIndex; private int nextNotifyPrimaryFormatMediaChunkIndex;
@Nullable private BaseMediaChunk canceledMediaChunk;
/* package */ boolean loadingFinished; /* package */ boolean loadingFinished;
...@@ -144,7 +147,7 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S ...@@ -144,7 +147,7 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
primarySampleQueue = primarySampleQueue =
new SampleQueue( new SampleQueue(
allocator, allocator,
/* playbackLooper= */ Assertions.checkNotNull(Looper.myLooper()), /* playbackLooper= */ checkNotNull(Looper.myLooper()),
drmSessionManager, drmSessionManager,
drmEventDispatcher); drmEventDispatcher);
trackTypes[0] = primaryTrackType; trackTypes[0] = primaryTrackType;
...@@ -154,7 +157,7 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S ...@@ -154,7 +157,7 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
SampleQueue sampleQueue = SampleQueue sampleQueue =
new SampleQueue( new SampleQueue(
allocator, allocator,
/* playbackLooper= */ Assertions.checkNotNull(Looper.myLooper()), /* playbackLooper= */ checkNotNull(Looper.myLooper()),
DrmSessionManager.getDummyDrmSessionManager(), DrmSessionManager.getDummyDrmSessionManager(),
drmEventDispatcher); drmEventDispatcher);
embeddedSampleQueues[i] = sampleQueue; embeddedSampleQueues[i] = sampleQueue;
...@@ -315,10 +318,7 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S ...@@ -315,10 +318,7 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
loader.cancelLoading(); loader.cancelLoading();
} else { } else {
loader.clearFatalError(); loader.clearFatalError();
primarySampleQueue.reset(); resetSampleQueues();
for (SampleQueue embeddedSampleQueue : embeddedSampleQueues) {
embeddedSampleQueue.reset();
}
} }
} }
} }
...@@ -386,6 +386,13 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S ...@@ -386,6 +386,13 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
if (isPendingReset()) { if (isPendingReset()) {
return C.RESULT_NOTHING_READ; 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(); maybeNotifyPrimaryTrackFormatChanged();
return primarySampleQueue.read(formatHolder, buffer, formatRequired, loadingFinished); return primarySampleQueue.read(formatHolder, buffer, formatRequired, loadingFinished);
...@@ -397,6 +404,14 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S ...@@ -397,6 +404,14 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
return 0; return 0;
} }
int skipCount = primarySampleQueue.getSkipCount(positionUs, loadingFinished); 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); primarySampleQueue.skip(skipCount);
maybeNotifyPrimaryTrackFormatChanged(); maybeNotifyPrimaryTrackFormatChanged();
return skipCount; return skipCount;
...@@ -406,6 +421,7 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S ...@@ -406,6 +421,7 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
@Override @Override
public void onLoadCompleted(Chunk loadable, long elapsedRealtimeMs, long loadDurationMs) { public void onLoadCompleted(Chunk loadable, long elapsedRealtimeMs, long loadDurationMs) {
loadingChunk = null;
chunkSource.onChunkLoadCompleted(loadable); chunkSource.onChunkLoadCompleted(loadable);
LoadEventInfo loadEventInfo = LoadEventInfo loadEventInfo =
new LoadEventInfo( new LoadEventInfo(
...@@ -432,6 +448,8 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S ...@@ -432,6 +448,8 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
@Override @Override
public void onLoadCanceled( public void onLoadCanceled(
Chunk loadable, long elapsedRealtimeMs, long loadDurationMs, boolean released) { Chunk loadable, long elapsedRealtimeMs, long loadDurationMs, boolean released) {
loadingChunk = null;
canceledMediaChunk = null;
LoadEventInfo loadEventInfo = LoadEventInfo loadEventInfo =
new LoadEventInfo( new LoadEventInfo(
loadable.loadTaskId, loadable.loadTaskId,
...@@ -452,9 +470,14 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S ...@@ -452,9 +470,14 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
loadable.startTimeUs, loadable.startTimeUs,
loadable.endTimeUs); loadable.endTimeUs);
if (!released) { if (!released) {
primarySampleQueue.reset(); if (isPendingReset()) {
for (SampleQueue embeddedSampleQueue : embeddedSampleQueues) { resetSampleQueues();
embeddedSampleQueue.reset(); } 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); callback.onContinueLoadingRequested(this);
} }
...@@ -535,6 +558,7 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S ...@@ -535,6 +558,7 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
error, error,
canceled); canceled);
if (canceled) { if (canceled) {
loadingChunk = null;
loadErrorHandlingPolicy.onLoadTaskConcluded(loadable.loadTaskId); loadErrorHandlingPolicy.onLoadTaskConcluded(loadable.loadTaskId);
callback.onContinueLoadingRequested(this); callback.onContinueLoadingRequested(this);
} }
...@@ -574,6 +598,7 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S ...@@ -574,6 +598,7 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
return false; return false;
} }
loadingChunk = loadable;
if (isMediaChunk(loadable)) { if (isMediaChunk(loadable)) {
BaseMediaChunk mediaChunk = (BaseMediaChunk) loadable; BaseMediaChunk mediaChunk = (BaseMediaChunk) loadable;
if (pendingReset) { if (pendingReset) {
...@@ -625,19 +650,41 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S ...@@ -625,19 +650,41 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
@Override @Override
public void reevaluateBuffer(long positionUs) { public void reevaluateBuffer(long positionUs) {
if (loader.isLoading() || loader.hasFatalError() || isPendingReset()) { if (loader.hasFatalError() || isPendingReset()) {
return; return;
} }
int currentQueueSize = mediaChunks.size(); if (loader.isLoading()) {
int preferredQueueSize = chunkSource.getPreferredQueueSize(positionUs, readOnlyMediaChunks); Chunk loadingChunk = checkNotNull(this.loadingChunk);
if (currentQueueSize <= preferredQueueSize) { 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; 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; int newQueueSize = C.LENGTH_UNSET;
for (int i = preferredQueueSize; i < currentQueueSize; i++) { for (int i = preferredQueueSize; i < currentQueueSize; i++) {
if (!haveReadFromMediaChunk(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; newQueueSize = i;
break; break;
} }
...@@ -656,12 +703,17 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S ...@@ -656,12 +703,17 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
primaryTrackType, firstRemovedChunk.startTimeUs, endTimeUs); primaryTrackType, firstRemovedChunk.startTimeUs, endTimeUs);
} }
// Internal methods
private boolean isMediaChunk(Chunk chunk) { private boolean isMediaChunk(Chunk chunk) {
return chunk instanceof BaseMediaChunk; 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. */ /** Returns whether samples have been read from media chunk at given index. */
private boolean haveReadFromMediaChunk(int mediaChunkIndex) { private boolean haveReadFromMediaChunk(int mediaChunkIndex) {
BaseMediaChunk mediaChunk = mediaChunks.get(mediaChunkIndex); BaseMediaChunk mediaChunk = mediaChunks.get(mediaChunkIndex);
...@@ -788,9 +840,19 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S ...@@ -788,9 +840,19 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
if (isPendingReset()) { if (isPendingReset()) {
return 0; return 0;
} }
maybeNotifyDownstreamFormat();
int skipCount = sampleQueue.getSkipCount(positionUs, loadingFinished); 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); sampleQueue.skip(skipCount);
if (skipCount > 0) {
maybeNotifyDownstreamFormat();
}
return skipCount; return skipCount;
} }
...@@ -805,6 +867,13 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S ...@@ -805,6 +867,13 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
if (isPendingReset()) { if (isPendingReset()) {
return C.RESULT_NOTHING_READ; 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(); maybeNotifyDownstreamFormat();
return sampleQueue.read(formatHolder, buffer, formatRequired, loadingFinished); return sampleQueue.read(formatHolder, buffer, formatRequired, loadingFinished);
} }
......
...@@ -52,13 +52,24 @@ public interface ChunkSource { ...@@ -52,13 +52,24 @@ public interface ChunkSource {
* *
* <p>Will only be called if no {@link MediaChunk} in the queue is currently loading. * <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. * @param queue The queue of buffered {@link MediaChunk}s.
* @return The preferred queue size. * @return The preferred queue size.
*/ */
int getPreferredQueueSize(long playbackPositionUs, List<? extends MediaChunk> queue); 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. * Returns the next chunk to load.
* *
* <p>If a chunk is available then {@link ChunkHolder#chunk} is set. If the end of the stream has * <p>If a chunk is available then {@link ChunkHolder#chunk} is set. If the end of the stream has
......
...@@ -248,6 +248,15 @@ public class DefaultDashChunkSource implements DashChunkSource { ...@@ -248,6 +248,15 @@ public class DefaultDashChunkSource implements DashChunkSource {
} }
@Override @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( public void getNextChunk(
long playbackPositionUs, long playbackPositionUs,
long loadPositionUs, long loadPositionUs,
......
...@@ -1133,6 +1133,8 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -1133,6 +1133,8 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
int discardFromIndex = mediaChunk.getFirstSampleIndex(/* sampleQueueIndex= */ i); int discardFromIndex = mediaChunk.getFirstSampleIndex(/* sampleQueueIndex= */ i);
if (sampleQueues[i].getReadIndex() > discardFromIndex) { if (sampleQueues[i].getReadIndex() > discardFromIndex) {
// Discarding not possible because we already read from the chunk. // 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; return false;
} }
} }
......
...@@ -187,6 +187,15 @@ public class DefaultSsChunkSource implements SsChunkSource { ...@@ -187,6 +187,15 @@ public class DefaultSsChunkSource implements SsChunkSource {
} }
@Override @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( public final void getNextChunk(
long playbackPositionUs, long playbackPositionUs,
long loadPositionUs, long loadPositionUs,
......
...@@ -103,6 +103,12 @@ public final class FakeChunkSource implements ChunkSource { ...@@ -103,6 +103,12 @@ public final class FakeChunkSource implements ChunkSource {
} }
@Override @Override
public boolean shouldCancelLoad(
long playbackPositionUs, Chunk loadingChunk, List<? extends MediaChunk> queue) {
return trackSelection.shouldCancelChunkLoad(playbackPositionUs, loadingChunk, queue);
}
@Override
public void getNextChunk( public void getNextChunk(
long playbackPositionUs, long playbackPositionUs,
long loadPositionUs, long loadPositionUs,
......
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