Commit a0466337 by aquilescanta Committed by Oliver Woodman

Detect playlist stuck and playlist reset conditions in HLS

This CL aims that the player fails upon:

- Playlist that don't change in a suspiciously long time,
  which might mean there are server side issues.
- Playlist with a media sequence lower that its last snapshot
  and no overlapping segments.

This two error conditions are propagated through the renderer,
but not through MediaSource#maybeThrowSourceInfoRefreshError.

Issue:#2872

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=160899995
parent dda3616f
...@@ -41,6 +41,38 @@ import java.util.List; ...@@ -41,6 +41,38 @@ import java.util.List;
public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable<HlsPlaylist>> { public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable<HlsPlaylist>> {
/** /**
* Thrown when a playlist is considered to be stuck due to a server side error.
*/
public static final class PlaylistStuckException extends IOException {
/**
* The url of the stuck playlist.
*/
public final String url;
private PlaylistStuckException(String url) {
this.url = url;
}
}
/**
* Thrown when the media sequence of a new snapshot indicates the server has reset.
*/
public static final class PlaylistResetException extends IOException {
/**
* The url of the reset playlist.
*/
public final String url;
private PlaylistResetException(String url) {
this.url = url;
}
}
/**
* Listener for primary playlist changes. * Listener for primary playlist changes.
*/ */
public interface PrimaryPlaylistListener { public interface PrimaryPlaylistListener {
...@@ -76,6 +108,11 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable ...@@ -76,6 +108,11 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
} }
/** /**
* Coefficient applied on the target duration of a playlist to determine the amount of time after
* which an unchanging playlist is considered stuck.
*/
private static final double PLAYLIST_STUCK_TARGET_DURATION_COEFFICIENT = 3.5;
/**
* The minimum number of milliseconds that a url is kept as primary url, if no * The minimum number of milliseconds that a url is kept as primary url, if no
* {@link #getPlaylistSnapshot} call is made for that url. * {@link #getPlaylistSnapshot} call is made for that url.
*/ */
...@@ -213,14 +250,14 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable ...@@ -213,14 +250,14 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
} }
/** /**
* If the playlist is having trouble loading the playlist referenced by the given {@link HlsUrl}, * If the playlist is having trouble refreshing the playlist referenced by the given
* this method throws the underlying error. * {@link HlsUrl}, this method throws the underlying error.
* *
* @param url The {@link HlsUrl}. * @param url The {@link HlsUrl}.
* @throws IOException The underyling error. * @throws IOException The underyling error.
*/ */
public void maybeThrowPlaylistRefreshError(HlsUrl url) throws IOException { public void maybeThrowPlaylistRefreshError(HlsUrl url) throws IOException {
playlistBundles.get(url).mediaPlaylistLoader.maybeThrowError(); playlistBundles.get(url).maybeThrowPlaylistRefreshError();
} }
/** /**
...@@ -441,9 +478,11 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable ...@@ -441,9 +478,11 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
private HlsMediaPlaylist playlistSnapshot; private HlsMediaPlaylist playlistSnapshot;
private long lastSnapshotLoadMs; private long lastSnapshotLoadMs;
private long lastSnapshotChangeMs;
private long lastSnapshotAccessTimeMs; private long lastSnapshotAccessTimeMs;
private long blacklistUntilMs; private long blacklistUntilMs;
private boolean pendingRefresh; private boolean pendingRefresh;
private IOException playlistError;
public MediaPlaylistBundle(HlsUrl playlistUrl, long initialLastSnapshotAccessTimeMs) { public MediaPlaylistBundle(HlsUrl playlistUrl, long initialLastSnapshotAccessTimeMs) {
this.playlistUrl = playlistUrl; this.playlistUrl = playlistUrl;
...@@ -483,6 +522,13 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable ...@@ -483,6 +522,13 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
} }
} }
public void maybeThrowPlaylistRefreshError() throws IOException {
mediaPlaylistLoader.maybeThrowError();
if (playlistError != null) {
throw playlistError;
}
}
// Loader.Callback implementation. // Loader.Callback implementation.
@Override @Override
...@@ -494,8 +540,7 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable ...@@ -494,8 +540,7 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
eventDispatcher.loadCompleted(loadable.dataSpec, C.DATA_TYPE_MANIFEST, elapsedRealtimeMs, eventDispatcher.loadCompleted(loadable.dataSpec, C.DATA_TYPE_MANIFEST, elapsedRealtimeMs,
loadDurationMs, loadable.bytesLoaded()); loadDurationMs, loadable.bytesLoaded());
} else { } else {
onLoadError(loadable, elapsedRealtimeMs, loadDurationMs, playlistError = new ParserException("Loaded playlist has unexpected type.");
new ParserException("Loaded playlist has unexpected type."));
} }
} }
...@@ -517,10 +562,7 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable ...@@ -517,10 +562,7 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
} }
boolean shouldRetry = true; boolean shouldRetry = true;
if (ChunkedTrackBlacklistUtil.shouldBlacklist(error)) { if (ChunkedTrackBlacklistUtil.shouldBlacklist(error)) {
blacklistUntilMs = blacklistPlaylist();
SystemClock.elapsedRealtime() + ChunkedTrackBlacklistUtil.DEFAULT_TRACK_BLACKLIST_MS;
notifyPlaylistBlacklisting(playlistUrl,
ChunkedTrackBlacklistUtil.DEFAULT_TRACK_BLACKLIST_MS);
shouldRetry = primaryHlsUrl == playlistUrl && !maybeSelectNewPrimaryUrl(); shouldRetry = primaryHlsUrl == playlistUrl && !maybeSelectNewPrimaryUrl();
} }
return shouldRetry ? Loader.RETRY : Loader.DONT_RETRY; return shouldRetry ? Loader.RETRY : Loader.DONT_RETRY;
...@@ -538,14 +580,28 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable ...@@ -538,14 +580,28 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
private void processLoadedPlaylist(HlsMediaPlaylist loadedPlaylist) { private void processLoadedPlaylist(HlsMediaPlaylist loadedPlaylist) {
HlsMediaPlaylist oldPlaylist = playlistSnapshot; HlsMediaPlaylist oldPlaylist = playlistSnapshot;
lastSnapshotLoadMs = SystemClock.elapsedRealtime(); long currentTimeMs = SystemClock.elapsedRealtime();
lastSnapshotLoadMs = currentTimeMs;
playlistSnapshot = getLatestPlaylistSnapshot(oldPlaylist, loadedPlaylist); playlistSnapshot = getLatestPlaylistSnapshot(oldPlaylist, loadedPlaylist);
long refreshDelayUs = C.TIME_UNSET; long refreshDelayUs = C.TIME_UNSET;
if (playlistSnapshot != oldPlaylist) { if (playlistSnapshot != oldPlaylist) {
playlistError = null;
lastSnapshotChangeMs = currentTimeMs;
if (onPlaylistUpdated(playlistUrl, playlistSnapshot)) { if (onPlaylistUpdated(playlistUrl, playlistSnapshot)) {
refreshDelayUs = playlistSnapshot.targetDurationUs; refreshDelayUs = playlistSnapshot.targetDurationUs;
} }
} else if (!playlistSnapshot.hasEndTag) { } else if (!playlistSnapshot.hasEndTag) {
if (currentTimeMs - lastSnapshotChangeMs
> C.usToMs(playlistSnapshot.targetDurationUs)
* PLAYLIST_STUCK_TARGET_DURATION_COEFFICIENT) {
// The playlist seems to be stuck, we blacklist it.
playlistError = new PlaylistStuckException(playlistUrl.url);
blacklistPlaylist();
} else if (loadedPlaylist.mediaSequence + loadedPlaylist.segments.size()
< playlistSnapshot.mediaSequence) {
// The media sequence has jumped backwards. The server has likely reset.
playlistError = new PlaylistResetException(playlistUrl.url);
}
refreshDelayUs = playlistSnapshot.targetDurationUs / 2; refreshDelayUs = playlistSnapshot.targetDurationUs / 2;
} }
if (refreshDelayUs != C.TIME_UNSET) { if (refreshDelayUs != C.TIME_UNSET) {
...@@ -554,6 +610,12 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable ...@@ -554,6 +610,12 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
} }
} }
private void blacklistPlaylist() {
blacklistUntilMs = SystemClock.elapsedRealtime()
+ ChunkedTrackBlacklistUtil.DEFAULT_TRACK_BLACKLIST_MS;
notifyPlaylistBlacklisting(playlistUrl, ChunkedTrackBlacklistUtil.DEFAULT_TRACK_BLACKLIST_MS);
}
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment