Skip to content

Commit

Permalink
Better handle inconsistent HLS timeline updates
Browse files Browse the repository at this point in the history
Issue: #2249

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=143555467
  • Loading branch information
ojw28 committed Jan 4, 2017
1 parent 44d6b1a commit f735a86
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -334,45 +334,47 @@ 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
// target duration.
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);
}

/**
Expand Down Expand Up @@ -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) {
Expand Down

0 comments on commit f735a86

Please sign in to comment.