From a8a2ef4a24a5e5bdd86309195a0ede904ef5e0e2 Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Thu, 17 Nov 2016 03:58:01 -0800 Subject: [PATCH] Blacklist HLS media playlists that return 4xx error codes Issue:#87 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=139443476 --- .../exoplayer2/source/hls/HlsChunkSource.java | 39 ++++++++++++++----- .../exoplayer2/source/hls/HlsMediaPeriod.java | 8 ++++ .../source/hls/HlsSampleStreamWrapper.java | 5 +++ .../hls/playlist/HlsPlaylistTracker.java | 25 +++++++++--- 4 files changed, 63 insertions(+), 14 deletions(-) diff --git a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java index cbef07f6fe..9e39b9adb5 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java @@ -33,7 +33,7 @@ import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.chunk.Chunk; import com.google.android.exoplayer2.source.chunk.ChunkedTrackBlacklistUtil; import com.google.android.exoplayer2.source.chunk.DataChunk; -import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist; +import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist.HlsUrl; import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist; import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Segment; import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistTracker; @@ -76,7 +76,7 @@ import java.util.Locale; /** * Indicates that the chunk source is waiting for the referred playlist to be refreshed. */ - public HlsMasterPlaylist.HlsUrl playlist; + public HlsUrl playlist; /** * Clears the holder. @@ -99,7 +99,7 @@ import java.util.Locale; private final DataSource dataSource; private final TimestampAdjusterProvider timestampAdjusterProvider; - private final HlsMasterPlaylist.HlsUrl[] variants; + private final HlsUrl[] variants; private final HlsPlaylistTracker playlistTracker; private final TrackGroup trackGroup; @@ -125,7 +125,7 @@ import java.util.Locale; * multiple {@link HlsChunkSource}s are used for a single playback, they should all share the * same provider. */ - public HlsChunkSource(HlsPlaylistTracker playlistTracker, HlsMasterPlaylist.HlsUrl[] variants, + public HlsChunkSource(HlsPlaylistTracker playlistTracker, HlsUrl[] variants, DataSource dataSource, TimestampAdjusterProvider timestampAdjusterProvider) { this.playlistTracker = playlistTracker; this.variants = variants; @@ -183,7 +183,7 @@ import java.util.Locale; * If a chunk is available then {@link HlsChunkHolder#chunk} is set. If the end of the stream has * been reached then {@link HlsChunkHolder#endOfStream} is set. If a chunk is not available but * the end of the stream has not been reached, {@link HlsChunkHolder#playlist} is set to - * contain the {@link HlsMasterPlaylist.HlsUrl} that refers to the playlist that needs refreshing. + * contain the {@link HlsUrl} that refers to the playlist that needs refreshing. * * @param previous The most recently loaded media chunk. * @param playbackPositionUs The current playback position. If {@code previous} is null then this @@ -198,6 +198,8 @@ import java.util.Locale; // require downloading overlapping segments. long bufferedDurationUs = previous == null ? 0 : Math.max(0, previous.getAdjustedStartTimeUs() - playbackPositionUs); + + // Select the variant. trackSelection.updateSelectedTrack(bufferedDurationUs); int newVariantIndex = trackSelection.getSelectedIndexInTrackGroup(); @@ -209,6 +211,7 @@ import java.util.Locale; return; } + // Select the chunk. int chunkMediaSequence; if (previous == null || switchingVariant) { long targetPositionUs = previous == null ? playbackPositionUs : previous.startTimeUs; @@ -244,6 +247,7 @@ import java.util.Locale; return; } + // Handle encryption. HlsMediaPlaylist.Segment segment = mediaPlaylist.segments.get(chunkIndex); // Check if encryption is specified. @@ -272,7 +276,7 @@ import java.util.Locale; Uri chunkUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, segment.url); - // Configure the extractor that will read the chunk. + // Set the extractor that will read the chunk. Extractor extractor; boolean useInitializedExtractor = lastLoadedInitializationChunk != null && lastLoadedInitializationChunk.format == format; @@ -343,6 +347,7 @@ import java.util.Locale; extractorNeedsInit = false; } + // Initialize the extractor. if (needNewExtractor && mediaPlaylist.initializationSegment != null && !useInitializedExtractor) { out.chunk = buildInitializationChunk(mediaPlaylist, extractor, format); @@ -388,12 +393,28 @@ import java.util.Locale; * * @param chunk The chunk whose load encountered the error. * @param cancelable Whether the load can be canceled. - * @param e The error. + * @param error The error. * @return Whether the load should be canceled. */ - public boolean onChunkLoadError(Chunk chunk, boolean cancelable, IOException e) { + public boolean onChunkLoadError(Chunk chunk, boolean cancelable, IOException error) { return cancelable && ChunkedTrackBlacklistUtil.maybeBlacklistTrack(trackSelection, - trackSelection.indexOf(trackGroup.indexOf(chunk.trackFormat)), e); + trackSelection.indexOf(trackGroup.indexOf(chunk.trackFormat)), error); + } + + /** + * Called when an error is encountered while loading a playlist. + * + * @param url The url that references the playlist whose load encountered the error. + * @param error The error. + */ + public void onPlaylistLoadError(HlsUrl url, IOException error) { + int trackGroupIndex = trackGroup.indexOf(url.format); + if (trackGroupIndex == C.INDEX_UNSET) { + // The url is not handled by this chunk source. + return; + } + ChunkedTrackBlacklistUtil.maybeBlacklistTrack(trackSelection, + trackSelection.indexOf(trackGroupIndex), error); } // Private methods. diff --git a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java index 0c27b3df7d..3951b30a78 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java @@ -268,6 +268,14 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper } } + @Override + public void onPlaylistLoadError(HlsMasterPlaylist.HlsUrl url, IOException error) { + for (HlsSampleStreamWrapper sampleStreamWrapper : enabledSampleStreamWrappers) { + sampleStreamWrapper.onPlaylistLoadError(url, error); + } + callback.onContinueLoadingRequested(this); + } + // Internal methods. private void buildAndPrepareSampleStreamWrappers() { diff --git a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java index c491dc9760..1bcc7fe878 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java @@ -33,6 +33,7 @@ import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.chunk.Chunk; import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist; +import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist.HlsUrl; import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.Loader; @@ -274,6 +275,10 @@ import java.util.LinkedList; return largestQueuedTimestampUs; } + public void onPlaylistLoadError(HlsUrl url, IOException error) { + chunkSource.onPlaylistLoadError(url, error); + } + // SampleStream implementation. /* package */ boolean isReady(int group) { diff --git a/library/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistTracker.java b/library/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistTracker.java index 13e64f5e42..abad300f70 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistTracker.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistTracker.java @@ -61,6 +61,14 @@ public final class HlsPlaylistTracker implements Loader.Callback loadable, long elapsedRealtimeMs, long loadDurationMs, IOException error) { - // TODO: Add support for playlist blacklisting in response to server error codes. + // TODO: Change primary playlist if this is the primary playlist bundle. boolean isFatal = error instanceof ParserException; eventDispatcher.loadError(loadable.dataSpec, C.DATA_TYPE_MANIFEST, elapsedRealtimeMs, loadDurationMs, loadable.bytesLoaded(), error, isFatal); - return isFatal ? Loader.DONT_RETRY_FATAL : Loader.RETRY; + if (callback != null) { + callback.onPlaylistLoadError(playlistUrl, error); + } + if (isFatal) { + return Loader.DONT_RETRY_FATAL; + } else { + return primaryHlsUrl == playlistUrl ? Loader.RETRY : Loader.DONT_RETRY; + } } // Runnable implementation.