Make customization of fallback selection more flexible
PiperOrigin-RevId: 383245932
This commit is contained in:
parent
15c565c7d7
commit
6dbc1eb189
@ -383,7 +383,8 @@ public class DefaultDrmSessionManager implements DrmSessionManager {
|
|||||||
multiSession,
|
multiSession,
|
||||||
/* useDrmSessionsForClearContentTrackTypes= */ new int[0],
|
/* useDrmSessionsForClearContentTrackTypes= */ new int[0],
|
||||||
/* playClearSamplesWithoutKeys= */ false,
|
/* playClearSamplesWithoutKeys= */ false,
|
||||||
new DefaultLoadErrorHandlingPolicy(initialDrmRequestRetryCount),
|
new DefaultLoadErrorHandlingPolicy(
|
||||||
|
initialDrmRequestRetryCount, /* locationExclusionEnabled= */ false),
|
||||||
DEFAULT_SESSION_KEEPALIVE_MS);
|
DEFAULT_SESSION_KEEPALIVE_MS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,10 +15,12 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.trackselection;
|
package com.google.android.exoplayer2.trackselection;
|
||||||
|
|
||||||
|
import android.os.SystemClock;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||||
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector.SelectionOverride;
|
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector.SelectionOverride;
|
||||||
import com.google.android.exoplayer2.trackselection.ExoTrackSelection.Definition;
|
import com.google.android.exoplayer2.trackselection.ExoTrackSelection.Definition;
|
||||||
|
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
|
||||||
import com.google.android.exoplayer2.util.MimeTypes;
|
import com.google.android.exoplayer2.util.MimeTypes;
|
||||||
import org.checkerframework.checker.nullness.compatqual.NullableType;
|
import org.checkerframework.checker.nullness.compatqual.NullableType;
|
||||||
|
|
||||||
@ -114,4 +116,29 @@ public final class TrackSelectionUtil {
|
|||||||
}
|
}
|
||||||
return false;
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,6 +45,7 @@ public class DefaultLoadErrorHandlingPolicy implements LoadErrorHandlingPolicy {
|
|||||||
private static final int DEFAULT_BEHAVIOR_MIN_LOADABLE_RETRY_COUNT = -1;
|
private static final int DEFAULT_BEHAVIOR_MIN_LOADABLE_RETRY_COUNT = -1;
|
||||||
|
|
||||||
private final int minimumLoadableRetryCount;
|
private final int minimumLoadableRetryCount;
|
||||||
|
private final boolean locationExclusionEnabled;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an instance with default behavior.
|
* 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
|
* #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
|
* C#DATA_TYPE_MEDIA_PROGRESSIVE_LIVE}. For other {@code dataType} values, it will return {@link
|
||||||
* #DEFAULT_MIN_LOADABLE_RETRY_COUNT}.
|
* #DEFAULT_MIN_LOADABLE_RETRY_COUNT}.
|
||||||
|
*
|
||||||
|
* <p>Exclusion of both fallback types {@link #FALLBACK_TYPE_TRACK} and {@link
|
||||||
|
* #FALLBACK_TYPE_TRACK} is enabled by default.
|
||||||
*/
|
*/
|
||||||
public DefaultLoadErrorHandlingPolicy() {
|
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)}.
|
* Creates an instance with the given value for {@link #getMinimumLoadableRetryCount(int)}.
|
||||||
*
|
*
|
||||||
* @param minimumLoadableRetryCount See {@link #getMinimumLoadableRetryCount}.
|
* @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.minimumLoadableRetryCount = minimumLoadableRetryCount;
|
||||||
|
this.locationExclusionEnabled = locationExclusionEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the exclusion duration, given by {@link #DEFAULT_TRACK_EXCLUSION_MS} or {@link
|
* Returns the fallback selection.
|
||||||
|
*
|
||||||
|
* <p>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}
|
* #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}
|
* with an HTTP response code indicating an unrecoverable error, or {@link C#TIME_UNSET}
|
||||||
* otherwise.
|
* otherwise.
|
||||||
|
*
|
||||||
|
* <p>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
|
@Override
|
||||||
public long getExclusionDurationMsFor(
|
public FallbackSelection getFallbackSelectionFor(
|
||||||
@FallbackType int fallbackType, LoadErrorInfo loadErrorInfo) {
|
FallbackOptions fallbackOptions, LoadErrorInfo loadErrorInfo) {
|
||||||
IOException exception = loadErrorInfo.exception;
|
@FallbackType int fallbackType = FALLBACK_TYPE_TRACK;
|
||||||
if (exception instanceof InvalidResponseCodeException) {
|
boolean fallbackAvailable =
|
||||||
int responseCode = ((InvalidResponseCodeException) exception).responseCode;
|
fallbackOptions.numberOfTracks - fallbackOptions.numberOfExcludedTracks > 1;
|
||||||
return responseCode == 403 // HTTP 403 Forbidden.
|
if (locationExclusionEnabled
|
||||||
|| responseCode == 404 // HTTP 404 Not Found.
|
&& fallbackOptions.numberOfLocations - fallbackOptions.numberOfExcludedLocations > 1) {
|
||||||
|| responseCode == 410 // HTTP 410 Gone.
|
fallbackType = FALLBACK_TYPE_LOCATION;
|
||||||
|| responseCode == 416 // HTTP 416 Range Not Satisfiable.
|
fallbackAvailable = true;
|
||||||
|| 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 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -30,11 +30,11 @@ import java.lang.annotation.RetentionPolicy;
|
|||||||
* Defines how errors encountered by loaders are handled.
|
* Defines how errors encountered by loaders are handled.
|
||||||
*
|
*
|
||||||
* <p>A loader that can choose between one of a number of resources can exclude a resource when a
|
* <p>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
|
* load error occurs. In this case, {@link #getFallbackSelectionFor(FallbackOptions, LoadErrorInfo)}
|
||||||
* whether the resource should be excluded for a given {@link FallbackType fallback type}, and if so
|
* defines whether the resource should be excluded for a given {@link FallbackType fallback type},
|
||||||
* for how long. If the policy indicates that a resource should be excluded, the loader will exclude
|
* and if so for how long. If the policy indicates that a resource should be excluded, the loader
|
||||||
* it for the specified amount of time unless all of the alternatives for the given fallback type
|
* will exclude it for the specified amount of time unless all of the alternatives for the given
|
||||||
* are already excluded.
|
* fallback type are already excluded.
|
||||||
*
|
*
|
||||||
* <p>When exclusion does not take place, {@link #getRetryDelayMsFor(LoadErrorInfo)} defines whether
|
* <p>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
|
* 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
|
* Returns the {@link FallbackSelection fallback selection} that determines the exclusion
|
||||||
* should be excluded for a given {@link FallbackType fallback type}, or {@link C#TIME_UNSET} if
|
* behaviour on load error.
|
||||||
* the resource should not be excluded.
|
|
||||||
*
|
*
|
||||||
* @param fallbackType The {@link FallbackType fallback type} used for exclusion.
|
* <p>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}.
|
||||||
|
*
|
||||||
|
* <p>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.
|
* @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
|
* @return The fallback selection indicating whether to apply exclusion, and if so for which type
|
||||||
* not be excluded.
|
* 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
|
* Returns the number of milliseconds to wait before attempting the load again, or {@link
|
||||||
|
@ -15,6 +15,11 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.upstream;
|
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 static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
@ -48,52 +53,214 @@ public final class DefaultLoadErrorHandlingPolicyTest {
|
|||||||
new MediaLoadData(/* dataType= */ C.DATA_TYPE_UNKNOWN);
|
new MediaLoadData(/* dataType= */ C.DATA_TYPE_UNKNOWN);
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getExclusionDurationMsFor_responseCode403() {
|
public void getFallbackSelectionFor_responseCode403() {
|
||||||
InvalidResponseCodeException exception = buildInvalidResponseCodeException(403, "Forbidden");
|
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
|
@Test
|
||||||
public void getExclusionDurationMsFor_responseCode404() {
|
public void getFallbackSelectionFor_responseCode404() {
|
||||||
InvalidResponseCodeException exception = buildInvalidResponseCodeException(404, "Not found");
|
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
|
@Test
|
||||||
public void getExclusionDurationMsFor_responseCode410() {
|
public void getFallbackSelectionFor_responseCode410() {
|
||||||
InvalidResponseCodeException exception = buildInvalidResponseCodeException(410, "Gone");
|
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
|
@Test
|
||||||
public void getExclusionDurationMsFor_responseCode500() {
|
public void getFallbackSelectionFor_responseCode500() {
|
||||||
InvalidResponseCodeException exception =
|
InvalidResponseCodeException exception =
|
||||||
buildInvalidResponseCodeException(500, "Internal server error");
|
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
|
@Test
|
||||||
public void getExclusionDurationMsFor_responseCode503() {
|
public void getFallbackSelectionFor_responseCode503() {
|
||||||
InvalidResponseCodeException exception =
|
InvalidResponseCodeException exception =
|
||||||
buildInvalidResponseCodeException(503, "Service unavailable");
|
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
|
@Test
|
||||||
public void getExclusionDurationMsFor_dontExcludeUnexpectedHttpCodes() {
|
public void getFallbackSelectionFor_dontExcludeUnexpectedHttpCodes() {
|
||||||
InvalidResponseCodeException exception = buildInvalidResponseCodeException(418, "I'm a teapot");
|
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
|
@Test
|
||||||
public void getExclusionDurationMsFor_dontExcludeUnexpectedExceptions() {
|
public void getFallbackSelectionFor_dontExcludeUnexpectedExceptions() {
|
||||||
IOException exception = new IOException();
|
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
|
@Test
|
||||||
@ -112,15 +279,38 @@ public final class DefaultLoadErrorHandlingPolicyTest {
|
|||||||
assertThat(getDefaultPolicyRetryDelayOutputFor(new IOException(), 9)).isEqualTo(5000);
|
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 =
|
LoadErrorInfo loadErrorInfo =
|
||||||
new LoadErrorInfo(
|
new LoadErrorInfo(
|
||||||
PLACEHOLDER_LOAD_EVENT_INFO,
|
PLACEHOLDER_LOAD_EVENT_INFO,
|
||||||
PLACEHOLDER_MEDIA_LOAD_DATA,
|
PLACEHOLDER_MEDIA_LOAD_DATA,
|
||||||
exception,
|
exception,
|
||||||
/* errorCount= */ 1);
|
/* errorCount= */ 1);
|
||||||
return new DefaultLoadErrorHandlingPolicy()
|
LoadErrorHandlingPolicy.FallbackOptions fallbackOptions =
|
||||||
.getExclusionDurationMsFor(LoadErrorHandlingPolicy.FALLBACK_TYPE_TRACK, loadErrorInfo);
|
new LoadErrorHandlingPolicy.FallbackOptions(
|
||||||
|
numberOfLocations, numberOfExcludedLocations, numberOfTracks, numberOfExcludedTracks);
|
||||||
|
return defaultLoadErrorHandlingPolicy.getFallbackSelectionFor(fallbackOptions, loadErrorInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static long getDefaultPolicyRetryDelayOutputFor(IOException exception, int errorCount) {
|
private static long getDefaultPolicyRetryDelayOutputFor(IOException exception, int errorCount) {
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.source.dash;
|
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.max;
|
||||||
import static java.lang.Math.min;
|
import static java.lang.Math.min;
|
||||||
|
|
||||||
@ -482,11 +483,17 @@ public class DefaultDashChunkSource implements DashChunkSource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
long exclusionDurationMs =
|
LoadErrorHandlingPolicy.FallbackOptions fallbackOptions = createFallbackOptions(trackSelection);
|
||||||
loadErrorHandlingPolicy.getExclusionDurationMsFor(
|
if (fallbackOptions.numberOfTracks - fallbackOptions.numberOfExcludedTracks <= 1) {
|
||||||
LoadErrorHandlingPolicy.FALLBACK_TYPE_TRACK, loadErrorInfo);
|
// No more alternative tracks remaining.
|
||||||
return exclusionDurationMs != C.TIME_UNSET
|
return false;
|
||||||
&& trackSelection.blacklist(trackSelection.indexOf(chunk.trackFormat), exclusionDurationMs);
|
}
|
||||||
|
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
|
@Override
|
||||||
|
@ -150,11 +150,11 @@ public class DefaultDashChunkSourceTest {
|
|||||||
DefaultLoadErrorHandlingPolicy loadErrorHandlingPolicy =
|
DefaultLoadErrorHandlingPolicy loadErrorHandlingPolicy =
|
||||||
new DefaultLoadErrorHandlingPolicy() {
|
new DefaultLoadErrorHandlingPolicy() {
|
||||||
@Override
|
@Override
|
||||||
public long getExclusionDurationMsFor(int fallbackType, LoadErrorInfo loadErrorInfo) {
|
public FallbackSelection getFallbackSelectionFor(
|
||||||
// Try to exclude tracks only.
|
FallbackOptions fallbackOptions, LoadErrorInfo loadErrorInfo) {
|
||||||
return fallbackType == FALLBACK_TYPE_LOCATION
|
// Exclude tracks only.
|
||||||
? C.TIME_UNSET
|
return new FallbackSelection(
|
||||||
: super.getExclusionDurationMsFor(fallbackType, loadErrorInfo);
|
FALLBACK_TYPE_TRACK, DefaultLoadErrorHandlingPolicy.DEFAULT_TRACK_EXCLUSION_MS);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
int numberOfTracks = 2;
|
int numberOfTracks = 2;
|
||||||
@ -186,9 +186,10 @@ public class DefaultDashChunkSourceTest {
|
|||||||
DefaultLoadErrorHandlingPolicy loadErrorHandlingPolicy =
|
DefaultLoadErrorHandlingPolicy loadErrorHandlingPolicy =
|
||||||
new DefaultLoadErrorHandlingPolicy() {
|
new DefaultLoadErrorHandlingPolicy() {
|
||||||
@Override
|
@Override
|
||||||
public long getExclusionDurationMsFor(int fallbackType, LoadErrorInfo loadErrorInfo) {
|
public FallbackSelection getFallbackSelectionFor(
|
||||||
|
FallbackOptions fallbackOptions, LoadErrorInfo loadErrorInfo) {
|
||||||
// Never exclude, neither tracks nor locations.
|
// Never exclude, neither tracks nor locations.
|
||||||
return C.TIME_UNSET;
|
return new FallbackSelection(FALLBACK_TYPE_TRACK, C.TIME_UNSET);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
DashChunkSource chunkSource = createDashChunkSource(/* numberOfTracks= */ 2);
|
DashChunkSource chunkSource = createDashChunkSource(/* numberOfTracks= */ 2);
|
||||||
|
@ -544,7 +544,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
}
|
}
|
||||||
seenExpectedPlaylistError |= playlistUrl.equals(expectedPlaylistUrl);
|
seenExpectedPlaylistError |= playlistUrl.equals(expectedPlaylistUrl);
|
||||||
return exclusionDurationMs == C.TIME_UNSET
|
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);
|
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.
|
// Private methods.
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -465,10 +465,11 @@ public final class HlsMediaPeriod
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onPlaylistError(Uri url, long exclusionDurationMs) {
|
public boolean onPlaylistError(
|
||||||
|
Uri url, LoadErrorHandlingPolicy.LoadErrorInfo loadErrorInfo, boolean forceRetry) {
|
||||||
boolean exclusionSucceeded = true;
|
boolean exclusionSucceeded = true;
|
||||||
for (HlsSampleStreamWrapper streamWrapper : sampleStreamWrappers) {
|
for (HlsSampleStreamWrapper streamWrapper : sampleStreamWrappers) {
|
||||||
exclusionSucceeded &= streamWrapper.onPlaylistError(url, exclusionDurationMs);
|
exclusionSucceeded &= streamWrapper.onPlaylistError(url, loadErrorInfo, forceRetry);
|
||||||
}
|
}
|
||||||
callback.onContinueLoadingRequested(this);
|
callback.onContinueLoadingRequested(this);
|
||||||
return exclusionSucceeded;
|
return exclusionSucceeded;
|
||||||
|
@ -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_PUBLISHED;
|
||||||
import static com.google.android.exoplayer2.source.hls.HlsChunkSource.CHUNK_PUBLICATION_STATE_REMOVED;
|
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.max;
|
||||||
import static java.lang.Math.min;
|
import static java.lang.Math.min;
|
||||||
|
|
||||||
@ -554,7 +555,21 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||||||
chunkSource.setIsTimestampMaster(isTimestampMaster);
|
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);
|
return chunkSource.onPlaylistError(playlistUrl, exclusionDurationMs);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -894,11 +909,13 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||||||
LoadErrorInfo loadErrorInfo =
|
LoadErrorInfo loadErrorInfo =
|
||||||
new LoadErrorInfo(loadEventInfo, mediaLoadData, error, errorCount);
|
new LoadErrorInfo(loadEventInfo, mediaLoadData, error, errorCount);
|
||||||
LoadErrorAction loadErrorAction;
|
LoadErrorAction loadErrorAction;
|
||||||
long exclusionDurationMs =
|
LoadErrorHandlingPolicy.FallbackSelection fallbackSelection =
|
||||||
loadErrorHandlingPolicy.getExclusionDurationMsFor(
|
loadErrorHandlingPolicy.getFallbackSelectionFor(
|
||||||
LoadErrorHandlingPolicy.FALLBACK_TYPE_TRACK, loadErrorInfo);
|
createFallbackOptions(chunkSource.getTrackSelection()), loadErrorInfo);
|
||||||
if (exclusionDurationMs != C.TIME_UNSET) {
|
if (fallbackSelection.type == LoadErrorHandlingPolicy.FALLBACK_TYPE_TRACK
|
||||||
exclusionSucceeded = chunkSource.maybeExcludeTrack(loadable, exclusionDurationMs);
|
&& fallbackSelection.exclusionDurationMs != C.TIME_UNSET) {
|
||||||
|
exclusionSucceeded =
|
||||||
|
chunkSource.maybeExcludeTrack(loadable, fallbackSelection.exclusionDurationMs);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (exclusionSucceeded) {
|
if (exclusionSucceeded) {
|
||||||
|
@ -228,6 +228,15 @@ public final class DefaultHlsPlaylistTracker
|
|||||||
return isLive;
|
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.
|
// Loader.Callback implementation.
|
||||||
|
|
||||||
@Override
|
@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();
|
int listenersSize = listeners.size();
|
||||||
boolean anyExclusionFailed = false;
|
boolean anyExclusionFailed = false;
|
||||||
for (int i = 0; i < listenersSize; i++) {
|
for (int i = 0; i < listenersSize; i++) {
|
||||||
anyExclusionFailed |= !listeners.get(i).onPlaylistError(playlistUrl, exclusionDurationMs);
|
anyExclusionFailed |=
|
||||||
|
!listeners.get(i).onPlaylistError(playlistUrl, loadErrorInfo, forceRetry);
|
||||||
}
|
}
|
||||||
return anyExclusionFailed;
|
return anyExclusionFailed;
|
||||||
}
|
}
|
||||||
@ -632,18 +643,9 @@ public final class DefaultHlsPlaylistTracker
|
|||||||
MediaLoadData mediaLoadData = new MediaLoadData(loadable.type);
|
MediaLoadData mediaLoadData = new MediaLoadData(loadable.type);
|
||||||
LoadErrorInfo loadErrorInfo =
|
LoadErrorInfo loadErrorInfo =
|
||||||
new LoadErrorInfo(loadEventInfo, mediaLoadData, error, errorCount);
|
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 =
|
boolean exclusionFailed =
|
||||||
notifyPlaylistError(playlistUrl, exclusionDurationMs) || !shouldExclude;
|
notifyPlaylistError(playlistUrl, loadErrorInfo, /* forceRetry= */ false);
|
||||||
if (shouldExclude) {
|
LoadErrorAction loadErrorAction;
|
||||||
exclusionFailed |= excludePlaylist(exclusionDurationMs);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (exclusionFailed) {
|
if (exclusionFailed) {
|
||||||
long retryDelay = loadErrorHandlingPolicy.getRetryDelayMsFor(loadErrorInfo);
|
long retryDelay = loadErrorHandlingPolicy.getRetryDelayMsFor(loadErrorInfo);
|
||||||
loadErrorAction =
|
loadErrorAction =
|
||||||
@ -696,7 +698,7 @@ public final class DefaultHlsPlaylistTracker
|
|||||||
long elapsedRealtime =
|
long elapsedRealtime =
|
||||||
mediaPlaylistLoader.startLoading(
|
mediaPlaylistLoader.startLoading(
|
||||||
mediaPlaylistLoadable,
|
mediaPlaylistLoadable,
|
||||||
this,
|
/* callback= */ this,
|
||||||
loadErrorHandlingPolicy.getMinimumLoadableRetryCount(mediaPlaylistLoadable.type));
|
loadErrorHandlingPolicy.getMinimumLoadableRetryCount(mediaPlaylistLoadable.type));
|
||||||
eventDispatcher.loadStarted(
|
eventDispatcher.loadStarted(
|
||||||
new LoadEventInfo(
|
new LoadEventInfo(
|
||||||
@ -715,31 +717,31 @@ public final class DefaultHlsPlaylistTracker
|
|||||||
lastSnapshotChangeMs = currentTimeMs;
|
lastSnapshotChangeMs = currentTimeMs;
|
||||||
onPlaylistUpdated(playlistUrl, playlistSnapshot);
|
onPlaylistUpdated(playlistUrl, playlistSnapshot);
|
||||||
} else if (!playlistSnapshot.hasEndTag) {
|
} else if (!playlistSnapshot.hasEndTag) {
|
||||||
|
boolean forceRetry = false;
|
||||||
|
@Nullable IOException playlistError = null;
|
||||||
if (loadedPlaylist.mediaSequence + loadedPlaylist.segments.size()
|
if (loadedPlaylist.mediaSequence + loadedPlaylist.segments.size()
|
||||||
< playlistSnapshot.mediaSequence) {
|
< playlistSnapshot.mediaSequence) {
|
||||||
// TODO: Allow customization of playlist resets handling.
|
// TODO: Allow customization of playlist resets handling.
|
||||||
// The media sequence jumped backwards. The server has probably reset. We do not try
|
// The media sequence jumped backwards. The server has probably reset. We do not try
|
||||||
// excluding in this case.
|
// excluding in this case.
|
||||||
|
forceRetry = true;
|
||||||
playlistError = new PlaylistResetException(playlistUrl);
|
playlistError = new PlaylistResetException(playlistUrl);
|
||||||
notifyPlaylistError(playlistUrl, C.TIME_UNSET);
|
|
||||||
} else if (currentTimeMs - lastSnapshotChangeMs
|
} else if (currentTimeMs - lastSnapshotChangeMs
|
||||||
> C.usToMs(playlistSnapshot.targetDurationUs)
|
> C.usToMs(playlistSnapshot.targetDurationUs)
|
||||||
* playlistStuckTargetDurationCoefficient) {
|
* playlistStuckTargetDurationCoefficient) {
|
||||||
// TODO: Allow customization of stuck playlists handling.
|
// TODO: Allow customization of stuck playlists handling.
|
||||||
playlistError = new PlaylistStuckException(playlistUrl);
|
playlistError = new PlaylistStuckException(playlistUrl);
|
||||||
LoadErrorInfo loadErrorInfo =
|
}
|
||||||
|
if (playlistError != null) {
|
||||||
|
this.playlistError = playlistError;
|
||||||
|
notifyPlaylistError(
|
||||||
|
playlistUrl,
|
||||||
new LoadErrorInfo(
|
new LoadErrorInfo(
|
||||||
loadEventInfo,
|
loadEventInfo,
|
||||||
new MediaLoadData(C.DATA_TYPE_MANIFEST),
|
new MediaLoadData(C.DATA_TYPE_MANIFEST),
|
||||||
playlistError,
|
playlistError,
|
||||||
/* errorCount= */ 1);
|
/* errorCount= */ 1),
|
||||||
long exclusionDurationMs =
|
forceRetry);
|
||||||
loadErrorHandlingPolicy.getExclusionDurationMsFor(
|
|
||||||
LoadErrorHandlingPolicy.FALLBACK_TYPE_TRACK, loadErrorInfo);
|
|
||||||
notifyPlaylistError(playlistUrl, exclusionDurationMs);
|
|
||||||
if (exclusionDurationMs != C.TIME_UNSET) {
|
|
||||||
excludePlaylist(exclusionDurationMs);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
long durationUntilNextLoadUs = 0L;
|
long durationUntilNextLoadUs = 0L;
|
||||||
|
@ -74,11 +74,12 @@ public interface HlsPlaylistTracker {
|
|||||||
* Called if an error is encountered while loading a playlist.
|
* Called if an error is encountered while loading a playlist.
|
||||||
*
|
*
|
||||||
* @param url The loaded url that caused the error.
|
* @param url The loaded url that caused the error.
|
||||||
* @param exclusionDurationMs The duration for which the playlist should be excluded. Or {@link
|
* @param loadErrorInfo The load error info.
|
||||||
* C#TIME_UNSET} if the playlist should not be excluded.
|
* @param forceRetry Whether retry should be forced without considering exclusion.
|
||||||
* @return True if excluding did not encounter errors. False otherwise.
|
* @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. */
|
/** 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;
|
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.
|
* Requests a playlist refresh and removes it from the exclusion list.
|
||||||
*
|
*
|
||||||
|
@ -15,6 +15,8 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.source.smoothstreaming;
|
package com.google.android.exoplayer2.source.smoothstreaming;
|
||||||
|
|
||||||
|
import static com.google.android.exoplayer2.trackselection.TrackSelectionUtil.createFallbackOptions;
|
||||||
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.C;
|
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.DataSource;
|
||||||
import com.google.android.exoplayer2.upstream.DataSpec;
|
import com.google.android.exoplayer2.upstream.DataSpec;
|
||||||
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
|
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.LoaderErrorThrower;
|
||||||
import com.google.android.exoplayer2.upstream.TransferListener;
|
import com.google.android.exoplayer2.upstream.TransferListener;
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
@ -287,12 +290,14 @@ public class DefaultSsChunkSource implements SsChunkSource {
|
|||||||
boolean cancelable,
|
boolean cancelable,
|
||||||
LoadErrorHandlingPolicy.LoadErrorInfo loadErrorInfo,
|
LoadErrorHandlingPolicy.LoadErrorInfo loadErrorInfo,
|
||||||
LoadErrorHandlingPolicy loadErrorHandlingPolicy) {
|
LoadErrorHandlingPolicy loadErrorHandlingPolicy) {
|
||||||
long exclusionDurationMs =
|
FallbackSelection fallbackSelection =
|
||||||
loadErrorHandlingPolicy.getExclusionDurationMsFor(
|
loadErrorHandlingPolicy.getFallbackSelectionFor(
|
||||||
LoadErrorHandlingPolicy.FALLBACK_TYPE_TRACK, loadErrorInfo);
|
createFallbackOptions(trackSelection), loadErrorInfo);
|
||||||
return cancelable
|
return cancelable
|
||||||
&& exclusionDurationMs != C.TIME_UNSET
|
&& fallbackSelection.type == LoadErrorHandlingPolicy.FALLBACK_TYPE_TRACK
|
||||||
&& trackSelection.blacklist(trackSelection.indexOf(chunk.trackFormat), exclusionDurationMs);
|
&& fallbackSelection.exclusionDurationMs != C.TIME_UNSET
|
||||||
|
&& trackSelection.blacklist(
|
||||||
|
trackSelection.indexOf(chunk.trackFormat), fallbackSelection.exclusionDurationMs);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -177,7 +177,8 @@ public class FakeAdaptiveMediaPeriod
|
|||||||
positionUs,
|
positionUs,
|
||||||
DrmSessionManager.DRM_UNSUPPORTED,
|
DrmSessionManager.DRM_UNSUPPORTED,
|
||||||
new DrmSessionEventListener.EventDispatcher(),
|
new DrmSessionEventListener.EventDispatcher(),
|
||||||
new DefaultLoadErrorHandlingPolicy(/* minimumLoadableRetryCount= */ 3),
|
new DefaultLoadErrorHandlingPolicy(
|
||||||
|
/* minimumLoadableRetryCount= */ 3, /* locationExclusionEnabled= */ true),
|
||||||
mediaSourceEventDispatcher);
|
mediaSourceEventDispatcher);
|
||||||
streams[i] = sampleStream;
|
streams[i] = sampleStream;
|
||||||
sampleStreams.add(sampleStream);
|
sampleStreams.add(sampleStream);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user