diff --git a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java index c2a345ace6d..c7c66fbd617 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java @@ -194,15 +194,16 @@ public void getNextChunk(HlsMediaChunk previous, long playbackPositionUs, HlsChu // Select the variant. trackSelection.updateSelectedTrack(bufferedDurationUs); - int newVariantIndex = trackSelection.getSelectedIndexInTrackGroup(); + int selectedVariantIndex = trackSelection.getSelectedIndexInTrackGroup(); - boolean switchingVariant = oldVariantIndex != newVariantIndex; - HlsMediaPlaylist mediaPlaylist = playlistTracker.getPlaylistSnapshot(variants[newVariantIndex]); - if (mediaPlaylist == null) { - out.playlist = variants[newVariantIndex]; + boolean switchingVariant = oldVariantIndex != selectedVariantIndex; + HlsUrl selectedUrl = variants[selectedVariantIndex]; + if (!playlistTracker.isSnapshotValid(selectedUrl)) { + out.playlist = selectedUrl; // Retry when playlist is refreshed. return; } + HlsMediaPlaylist mediaPlaylist = playlistTracker.getPlaylistSnapshot(selectedUrl); // Select the chunk. int chunkMediaSequence; @@ -218,8 +219,9 @@ public void getNextChunk(HlsMediaChunk previous, long playbackPositionUs, HlsChu if (chunkMediaSequence < mediaPlaylist.mediaSequence && previous != null) { // We try getting the next chunk without adapting in case that's the reason for falling // behind the live window. - newVariantIndex = oldVariantIndex; - mediaPlaylist = playlistTracker.getPlaylistSnapshot(variants[newVariantIndex]); + selectedVariantIndex = oldVariantIndex; + selectedUrl = variants[selectedVariantIndex]; + mediaPlaylist = playlistTracker.getPlaylistSnapshot(selectedUrl); chunkMediaSequence = previous.getNextChunkIndex(); } } @@ -236,7 +238,7 @@ public void getNextChunk(HlsMediaChunk previous, long playbackPositionUs, HlsChu if (mediaPlaylist.hasEndTag) { out.endOfStream = true; } else /* Live */ { - out.playlist = variants[newVariantIndex]; + out.playlist = selectedUrl; } return; } @@ -249,7 +251,7 @@ public void getNextChunk(HlsMediaChunk previous, long playbackPositionUs, HlsChu Uri keyUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, segment.encryptionKeyUri); if (!keyUri.equals(encryptionKeyUri)) { // Encryption is specified and the key has changed. - out.chunk = newEncryptionKeyChunk(keyUri, segment.encryptionIV, newVariantIndex, + out.chunk = newEncryptionKeyChunk(keyUri, segment.encryptionIV, selectedVariantIndex, trackSelection.getSelectionReason(), trackSelection.getSelectionData()); return; } @@ -279,7 +281,7 @@ public void getNextChunk(HlsMediaChunk previous, long playbackPositionUs, HlsChu Uri chunkUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, segment.url); DataSpec dataSpec = new DataSpec(chunkUri, segment.byterangeOffset, segment.byterangeLength, null); - out.chunk = new HlsMediaChunk(dataSource, dataSpec, initDataSpec, variants[newVariantIndex], + out.chunk = new HlsMediaChunk(dataSource, dataSpec, initDataSpec, selectedUrl, trackSelection.getSelectionReason(), trackSelection.getSelectionData(), startTimeUs, startTimeUs + segment.durationUs, chunkMediaSequence, discontinuitySequence, isTimestampMaster, timestampAdjuster, previous, encryptionKey, encryptionIv); diff --git a/library/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistTracker.java b/library/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistTracker.java index 92e2480da77..95784092a9c 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistTracker.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistTracker.java @@ -166,8 +166,24 @@ public HlsMasterPlaylist getMasterPlaylist() { * be null if no snapshot has been loaded yet. */ public HlsMediaPlaylist getPlaylistSnapshot(HlsUrl url) { - maybeSetPrimaryUrl(url); - return playlistBundles.get(url).getPlaylistSnapshot(); + HlsMediaPlaylist snapshot = playlistBundles.get(url).getPlaylistSnapshot(); + if (snapshot != null) { + maybeSetPrimaryUrl(url); + } + return snapshot; + } + + /** + * Returns whether the snapshot of the playlist referenced by the provided {@link HlsUrl} is + * valid, meaning all the segments referenced by the playlist are expected to be available. If the + * playlist is not valid then some of the segments may no longer be available. + + * @param url The {@link HlsUrl}. + * @return Whether the snapshot of the playlist referenced by the provided {@link HlsUrl} is + * valid. + */ + public boolean isSnapshotValid(HlsUrl url) { + return playlistBundles.get(url).isSnapshotValid(); } /** @@ -412,6 +428,7 @@ private final class MediaPlaylistBundle implements Loader.Callback mediaPlaylistLoadable; private HlsMediaPlaylist playlistSnapshot; + private long lastSnapshotLoadMs; private long lastSnapshotAccessTimeMs; private long blacklistUntilMs; @@ -429,6 +446,17 @@ public HlsMediaPlaylist getPlaylistSnapshot() { return playlistSnapshot; } + public boolean isSnapshotValid() { + if (playlistSnapshot == null) { + return false; + } + // TODO: Return true for event playlists once playlist types are supported. + long currentTimeMs = SystemClock.elapsedRealtime(); + long snapshotValidityDurationMs = Math.max(30000, C.usToMs(playlistSnapshot.durationUs)); + return playlistSnapshot.hasEndTag + || lastSnapshotLoadMs + snapshotValidityDurationMs > currentTimeMs; + } + public void release() { mediaPlaylistLoader.release(); } @@ -488,6 +516,7 @@ public void run() { private void processLoadedPlaylist(HlsMediaPlaylist loadedPlaylist) { HlsMediaPlaylist oldPlaylist = playlistSnapshot; + lastSnapshotLoadMs = SystemClock.elapsedRealtime(); playlistSnapshot = getLatestPlaylistSnapshot(oldPlaylist, loadedPlaylist); long refreshDelayUs = C.TIME_UNSET; if (playlistSnapshot != oldPlaylist) {