diff --git a/library/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java b/library/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java index fc70ec6de11..4f30e018ae8 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java @@ -96,19 +96,58 @@ public HlsMediaPlaylist(String baseUri, long startTimeUs, int mediaSequence, } } + /** + * Returns whether this playlist is newer than {@code other}. + * + * @param other The playlist to compare. + * @return Whether this playlist is newer than {@code other}. + */ public boolean isNewerThan(HlsMediaPlaylist other) { - return other == null || mediaSequence > other.mediaSequence - || (mediaSequence == other.mediaSequence && segments.size() > other.segments.size()) - || (hasEndTag && !other.hasEndTag); + if (other == null || mediaSequence > other.mediaSequence) { + return true; + } + if (mediaSequence < other.mediaSequence) { + return false; + } + // The media sequences are equal. + int segmentCount = segments.size(); + int otherSegmentCount = other.segments.size(); + return segmentCount > otherSegmentCount + || (segmentCount == otherSegmentCount && hasEndTag && !other.hasEndTag); } public long getEndTimeUs() { return startTimeUs + durationUs; } + /** + * Returns a playlist identical to this one except for the start time, which is set to the + * specified value. If the start time already equals the specified value then the playlist will + * return itself. + * + * @param startTimeUs The start time for the returned playlist. + * @return The playlist. + */ public HlsMediaPlaylist copyWithStartTimeUs(long startTimeUs) { + if (this.startTimeUs == startTimeUs) { + return this; + } return new HlsMediaPlaylist(baseUri, startTimeUs, mediaSequence, version, targetDurationUs, hasEndTag, hasProgramDateTime, initializationSegment, segments); } + /** + * Returns a playlist identical to this one except that an end tag is added. If an end tag is + * already present then the playlist will return itself. + * + * @return The playlist. + */ + public HlsMediaPlaylist copyWithEndTag() { + if (this.hasEndTag) { + return this; + } + return new HlsMediaPlaylist(baseUri, startTimeUs, mediaSequence, version, targetDurationUs, + true, hasProgramDateTime, initializationSegment, segments); + } + } 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 d25e5b1d9c1..711854b2772 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 @@ -334,16 +334,18 @@ private void notifyPlaylistBlacklisting(HlsUrl url, long blacklistMs) { } } - /** - * TODO: Track discontinuities for media playlists that don't include the discontinuity number. - */ - private HlsMediaPlaylist adjustPlaylistTimestamps(HlsMediaPlaylist oldPlaylist, - HlsMediaPlaylist newPlaylist) { - if (newPlaylist.hasProgramDateTime) { - if (newPlaylist.isNewerThan(oldPlaylist)) { - return newPlaylist; + // TODO: Track discontinuities for media playlists that don't include the discontinuity number. + private HlsMediaPlaylist getLatestPlaylistSnapshot(HlsMediaPlaylist oldPlaylist, + HlsMediaPlaylist loadedPlaylist) { + if (loadedPlaylist.hasProgramDateTime) { + if (loadedPlaylist.isNewerThan(oldPlaylist)) { + return loadedPlaylist; } else { - return oldPlaylist; + // If the loaded playlist has an end tag but is not newer than the old playlist then we have + // an inconsistent state. This is typically caused by the server incorrectly resetting the + // media sequence when appending the end tag. We resolve this case as best we can by + // returning the old playlist with the end tag appended. + return loadedPlaylist.hasEndTag ? oldPlaylist.copyWithEndTag() : oldPlaylist; } } // TODO: Once playlist type support is added, the snapshot's age can be added by using the @@ -351,28 +353,28 @@ private HlsMediaPlaylist adjustPlaylistTimestamps(HlsMediaPlaylist oldPlaylist, long primarySnapshotStartTimeUs = primaryUrlSnapshot != null ? primaryUrlSnapshot.startTimeUs : 0; if (oldPlaylist == null) { - if (newPlaylist.startTimeUs == primarySnapshotStartTimeUs) { + if (loadedPlaylist.startTimeUs == primarySnapshotStartTimeUs) { // Playback has just started or is VOD so no adjustment is needed. - return newPlaylist; + return loadedPlaylist; } else { - return newPlaylist.copyWithStartTimeUs(primarySnapshotStartTimeUs); + return loadedPlaylist.copyWithStartTimeUs(primarySnapshotStartTimeUs); } } + if (!loadedPlaylist.isNewerThan(oldPlaylist)) { + // See comment above. + return loadedPlaylist.hasEndTag ? oldPlaylist.copyWithEndTag() : oldPlaylist; + } List oldSegments = oldPlaylist.segments; int oldPlaylistSize = oldSegments.size(); - if (!newPlaylist.isNewerThan(oldPlaylist)) { - // Playlist has not changed. - return oldPlaylist; - } - int mediaSequenceOffset = newPlaylist.mediaSequence - oldPlaylist.mediaSequence; + int mediaSequenceOffset = loadedPlaylist.mediaSequence - oldPlaylist.mediaSequence; if (mediaSequenceOffset <= oldPlaylistSize) { long adjustedNewPlaylistStartTimeUs = mediaSequenceOffset == oldPlaylistSize ? oldPlaylist.getEndTimeUs() : oldPlaylist.startTimeUs + oldSegments.get(mediaSequenceOffset).relativeStartTimeUs; - return newPlaylist.copyWithStartTimeUs(adjustedNewPlaylistStartTimeUs); + return loadedPlaylist.copyWithStartTimeUs(adjustedNewPlaylistStartTimeUs); } // No segments overlap, we assume the new playlist start coincides with the primary playlist. - return newPlaylist.copyWithStartTimeUs(primarySnapshotStartTimeUs); + return loadedPlaylist.copyWithStartTimeUs(primarySnapshotStartTimeUs); } /** @@ -460,15 +462,15 @@ public void run() { // Internal methods. - private void processLoadedPlaylist(HlsMediaPlaylist loadedMediaPlaylist) { + private void processLoadedPlaylist(HlsMediaPlaylist loadedPlaylist) { HlsMediaPlaylist oldPlaylist = playlistSnapshot; - playlistSnapshot = adjustPlaylistTimestamps(oldPlaylist, loadedMediaPlaylist); + playlistSnapshot = getLatestPlaylistSnapshot(oldPlaylist, loadedPlaylist); long refreshDelayUs = C.TIME_UNSET; - if (oldPlaylist != playlistSnapshot) { + if (playlistSnapshot != oldPlaylist) { if (onPlaylistUpdated(playlistUrl, playlistSnapshot)) { refreshDelayUs = playlistSnapshot.targetDurationUs; } - } else if (!loadedMediaPlaylist.hasEndTag) { + } else if (!playlistSnapshot.hasEndTag) { refreshDelayUs = playlistSnapshot.targetDurationUs / 2; } if (refreshDelayUs != C.TIME_UNSET) {