From e247a08a3663907a7b0e0c25cbcab70da31ff5e9 Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Mon, 16 Jul 2018 04:14:28 -0700 Subject: [PATCH] Parameterize load error handling in HLS Issue:#2844 Issue:#2981 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=204718939 --- RELEASENOTES.md | 2 + .../android/exoplayer2/upstream/Loader.java | 5 ++ .../exoplayer2/source/hls/HlsChunkSource.java | 18 ++--- .../exoplayer2/source/hls/HlsMediaPeriod.java | 35 +++++++++- .../exoplayer2/source/hls/HlsMediaSource.java | 32 ++++++++- .../source/hls/HlsSampleStreamWrapper.java | 68 ++++++++++++------- .../playlist/DefaultHlsPlaylistTracker.java | 57 ++++++++++++---- 7 files changed, 165 insertions(+), 52 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index f6547bc68b8..1c43fc549c2 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -47,6 +47,8 @@ * HLS: * Pass HTTP response headers to `HlsExtractorFactory.createExtractor`. * 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: * Allow DrmInitData to carry a license server URL ([#3393](https://github.com/google/ExoPlayer/issues/3393)). diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/Loader.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/Loader.java index ce26f068480..e284310f9e1 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/Loader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/Loader.java @@ -179,6 +179,11 @@ private LoadErrorAction(@RetryActionType int type, long retryDelayMillis) { this.type = type; 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; diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java index 03a2af774ce..d3d8e77e943 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java @@ -397,17 +397,17 @@ public void onChunkLoadCompleted(Chunk chunk) { } /** - * Called when the {@link HlsSampleStreamWrapper} encounters an error loading a chunk obtained - * from this source. + * Attempts to blacklist the track associated with the given chunk. Blacklisting will fail if the + * track is the only non-blacklisted track in the selection. * - * @param chunk The chunk whose load encountered the error. - * @param cancelable Whether the load can be canceled. - * @param error The error. - * @return Whether the load should be canceled. + * @param chunk The chunk whose load caused the blacklisting attempt. + * @param blacklistDurationMs The number of milliseconds for which the track selection should be + * blacklisted. + * @return Whether the blacklisting succeeded. */ - public boolean onChunkLoadError(Chunk chunk, boolean cancelable, IOException error) { - return cancelable && ChunkedTrackBlacklistUtil.maybeBlacklistTrack(trackSelection, - trackSelection.indexOf(trackGroup.indexOf(chunk.trackFormat)), error); + public boolean maybeBlacklistTrack(Chunk chunk, long blacklistDurationMs) { + return trackSelection.blacklist( + trackSelection.indexOf(trackGroup.indexOf(chunk.trackFormat)), blacklistDurationMs); } /** diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java index a9f1adf4e6b..84b1d4591c2 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java @@ -19,6 +19,7 @@ import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; 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.MediaPeriod; import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispatcher; @@ -26,12 +27,14 @@ import com.google.android.exoplayer2.source.SequenceableLoader; 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.source.hls.playlist.HlsPlaylistTracker; import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.upstream.Allocator; 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.util.Assertions; import com.google.android.exoplayer2.util.MimeTypes; @@ -53,6 +56,7 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper private final HlsPlaylistTracker playlistTracker; private final HlsDataSourceFactory dataSourceFactory; private final @Nullable TransferListener mediaTransferListener; + private final LoadErrorHandlingPolicy chunkLoadErrorHandlingPolicy; private final int minLoadableRetryCount; private final EventDispatcher eventDispatcher; private final Allocator allocator; @@ -69,11 +73,29 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper private SequenceableLoader compositeSequenceableLoader; 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( HlsExtractorFactory extractorFactory, HlsPlaylistTracker playlistTracker, HlsDataSourceFactory dataSourceFactory, @Nullable TransferListener mediaTransferListener, + LoadErrorHandlingPolicy chunkLoadErrorHandlingPolicy, int minLoadableRetryCount, EventDispatcher eventDispatcher, Allocator allocator, @@ -83,6 +105,7 @@ public HlsMediaPeriod( this.playlistTracker = playlistTracker; this.dataSourceFactory = dataSourceFactory; this.mediaTransferListener = mediaTransferListener; + this.chunkLoadErrorHandlingPolicy = chunkLoadErrorHandlingPolicy; this.minLoadableRetryCount = minLoadableRetryCount; this.eventDispatcher = eventDispatcher; this.allocator = allocator; @@ -506,8 +529,16 @@ private HlsSampleStreamWrapper buildSampleStreamWrapper(int trackType, HlsUrl[] mediaTransferListener, timestampAdjusterProvider, muxedCaptionFormats); - return new HlsSampleStreamWrapper(trackType, this, defaultChunkSource, allocator, positionUs, - muxedAudioFormat, minLoadableRetryCount, eventDispatcher); + return new HlsSampleStreamWrapper( + trackType, + /* callback= */ this, + defaultChunkSource, + allocator, + positionUs, + muxedAudioFormat, + chunkLoadErrorHandlingPolicy, + minLoadableRetryCount, + eventDispatcher); } private static Format deriveVideoFormat(Format variantFormat) { diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java index 3dc34462d89..5cb8c42a211 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java @@ -32,6 +32,7 @@ import com.google.android.exoplayer2.source.SequenceableLoader; import com.google.android.exoplayer2.source.SinglePeriodTimeline; 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.HlsMediaPlaylist; import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylist; @@ -39,6 +40,7 @@ import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistTracker; import com.google.android.exoplayer2.upstream.Allocator; 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.TransferListener; import com.google.android.exoplayer2.util.Assertions; @@ -62,6 +64,7 @@ public static final class Factory implements AdsMediaSource.MediaSourceFactory { private @Nullable ParsingLoadable.Parser playlistParser; private @Nullable HlsPlaylistTracker playlistTracker; private CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory; + private LoadErrorHandlingPolicy chunkLoadErrorHandlingPolicy; private int minLoadableRetryCount; private boolean allowChunklessPreparation; private boolean isCreateCalled; @@ -87,6 +90,7 @@ public Factory(DataSource.Factory dataSourceFactory) { public Factory(HlsDataSourceFactory hlsDataSourceFactory) { this.hlsDataSourceFactory = Assertions.checkNotNull(hlsDataSourceFactory); extractorFactory = HlsExtractorFactory.DEFAULT; + chunkLoadErrorHandlingPolicy = LoadErrorHandlingPolicy.getDefault(); minLoadableRetryCount = DEFAULT_MIN_LOADABLE_RETRY_COUNT; compositeSequenceableLoaderFactory = new DefaultCompositeSequenceableLoaderFactory(); } @@ -121,6 +125,21 @@ public Factory setExtractorFactory(HlsExtractorFactory extractorFactory) { 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 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 * {@link #DEFAULT_MIN_LOADABLE_RETRY_COUNT}. @@ -215,6 +234,7 @@ public HlsMediaSource createMediaSource(Uri playlistUri) { playlistTracker = new DefaultHlsPlaylistTracker( hlsDataSourceFactory, + LoadErrorHandlingPolicy.getDefault(), minLoadableRetryCount, playlistParser != null ? playlistParser : new HlsPlaylistParser()); } @@ -223,6 +243,7 @@ public HlsMediaSource createMediaSource(Uri playlistUri) { hlsDataSourceFactory, extractorFactory, compositeSequenceableLoaderFactory, + chunkLoadErrorHandlingPolicy, minLoadableRetryCount, playlistTracker, allowChunklessPreparation, @@ -260,6 +281,7 @@ public int[] getSupportedTypes() { private final Uri manifestUri; private final HlsDataSourceFactory dataSourceFactory; private final CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory; + private final LoadErrorHandlingPolicy chunkLoadErrorHandlingPolicy; private final int minLoadableRetryCount; private final boolean allowChunklessPreparation; private final HlsPlaylistTracker playlistTracker; @@ -341,8 +363,13 @@ public HlsMediaSource( dataSourceFactory, extractorFactory, new DefaultCompositeSequenceableLoaderFactory(), + LoadErrorHandlingPolicy.getDefault(), minLoadableRetryCount, - new DefaultHlsPlaylistTracker(dataSourceFactory, minLoadableRetryCount, playlistParser), + new DefaultHlsPlaylistTracker( + dataSourceFactory, + LoadErrorHandlingPolicy.getDefault(), + minLoadableRetryCount, + playlistParser), /* allowChunklessPreparation= */ false, /* tag= */ null); if (eventHandler != null && eventListener != null) { @@ -355,6 +382,7 @@ private HlsMediaSource( HlsDataSourceFactory dataSourceFactory, HlsExtractorFactory extractorFactory, CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory, + LoadErrorHandlingPolicy chunkLoadErrorHandlingPolicy, int minLoadableRetryCount, HlsPlaylistTracker playlistTracker, boolean allowChunklessPreparation, @@ -363,6 +391,7 @@ private HlsMediaSource( this.dataSourceFactory = dataSourceFactory; this.extractorFactory = extractorFactory; this.compositeSequenceableLoaderFactory = compositeSequenceableLoaderFactory; + this.chunkLoadErrorHandlingPolicy = chunkLoadErrorHandlingPolicy; this.minLoadableRetryCount = minLoadableRetryCount; this.playlistTracker = playlistTracker; this.allowChunklessPreparation = allowChunklessPreparation; @@ -393,6 +422,7 @@ public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) { playlistTracker, dataSourceFactory, mediaTransferListener, + chunkLoadErrorHandlingPolicy, minLoadableRetryCount, eventDispatcher, allocator, diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java index 93895d0acc4..8ba34c84742 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java @@ -21,7 +21,6 @@ import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.FormatHolder; -import com.google.android.exoplayer2.ParserException; import com.google.android.exoplayer2.decoder.DecoderInputBuffer; import com.google.android.exoplayer2.extractor.DummyTrackOutput; import com.google.android.exoplayer2.extractor.ExtractorOutput; @@ -39,6 +38,7 @@ 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.LoadErrorHandlingPolicy; import com.google.android.exoplayer2.upstream.Loader; import com.google.android.exoplayer2.upstream.Loader.LoadErrorAction; import com.google.android.exoplayer2.util.Assertions; @@ -97,6 +97,7 @@ public interface Callback extends SequenceableLoader.Callback chunkLoadErrorHandlingPolicy; private final int minLoadableRetryCount; private final Loader loader; private final EventDispatcher eventDispatcher; @@ -149,18 +150,27 @@ public interface Callback extends SequenceableLoader.Callback chunkLoadErrorHandlingPolicy, + int minLoadableRetryCount, EventDispatcher eventDispatcher) { this.trackType = trackType; this.callback = callback; this.chunkSource = chunkSource; this.allocator = allocator; this.muxedAudioFormat = muxedAudioFormat; + this.chunkLoadErrorHandlingPolicy = chunkLoadErrorHandlingPolicy; this.minLoadableRetryCount = minLoadableRetryCount; this.eventDispatcher = eventDispatcher; loader = new Loader("Loader:HlsSampleStreamWrapper"); @@ -174,20 +184,8 @@ public HlsSampleStreamWrapper(int trackType, Callback callback, HlsChunkSource c mediaChunks = new ArrayList<>(); readOnlyMediaChunks = Collections.unmodifiableList(mediaChunks); hlsSampleStreams = new ArrayList<>(); - maybeFinishPrepareRunnable = - new Runnable() { - @Override - public void run() { - maybeFinishPrepare(); - } - }; - onTracksEndedRunnable = - new Runnable() { - @Override - public void run() { - onTracksEnded(); - } - }; + maybeFinishPrepareRunnable = this::maybeFinishPrepare; + onTracksEndedRunnable = this::onTracksEnded; handler = new Handler(); lastSeekPositionUs = positionUs; pendingResetPositionUs = positionUs; @@ -644,9 +642,19 @@ public LoadErrorAction onLoadError( int errorCount) { long bytesLoaded = loadable.bytesLoaded(); boolean isMediaChunk = isMediaChunk(loadable); - boolean cancelable = !isMediaChunk || bytesLoaded == 0; - boolean canceled = false; - if (chunkSource.onChunkLoadError(loadable, cancelable, error)) { + boolean blacklistSucceeded = false; + LoadErrorAction loadErrorAction; + + 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) { HlsMediaChunk removed = mediaChunks.remove(mediaChunks.size() - 1); Assertions.checkState(removed == loadable); @@ -654,8 +662,17 @@ public LoadErrorAction onLoadError( 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( loadable.dataSpec, loadable.getUri(), @@ -670,17 +687,16 @@ public LoadErrorAction onLoadError( loadDurationMs, loadable.bytesLoaded(), error, - canceled); - if (canceled) { + /* wasCanceled= */ !loadErrorAction.isRetry()); + + if (blacklistSucceeded) { if (!prepared) { continueLoading(lastSeekPositionUs); } else { 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. diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/DefaultHlsPlaylistTracker.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/DefaultHlsPlaylistTracker.java index 6e877b03212..099002a1093 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/DefaultHlsPlaylistTracker.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/DefaultHlsPlaylistTracker.java @@ -26,6 +26,7 @@ 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.upstream.DataSource; +import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy; import com.google.android.exoplayer2.upstream.Loader; import com.google.android.exoplayer2.upstream.Loader.LoadErrorAction; import com.google.android.exoplayer2.upstream.ParsingLoadable; @@ -48,6 +49,8 @@ public final class DefaultHlsPlaylistTracker private final HlsDataSourceFactory dataSourceFactory; private final ParsingLoadable.Parser playlistParser; + private final LoadErrorHandlingPolicy> + playlistLoadErrorHandlingPolicy; private final int minRetryCount; private final IdentityHashMap playlistBundles; private final List listeners; @@ -64,6 +67,7 @@ public final class DefaultHlsPlaylistTracker /** * @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 * #maybeThrowPlaylistRefreshError(HlsUrl)} and {@link * #maybeThrowPrimaryPlaylistRefreshError()} propagate any loading errors. @@ -71,11 +75,13 @@ public final class DefaultHlsPlaylistTracker */ public DefaultHlsPlaylistTracker( HlsDataSourceFactory dataSourceFactory, + LoadErrorHandlingPolicy> playlistLoadErrorHandlingPolicy, int minRetryCount, ParsingLoadable.Parser playlistParser) { this.dataSourceFactory = dataSourceFactory; this.minRetryCount = minRetryCount; this.playlistParser = playlistParser; + this.playlistLoadErrorHandlingPolicy = playlistLoadErrorHandlingPolicy; listeners = new ArrayList<>(); playlistBundles = new IdentityHashMap<>(); initialStartTimeUs = C.TIME_UNSET; @@ -241,7 +247,10 @@ public LoadErrorAction onLoadError( long loadDurationMs, IOException error, int errorCount) { - boolean isFatal = error instanceof ParserException; + long retryDelayMs = + playlistLoadErrorHandlingPolicy.getRetryDelayMsFor( + loadable, loadDurationMs, error, errorCount); + boolean isFatal = retryDelayMs == C.TIME_UNSET; eventDispatcher.loadError( loadable.dataSpec, loadable.getUri(), @@ -251,7 +260,9 @@ public LoadErrorAction onLoadError( loadable.bytesLoaded(), error, isFatal); - return isFatal ? Loader.DONT_RETRY_FATAL : Loader.RETRY; + return isFatal + ? Loader.DONT_RETRY_FATAL + : Loader.createRetryAction(/* resetErrorCount= */ false, retryDelayMs); } // Internal methods. @@ -501,7 +512,31 @@ public LoadErrorAction onLoadError( long loadDurationMs, IOException error, 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( loadable.dataSpec, loadable.getUri(), @@ -510,17 +545,9 @@ public LoadErrorAction onLoadError( loadDurationMs, loadable.bytesLoaded(), error, - isFatal); - boolean shouldBlacklist = ChunkedTrackBlacklistUtil.shouldBlacklist(error); - boolean shouldRetryIfNotFatal = - notifyPlaylistError(playlistUrl, shouldBlacklist) || !shouldBlacklist; - if (isFatal) { - return Loader.DONT_RETRY_FATAL; - } - if (shouldBlacklist) { - shouldRetryIfNotFatal |= blacklistPlaylist(); - } - return shouldRetryIfNotFatal ? Loader.RETRY : Loader.DONT_RETRY; + /* wasCanceled= */ !loadErrorAction.isRetry()); + + return loadErrorAction; } // Runnable implementation. @@ -558,12 +585,14 @@ private void processLoadedPlaylist(HlsMediaPlaylist loadedPlaylist) { } else if (!playlistSnapshot.hasEndTag) { if (loadedPlaylist.mediaSequence + loadedPlaylist.segments.size() < playlistSnapshot.mediaSequence) { + // TODO: Allow customization of playlist resets handling. // The media sequence jumped backwards. The server has probably reset. playlistError = new PlaylistResetException(playlistUrl.url); notifyPlaylistError(playlistUrl, false); } else if (currentTimeMs - lastSnapshotChangeMs > C.usToMs(playlistSnapshot.targetDurationUs) * PLAYLIST_STUCK_TARGET_DURATION_COEFFICIENT) { + // TODO: Allow customization of stuck playlists handling. // The playlist seems to be stuck. Blacklist it. playlistError = new PlaylistStuckException(playlistUrl.url); notifyPlaylistError(playlistUrl, true);