Commit 74b7b905 by aquilescanta Committed by Oliver Woodman

Propagate non-MediaSource-preparation playlist load errors through HlsChunkSource

Erroneous condition:
===================
If the track selection contains a subset of the available variants in the
master playlist, but only the selected variants return 404, the playlist
tracker will never propagate the error.

Fix:
====
The Chunk source will propagate the playlist load error if no more
alternative playlists are available (because all are already blacklisted).

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=190624484
parent 71e5b2c0
...@@ -50,6 +50,8 @@ ...@@ -50,6 +50,8 @@
* Removed default renderer time offset of 60000000 from internal player. The * Removed default renderer time offset of 60000000 from internal player. The
actual renderer timestamp offset can be obtained by listening to actual renderer timestamp offset can be obtained by listening to
`BaseRenderer.onStreamChanged`. `BaseRenderer.onStreamChanged`.
* HLS: Fix playlist loading error propagation when the current selection does
not include all of the playlist's variants.
### 2.7.1 ### ### 2.7.1 ###
......
...@@ -105,6 +105,7 @@ import java.util.List; ...@@ -105,6 +105,7 @@ import java.util.List;
// in TrackSelection to avoid unexpected behavior. // in TrackSelection to avoid unexpected behavior.
private TrackSelection trackSelection; private TrackSelection trackSelection;
private long liveEdgeTimeUs; private long liveEdgeTimeUs;
private boolean seenExpectedPlaylistError;
/** /**
* @param extractorFactory An {@link HlsExtractorFactory} from which to obtain the extractors for * @param extractorFactory An {@link HlsExtractorFactory} from which to obtain the extractors for
...@@ -150,7 +151,7 @@ import java.util.List; ...@@ -150,7 +151,7 @@ import java.util.List;
if (fatalError != null) { if (fatalError != null) {
throw fatalError; throw fatalError;
} }
if (expectedPlaylistUrl != null) { if (expectedPlaylistUrl != null && seenExpectedPlaylistError) {
playlistTracker.maybeThrowPlaylistRefreshError(expectedPlaylistUrl); playlistTracker.maybeThrowPlaylistRefreshError(expectedPlaylistUrl);
} }
} }
...@@ -217,8 +218,6 @@ import java.util.List; ...@@ -217,8 +218,6 @@ import java.util.List;
HlsChunkHolder out) { HlsChunkHolder out) {
int oldVariantIndex = previous == null ? C.INDEX_UNSET int oldVariantIndex = previous == null ? C.INDEX_UNSET
: trackGroup.indexOf(previous.trackFormat); : trackGroup.indexOf(previous.trackFormat);
expectedPlaylistUrl = null;
long bufferedDurationUs = loadPositionUs - playbackPositionUs; long bufferedDurationUs = loadPositionUs - playbackPositionUs;
long timeToLiveEdgeUs = resolveTimeToLiveEdgeUs(playbackPositionUs); long timeToLiveEdgeUs = resolveTimeToLiveEdgeUs(playbackPositionUs);
if (previous != null && !independentSegments) { if (previous != null && !independentSegments) {
...@@ -243,6 +242,7 @@ import java.util.List; ...@@ -243,6 +242,7 @@ import java.util.List;
HlsUrl selectedUrl = variants[selectedVariantIndex]; HlsUrl selectedUrl = variants[selectedVariantIndex];
if (!playlistTracker.isSnapshotValid(selectedUrl)) { if (!playlistTracker.isSnapshotValid(selectedUrl)) {
out.playlist = selectedUrl; out.playlist = selectedUrl;
seenExpectedPlaylistError &= expectedPlaylistUrl == selectedUrl;
expectedPlaylistUrl = selectedUrl; expectedPlaylistUrl = selectedUrl;
// Retry when playlist is refreshed. // Retry when playlist is refreshed.
return; return;
...@@ -291,10 +291,14 @@ import java.util.List; ...@@ -291,10 +291,14 @@ import java.util.List;
out.endOfStream = true; out.endOfStream = true;
} else /* Live */ { } else /* Live */ {
out.playlist = selectedUrl; out.playlist = selectedUrl;
seenExpectedPlaylistError &= expectedPlaylistUrl == selectedUrl;
expectedPlaylistUrl = selectedUrl; expectedPlaylistUrl = selectedUrl;
} }
return; return;
} }
// We have a valid playlist snapshot, we can discard any playlist errors at this point.
seenExpectedPlaylistError = false;
expectedPlaylistUrl = null;
// Handle encryption. // Handle encryption.
HlsMediaPlaylist.Segment segment = mediaPlaylist.segments.get(chunkIndex); HlsMediaPlaylist.Segment segment = mediaPlaylist.segments.get(chunkIndex);
...@@ -389,19 +393,25 @@ import java.util.List; ...@@ -389,19 +393,25 @@ import java.util.List;
} }
/** /**
* Called when a playlist is blacklisted. * Called when a playlist load encounters an error.
* *
* @param url The url that references the blacklisted playlist. * @param url The url of the playlist whose load encountered an error.
* @param blacklistMs The amount of milliseconds for which the playlist was blacklisted. * @param shouldBlacklist Whether the playlist should be blacklisted.
* @return True if blacklisting did not encounter errors. False otherwise.
*/ */
public void onPlaylistBlacklisted(HlsUrl url, long blacklistMs) { public boolean onPlaylistError(HlsUrl url, boolean shouldBlacklist) {
int trackGroupIndex = trackGroup.indexOf(url.format); int trackGroupIndex = trackGroup.indexOf(url.format);
if (trackGroupIndex != C.INDEX_UNSET) { if (trackGroupIndex == C.INDEX_UNSET) {
int trackSelectionIndex = trackSelection.indexOf(trackGroupIndex); return true;
if (trackSelectionIndex != C.INDEX_UNSET) { }
trackSelection.blacklist(trackSelectionIndex, blacklistMs); int trackSelectionIndex = trackSelection.indexOf(trackGroupIndex);
} if (trackSelectionIndex == C.INDEX_UNSET) {
return true;
} }
seenExpectedPlaylistError |= expectedPlaylistUrl == url;
return !shouldBlacklist
|| trackSelection.blacklist(
trackSelectionIndex, ChunkedTrackBlacklistUtil.DEFAULT_TRACK_BLACKLIST_MS);
} }
// Private methods. // Private methods.
......
...@@ -305,11 +305,13 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper ...@@ -305,11 +305,13 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
} }
@Override @Override
public void onPlaylistBlacklisted(HlsUrl url, long blacklistMs) { public boolean onPlaylistError(HlsUrl url, boolean shouldBlacklist) {
boolean noBlacklistingFailure = true;
for (HlsSampleStreamWrapper streamWrapper : sampleStreamWrappers) { for (HlsSampleStreamWrapper streamWrapper : sampleStreamWrappers) {
streamWrapper.onPlaylistBlacklisted(url, blacklistMs); noBlacklistingFailure &= streamWrapper.onPlaylistError(url, shouldBlacklist);
} }
callback.onContinueLoadingRequested(this); callback.onContinueLoadingRequested(this);
return noBlacklistingFailure;
} }
// Internal methods. // Internal methods.
......
...@@ -414,8 +414,8 @@ import java.util.Arrays; ...@@ -414,8 +414,8 @@ import java.util.Arrays;
chunkSource.setIsTimestampMaster(isTimestampMaster); chunkSource.setIsTimestampMaster(isTimestampMaster);
} }
public void onPlaylistBlacklisted(HlsUrl url, long blacklistMs) { public boolean onPlaylistError(HlsUrl url, boolean shouldBlacklist) {
chunkSource.onPlaylistBlacklisted(url, blacklistMs); return chunkSource.onPlaylistError(url, shouldBlacklist);
} }
// SampleStream implementation. // SampleStream implementation.
......
...@@ -99,11 +99,10 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable ...@@ -99,11 +99,10 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
* Called if an error is encountered while loading a playlist. * Called if an error is encountered while loading a playlist.
* *
* @param url The loaded url that caused the error. * @param url The loaded url that caused the error.
* @param blacklistDurationMs The number of milliseconds for which the playlist has been * @param shouldBlacklist Whether the playlist should be blacklisted.
* blacklisted. * @return True if blacklisting did not encounter errors. False otherwise.
*/ */
void onPlaylistBlacklisted(HlsUrl url, long blacklistDurationMs); boolean onPlaylistError(HlsUrl url, boolean shouldBlacklist);
} }
/** /**
...@@ -391,11 +390,13 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable ...@@ -391,11 +390,13 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
} }
} }
private void notifyPlaylistBlacklisting(HlsUrl url, long blacklistMs) { private boolean notifyPlaylistError(HlsUrl playlistUrl, boolean shouldBlacklist) {
int listenersSize = listeners.size(); int listenersSize = listeners.size();
boolean anyBlacklistingFailed = false;
for (int i = 0; i < listenersSize; i++) { for (int i = 0; i < listenersSize; i++) {
listeners.get(i).onPlaylistBlacklisted(url, blacklistMs); anyBlacklistingFailed |= !listeners.get(i).onPlaylistError(playlistUrl, shouldBlacklist);
} }
return anyBlacklistingFailed;
} }
private HlsMediaPlaylist getLatestPlaylistSnapshot(HlsMediaPlaylist oldPlaylist, private HlsMediaPlaylist getLatestPlaylistSnapshot(HlsMediaPlaylist oldPlaylist,
...@@ -565,14 +566,15 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable ...@@ -565,14 +566,15 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
boolean isFatal = error instanceof ParserException; boolean isFatal = error instanceof ParserException;
eventDispatcher.loadError(loadable.dataSpec, C.DATA_TYPE_MANIFEST, elapsedRealtimeMs, eventDispatcher.loadError(loadable.dataSpec, C.DATA_TYPE_MANIFEST, elapsedRealtimeMs,
loadDurationMs, loadable.bytesLoaded(), error, isFatal); loadDurationMs, loadable.bytesLoaded(), error, isFatal);
boolean shouldBlacklist = ChunkedTrackBlacklistUtil.shouldBlacklist(error);
boolean shouldRetryIfNotFatal = notifyPlaylistError(playlistUrl, shouldBlacklist);
if (isFatal) { if (isFatal) {
return Loader.DONT_RETRY_FATAL; return Loader.DONT_RETRY_FATAL;
} }
boolean shouldRetry = true; if (shouldBlacklist) {
if (ChunkedTrackBlacklistUtil.shouldBlacklist(error)) { shouldRetryIfNotFatal |= blacklistPlaylist();
shouldRetry = blacklistPlaylist();
} }
return shouldRetry ? Loader.RETRY : Loader.DONT_RETRY; return shouldRetryIfNotFatal ? Loader.RETRY : Loader.DONT_RETRY;
} }
// Runnable implementation. // Runnable implementation.
...@@ -603,11 +605,13 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable ...@@ -603,11 +605,13 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
< playlistSnapshot.mediaSequence) { < playlistSnapshot.mediaSequence) {
// The media sequence jumped backwards. The server has probably reset. // The media sequence jumped backwards. The server has probably reset.
playlistError = new PlaylistResetException(playlistUrl.url); playlistError = new PlaylistResetException(playlistUrl.url);
notifyPlaylistError(playlistUrl, false);
} else if (currentTimeMs - lastSnapshotChangeMs } else if (currentTimeMs - lastSnapshotChangeMs
> C.usToMs(playlistSnapshot.targetDurationUs) > C.usToMs(playlistSnapshot.targetDurationUs)
* PLAYLIST_STUCK_TARGET_DURATION_COEFFICIENT) { * PLAYLIST_STUCK_TARGET_DURATION_COEFFICIENT) {
// The playlist seems to be stuck. Blacklist it. // The playlist seems to be stuck. Blacklist it.
playlistError = new PlaylistStuckException(playlistUrl.url); playlistError = new PlaylistStuckException(playlistUrl.url);
notifyPlaylistError(playlistUrl, true);
blacklistPlaylist(); blacklistPlaylist();
} }
} }
...@@ -631,7 +635,6 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable ...@@ -631,7 +635,6 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
private boolean blacklistPlaylist() { private boolean blacklistPlaylist() {
blacklistUntilMs = SystemClock.elapsedRealtime() blacklistUntilMs = SystemClock.elapsedRealtime()
+ ChunkedTrackBlacklistUtil.DEFAULT_TRACK_BLACKLIST_MS; + ChunkedTrackBlacklistUtil.DEFAULT_TRACK_BLACKLIST_MS;
notifyPlaylistBlacklisting(playlistUrl, ChunkedTrackBlacklistUtil.DEFAULT_TRACK_BLACKLIST_MS);
return primaryHlsUrl == playlistUrl && !maybeSelectNewPrimaryUrl(); return primaryHlsUrl == playlistUrl && !maybeSelectNewPrimaryUrl();
} }
......
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