Commit 6a1331f1 by tonihei Committed by Toni

Gracefully handle chunkful preparation without chunks.

This situation happens if the first chunk to load is already behind the end
of the stream. In this case, the preparation never completes because
HlsSampleStreamWrapper gets stuck in a prepared=false and loadingFinished=true
state.

Gracefully handle this situation by attempting to load the last chunk if still
unprepared to ensure that track information is obtained as far as possible.
Otherwise, it wouldn't be possible to play anything even when seeking back.

Issue:#6314
PiperOrigin-RevId: 264599465
parent f0aae7ae
...@@ -42,6 +42,8 @@ ...@@ -42,6 +42,8 @@
([#5407](https://github.com/google/ExoPlayer/issues/5407)). ([#5407](https://github.com/google/ExoPlayer/issues/5407)).
* Deprecate `setTag` parameter of `Timeline.getWindow`. Tags will always be set. * Deprecate `setTag` parameter of `Timeline.getWindow`. Tags will always be set.
* Support out-of-band HDR10+ metadata for VP9 in WebM/Matroska. * 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 ### ### 2.10.4 ###
......
...@@ -225,10 +225,17 @@ import java.util.Map; ...@@ -225,10 +225,17 @@ import java.util.Map;
* media in previous periods still to be played. * media in previous periods still to be played.
* @param loadPositionUs The current load position relative to the period start in microseconds. * @param loadPositionUs The current load position relative to the period start in microseconds.
* @param queue The queue of buffered {@link HlsMediaChunk}s. * @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. * @param out A holder to populate.
*/ */
public void getNextChunk( public void getNextChunk(
long playbackPositionUs, long loadPositionUs, List<HlsMediaChunk> queue, HlsChunkHolder out) { long playbackPositionUs,
long loadPositionUs,
List<HlsMediaChunk> queue,
boolean allowEndOfStream,
HlsChunkHolder out) {
HlsMediaChunk previous = queue.isEmpty() ? null : queue.get(queue.size() - 1); HlsMediaChunk previous = queue.isEmpty() ? null : queue.get(queue.size() - 1);
int oldTrackIndex = previous == null ? C.INDEX_UNSET : trackGroup.indexOf(previous.trackFormat); int oldTrackIndex = previous == null ? C.INDEX_UNSET : trackGroup.indexOf(previous.trackFormat);
long bufferedDurationUs = loadPositionUs - playbackPositionUs; long bufferedDurationUs = loadPositionUs - playbackPositionUs;
...@@ -292,15 +299,20 @@ import java.util.Map; ...@@ -292,15 +299,20 @@ import java.util.Map;
} }
int segmentIndexInPlaylist = (int) (chunkMediaSequence - mediaPlaylist.mediaSequence); int segmentIndexInPlaylist = (int) (chunkMediaSequence - mediaPlaylist.mediaSequence);
if (segmentIndexInPlaylist >= mediaPlaylist.segments.size()) { int availableSegmentCount = mediaPlaylist.segments.size();
if (segmentIndexInPlaylist >= availableSegmentCount) {
if (mediaPlaylist.hasEndTag) { if (mediaPlaylist.hasEndTag) {
out.endOfStream = true; if (allowEndOfStream || availableSegmentCount == 0) {
out.endOfStream = true;
return;
}
segmentIndexInPlaylist = availableSegmentCount - 1;
} else /* Live */ { } else /* Live */ {
out.playlistUrl = selectedPlaylistUrl; out.playlistUrl = selectedPlaylistUrl;
seenExpectedPlaylistError &= selectedPlaylistUrl.equals(expectedPlaylistUrl); seenExpectedPlaylistError &= selectedPlaylistUrl.equals(expectedPlaylistUrl);
expectedPlaylistUrl = selectedPlaylistUrl; expectedPlaylistUrl = selectedPlaylistUrl;
return;
} }
return;
} }
// We have a valid playlist snapshot, we can discard any playlist errors at this point. // We have a valid playlist snapshot, we can discard any playlist errors at this point.
seenExpectedPlaylistError = false; seenExpectedPlaylistError = false;
......
...@@ -21,6 +21,7 @@ import androidx.annotation.Nullable; ...@@ -21,6 +21,7 @@ import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.FormatHolder; import com.google.android.exoplayer2.FormatHolder;
import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer; import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
import com.google.android.exoplayer2.drm.DrmInitData; import com.google.android.exoplayer2.drm.DrmInitData;
import com.google.android.exoplayer2.drm.DrmSession; import com.google.android.exoplayer2.drm.DrmSession;
...@@ -232,6 +233,9 @@ import java.util.Set; ...@@ -232,6 +233,9 @@ import java.util.Set;
public void maybeThrowPrepareError() throws IOException { public void maybeThrowPrepareError() throws IOException {
maybeThrowError(); maybeThrowError();
if (loadingFinished && !prepared) {
throw new ParserException("Loading finished before preparation is complete.");
}
} }
public TrackGroupArray getTrackGroups() { public TrackGroupArray getTrackGroups() {
...@@ -608,7 +612,12 @@ import java.util.Set; ...@@ -608,7 +612,12 @@ import java.util.Set;
? lastMediaChunk.endTimeUs ? lastMediaChunk.endTimeUs
: Math.max(lastSeekPositionUs, lastMediaChunk.startTimeUs); : 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; boolean endOfStream = nextChunkHolder.endOfStream;
Chunk loadable = nextChunkHolder.chunk; Chunk loadable = nextChunkHolder.chunk;
Uri playlistUrlToLoad = nextChunkHolder.playlistUrl; Uri playlistUrlToLoad = nextChunkHolder.playlistUrl;
......
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