mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
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
This commit is contained in:
parent
dda3616f5a
commit
a04663372f
1 changed files with 72 additions and 10 deletions
|
|
@ -40,6 +40,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.
|
||||||
*/
|
*/
|
||||||
|
|
@ -75,6 +107,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
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
|
|
||||||
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
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
|
||||||
}
|
}
|
||||||
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
|
||||||
|
|
||||||
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void blacklistPlaylist() {
|
||||||
|
blacklistUntilMs = SystemClock.elapsedRealtime()
|
||||||
|
+ ChunkedTrackBlacklistUtil.DEFAULT_TRACK_BLACKLIST_MS;
|
||||||
|
notifyPlaylistBlacklisting(playlistUrl, ChunkedTrackBlacklistUtil.DEFAULT_TRACK_BLACKLIST_MS);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue