Parameterize load error handling in HLS

Issue:#2844
Issue:#2981

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=204718939
This commit is contained in:
aquilescanta 2018-07-16 04:14:28 -07:00 committed by Oliver Woodman
parent e2059eabe1
commit e247a08a36
7 changed files with 165 additions and 52 deletions

View File

@ -47,6 +47,8 @@
* HLS: * HLS:
* Pass HTTP response headers to `HlsExtractorFactory.createExtractor`. * Pass HTTP response headers to `HlsExtractorFactory.createExtractor`.
* Add support for EXT-X-INDEPENDENT-SEGMENTS in the master playlist. * Add support for EXT-X-INDEPENDENT-SEGMENTS in the master playlist.
* Support load error handling customization
([#2981](https://github.com/google/ExoPlayer/issues/2981)).
* DRM: * DRM:
* Allow DrmInitData to carry a license server URL * Allow DrmInitData to carry a license server URL
([#3393](https://github.com/google/ExoPlayer/issues/3393)). ([#3393](https://github.com/google/ExoPlayer/issues/3393)).

View File

@ -179,6 +179,11 @@ public final class Loader implements LoaderErrorThrower {
this.type = type; this.type = type;
this.retryDelayMillis = retryDelayMillis; this.retryDelayMillis = retryDelayMillis;
} }
/** Returns whether this is a retry action. */
public boolean isRetry() {
return type == ACTION_TYPE_RETRY || type == ACTION_TYPE_RETRY_AND_RESET_ERROR_COUNT;
}
} }
private final ExecutorService downloadExecutorService; private final ExecutorService downloadExecutorService;

View File

@ -397,17 +397,17 @@ import java.util.List;
} }
/** /**
* Called when the {@link HlsSampleStreamWrapper} encounters an error loading a chunk obtained * Attempts to blacklist the track associated with the given chunk. Blacklisting will fail if the
* from this source. * track is the only non-blacklisted track in the selection.
* *
* @param chunk The chunk whose load encountered the error. * @param chunk The chunk whose load caused the blacklisting attempt.
* @param cancelable Whether the load can be canceled. * @param blacklistDurationMs The number of milliseconds for which the track selection should be
* @param error The error. * blacklisted.
* @return Whether the load should be canceled. * @return Whether the blacklisting succeeded.
*/ */
public boolean onChunkLoadError(Chunk chunk, boolean cancelable, IOException error) { public boolean maybeBlacklistTrack(Chunk chunk, long blacklistDurationMs) {
return cancelable && ChunkedTrackBlacklistUtil.maybeBlacklistTrack(trackSelection, return trackSelection.blacklist(
trackSelection.indexOf(trackGroup.indexOf(chunk.trackFormat)), error); trackSelection.indexOf(trackGroup.indexOf(chunk.trackFormat)), blacklistDurationMs);
} }
/** /**

View File

@ -19,6 +19,7 @@ import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.SeekParameters; import com.google.android.exoplayer2.SeekParameters;
import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.source.CompositeSequenceableLoaderFactory; import com.google.android.exoplayer2.source.CompositeSequenceableLoaderFactory;
import com.google.android.exoplayer2.source.MediaPeriod; import com.google.android.exoplayer2.source.MediaPeriod;
import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispatcher; import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispatcher;
@ -26,12 +27,14 @@ import com.google.android.exoplayer2.source.SampleStream;
import com.google.android.exoplayer2.source.SequenceableLoader; import com.google.android.exoplayer2.source.SequenceableLoader;
import com.google.android.exoplayer2.source.TrackGroup; 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.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.source.hls.playlist.HlsMasterPlaylist.HlsUrl;
import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistTracker; import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistTracker;
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.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
import com.google.android.exoplayer2.upstream.TransferListener; import com.google.android.exoplayer2.upstream.TransferListener;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
@ -53,6 +56,7 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
private final HlsPlaylistTracker playlistTracker; private final HlsPlaylistTracker playlistTracker;
private final HlsDataSourceFactory dataSourceFactory; private final HlsDataSourceFactory dataSourceFactory;
private final @Nullable TransferListener<? super DataSource> mediaTransferListener; private final @Nullable TransferListener<? super DataSource> mediaTransferListener;
private final LoadErrorHandlingPolicy<Chunk> chunkLoadErrorHandlingPolicy;
private final int minLoadableRetryCount; private final int minLoadableRetryCount;
private final EventDispatcher eventDispatcher; private final EventDispatcher eventDispatcher;
private final Allocator allocator; private final Allocator allocator;
@ -69,11 +73,29 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
private SequenceableLoader compositeSequenceableLoader; private SequenceableLoader compositeSequenceableLoader;
private boolean notifiedReadingStarted; private boolean notifiedReadingStarted;
/**
* Creates an HLS media period.
*
* @param extractorFactory An {@link HlsExtractorFactory} for {@link Extractor}s for the segments.
* @param playlistTracker A tracker for HLS playlists.
* @param dataSourceFactory An {@link HlsDataSourceFactory} for {@link DataSource}s for segments
* and keys.
* @param mediaTransferListener The transfer listener to inform of any media data transfers. May
* be null if no listener is available.
* @param chunkLoadErrorHandlingPolicy A {@link LoadErrorHandlingPolicy} for chunk loads.
* @param minLoadableRetryCount The minimum number of times to retry if a loading error occurs.
* @param eventDispatcher A dispatcher to notify of events.
* @param allocator An {@link Allocator} from which to obtain media buffer allocations.
* @param compositeSequenceableLoaderFactory A factory to create composite {@link
* SequenceableLoader}s for when this media source loads data from multiple streams.
* @param allowChunklessPreparation Whether chunkless preparation is allowed.
*/
public HlsMediaPeriod( public HlsMediaPeriod(
HlsExtractorFactory extractorFactory, HlsExtractorFactory extractorFactory,
HlsPlaylistTracker playlistTracker, HlsPlaylistTracker playlistTracker,
HlsDataSourceFactory dataSourceFactory, HlsDataSourceFactory dataSourceFactory,
@Nullable TransferListener<? super DataSource> mediaTransferListener, @Nullable TransferListener<? super DataSource> mediaTransferListener,
LoadErrorHandlingPolicy<Chunk> chunkLoadErrorHandlingPolicy,
int minLoadableRetryCount, int minLoadableRetryCount,
EventDispatcher eventDispatcher, EventDispatcher eventDispatcher,
Allocator allocator, Allocator allocator,
@ -83,6 +105,7 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
this.playlistTracker = playlistTracker; this.playlistTracker = playlistTracker;
this.dataSourceFactory = dataSourceFactory; this.dataSourceFactory = dataSourceFactory;
this.mediaTransferListener = mediaTransferListener; this.mediaTransferListener = mediaTransferListener;
this.chunkLoadErrorHandlingPolicy = chunkLoadErrorHandlingPolicy;
this.minLoadableRetryCount = minLoadableRetryCount; this.minLoadableRetryCount = minLoadableRetryCount;
this.eventDispatcher = eventDispatcher; this.eventDispatcher = eventDispatcher;
this.allocator = allocator; this.allocator = allocator;
@ -506,8 +529,16 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
mediaTransferListener, mediaTransferListener,
timestampAdjusterProvider, timestampAdjusterProvider,
muxedCaptionFormats); muxedCaptionFormats);
return new HlsSampleStreamWrapper(trackType, this, defaultChunkSource, allocator, positionUs, return new HlsSampleStreamWrapper(
muxedAudioFormat, minLoadableRetryCount, eventDispatcher); trackType,
/* callback= */ this,
defaultChunkSource,
allocator,
positionUs,
muxedAudioFormat,
chunkLoadErrorHandlingPolicy,
minLoadableRetryCount,
eventDispatcher);
} }
private static Format deriveVideoFormat(Format variantFormat) { private static Format deriveVideoFormat(Format variantFormat) {

View File

@ -32,6 +32,7 @@ import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispat
import com.google.android.exoplayer2.source.SequenceableLoader; import com.google.android.exoplayer2.source.SequenceableLoader;
import com.google.android.exoplayer2.source.SinglePeriodTimeline; import com.google.android.exoplayer2.source.SinglePeriodTimeline;
import com.google.android.exoplayer2.source.ads.AdsMediaSource; import com.google.android.exoplayer2.source.ads.AdsMediaSource;
import com.google.android.exoplayer2.source.chunk.Chunk;
import com.google.android.exoplayer2.source.hls.playlist.DefaultHlsPlaylistTracker; import com.google.android.exoplayer2.source.hls.playlist.DefaultHlsPlaylistTracker;
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.HlsPlaylist; import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylist;
@ -39,6 +40,7 @@ import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistParser;
import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistTracker; import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistTracker;
import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
import com.google.android.exoplayer2.upstream.ParsingLoadable; import com.google.android.exoplayer2.upstream.ParsingLoadable;
import com.google.android.exoplayer2.upstream.TransferListener; import com.google.android.exoplayer2.upstream.TransferListener;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
@ -62,6 +64,7 @@ public final class HlsMediaSource extends BaseMediaSource
private @Nullable ParsingLoadable.Parser<HlsPlaylist> playlistParser; private @Nullable ParsingLoadable.Parser<HlsPlaylist> playlistParser;
private @Nullable HlsPlaylistTracker playlistTracker; private @Nullable HlsPlaylistTracker playlistTracker;
private CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory; private CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory;
private LoadErrorHandlingPolicy<Chunk> chunkLoadErrorHandlingPolicy;
private int minLoadableRetryCount; private int minLoadableRetryCount;
private boolean allowChunklessPreparation; private boolean allowChunklessPreparation;
private boolean isCreateCalled; private boolean isCreateCalled;
@ -87,6 +90,7 @@ public final class HlsMediaSource extends BaseMediaSource
public Factory(HlsDataSourceFactory hlsDataSourceFactory) { public Factory(HlsDataSourceFactory hlsDataSourceFactory) {
this.hlsDataSourceFactory = Assertions.checkNotNull(hlsDataSourceFactory); this.hlsDataSourceFactory = Assertions.checkNotNull(hlsDataSourceFactory);
extractorFactory = HlsExtractorFactory.DEFAULT; extractorFactory = HlsExtractorFactory.DEFAULT;
chunkLoadErrorHandlingPolicy = LoadErrorHandlingPolicy.getDefault();
minLoadableRetryCount = DEFAULT_MIN_LOADABLE_RETRY_COUNT; minLoadableRetryCount = DEFAULT_MIN_LOADABLE_RETRY_COUNT;
compositeSequenceableLoaderFactory = new DefaultCompositeSequenceableLoaderFactory(); compositeSequenceableLoaderFactory = new DefaultCompositeSequenceableLoaderFactory();
} }
@ -121,6 +125,21 @@ public final class HlsMediaSource extends BaseMediaSource
return this; return this;
} }
/**
* Sets the {@link LoadErrorHandlingPolicy} for chunk loads. The default value is {@link
* LoadErrorHandlingPolicy#DEFAULT}.
*
* @param chunkLoadErrorHandlingPolicy A {@link LoadErrorHandlingPolicy} for chunk loads.
* @return This factory, for convenience.
* @throws IllegalStateException If one of the {@code create} methods has already been called.
*/
public Factory setChunkLoadErrorHandlingPolicy(
LoadErrorHandlingPolicy<Chunk> chunkLoadErrorHandlingPolicy) {
Assertions.checkState(!isCreateCalled);
this.chunkLoadErrorHandlingPolicy = chunkLoadErrorHandlingPolicy;
return this;
}
/** /**
* Sets the minimum number of times to retry if a loading error occurs. The default value is * Sets the minimum number of times to retry if a loading error occurs. The default value is
* {@link #DEFAULT_MIN_LOADABLE_RETRY_COUNT}. * {@link #DEFAULT_MIN_LOADABLE_RETRY_COUNT}.
@ -215,6 +234,7 @@ public final class HlsMediaSource extends BaseMediaSource
playlistTracker = playlistTracker =
new DefaultHlsPlaylistTracker( new DefaultHlsPlaylistTracker(
hlsDataSourceFactory, hlsDataSourceFactory,
LoadErrorHandlingPolicy.getDefault(),
minLoadableRetryCount, minLoadableRetryCount,
playlistParser != null ? playlistParser : new HlsPlaylistParser()); playlistParser != null ? playlistParser : new HlsPlaylistParser());
} }
@ -223,6 +243,7 @@ public final class HlsMediaSource extends BaseMediaSource
hlsDataSourceFactory, hlsDataSourceFactory,
extractorFactory, extractorFactory,
compositeSequenceableLoaderFactory, compositeSequenceableLoaderFactory,
chunkLoadErrorHandlingPolicy,
minLoadableRetryCount, minLoadableRetryCount,
playlistTracker, playlistTracker,
allowChunklessPreparation, allowChunklessPreparation,
@ -260,6 +281,7 @@ public final class HlsMediaSource extends BaseMediaSource
private final Uri manifestUri; private final Uri manifestUri;
private final HlsDataSourceFactory dataSourceFactory; private final HlsDataSourceFactory dataSourceFactory;
private final CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory; private final CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory;
private final LoadErrorHandlingPolicy<Chunk> chunkLoadErrorHandlingPolicy;
private final int minLoadableRetryCount; private final int minLoadableRetryCount;
private final boolean allowChunklessPreparation; private final boolean allowChunklessPreparation;
private final HlsPlaylistTracker playlistTracker; private final HlsPlaylistTracker playlistTracker;
@ -341,8 +363,13 @@ public final class HlsMediaSource extends BaseMediaSource
dataSourceFactory, dataSourceFactory,
extractorFactory, extractorFactory,
new DefaultCompositeSequenceableLoaderFactory(), new DefaultCompositeSequenceableLoaderFactory(),
LoadErrorHandlingPolicy.getDefault(),
minLoadableRetryCount, minLoadableRetryCount,
new DefaultHlsPlaylistTracker(dataSourceFactory, minLoadableRetryCount, playlistParser), new DefaultHlsPlaylistTracker(
dataSourceFactory,
LoadErrorHandlingPolicy.getDefault(),
minLoadableRetryCount,
playlistParser),
/* allowChunklessPreparation= */ false, /* allowChunklessPreparation= */ false,
/* tag= */ null); /* tag= */ null);
if (eventHandler != null && eventListener != null) { if (eventHandler != null && eventListener != null) {
@ -355,6 +382,7 @@ public final class HlsMediaSource extends BaseMediaSource
HlsDataSourceFactory dataSourceFactory, HlsDataSourceFactory dataSourceFactory,
HlsExtractorFactory extractorFactory, HlsExtractorFactory extractorFactory,
CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory, CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory,
LoadErrorHandlingPolicy<Chunk> chunkLoadErrorHandlingPolicy,
int minLoadableRetryCount, int minLoadableRetryCount,
HlsPlaylistTracker playlistTracker, HlsPlaylistTracker playlistTracker,
boolean allowChunklessPreparation, boolean allowChunklessPreparation,
@ -363,6 +391,7 @@ public final class HlsMediaSource extends BaseMediaSource
this.dataSourceFactory = dataSourceFactory; this.dataSourceFactory = dataSourceFactory;
this.extractorFactory = extractorFactory; this.extractorFactory = extractorFactory;
this.compositeSequenceableLoaderFactory = compositeSequenceableLoaderFactory; this.compositeSequenceableLoaderFactory = compositeSequenceableLoaderFactory;
this.chunkLoadErrorHandlingPolicy = chunkLoadErrorHandlingPolicy;
this.minLoadableRetryCount = minLoadableRetryCount; this.minLoadableRetryCount = minLoadableRetryCount;
this.playlistTracker = playlistTracker; this.playlistTracker = playlistTracker;
this.allowChunklessPreparation = allowChunklessPreparation; this.allowChunklessPreparation = allowChunklessPreparation;
@ -393,6 +422,7 @@ public final class HlsMediaSource extends BaseMediaSource
playlistTracker, playlistTracker,
dataSourceFactory, dataSourceFactory,
mediaTransferListener, mediaTransferListener,
chunkLoadErrorHandlingPolicy,
minLoadableRetryCount, minLoadableRetryCount,
eventDispatcher, eventDispatcher,
allocator, allocator,

View File

@ -21,7 +21,6 @@ import android.util.Log;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.FormatHolder; import com.google.android.exoplayer2.FormatHolder;
import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer; import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
import com.google.android.exoplayer2.extractor.DummyTrackOutput; import com.google.android.exoplayer2.extractor.DummyTrackOutput;
import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.extractor.ExtractorOutput;
@ -39,6 +38,7 @@ 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.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.LoadErrorHandlingPolicy;
import com.google.android.exoplayer2.upstream.Loader; import com.google.android.exoplayer2.upstream.Loader;
import com.google.android.exoplayer2.upstream.Loader.LoadErrorAction; import com.google.android.exoplayer2.upstream.Loader.LoadErrorAction;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
@ -97,6 +97,7 @@ import java.util.List;
private final HlsChunkSource chunkSource; private final HlsChunkSource chunkSource;
private final Allocator allocator; private final Allocator allocator;
private final Format muxedAudioFormat; private final Format muxedAudioFormat;
private final LoadErrorHandlingPolicy<Chunk> chunkLoadErrorHandlingPolicy;
private final int minLoadableRetryCount; private final int minLoadableRetryCount;
private final Loader loader; private final Loader loader;
private final EventDispatcher eventDispatcher; private final EventDispatcher eventDispatcher;
@ -149,18 +150,27 @@ import java.util.List;
* @param allocator An {@link Allocator} from which to obtain media buffer allocations. * @param allocator An {@link Allocator} from which to obtain media buffer allocations.
* @param positionUs The position from which to start loading media. * @param positionUs The position from which to start loading media.
* @param muxedAudioFormat Optional muxed audio {@link Format} as defined by the master playlist. * @param muxedAudioFormat Optional muxed audio {@link Format} as defined by the master playlist.
* @param chunkLoadErrorHandlingPolicy The {@link LoadErrorHandlingPolicy} for chunk loads.
* @param minLoadableRetryCount The minimum number of times that the source should retry a load * @param minLoadableRetryCount The minimum number of times that the source should retry a load
* before propagating an error. * before propagating an error.
* @param eventDispatcher A dispatcher to notify of events. * @param eventDispatcher A dispatcher to notify of events.
*/ */
public HlsSampleStreamWrapper(int trackType, Callback callback, HlsChunkSource chunkSource, public HlsSampleStreamWrapper(
Allocator allocator, long positionUs, Format muxedAudioFormat, int minLoadableRetryCount, int trackType,
Callback callback,
HlsChunkSource chunkSource,
Allocator allocator,
long positionUs,
Format muxedAudioFormat,
LoadErrorHandlingPolicy<Chunk> chunkLoadErrorHandlingPolicy,
int minLoadableRetryCount,
EventDispatcher eventDispatcher) { EventDispatcher eventDispatcher) {
this.trackType = trackType; this.trackType = trackType;
this.callback = callback; this.callback = callback;
this.chunkSource = chunkSource; this.chunkSource = chunkSource;
this.allocator = allocator; this.allocator = allocator;
this.muxedAudioFormat = muxedAudioFormat; this.muxedAudioFormat = muxedAudioFormat;
this.chunkLoadErrorHandlingPolicy = chunkLoadErrorHandlingPolicy;
this.minLoadableRetryCount = minLoadableRetryCount; this.minLoadableRetryCount = minLoadableRetryCount;
this.eventDispatcher = eventDispatcher; this.eventDispatcher = eventDispatcher;
loader = new Loader("Loader:HlsSampleStreamWrapper"); loader = new Loader("Loader:HlsSampleStreamWrapper");
@ -174,20 +184,8 @@ import java.util.List;
mediaChunks = new ArrayList<>(); mediaChunks = new ArrayList<>();
readOnlyMediaChunks = Collections.unmodifiableList(mediaChunks); readOnlyMediaChunks = Collections.unmodifiableList(mediaChunks);
hlsSampleStreams = new ArrayList<>(); hlsSampleStreams = new ArrayList<>();
maybeFinishPrepareRunnable = maybeFinishPrepareRunnable = this::maybeFinishPrepare;
new Runnable() { onTracksEndedRunnable = this::onTracksEnded;
@Override
public void run() {
maybeFinishPrepare();
}
};
onTracksEndedRunnable =
new Runnable() {
@Override
public void run() {
onTracksEnded();
}
};
handler = new Handler(); handler = new Handler();
lastSeekPositionUs = positionUs; lastSeekPositionUs = positionUs;
pendingResetPositionUs = positionUs; pendingResetPositionUs = positionUs;
@ -644,9 +642,19 @@ import java.util.List;
int errorCount) { int errorCount) {
long bytesLoaded = loadable.bytesLoaded(); long bytesLoaded = loadable.bytesLoaded();
boolean isMediaChunk = isMediaChunk(loadable); boolean isMediaChunk = isMediaChunk(loadable);
boolean cancelable = !isMediaChunk || bytesLoaded == 0; boolean blacklistSucceeded = false;
boolean canceled = false; LoadErrorAction loadErrorAction;
if (chunkSource.onChunkLoadError(loadable, cancelable, error)) {
if (!isMediaChunk || bytesLoaded == 0) {
long blacklistDurationMs =
chunkLoadErrorHandlingPolicy.getBlacklistDurationMsFor(
loadable, loadDurationMs, error, errorCount);
if (blacklistDurationMs != C.TIME_UNSET) {
blacklistSucceeded = chunkSource.maybeBlacklistTrack(loadable, blacklistDurationMs);
}
}
if (blacklistSucceeded) {
if (isMediaChunk) { if (isMediaChunk) {
HlsMediaChunk removed = mediaChunks.remove(mediaChunks.size() - 1); HlsMediaChunk removed = mediaChunks.remove(mediaChunks.size() - 1);
Assertions.checkState(removed == loadable); Assertions.checkState(removed == loadable);
@ -654,8 +662,17 @@ import java.util.List;
pendingResetPositionUs = lastSeekPositionUs; pendingResetPositionUs = lastSeekPositionUs;
} }
} }
canceled = true; loadErrorAction = Loader.DONT_RETRY;
} else /* did not blacklist */ {
long retryDelayMs =
chunkLoadErrorHandlingPolicy.getRetryDelayMsFor(
loadable, loadDurationMs, error, errorCount);
loadErrorAction =
retryDelayMs != C.TIME_UNSET
? Loader.createRetryAction(/* resetErrorCount= */ false, retryDelayMs)
: Loader.DONT_RETRY_FATAL;
} }
eventDispatcher.loadError( eventDispatcher.loadError(
loadable.dataSpec, loadable.dataSpec,
loadable.getUri(), loadable.getUri(),
@ -670,17 +687,16 @@ import java.util.List;
loadDurationMs, loadDurationMs,
loadable.bytesLoaded(), loadable.bytesLoaded(),
error, error,
canceled); /* wasCanceled= */ !loadErrorAction.isRetry());
if (canceled) {
if (blacklistSucceeded) {
if (!prepared) { if (!prepared) {
continueLoading(lastSeekPositionUs); continueLoading(lastSeekPositionUs);
} else { } else {
callback.onContinueLoadingRequested(this); callback.onContinueLoadingRequested(this);
} }
return Loader.DONT_RETRY;
} else {
return error instanceof ParserException ? Loader.DONT_RETRY_FATAL : Loader.RETRY;
} }
return loadErrorAction;
} }
// Called by the consuming thread, but only when there is no loading thread. // Called by the consuming thread, but only when there is no loading thread.

