Don't support upstream discard from spliced-in chunks.

We can't restore the previous state of the remaining chunk, so we can't
support discarding from spliced-in chunks. Mark this explicitly instead
of attempting to discard from the previous chunk.

PiperOrigin-RevId: 318983628
This commit is contained in:
tonihei 2020-06-30 10:25:49 +01:00 committed by Oliver Woodman
parent 311d21bf8d
commit 2e749f70ae
2 changed files with 53 additions and 47 deletions

View File

@ -25,7 +25,6 @@ import com.google.android.exoplayer2.extractor.ExtractorInput;
import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.metadata.id3.Id3Decoder;
import com.google.android.exoplayer2.metadata.id3.PrivFrame;
import com.google.android.exoplayer2.source.SampleQueue;
import com.google.android.exoplayer2.source.chunk.MediaChunk;
import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist;
import com.google.android.exoplayer2.upstream.DataSource;
@ -35,7 +34,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.TimestampAdjuster;
import com.google.android.exoplayer2.util.UriUtil;
import com.google.android.exoplayer2.util.Util;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableList;
import java.io.EOFException;
import java.io.IOException;
import java.io.InterruptedIOException;
@ -132,7 +131,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
Id3Decoder id3Decoder;
ParsableByteArray scratchId3Data;
boolean shouldSpliceIn;
ImmutableMap<SampleQueue, Integer> sampleQueueDiscardFromIndices = ImmutableMap.of();
if (previousChunk != null) {
boolean isFollowingChunk =
playlistUrl.equals(previousChunk.playlistUrl) && previousChunk.loadCompleted;
@ -143,9 +141,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|| (mediaPlaylist.hasIndependentSegments
&& segmentStartTimeInPeriodUs >= previousChunk.endTimeUs);
shouldSpliceIn = !canContinueWithoutSplice;
if (shouldSpliceIn) {
sampleQueueDiscardFromIndices = previousChunk.sampleQueueDiscardFromIndices;
}
previousExtractor =
isFollowingChunk
&& previousChunk.discontinuitySequenceNumber == discontinuitySequenceNumber
@ -181,8 +176,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
previousExtractor,
id3Decoder,
scratchId3Data,
shouldSpliceIn,
sampleQueueDiscardFromIndices);
shouldSpliceIn);
}
public static final String PRIV_TIMESTAMP_FRAME_OWNER =
@ -203,6 +197,9 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
/** The url of the playlist from which this chunk was obtained. */
public final Uri playlistUrl;
/** Whether samples for this chunk should be spliced into existing samples. */
public final boolean shouldSpliceIn;
@Nullable private final DataSource initDataSource;
@Nullable private final DataSpec initDataSpec;
@Nullable private final HlsMediaChunkExtractor previousExtractor;
@ -217,7 +214,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
private final ParsableByteArray scratchId3Data;
private final boolean mediaSegmentEncrypted;
private final boolean initSegmentEncrypted;
private final boolean shouldSpliceIn;
private @MonotonicNonNull HlsMediaChunkExtractor extractor;
private @MonotonicNonNull HlsSampleStreamWrapper output;
@ -227,7 +223,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
private boolean initDataLoadRequired;
private volatile boolean loadCanceled;
private boolean loadCompleted;
private ImmutableMap<SampleQueue, Integer> sampleQueueDiscardFromIndices;
private ImmutableList<Integer> sampleQueueFirstSampleIndices;
private HlsMediaChunk(
HlsExtractorFactory extractorFactory,
@ -253,8 +249,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
@Nullable HlsMediaChunkExtractor previousExtractor,
Id3Decoder id3Decoder,
ParsableByteArray scratchId3Data,
boolean shouldSpliceIn,
ImmutableMap<SampleQueue, Integer> sampleQueueDiscardFromIndices) {
boolean shouldSpliceIn) {
super(
mediaDataSource,
dataSpec,
@ -281,7 +276,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
this.id3Decoder = id3Decoder;
this.scratchId3Data = scratchId3Data;
this.shouldSpliceIn = shouldSpliceIn;
this.sampleQueueDiscardFromIndices = sampleQueueDiscardFromIndices;
sampleQueueFirstSampleIndices = ImmutableList.of();
uid = uidSource.getAndIncrement();
}
@ -289,35 +284,29 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
* Initializes the chunk for loading.
*
* @param output The {@link HlsSampleStreamWrapper} that will receive the loaded samples.
* @param sampleQueues The {@link SampleQueue sampleQueues} with already loaded samples.
* @param sampleQueueWriteIndices The current write indices in the existing sample queues of the
* output.
*/
public void init(HlsSampleStreamWrapper output, SampleQueue[] sampleQueues) {
public void init(HlsSampleStreamWrapper output, ImmutableList<Integer> sampleQueueWriteIndices) {
this.output = output;
if (shouldSpliceIn) {
for (SampleQueue sampleQueue : sampleQueues) {
sampleQueue.splice();
}
// sampleQueueDiscardFromIndices already set to values of previous chunk in constructor.
} else {
ImmutableMap.Builder<SampleQueue, Integer> mapBuilder = ImmutableMap.builder();
for (SampleQueue sampleQueue : sampleQueues) {
mapBuilder.put(sampleQueue, sampleQueue.getWriteIndex());
}
sampleQueueDiscardFromIndices = mapBuilder.build();
}
this.sampleQueueFirstSampleIndices = sampleQueueWriteIndices;
}
/**
* Returns the absolute index from which samples need to be discarded in the given {@link
* SampleQueue} when this media chunk is discarded.
* Returns the first sample index of this chunk in the specified sample queue in the output.
*
* @param sampleQueue The {@link SampleQueue}.
* @return The absolute index from which samples need to be discarded.
* <p>Must not be used if {@link #shouldSpliceIn} is true.
*
* @param sampleQueueIndex The index of the sample queue in the output.
* @return The first sample index of this chunk in the specified sample queue.
*/
int getSampleQueueDiscardFromIndex(SampleQueue sampleQueue) {
// If the sample queue was created by this chunk or a later chunk, return 0 to discard the whole
// stream from the beginning.
return sampleQueueDiscardFromIndices.getOrDefault(sampleQueue, /* defaultValue= */ 0);
int getFirstSampleIndex(int sampleQueueIndex) {
Assertions.checkState(!shouldSpliceIn);
if (sampleQueueIndex >= sampleQueueFirstSampleIndices.size()) {
// The sample queue was created by this chunk or a later chunk.
return 0;
}
return sampleQueueFirstSampleIndices.get(sampleQueueIndex);
}
@Override

View File

@ -61,6 +61,7 @@ import com.google.android.exoplayer2.util.MediaSourceEventDispatcher;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.Util;
import com.google.common.collect.ImmutableList;
import java.io.EOFException;
import java.io.IOException;
import java.util.ArrayList;
@ -873,9 +874,16 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
upstreamTrackFormat = chunk.trackFormat;
pendingResetPositionUs = C.TIME_UNSET;
mediaChunks.add(chunk);
chunk.init(/* output= */ this, sampleQueues);
ImmutableList.Builder<Integer> sampleQueueWriteIndicesBuilder = ImmutableList.builder();
for (SampleQueue sampleQueue : sampleQueues) {
sampleQueueWriteIndicesBuilder.add(sampleQueue.getWriteIndex());
}
chunk.init(/* output= */ this, sampleQueueWriteIndicesBuilder.build());
for (HlsSampleQueue sampleQueue : sampleQueues) {
sampleQueue.setSourceChunk(chunk);
if (chunk.shouldSpliceIn) {
sampleQueue.splice();
}
}
}
@ -884,7 +892,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
int newQueueSize = C.LENGTH_UNSET;
for (int i = preferredQueueSize; i < mediaChunks.size(); i++) {
if (!haveReadFromMediaChunkDiscardRange(i)) {
if (canDiscardUpstreamMediaChunksFromIndex(i)) {
newQueueSize = i;
break;
}
@ -1102,23 +1110,32 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
return true;
}
private boolean haveReadFromMediaChunkDiscardRange(int mediaChunkIndex) {
HlsMediaChunk mediaChunk = mediaChunks.get(mediaChunkIndex);
for (SampleQueue sampleQueue : sampleQueues) {
int discardFromIndex = mediaChunk.getSampleQueueDiscardFromIndex(sampleQueue);
if (sampleQueue.getReadIndex() > discardFromIndex) {
return true;
private boolean canDiscardUpstreamMediaChunksFromIndex(int mediaChunkIndex) {
for (int i = mediaChunkIndex; i < mediaChunks.size(); i++) {
if (mediaChunks.get(i).shouldSpliceIn) {
// Discarding not possible because a spliced-in chunk potentially removed sample metadata
// from the previous chunks.
// TODO: Keep sample metadata to allow restoring these chunks [internal b/159904763].
return false;
}
}
return false;
HlsMediaChunk mediaChunk = mediaChunks.get(mediaChunkIndex);
for (int i = 0; i < sampleQueues.length; i++) {
int discardFromIndex = mediaChunk.getFirstSampleIndex(/* sampleQueueIndex= */ i);
if (sampleQueues[i].getReadIndex() > discardFromIndex) {
// Discarding not possible because we already read from the chunk.
return false;
}
}
return true;
}
private HlsMediaChunk discardUpstreamMediaChunksFromIndex(int chunkIndex) {
HlsMediaChunk firstRemovedChunk = mediaChunks.get(chunkIndex);
Util.removeRange(mediaChunks, /* fromIndex= */ chunkIndex, /* toIndex= */ mediaChunks.size());
for (SampleQueue sampleQueue : sampleQueues) {
int discardFromIndex = firstRemovedChunk.getSampleQueueDiscardFromIndex(sampleQueue);
sampleQueue.discardUpstreamSamples(discardFromIndex);
for (int i = 0; i < sampleQueues.length; i++) {
int discardFromIndex = firstRemovedChunk.getFirstSampleIndex(/* sampleQueueIndex= */ i);
sampleQueues[i].discardUpstreamSamples(discardFromIndex);
}
return firstRemovedChunk;
}