From 6dbc1eb1895e846e941a94a5a692a42479c85bf3 Mon Sep 17 00:00:00 2001 From: bachinger Date: Tue, 6 Jul 2021 12:31:29 +0100 Subject: [PATCH] Make customization of fallback selection more flexible PiperOrigin-RevId: 383245932 --- .../drm/DefaultDrmSessionManager.java | 3 +- .../trackselection/TrackSelectionUtil.java | 27 ++ .../DefaultLoadErrorHandlingPolicy.java | 67 +++-- .../upstream/LoadErrorHandlingPolicy.java | 72 +++++- .../DefaultLoadErrorHandlingPolicyTest.java | 234 ++++++++++++++++-- .../source/dash/DefaultDashChunkSource.java | 17 +- .../dash/DefaultDashChunkSourceTest.java | 15 +- .../exoplayer2/source/hls/HlsChunkSource.java | 8 +- .../exoplayer2/source/hls/HlsMediaPeriod.java | 5 +- .../source/hls/HlsSampleStreamWrapper.java | 29 ++- .../playlist/DefaultHlsPlaylistTracker.java | 50 ++-- .../hls/playlist/HlsPlaylistTracker.java | 16 +- .../smoothstreaming/DefaultSsChunkSource.java | 15 +- .../testutil/FakeAdaptiveMediaPeriod.java | 3 +- 14 files changed, 453 insertions(+), 108 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java index 093257e870..f347cdae80 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java @@ -383,7 +383,8 @@ public class DefaultDrmSessionManager implements DrmSessionManager { multiSession, /* useDrmSessionsForClearContentTrackTypes= */ new int[0], /* playClearSamplesWithoutKeys= */ false, - new DefaultLoadErrorHandlingPolicy(initialDrmRequestRetryCount), + new DefaultLoadErrorHandlingPolicy( + initialDrmRequestRetryCount, /* locationExclusionEnabled= */ false), DEFAULT_SESSION_KEEPALIVE_MS); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionUtil.java index 53ae2e9cd6..a0cc74588d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionUtil.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionUtil.java @@ -15,10 +15,12 @@ */ package com.google.android.exoplayer2.trackselection; +import android.os.SystemClock; import androidx.annotation.Nullable; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector.SelectionOverride; import com.google.android.exoplayer2.trackselection.ExoTrackSelection.Definition; +import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy; import com.google.android.exoplayer2.util.MimeTypes; import org.checkerframework.checker.nullness.compatqual.NullableType; @@ -114,4 +116,29 @@ public final class TrackSelectionUtil { } return false; } + + /** + * Returns the {@link LoadErrorHandlingPolicy.FallbackOptions} with the tracks of the given {@link + * ExoTrackSelection} and with a single location option indicating that there are no alternative + * locations available. + * + * @param trackSelection The track selection to get the number of total and excluded tracks. + * @return The {@link LoadErrorHandlingPolicy.FallbackOptions} for the given track selection. + */ + public static LoadErrorHandlingPolicy.FallbackOptions createFallbackOptions( + ExoTrackSelection trackSelection) { + long nowMs = SystemClock.elapsedRealtime(); + int numberOfTracks = trackSelection.length(); + int numberOfExcludedTracks = 0; + for (int i = 0; i < numberOfTracks; i++) { + if (trackSelection.isBlacklisted(i, nowMs)) { + numberOfExcludedTracks++; + } + } + return new LoadErrorHandlingPolicy.FallbackOptions( + /* numberOfLocations= */ 1, + /* numberOfExcludedLocations= */ 0, + numberOfTracks, + numberOfExcludedTracks); + } } 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 13908cb07c..147d188104 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 @@ -45,6 +45,7 @@ public class DefaultLoadErrorHandlingPolicy implements LoadErrorHandlingPolicy { private static final int DEFAULT_BEHAVIOR_MIN_LOADABLE_RETRY_COUNT = -1; private final int minimumLoadableRetryCount; + private final boolean locationExclusionEnabled; /** * Creates an instance with default behavior. @@ -53,44 +54,72 @@ public class DefaultLoadErrorHandlingPolicy implements LoadErrorHandlingPolicy { * #DEFAULT_MIN_LOADABLE_RETRY_COUNT_PROGRESSIVE_LIVE} for {@code dataType} {@link * C#DATA_TYPE_MEDIA_PROGRESSIVE_LIVE}. For other {@code dataType} values, it will return {@link * #DEFAULT_MIN_LOADABLE_RETRY_COUNT}. + * + *

Exclusion of both fallback types {@link #FALLBACK_TYPE_TRACK} and {@link + * #FALLBACK_TYPE_TRACK} is enabled by default. */ public DefaultLoadErrorHandlingPolicy() { - this(DEFAULT_BEHAVIOR_MIN_LOADABLE_RETRY_COUNT); + this(DEFAULT_BEHAVIOR_MIN_LOADABLE_RETRY_COUNT, /* locationExclusionEnabled= */ true); + } + + /** @deprecated Use {@link #DefaultLoadErrorHandlingPolicy(int, boolean)} instead. */ + @Deprecated + public DefaultLoadErrorHandlingPolicy(int minimumLoadableRetryCount) { + this(minimumLoadableRetryCount, /* locationExclusionEnabled= */ true); } /** * Creates an instance with the given value for {@link #getMinimumLoadableRetryCount(int)}. * * @param minimumLoadableRetryCount See {@link #getMinimumLoadableRetryCount}. + * @param locationExclusionEnabled Whether location exclusion is enabled. */ - public DefaultLoadErrorHandlingPolicy(int minimumLoadableRetryCount) { + public DefaultLoadErrorHandlingPolicy( + int minimumLoadableRetryCount, boolean locationExclusionEnabled) { this.minimumLoadableRetryCount = minimumLoadableRetryCount; + this.locationExclusionEnabled = locationExclusionEnabled; } /** - * Returns the exclusion duration, given by {@link #DEFAULT_TRACK_EXCLUSION_MS} or {@link + * Returns the fallback selection. + * + *

The exclusion duration is 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. + * + *

If alternative locations are advertised by the {@link + * LoadErrorHandlingPolicy.FallbackOptions}, {@link #FALLBACK_TYPE_LOCATION} is selected until all + * locations are excluded, {@link #FALLBACK_TYPE_TRACK} otherwise. */ @Override - public long getExclusionDurationMsFor( - @FallbackType int fallbackType, LoadErrorInfo loadErrorInfo) { - IOException exception = loadErrorInfo.exception; - if (exception instanceof InvalidResponseCodeException) { - int responseCode = ((InvalidResponseCodeException) exception).responseCode; - return responseCode == 403 // HTTP 403 Forbidden. - || responseCode == 404 // HTTP 404 Not Found. - || responseCode == 410 // HTTP 410 Gone. - || responseCode == 416 // HTTP 416 Range Not Satisfiable. - || responseCode == 500 // HTTP 500 Internal Server Error. - || responseCode == 503 // HTTP 503 Service Unavailable. - ? (fallbackType == FALLBACK_TYPE_TRACK - ? DEFAULT_TRACK_EXCLUSION_MS - : DEFAULT_LOCATION_EXCLUSION_MS) - : C.TIME_UNSET; + public FallbackSelection getFallbackSelectionFor( + FallbackOptions fallbackOptions, LoadErrorInfo loadErrorInfo) { + @FallbackType int fallbackType = FALLBACK_TYPE_TRACK; + boolean fallbackAvailable = + fallbackOptions.numberOfTracks - fallbackOptions.numberOfExcludedTracks > 1; + if (locationExclusionEnabled + && fallbackOptions.numberOfLocations - fallbackOptions.numberOfExcludedLocations > 1) { + fallbackType = FALLBACK_TYPE_LOCATION; + fallbackAvailable = true; } - return C.TIME_UNSET; + long exclusionDurationMs = C.TIME_UNSET; + IOException exception = loadErrorInfo.exception; + if (fallbackAvailable && exception instanceof InvalidResponseCodeException) { + int responseCode = ((InvalidResponseCodeException) exception).responseCode; + exclusionDurationMs = + responseCode == 403 // HTTP 403 Forbidden. + || responseCode == 404 // HTTP 404 Not Found. + || responseCode == 410 // HTTP 410 Gone. + || responseCode == 416 // HTTP 416 Range Not Satisfiable. + || responseCode == 500 // HTTP 500 Internal Server Error. + || responseCode == 503 // HTTP 503 Service Unavailable. + ? (fallbackType == FALLBACK_TYPE_TRACK + ? DEFAULT_TRACK_EXCLUSION_MS + : DEFAULT_LOCATION_EXCLUSION_MS) + : C.TIME_UNSET; + } + return new FallbackSelection(fallbackType, exclusionDurationMs); } /** 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 9e048c1e85..3306ac7191 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 @@ -30,11 +30,11 @@ 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 #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. + * load error occurs. In this case, {@link #getFallbackSelectionFor(FallbackOptions, 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(LoadErrorInfo)} defines whether * the load is retried. An error that's not retried will always be propagated. An error that is @@ -87,17 +87,65 @@ public interface LoadErrorHandlingPolicy { } } + /** Holds information about the available fallback options. */ + final class FallbackOptions { + /** The number of total available alternative locations. */ + public final int numberOfLocations; + /** The number of locations that are already excluded. */ + public final int numberOfExcludedLocations; + /** The number of total available tracks. */ + public final int numberOfTracks; + /** The number of tracks that are already excluded. */ + public final int numberOfExcludedTracks; + + /** Creates an instance with the given values. */ + public FallbackOptions( + int numberOfLocations, + int numberOfExcludedLocations, + int numberOfTracks, + int numberOfExcludedTracks) { + this.numberOfLocations = numberOfLocations; + this.numberOfExcludedLocations = numberOfExcludedLocations; + this.numberOfTracks = numberOfTracks; + this.numberOfExcludedTracks = numberOfExcludedTracks; + } + } + + /** The selection of a fallback option determining the fallback behaviour on load error. */ + final class FallbackSelection { + /** The {@link FallbackType fallback type} to use. */ + @FallbackType public final int type; + /** + * The exclusion duration of the {@link #type} in milliseconds, or {@link C#TIME_UNSET} to + * disable exclusion of any fallback type. + */ + public final long exclusionDurationMs; + + /** Creates an instance with the given values. */ + public FallbackSelection(@FallbackType int type, long exclusionDurationMs) { + this.type = type; + this.exclusionDurationMs = exclusionDurationMs; + } + } + /** - * Returns the number of milliseconds for which a resource associated to a provided load error - * should be excluded for a given {@link FallbackType fallback type}, or {@link C#TIME_UNSET} if - * the resource should not be excluded. + * Returns the {@link FallbackSelection fallback selection} that determines the exclusion + * behaviour on load error. * - * @param fallbackType The {@link FallbackType fallback type} used for exclusion. + *

If {@link FallbackSelection#exclusionDurationMs} is {@link C#TIME_UNSET}, exclusion is + * disabled for any fallback type, regardless of the value of the {@link FallbackSelection#type + * selected fallback type}. + * + *

If {@link FallbackSelection#type} is of a type that is not advertised as available by the + * {@link FallbackOptions}, exclusion is disabled for any fallback type. + * + * @param fallbackOptions The available fallback options. * @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. + * @return The fallback selection indicating whether to apply exclusion, and if so for which type + * and how long the resource should be excluded. */ - long getExclusionDurationMsFor(@FallbackType int fallbackType, LoadErrorInfo loadErrorInfo); + FallbackSelection getFallbackSelectionFor( + FallbackOptions fallbackOptions, LoadErrorInfo loadErrorInfo); /** * Returns the number of milliseconds to wait before attempting the load again, or {@link 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 bc5e468220..aa8a6e2305 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 @@ -15,6 +15,11 @@ */ package com.google.android.exoplayer2.upstream; +import static com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy.DEFAULT_LOCATION_EXCLUSION_MS; +import static com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy.DEFAULT_MIN_LOADABLE_RETRY_COUNT; +import static com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy.DEFAULT_TRACK_EXCLUSION_MS; +import static com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy.FALLBACK_TYPE_LOCATION; +import static com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy.FALLBACK_TYPE_TRACK; import static com.google.common.truth.Truth.assertThat; import android.net.Uri; @@ -48,52 +53,214 @@ public final class DefaultLoadErrorHandlingPolicyTest { new MediaLoadData(/* dataType= */ C.DATA_TYPE_UNKNOWN); @Test - public void getExclusionDurationMsFor_responseCode403() { + public void getFallbackSelectionFor_responseCode403() { InvalidResponseCodeException exception = buildInvalidResponseCodeException(403, "Forbidden"); - assertThat(getDefaultPolicyTrackExclusionDurationMsFor(exception)) - .isEqualTo(DefaultLoadErrorHandlingPolicy.DEFAULT_TRACK_EXCLUSION_MS); + + LoadErrorHandlingPolicy.FallbackSelection defaultPolicyFallbackSelection = + getDefaultPolicyFallbackSelection( + exception, + /* numberOfLocations= */ 1, + /* numberOfExcludedLocations= */ 0, + /* numberOfTracks= */ 10, + /* numberOfExcludedTracks= */ 0); + assertThat(defaultPolicyFallbackSelection.type).isEqualTo(FALLBACK_TYPE_TRACK); + assertThat(defaultPolicyFallbackSelection.exclusionDurationMs) + .isEqualTo(DEFAULT_TRACK_EXCLUSION_MS); + + defaultPolicyFallbackSelection = + getDefaultPolicyFallbackSelection( + exception, + /* numberOfLocations= */ 2, + /* numberOfExcludedLocations= */ 0, + /* numberOfTracks= */ 4, + /* numberOfExcludedTracks= */ 1); + assertThat(defaultPolicyFallbackSelection.type).isEqualTo(FALLBACK_TYPE_LOCATION); + assertThat(defaultPolicyFallbackSelection.exclusionDurationMs) + .isEqualTo(DEFAULT_LOCATION_EXCLUSION_MS); } @Test - public void getExclusionDurationMsFor_responseCode404() { + public void getFallbackSelectionFor_responseCode404() { InvalidResponseCodeException exception = buildInvalidResponseCodeException(404, "Not found"); - assertThat(getDefaultPolicyTrackExclusionDurationMsFor(exception)) - .isEqualTo(DefaultLoadErrorHandlingPolicy.DEFAULT_TRACK_EXCLUSION_MS); + + LoadErrorHandlingPolicy.FallbackSelection defaultPolicyFallbackSelection = + getDefaultPolicyFallbackSelection( + exception, + /* numberOfLocations= */ 1, + /* numberOfExcludedLocations= */ 0, + /* numberOfTracks= */ 10, + /* numberOfExcludedTracks= */ 0); + + assertThat(defaultPolicyFallbackSelection.type).isEqualTo(FALLBACK_TYPE_TRACK); + assertThat(defaultPolicyFallbackSelection.exclusionDurationMs) + .isEqualTo(DEFAULT_TRACK_EXCLUSION_MS); + + defaultPolicyFallbackSelection = + getDefaultPolicyFallbackSelection( + exception, + /* numberOfLocations= */ 2, + /* numberOfExcludedLocations= */ 0, + /* numberOfTracks= */ 4, + /* numberOfExcludedTracks= */ 1); + assertThat(defaultPolicyFallbackSelection.type).isEqualTo(FALLBACK_TYPE_LOCATION); + assertThat(defaultPolicyFallbackSelection.exclusionDurationMs) + .isEqualTo(DEFAULT_LOCATION_EXCLUSION_MS); } @Test - public void getExclusionDurationMsFor_responseCode410() { + public void getFallbackSelectionFor_responseCode410() { InvalidResponseCodeException exception = buildInvalidResponseCodeException(410, "Gone"); - assertThat(getDefaultPolicyTrackExclusionDurationMsFor(exception)) - .isEqualTo(DefaultLoadErrorHandlingPolicy.DEFAULT_TRACK_EXCLUSION_MS); + + LoadErrorHandlingPolicy.FallbackSelection defaultPolicyFallbackSelection = + getDefaultPolicyFallbackSelection( + exception, + /* numberOfLocations= */ 1, + /* numberOfExcludedLocations= */ 0, + /* numberOfTracks= */ 10, + /* numberOfExcludedTracks= */ 0); + + assertThat(defaultPolicyFallbackSelection.type).isEqualTo(FALLBACK_TYPE_TRACK); + assertThat(defaultPolicyFallbackSelection.exclusionDurationMs) + .isEqualTo(DEFAULT_TRACK_EXCLUSION_MS); + + defaultPolicyFallbackSelection = + getDefaultPolicyFallbackSelection( + exception, + /* numberOfLocations= */ 2, + /* numberOfExcludedLocations= */ 0, + /* numberOfTracks= */ 4, + /* numberOfExcludedTracks= */ 1); + assertThat(defaultPolicyFallbackSelection.type).isEqualTo(FALLBACK_TYPE_LOCATION); + assertThat(defaultPolicyFallbackSelection.exclusionDurationMs) + .isEqualTo(DEFAULT_LOCATION_EXCLUSION_MS); } @Test - public void getExclusionDurationMsFor_responseCode500() { + public void getFallbackSelectionFor_responseCode500() { InvalidResponseCodeException exception = buildInvalidResponseCodeException(500, "Internal server error"); - assertThat(getDefaultPolicyTrackExclusionDurationMsFor(exception)) - .isEqualTo(DefaultLoadErrorHandlingPolicy.DEFAULT_TRACK_EXCLUSION_MS); + + LoadErrorHandlingPolicy.FallbackSelection defaultPolicyFallbackSelection = + getDefaultPolicyFallbackSelection( + exception, + /* numberOfLocations= */ 1, + /* numberOfExcludedLocations= */ 0, + /* numberOfTracks= */ 10, + /* numberOfExcludedTracks= */ 0); + + assertThat(defaultPolicyFallbackSelection.type).isEqualTo(FALLBACK_TYPE_TRACK); + assertThat(defaultPolicyFallbackSelection.exclusionDurationMs) + .isEqualTo(DEFAULT_TRACK_EXCLUSION_MS); + + defaultPolicyFallbackSelection = + getDefaultPolicyFallbackSelection( + exception, + /* numberOfLocations= */ 2, + /* numberOfExcludedLocations= */ 0, + /* numberOfTracks= */ 4, + /* numberOfExcludedTracks= */ 1); + assertThat(defaultPolicyFallbackSelection.type).isEqualTo(FALLBACK_TYPE_LOCATION); + assertThat(defaultPolicyFallbackSelection.exclusionDurationMs) + .isEqualTo(DEFAULT_LOCATION_EXCLUSION_MS); } @Test - public void getExclusionDurationMsFor_responseCode503() { + public void getFallbackSelectionFor_responseCode503() { InvalidResponseCodeException exception = buildInvalidResponseCodeException(503, "Service unavailable"); - assertThat(getDefaultPolicyTrackExclusionDurationMsFor(exception)) - .isEqualTo(DefaultLoadErrorHandlingPolicy.DEFAULT_TRACK_EXCLUSION_MS); + + LoadErrorHandlingPolicy.FallbackSelection defaultPolicyFallbackSelection = + getDefaultPolicyFallbackSelection( + exception, + /* numberOfLocations= */ 1, + /* numberOfExcludedLocations= */ 0, + /* numberOfTracks= */ 10, + /* numberOfExcludedTracks= */ 0); + + assertThat(defaultPolicyFallbackSelection.type).isEqualTo(FALLBACK_TYPE_TRACK); + assertThat(defaultPolicyFallbackSelection.exclusionDurationMs) + .isEqualTo(DEFAULT_TRACK_EXCLUSION_MS); + + defaultPolicyFallbackSelection = + getDefaultPolicyFallbackSelection( + exception, + /* numberOfLocations= */ 2, + /* numberOfExcludedLocations= */ 0, + /* numberOfTracks= */ 4, + /* numberOfExcludedTracks= */ 1); + assertThat(defaultPolicyFallbackSelection.type).isEqualTo(FALLBACK_TYPE_LOCATION); + assertThat(defaultPolicyFallbackSelection.exclusionDurationMs) + .isEqualTo(DEFAULT_LOCATION_EXCLUSION_MS); } @Test - public void getExclusionDurationMsFor_dontExcludeUnexpectedHttpCodes() { + public void getFallbackSelectionFor_dontExcludeUnexpectedHttpCodes() { InvalidResponseCodeException exception = buildInvalidResponseCodeException(418, "I'm a teapot"); - assertThat(getDefaultPolicyTrackExclusionDurationMsFor(exception)).isEqualTo(C.TIME_UNSET); + + LoadErrorHandlingPolicy.FallbackSelection defaultPolicyFallbackSelection = + getDefaultPolicyFallbackSelection( + exception, + /* numberOfLocations= */ 1, + /* numberOfExcludedLocations= */ 0, + /* numberOfTracks= */ 10, + /* numberOfExcludedTracks= */ 0); + + assertThat(defaultPolicyFallbackSelection.type).isEqualTo(FALLBACK_TYPE_TRACK); + assertThat(defaultPolicyFallbackSelection.exclusionDurationMs).isEqualTo(C.TIME_UNSET); + + defaultPolicyFallbackSelection = + getDefaultPolicyFallbackSelection( + exception, + /* numberOfLocations= */ 2, + /* numberOfExcludedLocations= */ 0, + /* numberOfTracks= */ 4, + /* numberOfExcludedTracks= */ 1); + assertThat(defaultPolicyFallbackSelection.type).isEqualTo(FALLBACK_TYPE_LOCATION); + assertThat(defaultPolicyFallbackSelection.exclusionDurationMs).isEqualTo(C.TIME_UNSET); } @Test - public void getExclusionDurationMsFor_dontExcludeUnexpectedExceptions() { + public void getFallbackSelectionFor_dontExcludeUnexpectedExceptions() { IOException exception = new IOException(); - assertThat(getDefaultPolicyTrackExclusionDurationMsFor(exception)).isEqualTo(C.TIME_UNSET); + + LoadErrorHandlingPolicy.FallbackSelection defaultPolicyFallbackSelection = + getDefaultPolicyFallbackSelection( + exception, + /* numberOfLocations= */ 1, + /* numberOfExcludedLocations= */ 0, + /* numberOfTracks= */ 10, + /* numberOfExcludedTracks= */ 0); + + assertThat(defaultPolicyFallbackSelection.type).isEqualTo(FALLBACK_TYPE_TRACK); + assertThat(defaultPolicyFallbackSelection.exclusionDurationMs).isEqualTo(C.TIME_UNSET); + + defaultPolicyFallbackSelection = + getDefaultPolicyFallbackSelection( + exception, + /* numberOfLocations= */ 2, + /* numberOfExcludedLocations= */ 0, + /* numberOfTracks= */ 4, + /* numberOfExcludedTracks= */ 1); + assertThat(defaultPolicyFallbackSelection.type).isEqualTo(FALLBACK_TYPE_LOCATION); + assertThat(defaultPolicyFallbackSelection.exclusionDurationMs).isEqualTo(C.TIME_UNSET); + } + + @Test + public void getFallbackSelectionFor_disabledLocationExclusion_useTrackExclusion() { + InvalidResponseCodeException exception = buildInvalidResponseCodeException(404, "Not found"); + + LoadErrorHandlingPolicy.FallbackSelection defaultPolicyFallbackSelection = + getDefaultPolicyFallbackSelection( + exception, + /* numberOfLocations= */ 2, + /* numberOfExcludedLocations= */ 0, + /* numberOfTracks= */ 4, + /* numberOfExcludedTracks= */ 1, + new DefaultLoadErrorHandlingPolicy( + DEFAULT_MIN_LOADABLE_RETRY_COUNT, /* locationExclusionEnabled= */ false)); + assertThat(defaultPolicyFallbackSelection.type).isEqualTo(FALLBACK_TYPE_TRACK); + assertThat(defaultPolicyFallbackSelection.exclusionDurationMs) + .isEqualTo(DEFAULT_TRACK_EXCLUSION_MS); } @Test @@ -112,15 +279,38 @@ public final class DefaultLoadErrorHandlingPolicyTest { assertThat(getDefaultPolicyRetryDelayOutputFor(new IOException(), 9)).isEqualTo(5000); } - private static long getDefaultPolicyTrackExclusionDurationMsFor(IOException exception) { + private static LoadErrorHandlingPolicy.FallbackSelection getDefaultPolicyFallbackSelection( + IOException exception, + int numberOfLocations, + int numberOfExcludedLocations, + int numberOfTracks, + int numberOfExcludedTracks) { + return getDefaultPolicyFallbackSelection( + exception, + numberOfLocations, + numberOfExcludedLocations, + numberOfTracks, + numberOfExcludedTracks, + new DefaultLoadErrorHandlingPolicy()); + } + + private static LoadErrorHandlingPolicy.FallbackSelection getDefaultPolicyFallbackSelection( + IOException exception, + int numberOfLocations, + int numberOfExcludedLocations, + int numberOfTracks, + int numberOfExcludedTracks, + DefaultLoadErrorHandlingPolicy defaultLoadErrorHandlingPolicy) { LoadErrorInfo loadErrorInfo = new LoadErrorInfo( PLACEHOLDER_LOAD_EVENT_INFO, PLACEHOLDER_MEDIA_LOAD_DATA, exception, /* errorCount= */ 1); - return new DefaultLoadErrorHandlingPolicy() - .getExclusionDurationMsFor(LoadErrorHandlingPolicy.FALLBACK_TYPE_TRACK, loadErrorInfo); + LoadErrorHandlingPolicy.FallbackOptions fallbackOptions = + new LoadErrorHandlingPolicy.FallbackOptions( + numberOfLocations, numberOfExcludedLocations, numberOfTracks, numberOfExcludedTracks); + return defaultLoadErrorHandlingPolicy.getFallbackSelectionFor(fallbackOptions, loadErrorInfo); } private static long getDefaultPolicyRetryDelayOutputFor(IOException exception, int errorCount) { 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 8b4268940b..5cf7e8836f 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 @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.source.dash; +import static com.google.android.exoplayer2.trackselection.TrackSelectionUtil.createFallbackOptions; import static java.lang.Math.max; import static java.lang.Math.min; @@ -482,11 +483,17 @@ public class DefaultDashChunkSource implements DashChunkSource { } } } - long exclusionDurationMs = - loadErrorHandlingPolicy.getExclusionDurationMsFor( - LoadErrorHandlingPolicy.FALLBACK_TYPE_TRACK, loadErrorInfo); - return exclusionDurationMs != C.TIME_UNSET - && trackSelection.blacklist(trackSelection.indexOf(chunk.trackFormat), exclusionDurationMs); + LoadErrorHandlingPolicy.FallbackOptions fallbackOptions = createFallbackOptions(trackSelection); + if (fallbackOptions.numberOfTracks - fallbackOptions.numberOfExcludedTracks <= 1) { + // No more alternative tracks remaining. + return false; + } + LoadErrorHandlingPolicy.FallbackSelection fallbackSelection = + loadErrorHandlingPolicy.getFallbackSelectionFor(fallbackOptions, loadErrorInfo); + return fallbackSelection.type == LoadErrorHandlingPolicy.FALLBACK_TYPE_TRACK + && fallbackSelection.exclusionDurationMs != C.TIME_UNSET + && trackSelection.blacklist( + trackSelection.indexOf(chunk.trackFormat), fallbackSelection.exclusionDurationMs); } @Override diff --git a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSourceTest.java b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSourceTest.java index d9d7ea2630..c90324b299 100644 --- a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSourceTest.java +++ b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSourceTest.java @@ -150,11 +150,11 @@ public class DefaultDashChunkSourceTest { DefaultLoadErrorHandlingPolicy loadErrorHandlingPolicy = new DefaultLoadErrorHandlingPolicy() { @Override - public long getExclusionDurationMsFor(int fallbackType, LoadErrorInfo loadErrorInfo) { - // Try to exclude tracks only. - return fallbackType == FALLBACK_TYPE_LOCATION - ? C.TIME_UNSET - : super.getExclusionDurationMsFor(fallbackType, loadErrorInfo); + public FallbackSelection getFallbackSelectionFor( + FallbackOptions fallbackOptions, LoadErrorInfo loadErrorInfo) { + // Exclude tracks only. + return new FallbackSelection( + FALLBACK_TYPE_TRACK, DefaultLoadErrorHandlingPolicy.DEFAULT_TRACK_EXCLUSION_MS); } }; int numberOfTracks = 2; @@ -186,9 +186,10 @@ public class DefaultDashChunkSourceTest { DefaultLoadErrorHandlingPolicy loadErrorHandlingPolicy = new DefaultLoadErrorHandlingPolicy() { @Override - public long getExclusionDurationMsFor(int fallbackType, LoadErrorInfo loadErrorInfo) { + public FallbackSelection getFallbackSelectionFor( + FallbackOptions fallbackOptions, LoadErrorInfo loadErrorInfo) { // Never exclude, neither tracks nor locations. - return C.TIME_UNSET; + return new FallbackSelection(FALLBACK_TYPE_TRACK, C.TIME_UNSET); } }; DashChunkSource chunkSource = createDashChunkSource(/* numberOfTracks= */ 2); 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 df52d5ec4b..7c36d257b1 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 @@ -544,7 +544,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; } seenExpectedPlaylistError |= playlistUrl.equals(expectedPlaylistUrl); return exclusionDurationMs == C.TIME_UNSET - || trackSelection.blacklist(trackSelectionIndex, exclusionDurationMs); + || (trackSelection.blacklist(trackSelectionIndex, exclusionDurationMs) + && playlistTracker.excludeMediaPlaylist(playlistUrl, exclusionDurationMs)); } /** @@ -669,6 +670,11 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; return Collections.unmodifiableList(segmentBases); } + /** Returns whether this chunk source obtains chunks for the playlist with the given url. */ + public boolean obtainsChunksForPlaylist(Uri playlistUrl) { + return Util.contains(playlistUrls, playlistUrl); + } + // Private methods. /** 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 287787acff..a13a7bb3a1 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 @@ -465,10 +465,11 @@ public final class HlsMediaPeriod } @Override - public boolean onPlaylistError(Uri url, long exclusionDurationMs) { + public boolean onPlaylistError( + Uri url, LoadErrorHandlingPolicy.LoadErrorInfo loadErrorInfo, boolean forceRetry) { boolean exclusionSucceeded = true; for (HlsSampleStreamWrapper streamWrapper : sampleStreamWrappers) { - exclusionSucceeded &= streamWrapper.onPlaylistError(url, exclusionDurationMs); + exclusionSucceeded &= streamWrapper.onPlaylistError(url, loadErrorInfo, forceRetry); } callback.onContinueLoadingRequested(this); return exclusionSucceeded; 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 7282ea6df6..b1104ba8f9 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 @@ -17,6 +17,7 @@ package com.google.android.exoplayer2.source.hls; import static com.google.android.exoplayer2.source.hls.HlsChunkSource.CHUNK_PUBLICATION_STATE_PUBLISHED; import static com.google.android.exoplayer2.source.hls.HlsChunkSource.CHUNK_PUBLICATION_STATE_REMOVED; +import static com.google.android.exoplayer2.trackselection.TrackSelectionUtil.createFallbackOptions; import static java.lang.Math.max; import static java.lang.Math.min; @@ -554,7 +555,21 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; chunkSource.setIsTimestampMaster(isTimestampMaster); } - public boolean onPlaylistError(Uri playlistUrl, long exclusionDurationMs) { + public boolean onPlaylistError(Uri playlistUrl, LoadErrorInfo loadErrorInfo, boolean forceRetry) { + if (!chunkSource.obtainsChunksForPlaylist(playlistUrl)) { + // Return early if the chunk source doesn't deliver chunks for the failing playlist. + return true; + } + long exclusionDurationMs = C.TIME_UNSET; + if (!forceRetry) { + LoadErrorHandlingPolicy.FallbackSelection fallbackSelection = + loadErrorHandlingPolicy.getFallbackSelectionFor( + createFallbackOptions(chunkSource.getTrackSelection()), loadErrorInfo); + exclusionDurationMs = + fallbackSelection.type == LoadErrorHandlingPolicy.FALLBACK_TYPE_TRACK + ? fallbackSelection.exclusionDurationMs + : C.TIME_UNSET; + } return chunkSource.onPlaylistError(playlistUrl, exclusionDurationMs); } @@ -894,11 +909,13 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; LoadErrorInfo loadErrorInfo = new LoadErrorInfo(loadEventInfo, mediaLoadData, error, errorCount); LoadErrorAction loadErrorAction; - long exclusionDurationMs = - loadErrorHandlingPolicy.getExclusionDurationMsFor( - LoadErrorHandlingPolicy.FALLBACK_TYPE_TRACK, loadErrorInfo); - if (exclusionDurationMs != C.TIME_UNSET) { - exclusionSucceeded = chunkSource.maybeExcludeTrack(loadable, exclusionDurationMs); + LoadErrorHandlingPolicy.FallbackSelection fallbackSelection = + loadErrorHandlingPolicy.getFallbackSelectionFor( + createFallbackOptions(chunkSource.getTrackSelection()), loadErrorInfo); + if (fallbackSelection.type == LoadErrorHandlingPolicy.FALLBACK_TYPE_TRACK + && fallbackSelection.exclusionDurationMs != C.TIME_UNSET) { + exclusionSucceeded = + chunkSource.maybeExcludeTrack(loadable, fallbackSelection.exclusionDurationMs); } if (exclusionSucceeded) { 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 ed7a4fe9a1..5c065e9c22 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 @@ -228,6 +228,15 @@ public final class DefaultHlsPlaylistTracker return isLive; } + @Override + public boolean excludeMediaPlaylist(Uri playlistUrl, long exclusionDurationMs) { + @Nullable MediaPlaylistBundle bundle = playlistBundles.get(playlistUrl); + if (bundle != null) { + return !bundle.excludePlaylist(exclusionDurationMs); + } + return false; + } + // Loader.Callback implementation. @Override @@ -413,11 +422,13 @@ public final class DefaultHlsPlaylistTracker } } - private boolean notifyPlaylistError(Uri playlistUrl, long exclusionDurationMs) { + private boolean notifyPlaylistError( + Uri playlistUrl, LoadErrorInfo loadErrorInfo, boolean forceRetry) { int listenersSize = listeners.size(); boolean anyExclusionFailed = false; for (int i = 0; i < listenersSize; i++) { - anyExclusionFailed |= !listeners.get(i).onPlaylistError(playlistUrl, exclusionDurationMs); + anyExclusionFailed |= + !listeners.get(i).onPlaylistError(playlistUrl, loadErrorInfo, forceRetry); } return anyExclusionFailed; } @@ -632,18 +643,9 @@ public final class DefaultHlsPlaylistTracker MediaLoadData mediaLoadData = new MediaLoadData(loadable.type); LoadErrorInfo loadErrorInfo = new LoadErrorInfo(loadEventInfo, mediaLoadData, error, errorCount); - LoadErrorAction loadErrorAction; - long exclusionDurationMs = - loadErrorHandlingPolicy.getExclusionDurationMsFor( - LoadErrorHandlingPolicy.FALLBACK_TYPE_TRACK, loadErrorInfo); - boolean shouldExclude = exclusionDurationMs != C.TIME_UNSET; - boolean exclusionFailed = - notifyPlaylistError(playlistUrl, exclusionDurationMs) || !shouldExclude; - if (shouldExclude) { - exclusionFailed |= excludePlaylist(exclusionDurationMs); - } - + notifyPlaylistError(playlistUrl, loadErrorInfo, /* forceRetry= */ false); + LoadErrorAction loadErrorAction; if (exclusionFailed) { long retryDelay = loadErrorHandlingPolicy.getRetryDelayMsFor(loadErrorInfo); loadErrorAction = @@ -696,7 +698,7 @@ public final class DefaultHlsPlaylistTracker long elapsedRealtime = mediaPlaylistLoader.startLoading( mediaPlaylistLoadable, - this, + /* callback= */ this, loadErrorHandlingPolicy.getMinimumLoadableRetryCount(mediaPlaylistLoadable.type)); eventDispatcher.loadStarted( new LoadEventInfo( @@ -715,31 +717,31 @@ public final class DefaultHlsPlaylistTracker lastSnapshotChangeMs = currentTimeMs; onPlaylistUpdated(playlistUrl, playlistSnapshot); } else if (!playlistSnapshot.hasEndTag) { + boolean forceRetry = false; + @Nullable IOException playlistError = null; 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. We do not try // excluding in this case. + forceRetry = true; playlistError = new PlaylistResetException(playlistUrl); - notifyPlaylistError(playlistUrl, C.TIME_UNSET); } else if (currentTimeMs - lastSnapshotChangeMs > C.usToMs(playlistSnapshot.targetDurationUs) * playlistStuckTargetDurationCoefficient) { // TODO: Allow customization of stuck playlists handling. playlistError = new PlaylistStuckException(playlistUrl); - LoadErrorInfo loadErrorInfo = + } + if (playlistError != null) { + this.playlistError = playlistError; + notifyPlaylistError( + playlistUrl, new LoadErrorInfo( loadEventInfo, new MediaLoadData(C.DATA_TYPE_MANIFEST), playlistError, - /* errorCount= */ 1); - long exclusionDurationMs = - loadErrorHandlingPolicy.getExclusionDurationMsFor( - LoadErrorHandlingPolicy.FALLBACK_TYPE_TRACK, loadErrorInfo); - notifyPlaylistError(playlistUrl, exclusionDurationMs); - if (exclusionDurationMs != C.TIME_UNSET) { - excludePlaylist(exclusionDurationMs); - } + /* errorCount= */ 1), + forceRetry); } } long durationUntilNextLoadUs = 0L; diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistTracker.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistTracker.java index 6f96bc5d76..09b1d7c02f 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistTracker.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistTracker.java @@ -74,11 +74,12 @@ public interface HlsPlaylistTracker { * Called if an error is encountered while loading a playlist. * * @param url The loaded url that caused the error. - * @param exclusionDurationMs The duration for which the playlist should be excluded. Or {@link - * C#TIME_UNSET} if the playlist should not be excluded. + * @param loadErrorInfo The load error info. + * @param forceRetry Whether retry should be forced without considering exclusion. * @return True if excluding did not encounter errors. False otherwise. */ - boolean onPlaylistError(Uri url, long exclusionDurationMs); + boolean onPlaylistError( + Uri url, LoadErrorHandlingPolicy.LoadErrorInfo loadErrorInfo, boolean forceRetry); } /** Thrown when a playlist is considered to be stuck due to a server side error. */ @@ -205,6 +206,15 @@ public interface HlsPlaylistTracker { */ void maybeThrowPlaylistRefreshError(Uri url) throws IOException; + /** + * Excludes the given media playlist for the given duration, in milliseconds. + * + * @param playlistUrl The URL of the media playlist. + * @param exclusionDurationMs The duration for which to exclude the playlist. + * @return Whether exclusion was successful. + */ + boolean excludeMediaPlaylist(Uri playlistUrl, long exclusionDurationMs); + /** * Requests a playlist refresh and removes it from the exclusion list. * 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 1183ef668c..6e2fa78af7 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 @@ -15,6 +15,8 @@ */ package com.google.android.exoplayer2.source.smoothstreaming; +import static com.google.android.exoplayer2.trackselection.TrackSelectionUtil.createFallbackOptions; + import android.net.Uri; import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; @@ -38,6 +40,7 @@ import com.google.android.exoplayer2.trackselection.ExoTrackSelection; import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy; +import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy.FallbackSelection; import com.google.android.exoplayer2.upstream.LoaderErrorThrower; import com.google.android.exoplayer2.upstream.TransferListener; import com.google.android.exoplayer2.util.Assertions; @@ -287,12 +290,14 @@ public class DefaultSsChunkSource implements SsChunkSource { boolean cancelable, LoadErrorHandlingPolicy.LoadErrorInfo loadErrorInfo, LoadErrorHandlingPolicy loadErrorHandlingPolicy) { - long exclusionDurationMs = - loadErrorHandlingPolicy.getExclusionDurationMsFor( - LoadErrorHandlingPolicy.FALLBACK_TYPE_TRACK, loadErrorInfo); + FallbackSelection fallbackSelection = + loadErrorHandlingPolicy.getFallbackSelectionFor( + createFallbackOptions(trackSelection), loadErrorInfo); return cancelable - && exclusionDurationMs != C.TIME_UNSET - && trackSelection.blacklist(trackSelection.indexOf(chunk.trackFormat), exclusionDurationMs); + && fallbackSelection.type == LoadErrorHandlingPolicy.FALLBACK_TYPE_TRACK + && fallbackSelection.exclusionDurationMs != C.TIME_UNSET + && trackSelection.blacklist( + trackSelection.indexOf(chunk.trackFormat), fallbackSelection.exclusionDurationMs); } @Override diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAdaptiveMediaPeriod.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAdaptiveMediaPeriod.java index 8db69b3dc7..81261b5b6c 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAdaptiveMediaPeriod.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAdaptiveMediaPeriod.java @@ -177,7 +177,8 @@ public class FakeAdaptiveMediaPeriod positionUs, DrmSessionManager.DRM_UNSUPPORTED, new DrmSessionEventListener.EventDispatcher(), - new DefaultLoadErrorHandlingPolicy(/* minimumLoadableRetryCount= */ 3), + new DefaultLoadErrorHandlingPolicy( + /* minimumLoadableRetryCount= */ 3, /* locationExclusionEnabled= */ true), mediaSourceEventDispatcher); streams[i] = sampleStream; sampleStreams.add(sampleStream);