From 1cb4fb290f39bdf29c6cc2a6da25e2e9b1a76227 Mon Sep 17 00:00:00 2001 From: bachinger Date: Wed, 23 Jun 2021 21:26:32 +0100 Subject: [PATCH] Change signature of ChunkSource.onChunkLoadError A no-op change that changes the signature of the onChunkLoadError method of the ChunkSource. Implementors can get the exclusion duration directly from the LoadErrorHndlingPolicy instead of receiving it as an argument of the callback. PiperOrigin-RevId: 381102935 --- .../source/chunk/ChunkSampleStream.java | 8 +- .../exoplayer2/source/chunk/ChunkSource.java | 14 +- .../source/dash/DefaultDashChunkSource.java | 13 +- .../dash/DefaultDashChunkSourceTest.java | 136 ++++++++++++++++++ .../smoothstreaming/DefaultSsChunkSource.java | 9 +- .../exoplayer2/testutil/FakeChunkSource.java | 6 +- 6 files changed, 170 insertions(+), 16 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java index 8089175287..b6cd400f22 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java @@ -516,13 +516,9 @@ public class ChunkSampleStream LoadErrorInfo loadErrorInfo = new LoadErrorInfo(loadEventInfo, mediaLoadData, error, errorCount); - long exclusionDurationMs = - cancelable - ? loadErrorHandlingPolicy.getExclusionDurationMsFor( - LoadErrorHandlingPolicy.FALLBACK_TYPE_TRACK, loadErrorInfo) - : C.TIME_UNSET; @Nullable LoadErrorAction loadErrorAction = null; - if (chunkSource.onChunkLoadError(loadable, cancelable, error, exclusionDurationMs)) { + if (chunkSource.onChunkLoadError( + loadable, cancelable, loadErrorInfo, loadErrorHandlingPolicy)) { if (cancelable) { loadErrorAction = Loader.DONT_RETRY; if (isMediaChunk) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSource.java index 81ce2d63de..b2a46a0cd4 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSource.java @@ -15,8 +15,8 @@ */ package com.google.android.exoplayer2.source.chunk; -import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.SeekParameters; +import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy; import java.io.IOException; import java.util.List; @@ -104,15 +104,19 @@ public interface ChunkSource { * * @param chunk The chunk whose load encountered the error. * @param cancelable Whether the load can be canceled. - * @param e The error. - * @param exclusionDurationMs The duration for which the associated track may be excluded, or - * {@link C#TIME_UNSET} if the track may not be excluded. + * @param loadErrorInfo The load error info. + * @param loadErrorHandlingPolicy The load error handling policy to customize the behaviour of + * handling the load error. * @return Whether the load should be canceled so that a replacement chunk can be loaded instead. * Must be {@code false} if {@code cancelable} is {@code false}. If {@code true}, {@link * #getNextChunk(long, long, List, ChunkHolder)} will be called to obtain the replacement * chunk. */ - boolean onChunkLoadError(Chunk chunk, boolean cancelable, Exception e, long exclusionDurationMs); + boolean onChunkLoadError( + Chunk chunk, + boolean cancelable, + LoadErrorHandlingPolicy.LoadErrorInfo loadErrorInfo, + LoadErrorHandlingPolicy loadErrorHandlingPolicy); /** Releases any held resources. */ void release(); 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 19dc560e7d..8b4268940b 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 @@ -46,6 +46,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.HttpDataSource.InvalidResponseCodeException; +import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy; import com.google.android.exoplayer2.upstream.LoaderErrorThrower; import com.google.android.exoplayer2.upstream.TransferListener; import com.google.android.exoplayer2.util.Util; @@ -455,7 +456,10 @@ public class DefaultDashChunkSource implements DashChunkSource { @Override public boolean onChunkLoadError( - Chunk chunk, boolean cancelable, Exception e, long exclusionDurationMs) { + Chunk chunk, + boolean cancelable, + LoadErrorHandlingPolicy.LoadErrorInfo loadErrorInfo, + LoadErrorHandlingPolicy loadErrorHandlingPolicy) { if (!cancelable) { return false; } @@ -465,8 +469,8 @@ public class DefaultDashChunkSource implements DashChunkSource { // Workaround for missing segment at the end of the period if (!manifest.dynamic && chunk instanceof MediaChunk - && e instanceof InvalidResponseCodeException - && ((InvalidResponseCodeException) e).responseCode == 404) { + && loadErrorInfo.exception instanceof InvalidResponseCodeException + && ((InvalidResponseCodeException) loadErrorInfo.exception).responseCode == 404) { RepresentationHolder representationHolder = representationHolders[trackSelection.indexOf(chunk.trackFormat)]; long segmentCount = representationHolder.getSegmentCount(); @@ -478,6 +482,9 @@ 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); } 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 fa5ee71e84..d9d7ea2630 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 @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.source.dash; +import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static com.google.common.truth.Truth.assertThat; import android.net.Uri; @@ -23,6 +24,8 @@ import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.source.LoadEventInfo; +import com.google.android.exoplayer2.source.MediaLoadData; import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.chunk.BundledChunkExtractor; import com.google.android.exoplayer2.source.chunk.ChunkHolder; @@ -30,10 +33,18 @@ import com.google.android.exoplayer2.source.dash.manifest.DashManifest; import com.google.android.exoplayer2.source.dash.manifest.DashManifestParser; import com.google.android.exoplayer2.testutil.FakeDataSource; import com.google.android.exoplayer2.testutil.TestUtil; +import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection; import com.google.android.exoplayer2.trackselection.FixedTrackSelection; import com.google.android.exoplayer2.upstream.DataSpec; +import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter; +import com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy; +import com.google.android.exoplayer2.upstream.HttpDataSource; +import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy; import com.google.android.exoplayer2.upstream.LoaderErrorThrower; +import com.google.android.exoplayer2.util.Assertions; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import java.io.IOException; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.annotation.internal.DoNotInstrument; @@ -132,4 +143,129 @@ public class DefaultDashChunkSourceTest { assertThat(output.chunk.dataSpec.flags & DataSpec.FLAG_MIGHT_NOT_USE_FULL_NETWORK_SPEED) .isEqualTo(0); } + + @Test + public void onChunkLoadError_trackExclusionEnabled_requestReplacementChunkAsLongAsAvailable() + throws Exception { + 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); + } + }; + int numberOfTracks = 2; + DashChunkSource chunkSource = createDashChunkSource(numberOfTracks); + ChunkHolder output = new ChunkHolder(); + for (int i = 0; i < numberOfTracks; i++) { + chunkSource.getNextChunk( + /* playbackPositionUs= */ 0, + /* loadPositionUs= */ 0, + /* queue= */ ImmutableList.of(), + output); + + boolean alternativeTrackAvailable = + chunkSource.onChunkLoadError( + checkNotNull(output.chunk), + /* cancelable= */ true, + createFakeLoadErrorInfo( + output.chunk.dataSpec, /* httpResponseCode= */ 404, /* errorCount= */ 1), + loadErrorHandlingPolicy); + + // Expect true except for the last track remaining. + assertThat(alternativeTrackAvailable).isEqualTo(i != numberOfTracks - 1); + } + } + + @Test + public void onChunkLoadError_trackExclusionDisabled_neverRequestReplacementChunk() + throws Exception { + DefaultLoadErrorHandlingPolicy loadErrorHandlingPolicy = + new DefaultLoadErrorHandlingPolicy() { + @Override + public long getExclusionDurationMsFor(int fallbackType, LoadErrorInfo loadErrorInfo) { + // Never exclude, neither tracks nor locations. + return C.TIME_UNSET; + } + }; + DashChunkSource chunkSource = createDashChunkSource(/* numberOfTracks= */ 2); + ChunkHolder output = new ChunkHolder(); + chunkSource.getNextChunk( + /* playbackPositionUs= */ 0, + /* loadPositionUs= */ 0, + /* queue= */ ImmutableList.of(), + output); + + boolean alternativeTrackAvailable = + chunkSource.onChunkLoadError( + checkNotNull(output.chunk), + /* cancelable= */ true, + createFakeLoadErrorInfo( + output.chunk.dataSpec, /* httpResponseCode= */ 404, /* errorCount= */ 1), + loadErrorHandlingPolicy); + + assertThat(alternativeTrackAvailable).isFalse(); + } + + private DashChunkSource createDashChunkSource(int numberOfTracks) throws IOException { + Assertions.checkArgument(numberOfTracks < 6); + DashManifest manifest = + new DashManifestParser() + .parse( + Uri.parse("https://example.com/test.mpd"), + TestUtil.getInputStream( + ApplicationProvider.getApplicationContext(), SAMPLE_MPD_VOD)); + int[] adaptationSetIndices = new int[] {0}; + int[] selectedTracks = new int[numberOfTracks]; + Format[] formats = new Format[numberOfTracks]; + for (int i = 0; i < numberOfTracks; i++) { + selectedTracks[i] = i; + formats[i] = + manifest + .getPeriod(0) + .adaptationSets + .get(adaptationSetIndices[0]) + .representations + .get(i) + .format; + } + AdaptiveTrackSelection adaptiveTrackSelection = + new AdaptiveTrackSelection( + new TrackGroup(formats), + selectedTracks, + new DefaultBandwidthMeter.Builder(ApplicationProvider.getApplicationContext()).build()); + return new DefaultDashChunkSource( + BundledChunkExtractor.FACTORY, + new LoaderErrorThrower.Dummy(), + manifest, + /* periodIndex= */ 0, + /* adaptationSetIndices= */ adaptationSetIndices, + adaptiveTrackSelection, + C.TRACK_TYPE_VIDEO, + new FakeDataSource(), + /* elapsedRealtimeOffsetMs= */ 0, + /* maxSegmentsPerLoad= */ 1, + /* enableEventMessageTrack= */ false, + /* closedCaptionFormats */ ImmutableList.of(), + /* playerTrackEmsgHandler= */ null); + } + + private LoadErrorHandlingPolicy.LoadErrorInfo createFakeLoadErrorInfo( + DataSpec dataSpec, int httpResponseCode, int errorCount) { + LoadEventInfo loadEventInfo = + new LoadEventInfo(/* loadTaskId= */ 0, dataSpec, SystemClock.elapsedRealtime()); + MediaLoadData mediaLoadData = new MediaLoadData(C.DATA_TYPE_MEDIA); + HttpDataSource.InvalidResponseCodeException invalidResponseCodeException = + new HttpDataSource.InvalidResponseCodeException( + httpResponseCode, + /* responseMessage= */ null, + ImmutableMap.of(), + dataSpec, + new byte[0]); + return new LoadErrorHandlingPolicy.LoadErrorInfo( + loadEventInfo, mediaLoadData, invalidResponseCodeException, errorCount); + } } 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 c5857c5f0f..1183ef668c 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 @@ -37,6 +37,7 @@ import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest. 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.LoaderErrorThrower; import com.google.android.exoplayer2.upstream.TransferListener; import com.google.android.exoplayer2.util.Assertions; @@ -282,7 +283,13 @@ public class DefaultSsChunkSource implements SsChunkSource { @Override public boolean onChunkLoadError( - Chunk chunk, boolean cancelable, Exception e, long exclusionDurationMs) { + Chunk chunk, + boolean cancelable, + LoadErrorHandlingPolicy.LoadErrorInfo loadErrorInfo, + LoadErrorHandlingPolicy loadErrorHandlingPolicy) { + long exclusionDurationMs = + loadErrorHandlingPolicy.getExclusionDurationMsFor( + LoadErrorHandlingPolicy.FALLBACK_TYPE_TRACK, loadErrorInfo); return cancelable && exclusionDurationMs != C.TIME_UNSET && trackSelection.blacklist(trackSelection.indexOf(chunk.trackFormat), exclusionDurationMs); diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeChunkSource.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeChunkSource.java index f6300bc45f..67c026f62f 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeChunkSource.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeChunkSource.java @@ -30,6 +30,7 @@ import com.google.android.exoplayer2.testutil.FakeDataSet.FakeData.Segment; 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.TransferListener; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.MimeTypes; @@ -156,7 +157,10 @@ public class FakeChunkSource implements ChunkSource { @Override public boolean onChunkLoadError( - Chunk chunk, boolean cancelable, Exception e, long exclusionDurationMs) { + Chunk chunk, + boolean cancelable, + LoadErrorHandlingPolicy.LoadErrorInfo loadErrorInfo, + LoadErrorHandlingPolicy loadErrorHandlingPolicy) { return false; }