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) { public boolean isNewerThan(HlsMediaPlaylist other) {
return other == null || mediaSequence > other.mediaSequence if (other == null || mediaSequence > other.mediaSequence) {
|| (mediaSequence == other.mediaSequence && segments.size() > other.segments.size()) return true;
|| (hasEndTag && !other.hasEndTag); }
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() { public long getEndTimeUs() {
return startTimeUs + durationUs; 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) { public HlsMediaPlaylist copyWithStartTimeUs(long startTimeUs) {
if (this.startTimeUs == startTimeUs) {
return this;
}
return new HlsMediaPlaylist(baseUri, startTimeUs, mediaSequence, version, targetDurationUs, return new HlsMediaPlaylist(baseUri, startTimeUs, mediaSequence, version, targetDurationUs,
hasEndTag, hasProgramDateTime, initializationSegment, segments); 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.
* TODO: Track discontinuities for media playlists that don't include the discontinuity number. private HlsMediaPlaylist getLatestPlaylistSnapshot(HlsMediaPlaylist oldPlaylist,
*/ HlsMediaPlaylist loadedPlaylist) {
private HlsMediaPlaylist adjustPlaylistTimestamps(HlsMediaPlaylist oldPlaylist, if (loadedPlaylist.hasProgramDateTime) {
HlsMediaPlaylist newPlaylist) { if (loadedPlaylist.isNewerThan(oldPlaylist)) {
if (newPlaylist.hasProgramDateTime) { return loadedPlaylist;
if (newPlaylist.isNewerThan(oldPlaylist)) {
return newPlaylist;
} else { } 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 // 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 long primarySnapshotStartTimeUs = primaryUrlSnapshot != null
? primaryUrlSnapshot.startTimeUs : 0; ? primaryUrlSnapshot.startTimeUs : 0;
if (oldPlaylist == null) { if (oldPlaylist == null) {
if (newPlaylist.startTimeUs == primarySnapshotStartTimeUs) { if (loadedPlaylist.startTimeUs == primarySnapshotStartTimeUs) {
// Playback has just started or is VOD so no adjustment is needed. // Playback has just started or is VOD so no adjustment is needed.
return newPlaylist; return loadedPlaylist;
} else { } 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; List<Segment> oldSegments = oldPlaylist.segments;
int oldPlaylistSize = oldSegments.size(); int oldPlaylistSize = oldSegments.size();
if (!newPlaylist.isNewerThan(oldPlaylist)) { int mediaSequenceOffset = loadedPlaylist.mediaSequence - oldPlaylist.mediaSequence;
// Playlist has not changed.
return oldPlaylist;
}
int mediaSequenceOffset = newPlaylist.mediaSequence - oldPlaylist.mediaSequence;
if (mediaSequenceOffset <= oldPlaylistSize) { if (mediaSequenceOffset <= oldPlaylistSize) {
long adjustedNewPlaylistStartTimeUs = mediaSequenceOffset == oldPlaylistSize long adjustedNewPlaylistStartTimeUs = mediaSequenceOffset == oldPlaylistSize
? oldPlaylist.getEndTimeUs() ? oldPlaylist.getEndTimeUs()
: oldPlaylist.startTimeUs + oldSegments.get(mediaSequenceOffset).relativeStartTimeUs; : 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. // 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. // Internal methods.
private void processLoadedPlaylist(HlsMediaPlaylist loadedMediaPlaylist) { private void processLoadedPlaylist(HlsMediaPlaylist loadedPlaylist) {
HlsMediaPlaylist oldPlaylist = playlistSnapshot; HlsMediaPlaylist oldPlaylist = playlistSnapshot;
playlistSnapshot = adjustPlaylistTimestamps(oldPlaylist, loadedMediaPlaylist); playlistSnapshot = getLatestPlaylistSnapshot(oldPlaylist, loadedPlaylist);
long refreshDelayUs = C.TIME_UNSET; long refreshDelayUs = C.TIME_UNSET;
if (oldPlaylist != playlistSnapshot) { if (playlistSnapshot != oldPlaylist) {
if (onPlaylistUpdated(playlistUrl, playlistSnapshot)) { if (onPlaylistUpdated(playlistUrl, playlistSnapshot)) {
refreshDelayUs = playlistSnapshot.targetDurationUs; refreshDelayUs = playlistSnapshot.targetDurationUs;
} }
} else if (!loadedMediaPlaylist.hasEndTag) { } else if (!playlistSnapshot.hasEndTag) {
refreshDelayUs = playlistSnapshot.targetDurationUs / 2; refreshDelayUs = playlistSnapshot.targetDurationUs / 2;
} }
if (refreshDelayUs != C.TIME_UNSET) { if (refreshDelayUs != C.TIME_UNSET) {