Better handle inconsistent HLS timeline updates

Issue: #2249

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=143555467
This commit is contained in:
olly 2017-01-04 08:13:05 -08:00 committed by Oliver Woodman
parent 44d6b1a271
commit f735a86ebb
2 changed files with 67 additions and 26 deletions

View File

@ -96,19 +96,58 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
}
}
/**
* 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);
}
}

View File

@ -334,16 +334,18 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
}
}
/**
* 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 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
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<Segment> 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 final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
// 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) {