From 57d516016189e68ef80063a61e08fcd306f8b1b8 Mon Sep 17 00:00:00 2001 From: Steve Mayhew Date: Fri, 16 Aug 2019 15:56:32 -0700 Subject: [PATCH 1/4] add support SequenceableLoader.reevaluateBuffer() for HLS DASH implements this feature, extend the feature for HLS as well. First change just drops video samples. For demuxed audio the audio samples will continue to play out to match the dropped video, so need to keep indexes in all the sample queues related to a chunk and discard them all. --- .../exoplayer2/source/hls/HlsChunkSource.java | 7 ++ .../exoplayer2/source/hls/HlsMediaChunk.java | 17 +++++ .../source/hls/HlsSampleStreamWrapper.java | 74 ++++++++++++++++++- 3 files changed, 97 insertions(+), 1 deletion(-) 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) { From a8755b5c253984420a03cd6171377dcfaacdc219 Mon Sep 17 00:00:00 2001 From: Steve Mayhew Date: Wed, 21 Aug 2019 15:38:27 -0700 Subject: [PATCH 2/4] Update to save all sample queue write indexes Finailzed logic to update the `SampleQueue` write positions (first index) to push these into `HlsMediaChunk` when the track is initially created (from the Extractor output) and as each new chunk is queued to load (`init()` callback). Add lots of debuging prints that can come out for the final merge. Code is very close to a clone of `ChunkSampleStream`. --- .../exoplayer2/source/hls/HlsMediaChunk.java | 18 ++- .../source/hls/HlsSampleStreamWrapper.java | 153 ++++++++++++------ 2 files changed, 119 insertions(+), 52 deletions(-) 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 562d820c49..aeccc6b8db 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 @@ -40,6 +40,7 @@ import com.google.android.exoplayer2.util.Util; import java.io.EOFException; import java.io.IOException; import java.math.BigInteger; +import java.util.Arrays; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import org.checkerframework.checker.nullness.qual.EnsuresNonNull; @@ -224,10 +225,10 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; private boolean loadCompleted; /** - * Index of first sample written to the SampleQueue for the primary track from - * this segment. + * Index of first sample written for each SampleQueue created from the extraction of this + * media chunk */ - private int firstSampleIndex = C.INDEX_UNSET; + private int [] firstSampleIndexes = new int[0]; private HlsMediaChunk( HlsExtractorFactory extractorFactory, @@ -303,8 +304,13 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; * * @return sample index {@link SampleQueue#getWriteIndex()} */ - public int getFirstPrimarySampleIndex() { - return firstSampleIndex; + int [] getFirstSampleIndexes() { + return firstSampleIndexes; + } + + void setFirstSampleIndex(int streamIndex, int firstSampleIndex) { + firstSampleIndexes = Arrays.copyOf(firstSampleIndexes, streamIndex + 1); + firstSampleIndexes[streamIndex] = firstSampleIndex; } // Loadable implementation @@ -324,7 +330,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; initDataLoadRequired = false; output.init(uid, shouldSpliceIn, /* reusingExtractor= */ true); } - firstSampleIndex = output.getPrimaryTrackWritePosition(); + firstSampleIndexes = output.getTrackWritePositions(); 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 4f4fe9e196..cb71cd5b36 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 @@ -62,6 +62,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; +import java.util.ListIterator; import java.util.Map; import java.util.Set; import org.checkerframework.checker.nullness.compatqual.NullableType; @@ -702,58 +703,86 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; 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); + if (currentQueueSize <= preferredQueueSize) { + return; } + + int newQueueSize = currentQueueSize; + for (int i = preferredQueueSize; i < currentQueueSize; i++) { + if (!haveReadFromMediaChunk(i)) { + newQueueSize = i; + break; + } + } + if (newQueueSize == currentQueueSize) { + return; + } + + Log.d(TAG, "Discarding MediaChunks - trackType: " + trackType + + " chunk count: " + currentQueueSize + + " target chunk count: " + newQueueSize); + + dumpCurrentChunkList(); + + long endTimeUs = getLastMediaChunk().endTimeUs; + HlsMediaChunk firstRemovedChunk = discardUpstreamMediaChunksFromIndex(newQueueSize); + if (mediaChunks.isEmpty()) { + pendingResetPositionUs = lastSeekPositionUs; + } + loadingFinished = false; + + dumpCurrentChunkList(); + eventDispatcher.upstreamDiscarded(primarySampleQueueType, firstRemovedChunk.startTimeUs, endTimeUs); + } + + /** + * Discard upstream media chunks from {@code chunkIndex} and corresponding samples from sample + * queues. + * + * @param chunkIndex The index of the first chunk to discard. + * @return The chunk at given index. + */ + private HlsMediaChunk discardUpstreamMediaChunksFromIndex(int chunkIndex) { + HlsMediaChunk firstRemovedChunk = mediaChunks.get(chunkIndex); + Util.removeRange(mediaChunks, /* fromIndex= */ chunkIndex, /* toIndex= */ mediaChunks.size()); + + + int [] firstSamples = firstRemovedChunk.getFirstSampleIndexes(); + for (int i=0; i < sampleQueues.length; i++) { + Log.d(TAG, "discardUpstreamSamples() - stream: " + " from index: " + firstSamples[i]); + + sampleQueues[i].discardUpstreamSamples(firstSamples[i]); + } + + dumpCurrentChunkList(); + + return firstRemovedChunk; } - /** - * 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() + public void dumpCurrentChunkList() { + Log.d(TAG, "Dump MediaChunks - trackType: " + trackType + " chunk count: " + mediaChunks.size() + + " primary sample write: "+sampleQueues[primarySampleQueueIndex].getWriteIndex() + + " primary sample 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(); + boolean haveRead = false; + int [] firstSamples = mediaChunk.getFirstSampleIndexes(); + for (int i=0; i < firstSamples.length; i++) { + haveRead = haveRead || sampleQueues[i].getReadIndex() > firstSamples[i]; + } + return haveRead; } // Loader.Callback implementation. @@ -887,8 +916,11 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; sampleQueueMappingDoneByType.clear(); } this.chunkUid = chunkUid; - for (SampleQueue sampleQueue : sampleQueues) { + HlsMediaChunk loadingChunk = findChunkMatching(chunkUid); + for (int i=0; i < sampleQueues.length; i++) { + SampleQueue sampleQueue = sampleQueues[i]; sampleQueue.sourceId(chunkUid); + loadingChunk.setFirstSampleIndex(i, sampleQueue.getWriteIndex()); } if (shouldSpliceIn) { for (SampleQueue sampleQueue : sampleQueues) { @@ -974,6 +1006,8 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; sampleQueueTrackIds = Arrays.copyOf(sampleQueueTrackIds, trackCount + 1); sampleQueueTrackIds[trackCount] = id; sampleQueues = Util.nullSafeArrayAppend(sampleQueues, trackOutput); + HlsMediaChunk mediaChunk = findChunkMatching(chunkUid); + mediaChunk.setFirstSampleIndex(trackCount, 0); sampleQueueIsAudioVideoFlags = Arrays.copyOf(sampleQueueIsAudioVideoFlags, trackCount + 1); sampleQueueIsAudioVideoFlags[trackCount] = type == C.TRACK_TYPE_AUDIO || type == C.TRACK_TYPE_VIDEO; @@ -1016,23 +1050,35 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; } /** - * Get the SampleQueue write position index. Used to associate group of + * Get the SampleQueue write position indexes. Used to associate group of * samples with a MediaChunk. * - * @return write position {@link SampleQueue#getWriteIndex()}, or 0 (safe bet) if sample queues not created + * @return array of write positions {@link SampleQueue#getWriteIndex()}, by sample queue index */ - int getPrimaryTrackWritePosition() { - int indexValue = 0; + int [] getTrackWritePositions() { + int [] writePositions; - if (primaryTrackGroupIndex != C.INDEX_UNSET && prepared) { - int sampleQueueIndex = trackGroupToSampleQueueIndex[primaryTrackGroupIndex]; - indexValue = sampleQueues[sampleQueueIndex].getWriteIndex(); + writePositions = new int[sampleQueues.length]; + for (int i=0; i < sampleQueues.length; i++) { + writePositions[i] = sampleQueues[i].getWriteIndex(); } - return indexValue; + + return writePositions; } // Internal methods. + private HlsMediaChunk findChunkMatching(int chunkUid) { + ListIterator iter = mediaChunks.listIterator(mediaChunks.size()); + while (iter.hasPrevious()) { + HlsMediaChunk chunk = (HlsMediaChunk) iter.previous(); + if (chunk.uid == chunkUid) { + return chunk; + } + } + return null; + } + private void updateSampleStreams(@NullableType SampleStream[] streams) { hlsSampleStreams.clear(); for (SampleStream stream : streams) { @@ -1082,6 +1128,21 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; // Tracks are created using media segment information. buildTracksFromSampleStreams(); setIsPrepared(); + + Log.d(TAG, "Wrapper prepared - trackType: " + trackType + " tracks: " + trackGroups.length + " sample queues: " + sampleQueues.length + " sample streams: " + hlsSampleStreams.size()); + + for (int i = 0; i < trackGroups.length; i++) { + TrackGroup group = trackGroups.get(i); + int sampleQueueIndex = this.trackGroupToSampleQueueIndex[i]; + if (sampleQueueIndex == C.INDEX_UNSET) { + Log.d(TAG, " track group " + i + " is unmapped, tracks: " + group.length); + } else { + Log.d(TAG, " track group " + i + " is maped to sample queue: " + sampleQueueIndex + " ,tracks: " + group.length); + for (int j=0; j Date: Wed, 21 Aug 2019 16:38:31 -0700 Subject: [PATCH 3/4] Remove unused methods, comment cleanup. --- .../exoplayer2/source/hls/HlsMediaChunk.java | 10 ++++++++-- .../source/hls/HlsSampleStreamWrapper.java | 17 ----------------- 2 files changed, 8 insertions(+), 19 deletions(-) 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 aeccc6b8db..0546fa5429 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 @@ -300,7 +300,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; } /** - * Return the index of the first sample from the primary sample stream for this media chunk + * Return the indexes of the first samples from each sample queue for this media chunk * * @return sample index {@link SampleQueue#getWriteIndex()} */ @@ -308,6 +308,13 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; return firstSampleIndexes; } + /** + * Set the index of the first sample written to a sample queue from this media chunk, + * indexed by the caller's stream index for the sample queue + * + * @param streamIndex - caller's index for the {@link SampleQueue} + * @param firstSampleIndex - index value to store + */ void setFirstSampleIndex(int streamIndex, int firstSampleIndex) { firstSampleIndexes = Arrays.copyOf(firstSampleIndexes, streamIndex + 1); firstSampleIndexes[streamIndex] = firstSampleIndex; @@ -330,7 +337,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; initDataLoadRequired = false; output.init(uid, shouldSpliceIn, /* reusingExtractor= */ true); } - firstSampleIndexes = output.getTrackWritePositions(); 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 cb71cd5b36..8d6db4b112 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 @@ -1049,23 +1049,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; } } - /** - * Get the SampleQueue write position indexes. Used to associate group of - * samples with a MediaChunk. - * - * @return array of write positions {@link SampleQueue#getWriteIndex()}, by sample queue index - */ - int [] getTrackWritePositions() { - int [] writePositions; - - writePositions = new int[sampleQueues.length]; - for (int i=0; i < sampleQueues.length; i++) { - writePositions[i] = sampleQueues[i].getWriteIndex(); - } - - return writePositions; - } - // Internal methods. private HlsMediaChunk findChunkMatching(int chunkUid) { From e4cb74057ae024af329e3dae6f25857746d6d21e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20Varga?= Date: Mon, 18 May 2020 13:41:07 +0200 Subject: [PATCH 4/4] add ability to interrupt HLS chunk download --- .../source/hls/HlsSampleStreamWrapper.java | 105 +++++++----------- 1 file changed, 42 insertions(+), 63 deletions(-) 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 a9baf53add..2eced4544b 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 @@ -68,7 +68,6 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; -import java.util.ListIterator; import java.util.Map; import java.util.Set; import org.checkerframework.checker.nullness.compatqual.NullableType; @@ -147,6 +146,8 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; private @MonotonicNonNull Format upstreamTrackFormat; @Nullable private Format downstreamTrackFormat; private boolean released; + private boolean interrupted; + private int preferredQueueSize; // Tracks are complicated in HLS. See documentation of buildTracksFromSampleStreams for details. // Indexed by track (as exposed by this source). @@ -697,12 +698,25 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; @Override public void reevaluateBuffer(long positionUs) { - if (loader.isLoading() || isPendingReset()) { + if (loader.hasFatalError() || isPendingReset()) { return; } int currentQueueSize = mediaChunks.size(); - int preferredQueueSize = chunkSource.getPreferredQueueSize(positionUs, readOnlyMediaChunks); + preferredQueueSize = chunkSource.getPreferredQueueSize(positionUs, readOnlyMediaChunks); + if (loader.isLoading()) { + if (currentQueueSize > preferredQueueSize) { + interrupted = true; + loader.cancelLoading(); + } + return; + } + + upstreamDiscard(); + } + + private void upstreamDiscard() { + int currentQueueSize = mediaChunks.size(); if (currentQueueSize <= preferredQueueSize) { return; } @@ -722,8 +736,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; + " chunk count: " + currentQueueSize + " target chunk count: " + newQueueSize); - dumpCurrentChunkList(); - long endTimeUs = getLastMediaChunk().endTimeUs; HlsMediaChunk firstRemovedChunk = discardUpstreamMediaChunksFromIndex(newQueueSize); if (mediaChunks.isEmpty()) { @@ -731,8 +743,8 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; } loadingFinished = false; - dumpCurrentChunkList(); - eventDispatcher.upstreamDiscarded(primarySampleQueueType, firstRemovedChunk.startTimeUs, endTimeUs); + eventDispatcher + .upstreamDiscarded(primarySampleQueueType, firstRemovedChunk.startTimeUs, endTimeUs); } /** @@ -746,40 +758,24 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; HlsMediaChunk firstRemovedChunk = mediaChunks.get(chunkIndex); Util.removeRange(mediaChunks, /* fromIndex= */ chunkIndex, /* toIndex= */ mediaChunks.size()); + int[] firstSamples = firstRemovedChunk.getFirstSampleIndexes(); + if (firstSamples.length != 0) { + for (int i = 0; i < sampleQueues.length; i++) { + Log.d(TAG, "discardUpstreamSamples() - stream: from index: " + firstSamples[i]); - int [] firstSamples = firstRemovedChunk.getFirstSampleIndexes(); - for (int i=0; i < sampleQueues.length; i++) { - Log.d(TAG, "discardUpstreamSamples() - stream: " + " from index: " + firstSamples[i]); - - sampleQueues[i].discardUpstreamSamples(firstSamples[i]); + sampleQueues[i].discardUpstreamSamples(firstSamples[i]); + } } - dumpCurrentChunkList(); - return firstRemovedChunk; } - - public void dumpCurrentChunkList() { - Log.d(TAG, "Dump MediaChunks - trackType: " + trackType + " chunk count: " + mediaChunks.size() - + " primary sample write: "+sampleQueues[primarySampleQueueIndex].getWriteIndex() - + " primary sample read: "+sampleQueues[primarySampleQueueIndex].getReadIndex() - ); - for (int i=0; i firstSamples[i]; } return haveRead; @@ -838,13 +834,23 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; loadable.startTimeUs, loadable.endTimeUs); if (!released) { - resetSampleQueues(); + if (interrupted) { + discardAfterInterrupt(); + } + else { + resetSampleQueues(); + } if (enabledTrackGroupCount > 0) { callback.onContinueLoadingRequested(this); } } } + private void discardAfterInterrupt() { + interrupted = false; + upstreamDiscard(); + } + @Override public LoadErrorAction onLoadError( Chunk loadable, @@ -938,11 +944,10 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; mediaChunks.add(chunk); chunk.init(this); - HlsMediaChunk loadingChunk = findChunkMatching(chunk.uid); - for (int i=0; i < sampleQueues.length; i++) { - SampleQueue sampleQueue = sampleQueues[i]; + for (int i = 0; i < sampleQueues.length; i++) { + HlsSampleQueue sampleQueue = sampleQueues[i]; sampleQueue.setSourceChunk(chunk); - loadingChunk.setFirstSampleIndex(i, sampleQueue.getWriteIndex()); + chunk.setFirstSampleIndex(i, sampleQueue.getWriteIndex()); } if (chunk.shouldSpliceIn) { for (SampleQueue sampleQueue : sampleQueues) { @@ -1039,8 +1044,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; sampleQueueTrackIds = Arrays.copyOf(sampleQueueTrackIds, trackCount + 1); sampleQueueTrackIds[trackCount] = id; sampleQueues = Util.nullSafeArrayAppend(sampleQueues, sampleQueue); - HlsMediaChunk mediaChunk = findChunkMatching(sourceChunk.uid); - mediaChunk.setFirstSampleIndex(trackCount, 0); + sourceChunk.setFirstSampleIndex(trackCount, sampleQueue.getWriteIndex()); sampleQueueIsAudioVideoFlags = Arrays.copyOf(sampleQueueIsAudioVideoFlags, trackCount + 1); sampleQueueIsAudioVideoFlags[trackCount] = isAudioVideo; haveAudioVideoSampleQueues |= sampleQueueIsAudioVideoFlags[trackCount]; @@ -1131,17 +1135,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; // Internal methods. - private HlsMediaChunk findChunkMatching(int chunkUid) { - ListIterator iter = mediaChunks.listIterator(mediaChunks.size()); - while (iter.hasPrevious()) { - HlsMediaChunk chunk = (HlsMediaChunk) iter.previous(); - if (chunk.uid == chunkUid) { - return chunk; - } - } - return null; - } - private void updateSampleStreams(@NullableType SampleStream[] streams) { hlsSampleStreams.clear(); for (@Nullable SampleStream stream : streams) { @@ -1192,20 +1185,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; buildTracksFromSampleStreams(); setIsPrepared(); - Log.d(TAG, "Wrapper prepared - trackType: " + trackType + " tracks: " + trackGroups.length + " sample queues: " + sampleQueues.length + " sample streams: " + hlsSampleStreams.size()); - - for (int i = 0; i < trackGroups.length; i++) { - TrackGroup group = trackGroups.get(i); - int sampleQueueIndex = this.trackGroupToSampleQueueIndex[i]; - if (sampleQueueIndex == C.INDEX_UNSET) { - Log.d(TAG, " track group " + i + " is unmapped, tracks: " + group.length); - } else { - Log.d(TAG, " track group " + i + " is maped to sample queue: " + sampleQueueIndex + " ,tracks: " + group.length); - for (int j=0; j