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
This commit is contained in:
tonihei 2019-08-21 14:59:05 +01:00 committed by Toni
parent f0aae7aee5
commit 6a1331f125
3 changed files with 28 additions and 5 deletions

View file

@ -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 ###

View file

@ -225,10 +225,17 @@ import java.util.Map;
* 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<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);
int oldTrackIndex = previous == null ? C.INDEX_UNSET : trackGroup.indexOf(previous.trackFormat);
long bufferedDurationUs = loadPositionUs - playbackPositionUs;
@ -292,15 +299,20 @@ import java.util.Map;
}
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;

View file

@ -21,6 +21,7 @@ import androidx.annotation.Nullable;
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 @@ import java.util.Set;
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 @@ import java.util.Set;
? 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;