Commit a6be8eeb by tonihei Committed by kim-vde

Prevent extractor reuse after upstream discard.

After discarding upstream we shouldn't reuse the extractor from the
(newly) last media chunk because the extractor may have been reused
already by the discarded chunks.

Also add an assertion to SampleQueue that prevents the hard-to-detect
failure mode of overlapping sample byte ranges.

Issue: #7690
PiperOrigin-RevId: 324785093
parent 33af7a45
...@@ -713,6 +713,13 @@ public class SampleQueue implements TrackOutput { ...@@ -713,6 +713,13 @@ public class SampleQueue implements TrackOutput {
long offset, long offset,
int size, int size,
@Nullable CryptoData cryptoData) { @Nullable CryptoData cryptoData) {
if (length > 0) {
// Ensure sample data doesn't overlap.
int previousSampleRelativeIndex = getRelativeIndex(length - 1);
checkArgument(
offsets[previousSampleRelativeIndex] + sizes[previousSampleRelativeIndex] <= offset);
}
isLastSampleQueued = (sampleFlags & C.BUFFER_FLAG_LAST_SAMPLE) != 0; isLastSampleQueued = (sampleFlags & C.BUFFER_FLAG_LAST_SAMPLE) != 0;
largestQueuedTimestampUs = max(largestQueuedTimestampUs, timeUs); largestQueuedTimestampUs = max(largestQueuedTimestampUs, timeUs);
......
...@@ -143,6 +143,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -143,6 +143,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
shouldSpliceIn = !canContinueWithoutSplice; shouldSpliceIn = !canContinueWithoutSplice;
previousExtractor = previousExtractor =
isFollowingChunk isFollowingChunk
&& !previousChunk.extractorInvalidated
&& previousChunk.discontinuitySequenceNumber == discontinuitySequenceNumber && previousChunk.discontinuitySequenceNumber == discontinuitySequenceNumber
? previousChunk.extractor ? previousChunk.extractor
: null; : null;
...@@ -224,6 +225,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -224,6 +225,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
private volatile boolean loadCanceled; private volatile boolean loadCanceled;
private boolean loadCompleted; private boolean loadCompleted;
private ImmutableList<Integer> sampleQueueFirstSampleIndices; private ImmutableList<Integer> sampleQueueFirstSampleIndices;
private boolean extractorInvalidated;
private HlsMediaChunk( private HlsMediaChunk(
HlsExtractorFactory extractorFactory, HlsExtractorFactory extractorFactory,
...@@ -300,7 +302,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -300,7 +302,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
* @param sampleQueueIndex The index of the sample queue in the output. * @param sampleQueueIndex The index of the sample queue in the output.
* @return The first sample index of this chunk in the specified sample queue. * @return The first sample index of this chunk in the specified sample queue.
*/ */
int getFirstSampleIndex(int sampleQueueIndex) { public int getFirstSampleIndex(int sampleQueueIndex) {
Assertions.checkState(!shouldSpliceIn); Assertions.checkState(!shouldSpliceIn);
if (sampleQueueIndex >= sampleQueueFirstSampleIndices.size()) { if (sampleQueueIndex >= sampleQueueFirstSampleIndices.size()) {
// The sample queue was created by this chunk or a later chunk. // The sample queue was created by this chunk or a later chunk.
...@@ -309,6 +311,11 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -309,6 +311,11 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
return sampleQueueFirstSampleIndices.get(sampleQueueIndex); return sampleQueueFirstSampleIndices.get(sampleQueueIndex);
} }
/** Prevents the extractor from being reused by a following media chunk. */
public void invalidateExtractor() {
extractorInvalidated = true;
}
@Override @Override
public boolean isLoadCompleted() { public boolean isLoadCompleted() {
return loadCompleted; return loadCompleted;
......
...@@ -64,6 +64,7 @@ import com.google.android.exoplayer2.util.MimeTypes; ...@@ -64,6 +64,7 @@ import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import java.io.EOFException; import java.io.EOFException;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
...@@ -833,6 +834,8 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -833,6 +834,8 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
Assertions.checkState(removed == loadable); Assertions.checkState(removed == loadable);
if (mediaChunks.isEmpty()) { if (mediaChunks.isEmpty()) {
pendingResetPositionUs = lastSeekPositionUs; pendingResetPositionUs = lastSeekPositionUs;
} else {
Iterables.getLast(mediaChunks).invalidateExtractor();
} }
} }
loadErrorAction = Loader.DONT_RETRY; loadErrorAction = Loader.DONT_RETRY;
...@@ -914,6 +917,8 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -914,6 +917,8 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
HlsMediaChunk firstRemovedChunk = discardUpstreamMediaChunksFromIndex(newQueueSize); HlsMediaChunk firstRemovedChunk = discardUpstreamMediaChunksFromIndex(newQueueSize);
if (mediaChunks.isEmpty()) { if (mediaChunks.isEmpty()) {
pendingResetPositionUs = lastSeekPositionUs; pendingResetPositionUs = lastSeekPositionUs;
} else {
Iterables.getLast(mediaChunks).invalidateExtractor();
} }
loadingFinished = false; loadingFinished = false;
......
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