diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java index 18c69d42b6..7b4dd96e4a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java @@ -178,11 +178,6 @@ public final class SingleSampleMediaSource extends BaseMediaSource { } - /** - * The default minimum number of times to retry loading data prior to failing. - */ - public static final int DEFAULT_MIN_LOADABLE_RETRY_COUNT = 3; - private final DataSpec dataSpec; private final DataSource.Factory dataSourceFactory; private final Format format; @@ -204,7 +199,12 @@ public final class SingleSampleMediaSource extends BaseMediaSource { @Deprecated public SingleSampleMediaSource( Uri uri, DataSource.Factory dataSourceFactory, Format format, long durationUs) { - this(uri, dataSourceFactory, format, durationUs, DEFAULT_MIN_LOADABLE_RETRY_COUNT); + this( + uri, + dataSourceFactory, + format, + durationUs, + DefaultLoadErrorHandlingPolicy.DEFAULT_MIN_LOADABLE_RETRY_COUNT); } /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java index b71b236c4f..78914e9f33 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java @@ -27,6 +27,8 @@ import com.google.android.exoplayer2.source.SampleQueue; import com.google.android.exoplayer2.source.SampleStream; import com.google.android.exoplayer2.source.SequenceableLoader; import com.google.android.exoplayer2.upstream.Allocator; +import com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy; +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; @@ -64,7 +66,7 @@ public class ChunkSampleStream implements SampleStream, S private final T chunkSource; private final SequenceableLoader.Callback> callback; private final EventDispatcher eventDispatcher; - private final int minLoadableRetryCount; + private final LoadErrorHandlingPolicy loadErrorHandlingPolicy; private final Loader loader; private final ChunkHolder nextChunkHolder; private final ArrayList mediaChunks; @@ -81,6 +83,8 @@ public class ChunkSampleStream implements SampleStream, S /* package */ boolean loadingFinished; /** + * Constructs an instance. + * * @param primaryTrackType The type of the primary track. One of the {@link C} {@code * TRACK_TYPE_*} constants. * @param embeddedTrackTypes The types of any embedded tracks, or null. @@ -92,7 +96,10 @@ public class ChunkSampleStream implements SampleStream, S * @param minLoadableRetryCount The minimum number of times that the source should retry a load * before propagating an error. * @param eventDispatcher A dispatcher to notify of events. + * @deprecated Use {@link #ChunkSampleStream(int, int[], Format[], ChunkSource, Callback, + * Allocator, long, LoadErrorHandlingPolicy, EventDispatcher)} instead. */ + @Deprecated public ChunkSampleStream( int primaryTrackType, int[] embeddedTrackTypes, @@ -103,13 +110,49 @@ public class ChunkSampleStream implements SampleStream, S long positionUs, int minLoadableRetryCount, EventDispatcher eventDispatcher) { + this( + primaryTrackType, + embeddedTrackTypes, + embeddedTrackFormats, + chunkSource, + callback, + allocator, + positionUs, + new DefaultLoadErrorHandlingPolicy(minLoadableRetryCount), + eventDispatcher); + } + + /** + * Constructs an instance. + * + * @param primaryTrackType The type of the primary track. One of the {@link C} {@code + * TRACK_TYPE_*} constants. + * @param embeddedTrackTypes The types of any embedded tracks, or null. + * @param embeddedTrackFormats The formats of the embedded tracks, or null. + * @param chunkSource A {@link ChunkSource} from which chunks to load are obtained. + * @param callback An {@link Callback} for the stream. + * @param allocator An {@link Allocator} from which allocations can be obtained. + * @param positionUs The position from which to start loading media. + * @param loadErrorHandlingPolicy The {@link LoadErrorHandlingPolicy}. + * @param eventDispatcher A dispatcher to notify of events. + */ + public ChunkSampleStream( + int primaryTrackType, + int[] embeddedTrackTypes, + Format[] embeddedTrackFormats, + T chunkSource, + Callback> callback, + Allocator allocator, + long positionUs, + LoadErrorHandlingPolicy loadErrorHandlingPolicy, + EventDispatcher eventDispatcher) { this.primaryTrackType = primaryTrackType; this.embeddedTrackTypes = embeddedTrackTypes; this.embeddedTrackFormats = embeddedTrackFormats; this.chunkSource = chunkSource; this.callback = callback; this.eventDispatcher = eventDispatcher; - this.minLoadableRetryCount = minLoadableRetryCount; + this.loadErrorHandlingPolicy = loadErrorHandlingPolicy; loader = new Loader("Loader:ChunkSampleStream"); nextChunkHolder = new ChunkHolder(); mediaChunks = new ArrayList<>(); @@ -439,12 +482,15 @@ public class ChunkSampleStream implements SampleStream, S int lastChunkIndex = mediaChunks.size() - 1; boolean cancelable = bytesLoaded == 0 || !isMediaChunk || !haveReadFromMediaChunk(lastChunkIndex); - boolean canceled = false; - if (chunkSource.onChunkLoadError(loadable, cancelable, error)) { - if (!cancelable) { - Log.w(TAG, "Ignoring attempt to cancel non-cancelable load."); - } else { - canceled = true; + long blacklistDurationMs = + cancelable + ? loadErrorHandlingPolicy.getBlacklistDurationMsFor( + loadable.type, loadDurationMs, error, errorCount) + : C.TIME_UNSET; + LoadErrorAction loadErrorAction = null; + if (chunkSource.onChunkLoadError(loadable, cancelable, error, blacklistDurationMs)) { + if (cancelable) { + loadErrorAction = Loader.DONT_RETRY; if (isMediaChunk) { BaseMediaChunk removed = discardUpstreamMediaChunksFromIndex(lastChunkIndex); Assertions.checkState(removed == loadable); @@ -452,8 +498,23 @@ public class ChunkSampleStream implements SampleStream, S pendingResetPositionUs = lastSeekPositionUs; } } + } else { + Log.w(TAG, "Ignoring attempt to cancel non-cancelable load."); } } + + if (loadErrorAction == null) { + // The load was not cancelled. Either the load must be retried or the error propagated. + long retryDelayMs = + loadErrorHandlingPolicy.getRetryDelayMsFor( + loadable.type, loadDurationMs, error, errorCount); + loadErrorAction = + retryDelayMs != C.TIME_UNSET + ? Loader.createRetryAction(/* resetErrorCount= */ false, retryDelayMs) + : Loader.DONT_RETRY_FATAL; + } + + boolean canceled = !loadErrorAction.isRetry(); eventDispatcher.loadError( loadable.dataSpec, loadable.getUri(), @@ -471,10 +532,8 @@ public class ChunkSampleStream implements SampleStream, S canceled); if (canceled) { callback.onContinueLoadingRequested(this); - return Loader.DONT_RETRY; - } else { - return Loader.RETRY; } + return loadErrorAction; } // SequenceableLoader implementation @@ -521,7 +580,9 @@ public class ChunkSampleStream implements SampleStream, S mediaChunk.init(mediaChunkOutput); mediaChunks.add(mediaChunk); } - long elapsedRealtimeMs = loader.startLoading(loadable, this, minLoadableRetryCount); + long elapsedRealtimeMs = + loader.startLoading( + loadable, this, loadErrorHandlingPolicy.getMinimumLoadableRetryCount(loadable.type)); eventDispatcher.loadStarted( loadable.dataSpec, loadable.dataSpec.uri, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSource.java index be45138bd9..ee940954bd 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSource.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.source.chunk; +import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.SeekParameters; import java.io.IOException; import java.util.List; @@ -83,8 +84,8 @@ public interface ChunkSource { /** * Called when the {@link ChunkSampleStream} has finished loading a chunk obtained from this * source. - *

- * This method should only be called when the source is enabled. + * + *

This method should only be called when the source is enabled. * * @param chunk The chunk whose load has been completed. */ @@ -93,15 +94,15 @@ public interface ChunkSource { /** * Called when the {@link ChunkSampleStream} encounters an error loading a chunk obtained from * this source. - *

- * This method should only be called when the source is enabled. + * + *

This method should only be called when the source is enabled. * * @param chunk The chunk whose load encountered the error. * @param cancelable Whether the load can be canceled. * @param e The error. - * @return Whether the load should be canceled. Should always be false if {@code cancelable} is - * false. + * @param blacklistDurationMs The duration for which the associated track may be blacklisted, or + * {@link C#TIME_UNSET} if the track may not be blacklisted. + * @return Whether the load should be canceled. Must be false if {@code cancelable} is false. */ - boolean onChunkLoadError(Chunk chunk, boolean cancelable, Exception e); - + boolean onChunkLoadError(Chunk chunk, boolean cancelable, Exception e, long blacklistDurationMs); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkedTrackBlacklistUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkedTrackBlacklistUtil.java deleted file mode 100644 index 38e0c0d51f..0000000000 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkedTrackBlacklistUtil.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.google.android.exoplayer2.source.chunk; - -import android.util.Log; -import com.google.android.exoplayer2.trackselection.TrackSelection; -import com.google.android.exoplayer2.upstream.HttpDataSource.InvalidResponseCodeException; - -/** - * Helper class for blacklisting tracks in a {@link TrackSelection} when 404 (Not Found) and 410 - * (Gone) HTTP response codes are encountered. - */ -public final class ChunkedTrackBlacklistUtil { - - /** - * The default duration for which a track is blacklisted in milliseconds. - */ - public static final long DEFAULT_TRACK_BLACKLIST_MS = 60000; - - private static final String TAG = "ChunkedTrackBlacklist"; - - /** - * Blacklists {@code trackSelectionIndex} in {@code trackSelection} for - * {@link #DEFAULT_TRACK_BLACKLIST_MS} if {@code e} is an {@link InvalidResponseCodeException} - * with {@link InvalidResponseCodeException#responseCode} equal to 404 or 410. Else does nothing. - * Note that blacklisting will fail if the track is the only non-blacklisted track in the - * selection. - * - * @param trackSelection The track selection. - * @param trackSelectionIndex The index in the selection to consider blacklisting. - * @param e The error to inspect. - * @return Whether the track was blacklisted in the selection. - */ - public static boolean maybeBlacklistTrack(TrackSelection trackSelection, int trackSelectionIndex, - Exception e) { - return maybeBlacklistTrack(trackSelection, trackSelectionIndex, e, DEFAULT_TRACK_BLACKLIST_MS); - } - - /** - * Blacklists {@code trackSelectionIndex} in {@code trackSelection} for - * {@code blacklistDurationMs} if calling {@link #shouldBlacklist(Exception)} for {@code e} - * returns true. Else does nothing. Note that blacklisting will fail if the track is the only - * non-blacklisted track in the selection. - * - * @param trackSelection The track selection. - * @param trackSelectionIndex The index in the selection to consider blacklisting. - * @param e The error to inspect. - * @param blacklistDurationMs The duration to blacklist the track for, if it is blacklisted. - * @return Whether the track was blacklisted. - */ - public static boolean maybeBlacklistTrack(TrackSelection trackSelection, int trackSelectionIndex, - Exception e, long blacklistDurationMs) { - if (shouldBlacklist(e)) { - boolean blacklisted = trackSelection.blacklist(trackSelectionIndex, blacklistDurationMs); - int responseCode = ((InvalidResponseCodeException) e).responseCode; - if (blacklisted) { - Log.w(TAG, "Blacklisted: duration=" + blacklistDurationMs + ", responseCode=" - + responseCode + ", format=" + trackSelection.getFormat(trackSelectionIndex)); - } else { - Log.w(TAG, "Blacklisting failed (cannot blacklist last enabled track): responseCode=" - + responseCode + ", format=" + trackSelection.getFormat(trackSelectionIndex)); - } - return blacklisted; - } - return false; - } - - /** - * Returns whether a loading error is an {@link InvalidResponseCodeException} with - * {@link InvalidResponseCodeException#responseCode} equal to 404 or 410. - * - * @param e The loading error. - * @return Wheter the loading error is an {@link InvalidResponseCodeException} with - * {@link InvalidResponseCodeException#responseCode} equal to 404 or 410. - */ - public static boolean shouldBlacklist(Exception e) { - if (e instanceof InvalidResponseCodeException) { - int responseCode = ((InvalidResponseCodeException) e).responseCode; - return responseCode == 404 || responseCode == 410; - } - return false; - } - - private ChunkedTrackBlacklistUtil() {} - -} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultLoadErrorHandlingPolicy.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultLoadErrorHandlingPolicy.java index 5514971ded..4eec495cab 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultLoadErrorHandlingPolicy.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultLoadErrorHandlingPolicy.java @@ -17,7 +17,6 @@ package com.google.android.exoplayer2.upstream; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ParserException; -import com.google.android.exoplayer2.source.chunk.ChunkedTrackBlacklistUtil; import com.google.android.exoplayer2.upstream.HttpDataSource.InvalidResponseCodeException; import java.io.IOException; @@ -31,6 +30,8 @@ public final class DefaultLoadErrorHandlingPolicy implements LoadErrorHandlingPo * streams. */ public static final int DEFAULT_MIN_LOADABLE_RETRY_COUNT_PROGRESSIVE_LIVE = 6; + /** The default duration for which a track is blacklisted in milliseconds. */ + public static final long DEFAULT_TRACK_BLACKLIST_MS = 60000; private static final int DEFAULT_BEHAVIOR_MIN_LOADABLE_RETRY_COUNT = -1; @@ -59,8 +60,7 @@ public final class DefaultLoadErrorHandlingPolicy implements LoadErrorHandlingPo /** * Blacklists resources whose load error was an {@link InvalidResponseCodeException} with response - * code HTTP 404 or 410. The duration of the blacklisting is {@link - * ChunkedTrackBlacklistUtil#DEFAULT_TRACK_BLACKLIST_MS}. + * code HTTP 404 or 410. The duration of the blacklisting is {@link #DEFAULT_TRACK_BLACKLIST_MS}. */ @Override public long getBlacklistDurationMsFor( @@ -69,7 +69,7 @@ public final class DefaultLoadErrorHandlingPolicy implements LoadErrorHandlingPo int responseCode = ((InvalidResponseCodeException) exception).responseCode; return responseCode == 404 // HTTP 404 Not Found. || responseCode == 410 // HTTP 410 Gone. - ? ChunkedTrackBlacklistUtil.DEFAULT_TRACK_BLACKLIST_MS + ? DEFAULT_TRACK_BLACKLIST_MS : C.TIME_UNSET; } return C.TIME_UNSET; diff --git a/library/core/src/test/java/com/google/android/exoplayer2/upstream/DefaultLoadErrorHandlingPolicyTest.java b/library/core/src/test/java/com/google/android/exoplayer2/upstream/DefaultLoadErrorHandlingPolicyTest.java index 3879c62650..e1700e3b20 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/upstream/DefaultLoadErrorHandlingPolicyTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/upstream/DefaultLoadErrorHandlingPolicyTest.java @@ -20,9 +20,7 @@ import static com.google.common.truth.Truth.assertThat; import android.net.Uri; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ParserException; -import com.google.android.exoplayer2.source.chunk.ChunkedTrackBlacklistUtil; import com.google.android.exoplayer2.upstream.HttpDataSource.InvalidResponseCodeException; -import com.google.android.exoplayer2.upstream.Loader.Loadable; import java.io.FileNotFoundException; import java.io.IOException; import java.util.Collections; @@ -34,56 +32,43 @@ import org.robolectric.RobolectricTestRunner; @RunWith(RobolectricTestRunner.class) public final class DefaultLoadErrorHandlingPolicyTest { - private static final Loadable DUMMY_LOADABLE = - new Loadable() { - @Override - public void cancelLoad() { - // Do nothing. - } - - @Override - public void load() throws IOException, InterruptedException { - // Do nothing. - } - }; - @Test - public void getBlacklistDurationMsFor_blacklist404() throws Exception { + public void getBlacklistDurationMsFor_blacklist404() { InvalidResponseCodeException exception = new InvalidResponseCodeException(404, Collections.emptyMap(), new DataSpec(Uri.EMPTY)); assertThat(getDefaultPolicyBlacklistOutputFor(exception)) - .isEqualTo(ChunkedTrackBlacklistUtil.DEFAULT_TRACK_BLACKLIST_MS); + .isEqualTo(DefaultLoadErrorHandlingPolicy.DEFAULT_TRACK_BLACKLIST_MS); } @Test - public void getBlacklistDurationMsFor_blacklist410() throws Exception { + public void getBlacklistDurationMsFor_blacklist410() { InvalidResponseCodeException exception = new InvalidResponseCodeException(410, Collections.emptyMap(), new DataSpec(Uri.EMPTY)); assertThat(getDefaultPolicyBlacklistOutputFor(exception)) - .isEqualTo(ChunkedTrackBlacklistUtil.DEFAULT_TRACK_BLACKLIST_MS); + .isEqualTo(DefaultLoadErrorHandlingPolicy.DEFAULT_TRACK_BLACKLIST_MS); } @Test - public void getBlacklistDurationMsFor_dontBlacklistUnexpectedHttpCodes() throws Exception { + public void getBlacklistDurationMsFor_dontBlacklistUnexpectedHttpCodes() { InvalidResponseCodeException exception = new InvalidResponseCodeException(500, Collections.emptyMap(), new DataSpec(Uri.EMPTY)); assertThat(getDefaultPolicyBlacklistOutputFor(exception)).isEqualTo(C.TIME_UNSET); } @Test - public void getBlacklistDurationMsFor_dontBlacklistUnexpectedExceptions() throws Exception { + public void getBlacklistDurationMsFor_dontBlacklistUnexpectedExceptions() { FileNotFoundException exception = new FileNotFoundException(); assertThat(getDefaultPolicyBlacklistOutputFor(exception)).isEqualTo(C.TIME_UNSET); } @Test - public void getRetryDelayMsFor_dontRetryParserException() throws Exception { + public void getRetryDelayMsFor_dontRetryParserException() { assertThat(getDefaultPolicyRetryDelayOutputFor(new ParserException(), 1)) .isEqualTo(C.TIME_UNSET); } @Test - public void getRetryDelayMsFor_successiveRetryDelays() throws Exception { + public void getRetryDelayMsFor_successiveRetryDelays() { assertThat(getDefaultPolicyRetryDelayOutputFor(new FileNotFoundException(), 3)).isEqualTo(2000); assertThat(getDefaultPolicyRetryDelayOutputFor(new FileNotFoundException(), 5)).isEqualTo(4000); assertThat(getDefaultPolicyRetryDelayOutputFor(new FileNotFoundException(), 9)).isEqualTo(5000); diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java index eeb27261a7..d52049931f 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java @@ -42,6 +42,7 @@ import com.google.android.exoplayer2.source.dash.manifest.Period; import com.google.android.exoplayer2.source.dash.manifest.Representation; 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.LoaderErrorThrower; import com.google.android.exoplayer2.upstream.TransferListener; import com.google.android.exoplayer2.util.MimeTypes; @@ -62,7 +63,7 @@ import java.util.List; /* package */ final int id; private final DashChunkSource.Factory chunkSourceFactory; private final @Nullable TransferListener transferListener; - private final int minLoadableRetryCount; + private final LoadErrorHandlingPolicy loadErrorHandlingPolicy; private final long elapsedRealtimeOffset; private final LoaderErrorThrower manifestLoaderErrorThrower; private final Allocator allocator; @@ -89,7 +90,7 @@ import java.util.List; int periodIndex, DashChunkSource.Factory chunkSourceFactory, @Nullable TransferListener transferListener, - int minLoadableRetryCount, + LoadErrorHandlingPolicy loadErrorHandlingPolicy, EventDispatcher eventDispatcher, long elapsedRealtimeOffset, LoaderErrorThrower manifestLoaderErrorThrower, @@ -101,7 +102,7 @@ import java.util.List; this.periodIndex = periodIndex; this.chunkSourceFactory = chunkSourceFactory; this.transferListener = transferListener; - this.minLoadableRetryCount = minLoadableRetryCount; + this.loadErrorHandlingPolicy = loadErrorHandlingPolicy; this.eventDispatcher = eventDispatcher; this.elapsedRealtimeOffset = elapsedRealtimeOffset; this.manifestLoaderErrorThrower = manifestLoaderErrorThrower; @@ -612,7 +613,7 @@ import java.util.List; this, allocator, positionUs, - minLoadableRetryCount, + loadErrorHandlingPolicy, eventDispatcher); synchronized (this) { // The map is also accessed on the loading thread so synchronize access. diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java index 9e1d3c783d..cc9651ad5c 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java @@ -43,6 +43,8 @@ import com.google.android.exoplayer2.source.dash.manifest.DashManifestParser; import com.google.android.exoplayer2.source.dash.manifest.UtcTimingElement; import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.DataSource; +import com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy; +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.LoaderErrorThrower; @@ -77,7 +79,7 @@ public final class DashMediaSource extends BaseMediaSource { private @Nullable ParsingLoadable.Parser manifestParser; private CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory; - private int minLoadableRetryCount; + private LoadErrorHandlingPolicy loadErrorHandlingPolicy; private long livePresentationDelayMs; private boolean livePresentationDelayOverridesManifest; private boolean isCreateCalled; @@ -107,7 +109,7 @@ public final class DashMediaSource extends BaseMediaSource { @Nullable DataSource.Factory manifestDataSourceFactory) { this.chunkSourceFactory = Assertions.checkNotNull(chunkSourceFactory); this.manifestDataSourceFactory = manifestDataSourceFactory; - minLoadableRetryCount = DEFAULT_MIN_LOADABLE_RETRY_COUNT; + loadErrorHandlingPolicy = new DefaultLoadErrorHandlingPolicy(); livePresentationDelayMs = DEFAULT_LIVE_PRESENTATION_DELAY_MS; compositeSequenceableLoaderFactory = new DefaultCompositeSequenceableLoaderFactory(); } @@ -128,16 +130,36 @@ public final class DashMediaSource extends BaseMediaSource { } /** - * Sets the minimum number of times to retry if a loading error occurs. The default value is - * {@link #DEFAULT_MIN_LOADABLE_RETRY_COUNT}. + * Sets the minimum number of times to retry if a loading error occurs. See {@link + * #setLoadErrorHandlingPolicy} for the default value. + * + *

Calling this method is equivalent to calling {@link #setLoadErrorHandlingPolicy} with + * {@link DefaultLoadErrorHandlingPolicy (int) + * DefaultLoadErrorHandlingPolicy(minLoadableRetryCount)} * * @param minLoadableRetryCount The minimum number of times to retry if a loading error occurs. * @return This factory, for convenience. * @throws IllegalStateException If one of the {@code create} methods has already been called. + * @deprecated Use {@link #setLoadErrorHandlingPolicy(LoadErrorHandlingPolicy)} instead. */ + @Deprecated public Factory setMinLoadableRetryCount(int minLoadableRetryCount) { + return setLoadErrorHandlingPolicy(new DefaultLoadErrorHandlingPolicy(minLoadableRetryCount)); + } + + /** + * Sets the {@link LoadErrorHandlingPolicy}. The default value is created by calling {@link + * DefaultLoadErrorHandlingPolicy()}. + * + *

Calling this method overrides any calls to {@link #setMinLoadableRetryCount(int)}. + * + * @param loadErrorHandlingPolicy A {@link LoadErrorHandlingPolicy}. + * @return This factory, for convenience. + * @throws IllegalStateException If one of the {@code create} methods has already been called. + */ + public Factory setLoadErrorHandlingPolicy(LoadErrorHandlingPolicy loadErrorHandlingPolicy) { Assertions.checkState(!isCreateCalled); - this.minLoadableRetryCount = minLoadableRetryCount; + this.loadErrorHandlingPolicy = loadErrorHandlingPolicy; return this; } @@ -225,7 +247,7 @@ public final class DashMediaSource extends BaseMediaSource { /* manifestParser= */ null, chunkSourceFactory, compositeSequenceableLoaderFactory, - minLoadableRetryCount, + loadErrorHandlingPolicy, livePresentationDelayMs, livePresentationDelayOverridesManifest, tag); @@ -266,7 +288,7 @@ public final class DashMediaSource extends BaseMediaSource { manifestParser, chunkSourceFactory, compositeSequenceableLoaderFactory, - minLoadableRetryCount, + loadErrorHandlingPolicy, livePresentationDelayMs, livePresentationDelayOverridesManifest, tag); @@ -294,11 +316,6 @@ public final class DashMediaSource extends BaseMediaSource { } } - /** - * The default minimum number of times to retry loading data prior to failing. - */ - public static final int DEFAULT_MIN_LOADABLE_RETRY_COUNT = 3; - /** * The default presentation delay for live streams. The presentation delay is the duration by * which the default start position precedes the end of the live window. @@ -328,7 +345,7 @@ public final class DashMediaSource extends BaseMediaSource { private final DataSource.Factory manifestDataSourceFactory; private final DashChunkSource.Factory chunkSourceFactory; private final CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory; - private final int minLoadableRetryCount; + private final LoadErrorHandlingPolicy loadErrorHandlingPolicy; private final long livePresentationDelayMs; private final boolean livePresentationDelayOverridesManifest; private final EventDispatcher manifestEventDispatcher; @@ -379,7 +396,11 @@ public final class DashMediaSource extends BaseMediaSource { DashChunkSource.Factory chunkSourceFactory, Handler eventHandler, MediaSourceEventListener eventListener) { - this(manifest, chunkSourceFactory, DEFAULT_MIN_LOADABLE_RETRY_COUNT, eventHandler, + this( + manifest, + chunkSourceFactory, + DefaultLoadErrorHandlingPolicy.DEFAULT_MIN_LOADABLE_RETRY_COUNT, + eventHandler, eventListener); } @@ -407,7 +428,7 @@ public final class DashMediaSource extends BaseMediaSource { /* manifestParser= */ null, chunkSourceFactory, new DefaultCompositeSequenceableLoaderFactory(), - minLoadableRetryCount, + new DefaultLoadErrorHandlingPolicy(minLoadableRetryCount), DEFAULT_LIVE_PRESENTATION_DELAY_MS, /* livePresentationDelayOverridesManifest= */ false, /* tag= */ null); @@ -436,9 +457,14 @@ public final class DashMediaSource extends BaseMediaSource { DashChunkSource.Factory chunkSourceFactory, Handler eventHandler, MediaSourceEventListener eventListener) { - this(manifestUri, manifestDataSourceFactory, chunkSourceFactory, - DEFAULT_MIN_LOADABLE_RETRY_COUNT, DEFAULT_LIVE_PRESENTATION_DELAY_PREFER_MANIFEST_MS, - eventHandler, eventListener); + this( + manifestUri, + manifestDataSourceFactory, + chunkSourceFactory, + DefaultLoadErrorHandlingPolicy.DEFAULT_MIN_LOADABLE_RETRY_COUNT, + DEFAULT_LIVE_PRESENTATION_DELAY_PREFER_MANIFEST_MS, + eventHandler, + eventListener); } /** @@ -515,7 +541,7 @@ public final class DashMediaSource extends BaseMediaSource { manifestParser, chunkSourceFactory, new DefaultCompositeSequenceableLoaderFactory(), - minLoadableRetryCount, + new DefaultLoadErrorHandlingPolicy(minLoadableRetryCount), livePresentationDelayMs == DEFAULT_LIVE_PRESENTATION_DELAY_PREFER_MANIFEST_MS ? DEFAULT_LIVE_PRESENTATION_DELAY_MS : livePresentationDelayMs, @@ -533,7 +559,7 @@ public final class DashMediaSource extends BaseMediaSource { ParsingLoadable.Parser manifestParser, DashChunkSource.Factory chunkSourceFactory, CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory, - int minLoadableRetryCount, + LoadErrorHandlingPolicy loadErrorHandlingPolicy, long livePresentationDelayMs, boolean livePresentationDelayOverridesManifest, @Nullable Object tag) { @@ -543,7 +569,7 @@ public final class DashMediaSource extends BaseMediaSource { this.manifestDataSourceFactory = manifestDataSourceFactory; this.manifestParser = manifestParser; this.chunkSourceFactory = chunkSourceFactory; - this.minLoadableRetryCount = minLoadableRetryCount; + this.loadErrorHandlingPolicy = loadErrorHandlingPolicy; this.livePresentationDelayMs = livePresentationDelayMs; this.livePresentationDelayOverridesManifest = livePresentationDelayOverridesManifest; this.compositeSequenceableLoaderFactory = compositeSequenceableLoaderFactory; @@ -625,7 +651,7 @@ public final class DashMediaSource extends BaseMediaSource { periodIndex, chunkSourceFactory, mediaTransferListener, - minLoadableRetryCount, + loadErrorHandlingPolicy, periodEventDispatcher, elapsedRealtimeOffsetMs, manifestLoadErrorThrower, @@ -735,7 +761,8 @@ public final class DashMediaSource extends BaseMediaSource { } if (isManifestStale) { - if (staleManifestReloadAttempt++ < minLoadableRetryCount) { + if (staleManifestReloadAttempt++ + < loadErrorHandlingPolicy.getMinimumLoadableRetryCount(loadable.type)) { scheduleManifestRefresh(getManifestLoadRetryDelayMillis()); } else { manifestFatalError = new DashManifestStaleException(); @@ -1004,7 +1031,7 @@ public final class DashMediaSource extends BaseMediaSource { startLoading( new ParsingLoadable<>(dataSource, manifestUri, C.DATA_TYPE_MANIFEST, manifestParser), manifestCallback, - minLoadableRetryCount); + loadErrorHandlingPolicy.getMinimumLoadableRetryCount(C.DATA_TYPE_MANIFEST)); } private long getManifestLoadRetryDelayMillis() { diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java index 7aea84f0cb..2574b0a12e 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java @@ -34,7 +34,6 @@ import com.google.android.exoplayer2.source.chunk.BaseMediaChunkIterator; import com.google.android.exoplayer2.source.chunk.Chunk; import com.google.android.exoplayer2.source.chunk.ChunkExtractorWrapper; import com.google.android.exoplayer2.source.chunk.ChunkHolder; -import com.google.android.exoplayer2.source.chunk.ChunkedTrackBlacklistUtil; import com.google.android.exoplayer2.source.chunk.ContainerMediaChunk; import com.google.android.exoplayer2.source.chunk.InitializationChunk; import com.google.android.exoplayer2.source.chunk.MediaChunk; @@ -380,7 +379,8 @@ public class DefaultDashChunkSource implements DashChunkSource { } @Override - public boolean onChunkLoadError(Chunk chunk, boolean cancelable, Exception e) { + public boolean onChunkLoadError( + Chunk chunk, boolean cancelable, Exception e, long blacklistDurationMs) { if (!cancelable) { return false; } @@ -403,9 +403,8 @@ public class DefaultDashChunkSource implements DashChunkSource { } } } - // Blacklist if appropriate. - return ChunkedTrackBlacklistUtil.maybeBlacklistTrack(trackSelection, - trackSelection.indexOf(chunk.trackFormat), e); + return blacklistDurationMs != C.TIME_UNSET + && trackSelection.blacklist(trackSelection.indexOf(chunk.trackFormat), blacklistDurationMs); } // Internal methods. 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 f82432d602..16e706ae92 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 @@ -23,7 +23,6 @@ import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.source.BehindLiveWindowException; 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.HlsUrl; import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist; @@ -33,6 +32,7 @@ import com.google.android.exoplayer2.trackselection.BaseTrackSelection; import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSpec; +import com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy; import com.google.android.exoplayer2.upstream.TransferListener; import com.google.android.exoplayer2.util.TimestampAdjuster; import com.google.android.exoplayer2.util.UriUtil; @@ -429,7 +429,7 @@ import java.util.List; seenExpectedPlaylistError |= expectedPlaylistUrl == url; return !shouldBlacklist || trackSelection.blacklist( - trackSelectionIndex, ChunkedTrackBlacklistUtil.DEFAULT_TRACK_BLACKLIST_MS); + trackSelectionIndex, DefaultLoadErrorHandlingPolicy.DEFAULT_TRACK_BLACKLIST_MS); } // Private methods. 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 5cf3765b71..60f5619714 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 @@ -21,11 +21,11 @@ import android.os.SystemClock; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ParserException; import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispatcher; -import com.google.android.exoplayer2.source.chunk.ChunkedTrackBlacklistUtil; 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.HlsMediaPlaylist.Segment; import com.google.android.exoplayer2.upstream.DataSource; +import com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy; import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy; import com.google.android.exoplayer2.upstream.Loader; import com.google.android.exoplayer2.upstream.Loader.LoadErrorAction; @@ -621,7 +621,7 @@ public final class DefaultHlsPlaylistTracker */ private boolean blacklistPlaylist() { blacklistUntilMs = - SystemClock.elapsedRealtime() + ChunkedTrackBlacklistUtil.DEFAULT_TRACK_BLACKLIST_MS; + SystemClock.elapsedRealtime() + DefaultLoadErrorHandlingPolicy.DEFAULT_TRACK_BLACKLIST_MS; return primaryHlsUrl == playlistUrl && !maybeSelectNewPrimaryUrl(); } } diff --git a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/DefaultSsChunkSource.java b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/DefaultSsChunkSource.java index aa20c12cf6..831d21eeb7 100644 --- a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/DefaultSsChunkSource.java +++ b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/DefaultSsChunkSource.java @@ -27,7 +27,6 @@ import com.google.android.exoplayer2.source.BehindLiveWindowException; import com.google.android.exoplayer2.source.chunk.Chunk; import com.google.android.exoplayer2.source.chunk.ChunkExtractorWrapper; import com.google.android.exoplayer2.source.chunk.ChunkHolder; -import com.google.android.exoplayer2.source.chunk.ChunkedTrackBlacklistUtil; import com.google.android.exoplayer2.source.chunk.ContainerMediaChunk; import com.google.android.exoplayer2.source.chunk.MediaChunk; import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest; @@ -246,9 +245,11 @@ public class DefaultSsChunkSource implements SsChunkSource { } @Override - public boolean onChunkLoadError(Chunk chunk, boolean cancelable, Exception e) { - return cancelable && ChunkedTrackBlacklistUtil.maybeBlacklistTrack(trackSelection, - trackSelection.indexOf(chunk.trackFormat), e); + public boolean onChunkLoadError( + Chunk chunk, boolean cancelable, Exception e, long blacklistDurationMs) { + return cancelable + && blacklistDurationMs != C.TIME_UNSET + && trackSelection.blacklist(trackSelection.indexOf(chunk.trackFormat), blacklistDurationMs); } // Private methods. diff --git a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaPeriod.java b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaPeriod.java index a1fe499c29..14b54bc471 100644 --- a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaPeriod.java +++ b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaPeriod.java @@ -32,6 +32,7 @@ import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest; import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest.ProtectionElement; 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.LoaderErrorThrower; import com.google.android.exoplayer2.upstream.TransferListener; import java.io.IOException; @@ -48,7 +49,7 @@ import java.util.ArrayList; private final SsChunkSource.Factory chunkSourceFactory; private final @Nullable TransferListener transferListener; private final LoaderErrorThrower manifestLoaderErrorThrower; - private final int minLoadableRetryCount; + private final LoadErrorHandlingPolicy loadErrorHandlingPolicy; private final EventDispatcher eventDispatcher; private final Allocator allocator; private final TrackGroupArray trackGroups; @@ -66,14 +67,14 @@ import java.util.ArrayList; SsChunkSource.Factory chunkSourceFactory, @Nullable TransferListener transferListener, CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory, - int minLoadableRetryCount, + LoadErrorHandlingPolicy loadErrorHandlingPolicy, EventDispatcher eventDispatcher, LoaderErrorThrower manifestLoaderErrorThrower, Allocator allocator) { this.chunkSourceFactory = chunkSourceFactory; this.transferListener = transferListener; this.manifestLoaderErrorThrower = manifestLoaderErrorThrower; - this.minLoadableRetryCount = minLoadableRetryCount; + this.loadErrorHandlingPolicy = loadErrorHandlingPolicy; this.eventDispatcher = eventDispatcher; this.allocator = allocator; this.compositeSequenceableLoaderFactory = compositeSequenceableLoaderFactory; @@ -238,7 +239,7 @@ import java.util.ArrayList; this, allocator, positionUs, - minLoadableRetryCount, + loadErrorHandlingPolicy, eventDispatcher); } diff --git a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java index 692ebd0e27..03f64bfd38 100644 --- a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java +++ b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java @@ -40,6 +40,8 @@ import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifestP import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsUtil; import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.DataSource; +import com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy; +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.LoaderErrorThrower; @@ -65,7 +67,7 @@ public final class SsMediaSource extends BaseMediaSource private @Nullable ParsingLoadable.Parser manifestParser; private CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory; - private int minLoadableRetryCount; + private LoadErrorHandlingPolicy loadErrorHandlingPolicy; private long livePresentationDelayMs; private boolean isCreateCalled; private @Nullable Object tag; @@ -94,7 +96,7 @@ public final class SsMediaSource extends BaseMediaSource @Nullable DataSource.Factory manifestDataSourceFactory) { this.chunkSourceFactory = Assertions.checkNotNull(chunkSourceFactory); this.manifestDataSourceFactory = manifestDataSourceFactory; - minLoadableRetryCount = DEFAULT_MIN_LOADABLE_RETRY_COUNT; + loadErrorHandlingPolicy = new DefaultLoadErrorHandlingPolicy(); livePresentationDelayMs = DEFAULT_LIVE_PRESENTATION_DELAY_MS; compositeSequenceableLoaderFactory = new DefaultCompositeSequenceableLoaderFactory(); } @@ -114,16 +116,36 @@ public final class SsMediaSource extends BaseMediaSource } /** - * Sets the minimum number of times to retry if a loading error occurs. The default value is - * {@link #DEFAULT_MIN_LOADABLE_RETRY_COUNT}. + * Sets the minimum number of times to retry if a loading error occurs. See {@link + * #setLoadErrorHandlingPolicy} for the default value. + * + *

Calling this method is equivalent to calling {@link #setLoadErrorHandlingPolicy} with + * {@link DefaultLoadErrorHandlingPolicy(int) + * DefaultLoadErrorHandlingPolicy(minLoadableRetryCount)} * * @param minLoadableRetryCount The minimum number of times to retry if a loading error occurs. * @return This factory, for convenience. * @throws IllegalStateException If one of the {@code create} methods has already been called. + * @deprecated Use {@link #setLoadErrorHandlingPolicy(LoadErrorHandlingPolicy)} instead. */ + @Deprecated public Factory setMinLoadableRetryCount(int minLoadableRetryCount) { + return setLoadErrorHandlingPolicy(new DefaultLoadErrorHandlingPolicy(minLoadableRetryCount)); + } + + /** + * Sets the {@link LoadErrorHandlingPolicy}. The default value is created by calling {@link + * DefaultLoadErrorHandlingPolicy()}. + * + *

Calling this method overrides any calls to {@link #setMinLoadableRetryCount(int)}. + * + * @param loadErrorHandlingPolicy A {@link LoadErrorHandlingPolicy}. + * @return This factory, for convenience. + * @throws IllegalStateException If one of the {@code create} methods has already been called. + */ + public Factory setLoadErrorHandlingPolicy(LoadErrorHandlingPolicy loadErrorHandlingPolicy) { Assertions.checkState(!isCreateCalled); - this.minLoadableRetryCount = minLoadableRetryCount; + this.loadErrorHandlingPolicy = loadErrorHandlingPolicy; return this; } @@ -193,7 +215,7 @@ public final class SsMediaSource extends BaseMediaSource /* manifestParser= */ null, chunkSourceFactory, compositeSequenceableLoaderFactory, - minLoadableRetryCount, + loadErrorHandlingPolicy, livePresentationDelayMs, tag); } @@ -233,7 +255,7 @@ public final class SsMediaSource extends BaseMediaSource manifestParser, chunkSourceFactory, compositeSequenceableLoaderFactory, - minLoadableRetryCount, + loadErrorHandlingPolicy, livePresentationDelayMs, tag); } @@ -261,10 +283,6 @@ public final class SsMediaSource extends BaseMediaSource } - /** - * The default minimum number of times to retry loading data prior to failing. - */ - public static final int DEFAULT_MIN_LOADABLE_RETRY_COUNT = 3; /** * The default presentation delay for live streams. The presentation delay is the duration by * which the default start position precedes the end of the live window. @@ -285,7 +303,7 @@ public final class SsMediaSource extends BaseMediaSource private final DataSource.Factory manifestDataSourceFactory; private final SsChunkSource.Factory chunkSourceFactory; private final CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory; - private final int minLoadableRetryCount; + private final LoadErrorHandlingPolicy loadErrorHandlingPolicy; private final long livePresentationDelayMs; private final EventDispatcher manifestEventDispatcher; private final ParsingLoadable.Parser manifestParser; @@ -317,8 +335,12 @@ public final class SsMediaSource extends BaseMediaSource SsChunkSource.Factory chunkSourceFactory, Handler eventHandler, MediaSourceEventListener eventListener) { - this(manifest, chunkSourceFactory, DEFAULT_MIN_LOADABLE_RETRY_COUNT, - eventHandler, eventListener); + this( + manifest, + chunkSourceFactory, + DefaultLoadErrorHandlingPolicy.DEFAULT_MIN_LOADABLE_RETRY_COUNT, + eventHandler, + eventListener); } /** @@ -345,7 +367,7 @@ public final class SsMediaSource extends BaseMediaSource /* manifestParser= */ null, chunkSourceFactory, new DefaultCompositeSequenceableLoaderFactory(), - minLoadableRetryCount, + new DefaultLoadErrorHandlingPolicy(minLoadableRetryCount), DEFAULT_LIVE_PRESENTATION_DELAY_MS, /* tag= */ null); if (eventHandler != null && eventListener != null) { @@ -372,8 +394,13 @@ public final class SsMediaSource extends BaseMediaSource SsChunkSource.Factory chunkSourceFactory, Handler eventHandler, MediaSourceEventListener eventListener) { - this(manifestUri, manifestDataSourceFactory, chunkSourceFactory, - DEFAULT_MIN_LOADABLE_RETRY_COUNT, DEFAULT_LIVE_PRESENTATION_DELAY_MS, eventHandler, + this( + manifestUri, + manifestDataSourceFactory, + chunkSourceFactory, + DefaultLoadErrorHandlingPolicy.DEFAULT_MIN_LOADABLE_RETRY_COUNT, + DEFAULT_LIVE_PRESENTATION_DELAY_MS, + eventHandler, eventListener); } @@ -438,7 +465,7 @@ public final class SsMediaSource extends BaseMediaSource manifestParser, chunkSourceFactory, new DefaultCompositeSequenceableLoaderFactory(), - minLoadableRetryCount, + new DefaultLoadErrorHandlingPolicy(minLoadableRetryCount), livePresentationDelayMs, /* tag= */ null); if (eventHandler != null && eventListener != null) { @@ -453,7 +480,7 @@ public final class SsMediaSource extends BaseMediaSource ParsingLoadable.Parser manifestParser, SsChunkSource.Factory chunkSourceFactory, CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory, - int minLoadableRetryCount, + LoadErrorHandlingPolicy loadErrorHandlingPolicy, long livePresentationDelayMs, @Nullable Object tag) { Assertions.checkState(manifest == null || !manifest.isLive); @@ -463,7 +490,7 @@ public final class SsMediaSource extends BaseMediaSource this.manifestParser = manifestParser; this.chunkSourceFactory = chunkSourceFactory; this.compositeSequenceableLoaderFactory = compositeSequenceableLoaderFactory; - this.minLoadableRetryCount = minLoadableRetryCount; + this.loadErrorHandlingPolicy = loadErrorHandlingPolicy; this.livePresentationDelayMs = livePresentationDelayMs; this.manifestEventDispatcher = createEventDispatcher(/* mediaPeriodId= */ null); this.tag = tag; @@ -506,7 +533,7 @@ public final class SsMediaSource extends BaseMediaSource chunkSourceFactory, mediaTransferListener, compositeSequenceableLoaderFactory, - minLoadableRetryCount, + loadErrorHandlingPolicy, eventDispatcher, manifestLoaderErrorThrower, allocator); @@ -668,7 +695,9 @@ public final class SsMediaSource extends BaseMediaSource private void startLoadingManifest() { ParsingLoadable loadable = new ParsingLoadable<>(manifestDataSource, manifestUri, C.DATA_TYPE_MANIFEST, manifestParser); - long elapsedRealtimeMs = manifestLoader.startLoading(loadable, this, minLoadableRetryCount); + long elapsedRealtimeMs = + manifestLoader.startLoading( + loadable, this, loadErrorHandlingPolicy.getMinimumLoadableRetryCount(loadable.type)); manifestEventDispatcher.loadStarted( loadable.dataSpec, loadable.dataSpec.uri, loadable.type, elapsedRealtimeMs); } diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeChunkSource.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeChunkSource.java index 193351ceac..019d3696a7 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeChunkSource.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeChunkSource.java @@ -138,7 +138,8 @@ public final class FakeChunkSource implements ChunkSource { } @Override - public boolean onChunkLoadError(Chunk chunk, boolean cancelable, Exception e) { + public boolean onChunkLoadError( + Chunk chunk, boolean cancelable, Exception e, long blacklistDurationMs) { return false; }