mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
Blacklist HLS media playlists that return 4xx error codes
Issue:#87 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=139443476
This commit is contained in:
parent
2add12d5f7
commit
a8a2ef4a24
4 changed files with 63 additions and 14 deletions
|
|
@ -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;
|
||||||
/**
|
/**
|
||||||
* 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;
|
||||||
|
|
||||||
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;
|
||||||
* 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;
|
||||||
* 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;
|
||||||
// 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;
|
||||||
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;
|
||||||
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;
|
||||||
|
|
||||||
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;
|
||||||
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;
|
||||||
*
|
*
|
||||||
* @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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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;
|
||||||
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;
|
||||||
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
|
||||||
*/
|
*/
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
@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.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue