Commit a8a2ef4a by aquilescanta Committed by Oliver Woodman

Blacklist HLS media playlists that return 4xx error codes

Issue:#87

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=139443476
parent 2add12d5
...@@ -33,7 +33,7 @@ import com.google.android.exoplayer2.source.TrackGroup; ...@@ -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.Chunk;
import com.google.android.exoplayer2.source.chunk.ChunkedTrackBlacklistUtil; import com.google.android.exoplayer2.source.chunk.ChunkedTrackBlacklistUtil;
import com.google.android.exoplayer2.source.chunk.DataChunk; 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;
import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Segment; import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Segment;
import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistTracker; import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistTracker;
...@@ -76,7 +76,7 @@ import java.util.Locale; ...@@ -76,7 +76,7 @@ import java.util.Locale;
/** /**
* Indicates that the chunk source is waiting for the referred playlist to be refreshed. * Indicates that the chunk source is waiting for the referred playlist to be refreshed.
*/ */
public HlsMasterPlaylist.HlsUrl playlist; public HlsUrl playlist;
/** /**
* Clears the holder. * Clears the holder.
...@@ -99,7 +99,7 @@ import java.util.Locale; ...@@ -99,7 +99,7 @@ import java.util.Locale;
private final DataSource dataSource; private final DataSource dataSource;
private final TimestampAdjusterProvider timestampAdjusterProvider; private final TimestampAdjusterProvider timestampAdjusterProvider;
private final HlsMasterPlaylist.HlsUrl[] variants; private final HlsUrl[] variants;
private final HlsPlaylistTracker playlistTracker; private final HlsPlaylistTracker playlistTracker;
private final TrackGroup trackGroup; private final TrackGroup trackGroup;
...@@ -125,7 +125,7 @@ import java.util.Locale; ...@@ -125,7 +125,7 @@ import java.util.Locale;
* multiple {@link HlsChunkSource}s are used for a single playback, they should all share the * multiple {@link HlsChunkSource}s are used for a single playback, they should all share the
* same provider. * same provider.
*/ */
public HlsChunkSource(HlsPlaylistTracker playlistTracker, HlsMasterPlaylist.HlsUrl[] variants, public HlsChunkSource(HlsPlaylistTracker playlistTracker, HlsUrl[] variants,
DataSource dataSource, TimestampAdjusterProvider timestampAdjusterProvider) { DataSource dataSource, TimestampAdjusterProvider timestampAdjusterProvider) {
this.playlistTracker = playlistTracker; this.playlistTracker = playlistTracker;
this.variants = variants; this.variants = variants;
...@@ -183,7 +183,7 @@ import java.util.Locale; ...@@ -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 * 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 * 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 * 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 previous The most recently loaded media chunk.
* @param playbackPositionUs The current playback position. If {@code previous} is null then this * @param playbackPositionUs The current playback position. If {@code previous} is null then this
...@@ -198,6 +198,8 @@ import java.util.Locale; ...@@ -198,6 +198,8 @@ import java.util.Locale;
// require downloading overlapping segments. // require downloading overlapping segments.
long bufferedDurationUs = previous == null ? 0 long bufferedDurationUs = previous == null ? 0
: Math.max(0, previous.getAdjustedStartTimeUs() - playbackPositionUs); : Math.max(0, previous.getAdjustedStartTimeUs() - playbackPositionUs);
// Select the variant.
trackSelection.updateSelectedTrack(bufferedDurationUs); trackSelection.updateSelectedTrack(bufferedDurationUs);
int newVariantIndex = trackSelection.getSelectedIndexInTrackGroup(); int newVariantIndex = trackSelection.getSelectedIndexInTrackGroup();
...@@ -209,6 +211,7 @@ import java.util.Locale; ...@@ -209,6 +211,7 @@ import java.util.Locale;
return; return;
} }
// Select the chunk.
int chunkMediaSequence; int chunkMediaSequence;
if (previous == null || switchingVariant) { if (previous == null || switchingVariant) {
long targetPositionUs = previous == null ? playbackPositionUs : previous.startTimeUs; long targetPositionUs = previous == null ? playbackPositionUs : previous.startTimeUs;
...@@ -244,6 +247,7 @@ import java.util.Locale; ...@@ -244,6 +247,7 @@ import java.util.Locale;
return; return;
} }
// Handle encryption.
HlsMediaPlaylist.Segment segment = mediaPlaylist.segments.get(chunkIndex); HlsMediaPlaylist.Segment segment = mediaPlaylist.segments.get(chunkIndex);
// Check if encryption is specified. // Check if encryption is specified.
...@@ -272,7 +276,7 @@ import java.util.Locale; ...@@ -272,7 +276,7 @@ import java.util.Locale;
Uri chunkUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, segment.url); 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; Extractor extractor;
boolean useInitializedExtractor = lastLoadedInitializationChunk != null boolean useInitializedExtractor = lastLoadedInitializationChunk != null
&& lastLoadedInitializationChunk.format == format; && lastLoadedInitializationChunk.format == format;
...@@ -343,6 +347,7 @@ import java.util.Locale; ...@@ -343,6 +347,7 @@ import java.util.Locale;
extractorNeedsInit = false; extractorNeedsInit = false;
} }
// Initialize the extractor.
if (needNewExtractor && mediaPlaylist.initializationSegment != null if (needNewExtractor && mediaPlaylist.initializationSegment != null
&& !useInitializedExtractor) { && !useInitializedExtractor) {
out.chunk = buildInitializationChunk(mediaPlaylist, extractor, format); out.chunk = buildInitializationChunk(mediaPlaylist, extractor, format);
...@@ -388,12 +393,28 @@ import java.util.Locale; ...@@ -388,12 +393,28 @@ import java.util.Locale;
* *
* @param chunk The chunk whose load encountered the error. * @param chunk The chunk whose load encountered the error.
* @param cancelable Whether the load can be canceled. * @param cancelable Whether the load can be canceled.
* @param e The error. * @param error The error.
* @return Whether the load should be canceled. * @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, 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. // Private methods.
......
...@@ -268,6 +268,14 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper ...@@ -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. // Internal methods.
private void buildAndPrepareSampleStreamWrappers() { private void buildAndPrepareSampleStreamWrappers() {
......
...@@ -33,6 +33,7 @@ import com.google.android.exoplayer2.source.TrackGroup; ...@@ -33,6 +33,7 @@ import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.source.chunk.Chunk; 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;
import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist.HlsUrl;
import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.upstream.Loader; import com.google.android.exoplayer2.upstream.Loader;
...@@ -274,6 +275,10 @@ import java.util.LinkedList; ...@@ -274,6 +275,10 @@ import java.util.LinkedList;
return largestQueuedTimestampUs; return largestQueuedTimestampUs;
} }
public void onPlaylistLoadError(HlsUrl url, IOException error) {
chunkSource.onPlaylistLoadError(url, error);
}
// SampleStream implementation. // SampleStream implementation.
/* package */ boolean isReady(int group) { /* package */ boolean isReady(int group) {
......
...@@ -61,6 +61,14 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable ...@@ -61,6 +61,14 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
*/ */
void onPlaylistChanged(); void onPlaylistChanged();
/**
* Called if an error is encountered while loading the target playlist.
*
* @param url The loaded url that caused the error.
* @param error The loading error.
*/
void onPlaylistLoadError(HlsUrl url, IOException error);
} }
/** /**
...@@ -131,11 +139,11 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable ...@@ -131,11 +139,11 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
} }
/** /**
* Gets the most recent snapshot available of the playlist referred by the provided * Returns the most recent snapshot available of the playlist referenced by the provided
* {@link HlsUrl}. * {@link HlsUrl}.
* *
* @param url The {@link HlsUrl} corresponding to the requested media playlist. * @param url The {@link HlsUrl} corresponding to the requested media playlist.
* @return The most recent snapshot of the playlist referred by the provided {@link HlsUrl}. May * @return The most recent snapshot of the playlist referenced by the provided {@link HlsUrl}. May
* be null if no snapshot has been loaded yet. * be null if no snapshot has been loaded yet.
*/ */
public HlsMediaPlaylist getPlaylistSnapshot(HlsUrl url) { public HlsMediaPlaylist getPlaylistSnapshot(HlsUrl url) {
...@@ -168,7 +176,7 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable ...@@ -168,7 +176,7 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
} }
/** /**
* Triggers a playlist refresh and sets the callback to be called once the playlist referred by * Triggers a playlist refresh and sets the callback to be called once the playlist referenced by
* the provided {@link HlsUrl} changes. * the provided {@link HlsUrl} changes.
* *
* @param key The {@link HlsUrl} of the playlist to be refreshed. * @param key The {@link HlsUrl} of the playlist to be refreshed.
...@@ -410,11 +418,18 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable ...@@ -410,11 +418,18 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
@Override @Override
public int onLoadError(ParsingLoadable<HlsPlaylist> loadable, long elapsedRealtimeMs, public int onLoadError(ParsingLoadable<HlsPlaylist> loadable, long elapsedRealtimeMs,
long loadDurationMs, IOException error) { 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; 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);
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. // Runnable implementation.
......
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