View File

@ -26,6 +26,7 @@ import com.google.android.exoplayer2.source.hls.HlsDataSourceFactory;
import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist.HlsUrl; import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist.HlsUrl;
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.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
import com.google.android.exoplayer2.upstream.Loader; import com.google.android.exoplayer2.upstream.Loader;
import com.google.android.exoplayer2.upstream.Loader.LoadErrorAction; import com.google.android.exoplayer2.upstream.Loader.LoadErrorAction;
import com.google.android.exoplayer2.upstream.ParsingLoadable; import com.google.android.exoplayer2.upstream.ParsingLoadable;
@ -48,6 +49,8 @@ public final class DefaultHlsPlaylistTracker
private final HlsDataSourceFactory dataSourceFactory; private final HlsDataSourceFactory dataSourceFactory;
private final ParsingLoadable.Parser<HlsPlaylist> playlistParser; private final ParsingLoadable.Parser<HlsPlaylist> playlistParser;
private final LoadErrorHandlingPolicy<ParsingLoadable<HlsPlaylist>>
playlistLoadErrorHandlingPolicy;
private final int minRetryCount; private final int minRetryCount;
private final IdentityHashMap<HlsUrl, MediaPlaylistBundle> playlistBundles; private final IdentityHashMap<HlsUrl, MediaPlaylistBundle> playlistBundles;
private final List<PlaylistEventListener> listeners; private final List<PlaylistEventListener> listeners;
@ -64,6 +67,7 @@ public final class DefaultHlsPlaylistTracker
/** /**
* @param dataSourceFactory A factory for {@link DataSource} instances. * @param dataSourceFactory A factory for {@link DataSource} instances.
* @param playlistLoadErrorHandlingPolicy The {@link LoadErrorHandlingPolicy} for playlist loads.
* @param minRetryCount The minimum number of times loads must be retried before {@link * @param minRetryCount The minimum number of times loads must be retried before {@link
* #maybeThrowPlaylistRefreshError(HlsUrl)} and {@link * #maybeThrowPlaylistRefreshError(HlsUrl)} and {@link
* #maybeThrowPrimaryPlaylistRefreshError()} propagate any loading errors. * #maybeThrowPrimaryPlaylistRefreshError()} propagate any loading errors.
@ -71,11 +75,13 @@ public final class DefaultHlsPlaylistTracker
*/ */
public DefaultHlsPlaylistTracker( public DefaultHlsPlaylistTracker(
HlsDataSourceFactory dataSourceFactory, HlsDataSourceFactory dataSourceFactory,
LoadErrorHandlingPolicy<ParsingLoadable<HlsPlaylist>> playlistLoadErrorHandlingPolicy,
int minRetryCount, int minRetryCount,
ParsingLoadable.Parser<HlsPlaylist> playlistParser) { ParsingLoadable.Parser<HlsPlaylist> playlistParser) {
this.dataSourceFactory = dataSourceFactory; this.dataSourceFactory = dataSourceFactory;
this.minRetryCount = minRetryCount; this.minRetryCount = minRetryCount;
this.playlistParser = playlistParser; this.playlistParser = playlistParser;
this.playlistLoadErrorHandlingPolicy = playlistLoadErrorHandlingPolicy;
listeners = new ArrayList<>(); listeners = new ArrayList<>();
playlistBundles = new IdentityHashMap<>(); playlistBundles = new IdentityHashMap<>();
initialStartTimeUs = C.TIME_UNSET; initialStartTimeUs = C.TIME_UNSET;
@ -241,7 +247,10 @@ public final class DefaultHlsPlaylistTracker
long loadDurationMs, long loadDurationMs,
IOException error, IOException error,
int errorCount) { int errorCount) {
boolean isFatal = error instanceof ParserException; long retryDelayMs =
playlistLoadErrorHandlingPolicy.getRetryDelayMsFor(
loadable, loadDurationMs, error, errorCount);
boolean isFatal = retryDelayMs == C.TIME_UNSET;
eventDispatcher.loadError( eventDispatcher.loadError(
loadable.dataSpec, loadable.dataSpec,
loadable.getUri(), loadable.getUri(),
@ -251,7 +260,9 @@ public final class DefaultHlsPlaylistTracker
loadable.bytesLoaded(), loadable.bytesLoaded(),
error, error,
isFatal); isFatal);
return isFatal ? Loader.DONT_RETRY_FATAL : Loader.RETRY; return isFatal
? Loader.DONT_RETRY_FATAL
: Loader.createRetryAction(/* resetErrorCount= */ false, retryDelayMs);
} }
// Internal methods. // Internal methods.
@ -501,7 +512,31 @@ public final class DefaultHlsPlaylistTracker
long loadDurationMs, long loadDurationMs,
IOException error, IOException error,
int errorCount) { int errorCount) {
boolean isFatal = error instanceof ParserException; LoadErrorAction loadErrorAction;
long blacklistDurationMs =
playlistLoadErrorHandlingPolicy.getBlacklistDurationMsFor(
loadable, loadDurationMs, error, errorCount);
boolean shouldBlacklist = blacklistDurationMs != C.TIME_UNSET;
boolean blacklistingFailed =
notifyPlaylistError(playlistUrl, shouldBlacklist) || !shouldBlacklist;
if (shouldBlacklist) {
blacklistingFailed |= blacklistPlaylist();
}
if (blacklistingFailed) {
long retryDelay =
playlistLoadErrorHandlingPolicy.getRetryDelayMsFor(
loadable, loadDurationMs, error, errorCount);
loadErrorAction =
retryDelay != C.TIME_UNSET
? Loader.createRetryAction(false, retryDelay)
: Loader.DONT_RETRY_FATAL;
} else {
loadErrorAction = Loader.DONT_RETRY;
}
eventDispatcher.loadError( eventDispatcher.loadError(
loadable.dataSpec, loadable.dataSpec,
loadable.getUri(), loadable.getUri(),
@ -510,17 +545,9 @@ public final class DefaultHlsPlaylistTracker
loadDurationMs, loadDurationMs,
loadable.bytesLoaded(), loadable.bytesLoaded(),
error, error,
isFatal); /* wasCanceled= */ !loadErrorAction.isRetry());
boolean shouldBlacklist = ChunkedTrackBlacklistUtil.shouldBlacklist(error);
boolean shouldRetryIfNotFatal = return loadErrorAction;
notifyPlaylistError(playlistUrl, shouldBlacklist) || !shouldBlacklist;
if (isFatal) {
return Loader.DONT_RETRY_FATAL;
}
if (shouldBlacklist) {
shouldRetryIfNotFatal |= blacklistPlaylist();
}
return shouldRetryIfNotFatal ? Loader.RETRY : Loader.DONT_RETRY;
} }
// Runnable implementation. // Runnable implementation.
@ -558,12 +585,14 @@ public final class DefaultHlsPlaylistTracker
} else if (!playlistSnapshot.hasEndTag) { } else if (!playlistSnapshot.hasEndTag) {
if (loadedPlaylist.mediaSequence + loadedPlaylist.segments.size() if (loadedPlaylist.mediaSequence + loadedPlaylist.segments.size()
< playlistSnapshot.mediaSequence) { < playlistSnapshot.mediaSequence) {
// TODO: Allow customization of playlist resets handling.
// 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); 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) {
// TODO: Allow customization of stuck playlists handling.
// 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); notifyPlaylistError(playlistUrl, true);