Commit 2e749f70 by tonihei Committed by Oliver Woodman

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
parent 311d21bf
...@@ -25,7 +25,6 @@ import com.google.android.exoplayer2.extractor.ExtractorInput; ...@@ -25,7 +25,6 @@ import com.google.android.exoplayer2.extractor.ExtractorInput;
import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.metadata.id3.Id3Decoder; import com.google.android.exoplayer2.metadata.id3.Id3Decoder;
import com.google.android.exoplayer2.metadata.id3.PrivFrame; 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.chunk.MediaChunk;
import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist; import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
...@@ -35,7 +34,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray; ...@@ -35,7 +34,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.TimestampAdjuster; import com.google.android.exoplayer2.util.TimestampAdjuster;
import com.google.android.exoplayer2.util.UriUtil; import com.google.android.exoplayer2.util.UriUtil;
import com.google.android.exoplayer2.util.Util; 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.EOFException;
import java.io.IOException; import java.io.IOException;
import java.io.InterruptedIOException; import java.io.InterruptedIOException;
...@@ -132,7 +131,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -132,7 +131,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
Id3Decoder id3Decoder; Id3Decoder id3Decoder;
ParsableByteArray scratchId3Data; ParsableByteArray scratchId3Data;
boolean shouldSpliceIn; boolean shouldSpliceIn;
ImmutableMap<SampleQueue, Integer> sampleQueueDiscardFromIndices = ImmutableMap.of();
if (previousChunk != null) { if (previousChunk != null) {
boolean isFollowingChunk = boolean isFollowingChunk =
playlistUrl.equals(previousChunk.playlistUrl) && previousChunk.loadCompleted; playlistUrl.equals(previousChunk.playlistUrl) && previousChunk.loadCompleted;
...@@ -143,9 +141,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -143,9 +141,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|| (mediaPlaylist.hasIndependentSegments || (mediaPlaylist.hasIndependentSegments
&& segmentStartTimeInPeriodUs >= previousChunk.endTimeUs); && segmentStartTimeInPeriodUs >= previousChunk.endTimeUs);
shouldSpliceIn = !canContinueWithoutSplice; shouldSpliceIn = !canContinueWithoutSplice;
if (shouldSpliceIn) {
sampleQueueDiscardFromIndices = previousChunk.sampleQueueDiscardFromIndices;
}
previousExtractor = previousExtractor =
isFollowingChunk isFollowingChunk
&& previousChunk.discontinuitySequenceNumber == discontinuitySequenceNumber && previousChunk.discontinuitySequenceNumber == discontinuitySequenceNumber
...@@ -181,8 +176,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -181,8 +176,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
previousExtractor, previousExtractor,
id3Decoder, id3Decoder,
scratchId3Data, scratchId3Data,
shouldSpliceIn, shouldSpliceIn);
sampleQueueDiscardFromIndices);
} }
public static final String PRIV_TIMESTAMP_FRAME_OWNER = public static final String PRIV_TIMESTAMP_FRAME_OWNER =
...@@ -203,6 +197,9 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -203,6 +197,9 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
/** The url of the playlist from which this chunk was obtained. */ /** The url of the playlist from which this chunk was obtained. */
public final Uri playlistUrl; 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 DataSource initDataSource;
@Nullable private final DataSpec initDataSpec; @Nullable private final DataSpec initDataSpec;
@Nullable private final HlsMediaChunkExtractor previousExtractor; @Nullable private final HlsMediaChunkExtractor previousExtractor;
...@@ -217,7 +214,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -217,7 +214,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
private final ParsableByteArray scratchId3Data; private final ParsableByteArray scratchId3Data;
private final boolean mediaSegmentEncrypted; private final boolean mediaSegmentEncrypted;
private final boolean initSegmentEncrypted; private final boolean initSegmentEncrypted;
private final boolean shouldSpliceIn;
private @MonotonicNonNull HlsMediaChunkExtractor extractor; private @MonotonicNonNull HlsMediaChunkExtractor extractor;
private @MonotonicNonNull HlsSampleStreamWrapper output; private @MonotonicNonNull HlsSampleStreamWrapper output;
...@@ -227,7 +223,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -227,7 +223,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
private boolean initDataLoadRequired; private boolean initDataLoadRequired;
private volatile boolean loadCanceled; private volatile boolean loadCanceled;
private boolean loadCompleted; private boolean loadCompleted;
private ImmutableMap<SampleQueue, Integer> sampleQueueDiscardFromIndices; private ImmutableList<Integer> sampleQueueFirstSampleIndices;
private HlsMediaChunk( private HlsMediaChunk(
HlsExtractorFactory extractorFactory, HlsExtractorFactory extractorFactory,
...@@ -253,8 +249,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -253,8 +249,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
@Nullable HlsMediaChunkExtractor previousExtractor, @Nullable HlsMediaChunkExtractor previousExtractor,
Id3Decoder id3Decoder, Id3Decoder id3Decoder,
ParsableByteArray scratchId3Data, ParsableByteArray scratchId3Data,
boolean shouldSpliceIn, boolean shouldSpliceIn) {
ImmutableMap<SampleQueue, Integer> sampleQueueDiscardFromIndices) {
super( super(
mediaDataSource, mediaDataSource,
dataSpec, dataSpec,
...@@ -281,7 +276,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -281,7 +276,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
this.id3Decoder = id3Decoder; this.id3Decoder = id3Decoder;
this.scratchId3Data = scratchId3Data; this.scratchId3Data = scratchId3Data;
this.shouldSpliceIn = shouldSpliceIn; this.shouldSpliceIn = shouldSpliceIn;
this.sampleQueueDiscardFromIndices = sampleQueueDiscardFromIndices; sampleQueueFirstSampleIndices = ImmutableList.of();
uid = uidSource.getAndIncrement(); uid = uidSource.getAndIncrement();
} }
...@@ -289,35 +284,29 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -289,35 +284,29 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
* Initializes the chunk for loading. * Initializes the chunk for loading.
* *
* @param output The {@link HlsSampleStreamWrapper} that will receive the loaded samples. * @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; this.output = output;
if (shouldSpliceIn) { this.sampleQueueFirstSampleIndices = sampleQueueWriteIndices;
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();
}
} }
/** /**
* Returns the absolute index from which samples need to be discarded in the given {@link * Returns the first sample index of this chunk in the specified sample queue in the output.
* SampleQueue} when this media chunk is discarded.
* *
* @param sampleQueue The {@link SampleQueue}. * <p>Must not be used if {@link #shouldSpliceIn} is true.
* @return The absolute index from which samples need to be discarded. *
* @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) { int getFirstSampleIndex(int sampleQueueIndex) {
// If the sample queue was created by this chunk or a later chunk, return 0 to discard the whole Assertions.checkState(!shouldSpliceIn);
// stream from the beginning. if (sampleQueueIndex >= sampleQueueFirstSampleIndices.size()) {
return sampleQueueDiscardFromIndices.getOrDefault(sampleQueue, /* defaultValue= */ 0); // The sample queue was created by this chunk or a later chunk.
return 0;
}
return sampleQueueFirstSampleIndices.get(sampleQueueIndex);
} }
@Override @Override
......
...@@ -61,6 +61,7 @@ import com.google.android.exoplayer2.util.MediaSourceEventDispatcher; ...@@ -61,6 +61,7 @@ import com.google.android.exoplayer2.util.MediaSourceEventDispatcher;
import com.google.android.exoplayer2.util.MimeTypes; 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 java.io.EOFException; import java.io.EOFException;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
...@@ -873,9 +874,16 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -873,9 +874,16 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
upstreamTrackFormat = chunk.trackFormat; upstreamTrackFormat = chunk.trackFormat;
pendingResetPositionUs = C.TIME_UNSET; pendingResetPositionUs = C.TIME_UNSET;
mediaChunks.add(chunk); 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) { for (HlsSampleQueue sampleQueue : sampleQueues) {
sampleQueue.setSourceChunk(chunk); sampleQueue.setSourceChunk(chunk);
if (chunk.shouldSpliceIn) {
sampleQueue.splice();
}
} }
} }
...@@ -884,7 +892,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -884,7 +892,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
int newQueueSize = C.LENGTH_UNSET; int newQueueSize = C.LENGTH_UNSET;
for (int i = preferredQueueSize; i < mediaChunks.size(); i++) { for (int i = preferredQueueSize; i < mediaChunks.size(); i++) {
if (!haveReadFromMediaChunkDiscardRange(i)) { if (canDiscardUpstreamMediaChunksFromIndex(i)) {
newQueueSize = i; newQueueSize = i;
break; break;
} }
...@@ -1102,23 +1110,32 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -1102,23 +1110,32 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
return true; return true;
} }
private boolean haveReadFromMediaChunkDiscardRange(int mediaChunkIndex) { 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;
}
}
HlsMediaChunk mediaChunk = mediaChunks.get(mediaChunkIndex); HlsMediaChunk mediaChunk = mediaChunks.get(mediaChunkIndex);
for (SampleQueue sampleQueue : sampleQueues) { for (int i = 0; i < sampleQueues.length; i++) {
int discardFromIndex = mediaChunk.getSampleQueueDiscardFromIndex(sampleQueue); int discardFromIndex = mediaChunk.getFirstSampleIndex(/* sampleQueueIndex= */ i);
if (sampleQueue.getReadIndex() > discardFromIndex) { if (sampleQueues[i].getReadIndex() > discardFromIndex) {
return true; // Discarding not possible because we already read from the chunk.
return false;
} }
} }
return false; return true;
} }
private HlsMediaChunk discardUpstreamMediaChunksFromIndex(int chunkIndex) { private HlsMediaChunk discardUpstreamMediaChunksFromIndex(int chunkIndex) {
HlsMediaChunk firstRemovedChunk = mediaChunks.get(chunkIndex); HlsMediaChunk firstRemovedChunk = mediaChunks.get(chunkIndex);
Util.removeRange(mediaChunks, /* fromIndex= */ chunkIndex, /* toIndex= */ mediaChunks.size()); Util.removeRange(mediaChunks, /* fromIndex= */ chunkIndex, /* toIndex= */ mediaChunks.size());
for (SampleQueue sampleQueue : sampleQueues) { for (int i = 0; i < sampleQueues.length; i++) {
int discardFromIndex = firstRemovedChunk.getSampleQueueDiscardFromIndex(sampleQueue); int discardFromIndex = firstRemovedChunk.getFirstSampleIndex(/* sampleQueueIndex= */ i);
sampleQueue.discardUpstreamSamples(discardFromIndex); sampleQueues[i].discardUpstreamSamples(discardFromIndex);
} }
return firstRemovedChunk; return firstRemovedChunk;
} }
......
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