diff --git a/RELEASENOTES.md b/RELEASENOTES.md index ca4d0d19be..5f6c6811a0 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -19,6 +19,8 @@ `ExoPlaybackException.rendererIndex`). * `PlaybackException` introduces an `errorCode` which identifies the cause of the failure in order to simplify error handling. + * Add `@FallbackType` to `LoadErrorHandlingPolicy` to support + customization of the exclusion duration for locations and tracks. * Remove deprecated symbols: * Remove `Player.getPlaybackError`. Use `Player.getPlayerError` instead. * Remove `Player.getCurrentTag`. Use `Player.getCurrentMediaItem` and 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 1dd1186a3b..8089175287 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 @@ -518,7 +518,8 @@ public class ChunkSampleStream long exclusionDurationMs = cancelable - ? loadErrorHandlingPolicy.getBlacklistDurationMsFor(loadErrorInfo) + ? loadErrorHandlingPolicy.getExclusionDurationMsFor( + LoadErrorHandlingPolicy.FALLBACK_TYPE_TRACK, loadErrorInfo) : C.TIME_UNSET; @Nullable LoadErrorAction loadErrorAction = null; if (chunkSource.onChunkLoadError(loadable, cancelable, error, exclusionDurationMs)) { 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 091c57de10..13908cb07c 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 @@ -36,7 +36,11 @@ public class DefaultLoadErrorHandlingPolicy implements LoadErrorHandlingPolicy { */ public static final int DEFAULT_MIN_LOADABLE_RETRY_COUNT_PROGRESSIVE_LIVE = 6; /** The default duration for which a track is excluded in milliseconds. */ - public static final long DEFAULT_TRACK_BLACKLIST_MS = 60_000; + public static final long DEFAULT_TRACK_EXCLUSION_MS = 60_000; + /** @deprecated Use {@link #DEFAULT_TRACK_EXCLUSION_MS} instead. */ + @Deprecated public static final long DEFAULT_TRACK_BLACKLIST_MS = DEFAULT_TRACK_EXCLUSION_MS; + /** The default duration for which a location is excluded in milliseconds. */ + public static final long DEFAULT_LOCATION_EXCLUSION_MS = 5 * 60_000; private static final int DEFAULT_BEHAVIOR_MIN_LOADABLE_RETRY_COUNT = -1; @@ -64,12 +68,14 @@ public class DefaultLoadErrorHandlingPolicy implements LoadErrorHandlingPolicy { } /** - * Returns the exclusion duration, given by {@link #DEFAULT_TRACK_BLACKLIST_MS}, if the load error - * was an {@link InvalidResponseCodeException} with response code HTTP 404, 410 or 416, or {@link - * C#TIME_UNSET} otherwise. + * Returns the exclusion duration, given by {@link #DEFAULT_TRACK_EXCLUSION_MS} or {@link + * #DEFAULT_LOCATION_EXCLUSION_MS}, if the load error was an {@link InvalidResponseCodeException} + * with an HTTP response code indicating an unrecoverable error, or {@link C#TIME_UNSET} + * otherwise. */ @Override - public long getBlacklistDurationMsFor(LoadErrorInfo loadErrorInfo) { + public long getExclusionDurationMsFor( + @FallbackType int fallbackType, LoadErrorInfo loadErrorInfo) { IOException exception = loadErrorInfo.exception; if (exception instanceof InvalidResponseCodeException) { int responseCode = ((InvalidResponseCodeException) exception).responseCode; @@ -79,7 +85,9 @@ public class DefaultLoadErrorHandlingPolicy implements LoadErrorHandlingPolicy { || responseCode == 416 // HTTP 416 Range Not Satisfiable. || responseCode == 500 // HTTP 500 Internal Server Error. || responseCode == 503 // HTTP 503 Service Unavailable. - ? DEFAULT_TRACK_BLACKLIST_MS + ? (fallbackType == FALLBACK_TYPE_TRACK + ? DEFAULT_TRACK_EXCLUSION_MS + : DEFAULT_LOCATION_EXCLUSION_MS) : C.TIME_UNSET; } return C.TIME_UNSET; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/LoadErrorHandlingPolicy.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/LoadErrorHandlingPolicy.java index 0102ea7871..9e048c1e85 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/LoadErrorHandlingPolicy.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/LoadErrorHandlingPolicy.java @@ -15,29 +15,53 @@ */ package com.google.android.exoplayer2.upstream; +import androidx.annotation.IntDef; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.source.LoadEventInfo; import com.google.android.exoplayer2.source.MediaLoadData; import com.google.android.exoplayer2.upstream.Loader.Callback; import com.google.android.exoplayer2.upstream.Loader.Loadable; import java.io.IOException; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; /** * Defines how errors encountered by loaders are handled. * *

A loader that can choose between one of a number of resources can exclude a resource when a - * load error occurs. In this case, {@link #getBlacklistDurationMsFor(int, long, IOException, int)} - * defines whether the resource should be excluded. Exclusion will succeed unless all of the - * alternatives are already excluded. + * load error occurs. In this case, {@link #getExclusionDurationMsFor(int, LoadErrorInfo)} defines + * whether the resource should be excluded for a given {@link FallbackType fallback type}, and if so + * for how long. If the policy indicates that a resource should be excluded, the loader will exclude + * it for the specified amount of time unless all of the alternatives for the given fallback type + * are already excluded. * - *

When exclusion does not take place, {@link #getRetryDelayMsFor(int, long, IOException, int)} - * defines whether the load is retried. An error that's not retried will always be propagated. An - * error that is retried will be propagated according to {@link #getMinimumLoadableRetryCount(int)}. + *

When exclusion does not take place, {@link #getRetryDelayMsFor(LoadErrorInfo)} defines whether + * the load is retried. An error that's not retried will always be propagated. An error that is + * retried will be propagated according to {@link #getMinimumLoadableRetryCount(int)}. * *

Methods are invoked on the playback thread. */ public interface LoadErrorHandlingPolicy { + /** Fallback type. One of {@link #FALLBACK_TYPE_LOCATION} or {@link #FALLBACK_TYPE_TRACK}. */ + @Documented + @Retention(RetentionPolicy.SOURCE) + @IntDef({FALLBACK_TYPE_LOCATION, FALLBACK_TYPE_TRACK}) + @interface FallbackType {} + + /** + * Fallback type that is using exclusion of locations (i.e., multiple URLs through which the same + * data is accessible). + */ + int FALLBACK_TYPE_LOCATION = 1; + /** + * Fallback type that is using exclusion of tracks (i.e., multiple URLs through which different + * representations of the same content are available; for example the same video encoded at + * different bitrates or resolutions). + */ + int FALLBACK_TYPE_TRACK = 2; + /** Holds information about a load task error. */ final class LoadErrorInfo { @@ -63,36 +87,17 @@ public interface LoadErrorHandlingPolicy { } } - /** @deprecated Implement {@link #getBlacklistDurationMsFor(LoadErrorInfo)} instead. */ - @Deprecated - default long getBlacklistDurationMsFor( - int dataType, long loadDurationMs, IOException exception, int errorCount) { - throw new UnsupportedOperationException(); - } - /** * Returns the number of milliseconds for which a resource associated to a provided load error - * should be excluded, or {@link C#TIME_UNSET} if the resource should not be excluded. + * should be excluded for a given {@link FallbackType fallback type}, or {@link C#TIME_UNSET} if + * the resource should not be excluded. * + * @param fallbackType The {@link FallbackType fallback type} used for exclusion. * @param loadErrorInfo A {@link LoadErrorInfo} holding information about the load error. * @return The exclusion duration in milliseconds, or {@link C#TIME_UNSET} if the resource should * not be excluded. */ - @SuppressWarnings("deprecation") - default long getBlacklistDurationMsFor(LoadErrorInfo loadErrorInfo) { - return getBlacklistDurationMsFor( - loadErrorInfo.mediaLoadData.dataType, - loadErrorInfo.loadEventInfo.loadDurationMs, - loadErrorInfo.exception, - loadErrorInfo.errorCount); - } - - /** @deprecated Implement {@link #getRetryDelayMsFor(LoadErrorInfo)} instead. */ - @Deprecated - default long getRetryDelayMsFor( - int dataType, long loadDurationMs, IOException exception, int errorCount) { - throw new UnsupportedOperationException(); - } + long getExclusionDurationMsFor(@FallbackType int fallbackType, LoadErrorInfo loadErrorInfo); /** * Returns the number of milliseconds to wait before attempting the load again, or {@link @@ -106,14 +111,7 @@ public interface LoadErrorHandlingPolicy { * @return The number of milliseconds to wait before attempting the load again, or {@link * C#TIME_UNSET} if the error is fatal and should not be retried. */ - @SuppressWarnings("deprecation") - default long getRetryDelayMsFor(LoadErrorInfo loadErrorInfo) { - return getRetryDelayMsFor( - loadErrorInfo.mediaLoadData.dataType, - loadErrorInfo.loadEventInfo.loadDurationMs, - loadErrorInfo.exception, - loadErrorInfo.errorCount); - } + long getRetryDelayMsFor(LoadErrorInfo loadErrorInfo); /** * Called once {@code loadTaskId} will not be associated with any more load errors. 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 8cf4e92075..bc5e468220 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 @@ -50,50 +50,50 @@ public final class DefaultLoadErrorHandlingPolicyTest { @Test public void getExclusionDurationMsFor_responseCode403() { InvalidResponseCodeException exception = buildInvalidResponseCodeException(403, "Forbidden"); - assertThat(getDefaultPolicyExclusionDurationMsFor(exception)) - .isEqualTo(DefaultLoadErrorHandlingPolicy.DEFAULT_TRACK_BLACKLIST_MS); + assertThat(getDefaultPolicyTrackExclusionDurationMsFor(exception)) + .isEqualTo(DefaultLoadErrorHandlingPolicy.DEFAULT_TRACK_EXCLUSION_MS); } @Test public void getExclusionDurationMsFor_responseCode404() { InvalidResponseCodeException exception = buildInvalidResponseCodeException(404, "Not found"); - assertThat(getDefaultPolicyExclusionDurationMsFor(exception)) - .isEqualTo(DefaultLoadErrorHandlingPolicy.DEFAULT_TRACK_BLACKLIST_MS); + assertThat(getDefaultPolicyTrackExclusionDurationMsFor(exception)) + .isEqualTo(DefaultLoadErrorHandlingPolicy.DEFAULT_TRACK_EXCLUSION_MS); } @Test public void getExclusionDurationMsFor_responseCode410() { InvalidResponseCodeException exception = buildInvalidResponseCodeException(410, "Gone"); - assertThat(getDefaultPolicyExclusionDurationMsFor(exception)) - .isEqualTo(DefaultLoadErrorHandlingPolicy.DEFAULT_TRACK_BLACKLIST_MS); + assertThat(getDefaultPolicyTrackExclusionDurationMsFor(exception)) + .isEqualTo(DefaultLoadErrorHandlingPolicy.DEFAULT_TRACK_EXCLUSION_MS); } @Test public void getExclusionDurationMsFor_responseCode500() { InvalidResponseCodeException exception = buildInvalidResponseCodeException(500, "Internal server error"); - assertThat(getDefaultPolicyExclusionDurationMsFor(exception)) - .isEqualTo(DefaultLoadErrorHandlingPolicy.DEFAULT_TRACK_BLACKLIST_MS); + assertThat(getDefaultPolicyTrackExclusionDurationMsFor(exception)) + .isEqualTo(DefaultLoadErrorHandlingPolicy.DEFAULT_TRACK_EXCLUSION_MS); } @Test public void getExclusionDurationMsFor_responseCode503() { InvalidResponseCodeException exception = buildInvalidResponseCodeException(503, "Service unavailable"); - assertThat(getDefaultPolicyExclusionDurationMsFor(exception)) - .isEqualTo(DefaultLoadErrorHandlingPolicy.DEFAULT_TRACK_BLACKLIST_MS); + assertThat(getDefaultPolicyTrackExclusionDurationMsFor(exception)) + .isEqualTo(DefaultLoadErrorHandlingPolicy.DEFAULT_TRACK_EXCLUSION_MS); } @Test public void getExclusionDurationMsFor_dontExcludeUnexpectedHttpCodes() { InvalidResponseCodeException exception = buildInvalidResponseCodeException(418, "I'm a teapot"); - assertThat(getDefaultPolicyExclusionDurationMsFor(exception)).isEqualTo(C.TIME_UNSET); + assertThat(getDefaultPolicyTrackExclusionDurationMsFor(exception)).isEqualTo(C.TIME_UNSET); } @Test public void getExclusionDurationMsFor_dontExcludeUnexpectedExceptions() { IOException exception = new IOException(); - assertThat(getDefaultPolicyExclusionDurationMsFor(exception)).isEqualTo(C.TIME_UNSET); + assertThat(getDefaultPolicyTrackExclusionDurationMsFor(exception)).isEqualTo(C.TIME_UNSET); } @Test @@ -112,14 +112,15 @@ public final class DefaultLoadErrorHandlingPolicyTest { assertThat(getDefaultPolicyRetryDelayOutputFor(new IOException(), 9)).isEqualTo(5000); } - private static long getDefaultPolicyExclusionDurationMsFor(IOException exception) { + private static long getDefaultPolicyTrackExclusionDurationMsFor(IOException exception) { LoadErrorInfo loadErrorInfo = new LoadErrorInfo( PLACEHOLDER_LOAD_EVENT_INFO, PLACEHOLDER_MEDIA_LOAD_DATA, exception, /* errorCount= */ 1); - return new DefaultLoadErrorHandlingPolicy().getBlacklistDurationMsFor(loadErrorInfo); + return new DefaultLoadErrorHandlingPolicy() + .getExclusionDurationMsFor(LoadErrorHandlingPolicy.FALLBACK_TYPE_TRACK, loadErrorInfo); } private static long getDefaultPolicyRetryDelayOutputFor(IOException exception, int errorCount) { 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 0a6cb78774..fd028caddc 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 @@ -893,7 +893,9 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; LoadErrorInfo loadErrorInfo = new LoadErrorInfo(loadEventInfo, mediaLoadData, error, errorCount); LoadErrorAction loadErrorAction; - long exclusionDurationMs = loadErrorHandlingPolicy.getBlacklistDurationMsFor(loadErrorInfo); + long exclusionDurationMs = + loadErrorHandlingPolicy.getExclusionDurationMsFor( + LoadErrorHandlingPolicy.FALLBACK_TYPE_TRACK, loadErrorInfo); if (exclusionDurationMs != C.TIME_UNSET) { exclusionSucceeded = chunkSource.maybeExcludeTrack(loadable, exclusionDurationMs); } 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 9ad1cb5934..9df0b69b58 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 @@ -631,7 +631,9 @@ public final class DefaultHlsPlaylistTracker LoadErrorInfo loadErrorInfo = new LoadErrorInfo(loadEventInfo, mediaLoadData, error, errorCount); LoadErrorAction loadErrorAction; - long exclusionDurationMs = loadErrorHandlingPolicy.getBlacklistDurationMsFor(loadErrorInfo); + long exclusionDurationMs = + loadErrorHandlingPolicy.getExclusionDurationMsFor( + LoadErrorHandlingPolicy.FALLBACK_TYPE_TRACK, loadErrorInfo); boolean shouldExclude = exclusionDurationMs != C.TIME_UNSET; boolean exclusionFailed = @@ -730,7 +732,8 @@ public final class DefaultHlsPlaylistTracker playlistError, /* errorCount= */ 1); long exclusionDurationMs = - loadErrorHandlingPolicy.getBlacklistDurationMsFor(loadErrorInfo); + loadErrorHandlingPolicy.getExclusionDurationMsFor( + LoadErrorHandlingPolicy.FALLBACK_TYPE_TRACK, loadErrorInfo); notifyPlaylistError(playlistUrl, exclusionDurationMs); if (exclusionDurationMs != C.TIME_UNSET) { excludePlaylist(exclusionDurationMs);