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
This commit is contained in:
parent
33af7a4536
commit
a6be8eeb6b
@ -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;
|
|||||||
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;
|
|||||||
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;
|
|||||||
* @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;
|
|||||||
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;
|
|||||||
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;
|
|||||||
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;
|
|||||||
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;
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user