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 f18154bb76..a8cf1a1437 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 @@ -223,9 +223,6 @@ import java.util.List; * started, the value will be the starting position in the period minus the duration of any * media in previous periods still to be played. * @param loadPositionUs The current load position relative to the period start in microseconds. - * If {@code queue} is empty, this is the starting position from which chunks should be - * provided. Else it's equal to {@link HlsMediaChunk#endTimeUs} of the last chunk in the - * {@code queue}. * @param queue The queue of buffered {@link HlsMediaChunk}s. * @param out A holder to populate. */ @@ -237,12 +234,12 @@ import java.util.List; long bufferedDurationUs = loadPositionUs - playbackPositionUs; long timeToLiveEdgeUs = resolveTimeToLiveEdgeUs(playbackPositionUs); if (previous != null && !independentSegments) { - // Unless segments are known to be independent, switching variant will require downloading - // overlapping segments. Hence we will subtract previous chunk's duration from buffered + // Unless segments are known to be independent, switching variant requires downloading + // overlapping segments. Hence we subtract the previous segment's duration from the buffered // duration. - // This may affect the live-streaming adaptive track selection logic, when we are comparing - // buffered duration to time to live edge to decide whether to switch. Therefore, - // we will subtract this same amount from timeToLiveEdgeUs as well. + // This may affect the live-streaming adaptive track selection logic, when we compare the + // buffered duration to time-to-live-edge to decide whether to switch. Therefore, we subtract + // the duration of the last loaded segment from timeToLiveEdgeUs as well. long subtractedDurationUs = previous.getDurationUs(); bufferedDurationUs = Math.max(0, bufferedDurationUs - subtractedDurationUs); if (timeToLiveEdgeUs != C.TIME_UNSET) { @@ -420,7 +417,7 @@ import java.util.List; } /** - * Returns list of {@link MediaChunkIterator}s for upcoming media chunks. + * Returns an array of {@link MediaChunkIterator}s for upcoming media chunks. * * @param previous The previous media chunk. May be null. * @param loadPositionUs The position at which the iterators will start. @@ -458,6 +455,18 @@ import java.util.List; // Private methods. + /** + * Returns the media sequence number of the segment to load next in {@code mediaPlaylist}. + * + * @param previous The last (at least partially) loaded segment. + * @param switchingVariant Whether the segment to load is not preceded by a segment in the same + * variant. + * @param mediaPlaylist The media playlist to which the segment to load belongs. + * @param startOfPlaylistInPeriodUs The start of {@code mediaPlaylist} relative to the period + * start in microseconds. + * @param loadPositionUs The current load position relative to the period start in microseconds. + * @return The media sequence of the segment to load. + */ private long getChunkMediaSequence( @Nullable HlsMediaChunk previous, boolean switchingVariant, @@ -480,6 +489,8 @@ import java.util.List; /* stayInBounds= */ !playlistTracker.isLive() || previous == null) + mediaPlaylist.mediaSequence; } + // We ignore the case of previous not having loaded completely, in which case we load the next + // segment. return previous.getNextChunkIndex(); } 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 37fa8647ec..0cfe31f3b9 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 @@ -152,7 +152,7 @@ import java.util.concurrent.atomic.AtomicInteger; if (previousChunk != null) { id3Decoder = previousChunk.id3Decoder; id3Data = previousChunk.id3Data; - shouldSpliceIn = previousChunk.hlsUrl != hlsUrl; + shouldSpliceIn = previousChunk.hlsUrl != hlsUrl || !previousChunk.loadCompleted; previousExtractor = previousChunk.discontinuitySequenceNumber != discontinuitySequenceNumber || shouldSpliceIn ? null : previousChunk.extractor; } else { 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 d85a3fa8ae..e60dacb8f4 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 @@ -460,9 +460,7 @@ import java.util.List; && finishedReadingChunk(mediaChunks.get(discardToMediaChunkIndex))) { discardToMediaChunkIndex++; } - if (discardToMediaChunkIndex > 0) { - Util.removeRange(mediaChunks, 0, discardToMediaChunkIndex); - } + Util.removeRange(mediaChunks, 0, discardToMediaChunkIndex); HlsMediaChunk currentChunk = mediaChunks.get(0); Format trackFormat = currentChunk.trackFormat; if (!trackFormat.equals(downstreamTrackFormat)) { @@ -554,7 +552,11 @@ import java.util.List; loadPositionUs = pendingResetPositionUs; } else { chunkQueue = readOnlyMediaChunks; - loadPositionUs = getLastMediaChunk().endTimeUs; + HlsMediaChunk lastMediaChunk = getLastMediaChunk(); + loadPositionUs = + lastMediaChunk.isLoadCompleted() + ? lastMediaChunk.endTimeUs + : Math.max(lastSeekPositionUs, lastMediaChunk.startTimeUs); } chunkSource.getNextChunk(positionUs, loadPositionUs, chunkQueue, nextChunkHolder); boolean endOfStream = nextChunkHolder.endOfStream; @@ -666,17 +668,15 @@ import java.util.List; boolean blacklistSucceeded = false; LoadErrorAction loadErrorAction; - if (!isMediaChunk || bytesLoaded == 0) { - long blacklistDurationMs = - loadErrorHandlingPolicy.getBlacklistDurationMsFor( - loadable.type, loadDurationMs, error, errorCount); - if (blacklistDurationMs != C.TIME_UNSET) { - blacklistSucceeded = chunkSource.maybeBlacklistTrack(loadable, blacklistDurationMs); - } + long blacklistDurationMs = + loadErrorHandlingPolicy.getBlacklistDurationMsFor( + loadable.type, loadDurationMs, error, errorCount); + if (blacklistDurationMs != C.TIME_UNSET) { + blacklistSucceeded = chunkSource.maybeBlacklistTrack(loadable, blacklistDurationMs); } if (blacklistSucceeded) { - if (isMediaChunk) { + if (isMediaChunk && bytesLoaded == 0) { HlsMediaChunk removed = mediaChunks.remove(mediaChunks.size() - 1); Assertions.checkState(removed == loadable); if (mediaChunks.isEmpty()) { @@ -707,7 +707,7 @@ import java.util.List; loadable.endTimeUs, elapsedRealtimeMs, loadDurationMs, - loadable.bytesLoaded(), + bytesLoaded, error, /* wasCanceled= */ !loadErrorAction.isRetry()); diff --git a/playbacktests/build.gradle b/playbacktests/build.gradle index a06a3160f1..fe16d3b2c7 100644 --- a/playbacktests/build.gradle +++ b/playbacktests/build.gradle @@ -33,6 +33,7 @@ android { dependencies { androidTestImplementation 'androidx.test:rules:' + testRunnerVersion androidTestImplementation 'androidx.test:runner:' + testRunnerVersion + androidTestImplementation 'com.android.support:support-annotations:' + supportLibraryVersion androidTestImplementation project(modulePrefix + 'library-core') androidTestImplementation project(modulePrefix + 'library-dash') androidTestImplementation project(modulePrefix + 'library-hls')