diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java index 1a77715e71..a7caf2d7aa 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java @@ -451,6 +451,13 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; return chunkIterators; } + public int getPreferredQueueSize(long playbackPositionUs, List queue) { + if (fatalError != null || trackSelection.length() < 2) { + return queue.size(); + } + return trackSelection.evaluateQueueSize(playbackPositionUs, queue); + } + // Private methods. /** diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java index 8845228900..562d820c49 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java @@ -27,6 +27,7 @@ import com.google.android.exoplayer2.extractor.PositionHolder; 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; @@ -222,6 +223,12 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; private volatile boolean loadCanceled; private boolean loadCompleted; + /** + * Index of first sample written to the SampleQueue for the primary track from + * this segment. + */ + private int firstSampleIndex = C.INDEX_UNSET; + private HlsMediaChunk( HlsExtractorFactory extractorFactory, DataSource mediaDataSource, @@ -291,6 +298,15 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; return loadCompleted; } + /** + * Return the index of the first sample from the primary sample stream for this media chunk + * + * @return sample index {@link SampleQueue#getWriteIndex()} + */ + public int getFirstPrimarySampleIndex() { + return firstSampleIndex; + } + // Loadable implementation @Override @@ -308,6 +324,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; initDataLoadRequired = false; output.init(uid, shouldSpliceIn, /* reusingExtractor= */ true); } + firstSampleIndex = output.getPrimaryTrackWritePosition(); maybeLoadInitData(); if (!loadCanceled) { if (!hasGapTag) { diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java index d99bb817c1..4f4fe9e196 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java @@ -696,9 +696,65 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; @Override public void reevaluateBuffer(long positionUs) { - // Do nothing. + if (loader.isLoading() || isPendingReset()) { + return; + } + + int currentQueueSize = mediaChunks.size(); + int preferredQueueSize = chunkSource.getPreferredQueueSize(positionUs, readOnlyMediaChunks); + if (currentQueueSize > preferredQueueSize) { + Log.d(TAG, "reevaluateBuffer() - position: " + positionUs + " preferredQueueSize: " + + preferredQueueSize + " current size: "+ currentQueueSize); + + long firstRemovedStartTimeUs = discardMediaChunks(preferredQueueSize); + long endTimeUs = getLastMediaChunk().endTimeUs; + eventDispatcher.upstreamDiscarded(primarySampleQueueType, firstRemovedStartTimeUs, endTimeUs); + } } + + /** + * Discards HlsMediaChunks, after currently playing chunk {@see #haveReadFromMediaChunk}, that have + * not yet started to play to allow (hopefully) higher quality chunks to replace them + * + * @param preferredQueueSize - desired media chunk queue size (always < mediaChunks.size()) + * @return endTimeUs of first chunk removed + */ + private long discardMediaChunks(int preferredQueueSize) { + Log.d(TAG, "discardChunksToIndex() - preferredQueueSize " + preferredQueueSize + + " currentSize " + mediaChunks.size() + + " write: "+sampleQueues[primarySampleQueueIndex].getWriteIndex() + + " read: "+sampleQueues[primarySampleQueueIndex].getReadIndex() + ); + for (int i=0; i 0) { + firstRemovedChunkIndex++; + preferredQueueSize--; + } + + HlsMediaChunk firstRemovedChunk = mediaChunks.get(firstRemovedChunkIndex); + Util.removeRange(mediaChunks, firstRemovedChunkIndex, mediaChunks.size() - 1); + Log.d(TAG, "discardChunksToIndex() - discard from: " + firstRemovedChunk.getFirstPrimarySampleIndex()); + + sampleQueues[primarySampleQueueIndex].discardUpstreamSamples(firstRemovedChunk.getFirstPrimarySampleIndex()); + + return firstRemovedChunk.endTimeUs; + } + + + /** Returns whether samples have been read from primary sample queue of the indicated chunk */ + private boolean haveReadFromMediaChunk(int mediaChunkIndex) { + HlsMediaChunk mediaChunk = mediaChunks.get(mediaChunkIndex); + return sampleQueues[primarySampleQueueIndex].getReadIndex() > mediaChunk.getFirstPrimarySampleIndex(); + } // Loader.Callback implementation. @Override @@ -959,6 +1015,22 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; } } + /** + * Get the SampleQueue write position index. Used to associate group of + * samples with a MediaChunk. + * + * @return write position {@link SampleQueue#getWriteIndex()}, or 0 (safe bet) if sample queues not created + */ + int getPrimaryTrackWritePosition() { + int indexValue = 0; + + if (primaryTrackGroupIndex != C.INDEX_UNSET && prepared) { + int sampleQueueIndex = trackGroupToSampleQueueIndex[primaryTrackGroupIndex]; + indexValue = sampleQueues[sampleQueueIndex].getWriteIndex(); + } + return indexValue; + } + // Internal methods. private void updateSampleStreams(@NullableType SampleStream[] streams) {