diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 6b5f4a0002b..dca6e13cc92 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -42,6 +42,8 @@ ([#5407](https://github.com/google/ExoPlayer/issues/5407)). * Deprecate `setTag` parameter of `Timeline.getWindow`. Tags will always be set. * Support out-of-band HDR10+ metadata for VP9 in WebM/Matroska. +* Fix issue where HLS streams get stuck in infinite buffering state after + postroll ad ([#6314](https://github.com/google/ExoPlayer/issues/6314)). ### 2.10.4 ### 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 c452a29cf92..370d79edc75 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 @@ -225,10 +225,17 @@ public void setIsTimestampMaster(boolean isTimestampMaster) { * media in previous periods still to be played. * @param loadPositionUs The current load position relative to the period start in microseconds. * @param queue The queue of buffered {@link HlsMediaChunk}s. + * @param allowEndOfStream Whether {@link HlsChunkHolder#endOfStream} is allowed to be set for + * non-empty media playlists. If {@code false}, the last available chunk is returned instead. + * If the media playlist is empty, {@link HlsChunkHolder#endOfStream} is always set. * @param out A holder to populate. */ public void getNextChunk( - long playbackPositionUs, long loadPositionUs, List queue, HlsChunkHolder out) { + long playbackPositionUs, + long loadPositionUs, + List queue, + boolean allowEndOfStream, + HlsChunkHolder out) { HlsMediaChunk previous = queue.isEmpty() ? null : queue.get(queue.size() - 1); int oldTrackIndex = previous == null ? C.INDEX_UNSET : trackGroup.indexOf(previous.trackFormat); long bufferedDurationUs = loadPositionUs - playbackPositionUs; @@ -292,15 +299,20 @@ public void getNextChunk( } int segmentIndexInPlaylist = (int) (chunkMediaSequence - mediaPlaylist.mediaSequence); - if (segmentIndexInPlaylist >= mediaPlaylist.segments.size()) { + int availableSegmentCount = mediaPlaylist.segments.size(); + if (segmentIndexInPlaylist >= availableSegmentCount) { if (mediaPlaylist.hasEndTag) { - out.endOfStream = true; + if (allowEndOfStream || availableSegmentCount == 0) { + out.endOfStream = true; + return; + } + segmentIndexInPlaylist = availableSegmentCount - 1; } else /* Live */ { out.playlistUrl = selectedPlaylistUrl; seenExpectedPlaylistError &= selectedPlaylistUrl.equals(expectedPlaylistUrl); expectedPlaylistUrl = selectedPlaylistUrl; + return; } - return; } // We have a valid playlist snapshot, we can discard any playlist errors at this point. seenExpectedPlaylistError = false; 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 ff725ec6f7f..e4c756c6b64 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 @@ -21,6 +21,7 @@ import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.FormatHolder; +import com.google.android.exoplayer2.ParserException; import com.google.android.exoplayer2.decoder.DecoderInputBuffer; import com.google.android.exoplayer2.drm.DrmInitData; import com.google.android.exoplayer2.drm.DrmSession; @@ -232,6 +233,9 @@ public void prepareWithMasterPlaylistInfo( public void maybeThrowPrepareError() throws IOException { maybeThrowError(); + if (loadingFinished && !prepared) { + throw new ParserException("Loading finished before preparation is complete."); + } } public TrackGroupArray getTrackGroups() { @@ -608,7 +612,12 @@ public boolean continueLoading(long positionUs) { ? lastMediaChunk.endTimeUs : Math.max(lastSeekPositionUs, lastMediaChunk.startTimeUs); } - chunkSource.getNextChunk(positionUs, loadPositionUs, chunkQueue, nextChunkHolder); + chunkSource.getNextChunk( + positionUs, + loadPositionUs, + chunkQueue, + /* allowEndOfStream= */ prepared || !chunkQueue.isEmpty(), + nextChunkHolder); boolean endOfStream = nextChunkHolder.endOfStream; Chunk loadable = nextChunkHolder.chunk; Uri playlistUrlToLoad = nextChunkHolder.playlistUrl;