From 0206622370221dc6a6e5feb48d10ba18c319941e Mon Sep 17 00:00:00 2001 From: olly Date: Fri, 4 Mar 2022 17:06:26 +0000 Subject: [PATCH] DownloadHelper: Support multiple track selection overrides addTrackSelectionForSingleRenderer takes a list of legacy overrides, which are then set on the supplied parameters one at a time to run track selection. This allows multiple overrides for a single track type to be applied in the download use case, despite it not being possible to place such overrides directly into a single parameters. For new style overrides, multiple overrides for the same track type can be placed directly into a single parameters. Therefore we'll be able to replace use of addTrackSelectionForSingleRenderer with use of addTrackSelection, which is a much cleaner API. For this to work, we need to make DownloadHelper apply multiple overrides in this case. PiperOrigin-RevId: 432459834 --- .../exoplayer/offline/DownloadHelper.java | 39 ++++-- .../exoplayer/offline/DownloadHelperTest.java | 128 ++++++++++-------- 2 files changed, 105 insertions(+), 62 deletions(-) diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/offline/DownloadHelper.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/offline/DownloadHelper.java index c9902189f5..4aabd38c40 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/offline/DownloadHelper.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/offline/DownloadHelper.java @@ -32,6 +32,7 @@ import androidx.media3.common.StreamKey; import androidx.media3.common.Timeline; import androidx.media3.common.TrackGroup; import androidx.media3.common.TrackGroupArray; +import androidx.media3.common.TrackSelectionOverride; import androidx.media3.common.TrackSelectionParameters; import androidx.media3.common.util.Assertions; import androidx.media3.common.util.UnstableApi; @@ -605,8 +606,9 @@ public final class DownloadHelper { */ public void replaceTrackSelections( int periodIndex, TrackSelectionParameters trackSelectionParameters) { + assertPreparedWithMedia(); clearTrackSelections(periodIndex); - addTrackSelection(periodIndex, trackSelectionParameters); + addTrackSelectionInternal(periodIndex, trackSelectionParameters); } /** @@ -620,8 +622,7 @@ public final class DownloadHelper { public void addTrackSelection( int periodIndex, TrackSelectionParameters trackSelectionParameters) { assertPreparedWithMedia(); - trackSelector.setParameters(trackSelectionParameters); - runTrackSelection(periodIndex); + addTrackSelectionInternal(periodIndex, trackSelectionParameters); } /** @@ -652,7 +653,7 @@ public final class DownloadHelper { TrackSelectionParameters parameters = parametersBuilder.setPreferredAudioLanguage(language).build(); for (int periodIndex = 0; periodIndex < periodCount; periodIndex++) { - addTrackSelection(periodIndex, parameters); + addTrackSelectionInternal(periodIndex, parameters); } } } @@ -689,7 +690,7 @@ public final class DownloadHelper { TrackSelectionParameters parameters = parametersBuilder.setPreferredTextLanguage(language).build(); for (int periodIndex = 0; periodIndex < periodCount; periodIndex++) { - addTrackSelection(periodIndex, parameters); + addTrackSelectionInternal(periodIndex, parameters); } } } @@ -716,12 +717,12 @@ public final class DownloadHelper { builder.setRendererDisabled(/* rendererIndex= */ i, /* disabled= */ i != rendererIndex); } if (overrides.isEmpty()) { - addTrackSelection(periodIndex, builder.build()); + addTrackSelectionInternal(periodIndex, builder.build()); } else { TrackGroupArray trackGroupArray = mappedTrackInfos[periodIndex].getTrackGroups(rendererIndex); for (int i = 0; i < overrides.size(); i++) { builder.setSelectionOverride(rendererIndex, trackGroupArray, overrides.get(i)); - addTrackSelection(periodIndex, builder.build()); + addTrackSelectionInternal(periodIndex, builder.build()); } } } @@ -773,8 +774,28 @@ public final class DownloadHelper { return requestBuilder.setStreamKeys(streamKeys).build(); } - // Initialization of array of Lists. - @SuppressWarnings("unchecked") + @RequiresNonNull({ + "trackGroupArrays", + "trackSelectionsByPeriodAndRenderer", + "mediaPreparer", + "mediaPreparer.timeline" + }) + private void addTrackSelectionInternal( + int periodIndex, TrackSelectionParameters trackSelectionParameters) { + trackSelector.setParameters(trackSelectionParameters); + runTrackSelection(periodIndex); + // TrackSelectionParameters can contain multiple overrides for each track type. The track + // selector will only use one of them (because it's designed for playback), but for downloads we + // want to use all of them. Run selection again with each override being the only one of its + // type, to ensure that all of the desired tracks are included. + for (TrackSelectionOverride override : trackSelectionParameters.overrides.values()) { + trackSelector.setParameters( + trackSelectionParameters.buildUpon().setOverrideForType(override).build()); + runTrackSelection(periodIndex); + } + } + + @SuppressWarnings("unchecked") // Initialization of array of Lists. private void onMediaPrepared() { checkNotNull(mediaPreparer); checkNotNull(mediaPreparer.mediaPeriods); diff --git a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/offline/DownloadHelperTest.java b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/offline/DownloadHelperTest.java index 68b6769fa8..409777e1cb 100644 --- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/offline/DownloadHelperTest.java +++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/offline/DownloadHelperTest.java @@ -15,6 +15,7 @@ */ package androidx.media3.exoplayer.offline; +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; import static com.google.common.truth.Truth.assertThat; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.robolectric.shadows.ShadowLooper.shadowMainLooper; @@ -27,6 +28,8 @@ import androidx.media3.common.StreamKey; import androidx.media3.common.Timeline; import androidx.media3.common.TrackGroup; import androidx.media3.common.TrackGroupArray; +import androidx.media3.common.TrackSelectionOverride; +import androidx.media3.common.TrackSelectionParameters; import androidx.media3.exoplayer.Renderer; import androidx.media3.exoplayer.RenderersFactory; import androidx.media3.exoplayer.offline.DownloadHelper.Callback; @@ -41,7 +44,7 @@ import androidx.media3.test.utils.FakeMediaSource; import androidx.media3.test.utils.FakeRenderer; import androidx.media3.test.utils.FakeTimeline; import androidx.media3.test.utils.FakeTimeline.TimelineWindowDefinition; -import androidx.test.core.app.ApplicationProvider; +import androidx.media3.test.utils.TestUtil; import androidx.test.ext.junit.runners.AndroidJUnit4; import java.io.IOException; import java.util.ArrayList; @@ -64,12 +67,8 @@ public class DownloadHelperTest { new Object[] {TEST_MANIFEST}, new TimelineWindowDefinition(/* periodCount= */ 2, /* id= */ new Object())); - private static final Format VIDEO_FORMAT_LOW = createVideoFormat(/* bitrate= */ 200_000); - private static final Format VIDEO_FORMAT_HIGH = createVideoFormat(/* bitrate= */ 800_000); - - private static final TrackGroup TRACK_GROUP_VIDEO_BOTH = - new TrackGroup(VIDEO_FORMAT_LOW, VIDEO_FORMAT_HIGH); - private static final TrackGroup TRACK_GROUP_VIDEO_SINGLE = new TrackGroup(VIDEO_FORMAT_LOW); + private static TrackGroup trackGroupVideoLow; + private static TrackGroup trackGroupVideoLowAndHigh; private static TrackGroup trackGroupAudioUs; private static TrackGroup trackGroupAudioZh; private static TrackGroup trackGroupTextUs; @@ -81,25 +80,29 @@ public class DownloadHelperTest { @BeforeClass public static void staticSetUp() { - Format audioFormatUs = createAudioFormat(/* language= */ "US"); - Format audioFormatZh = createAudioFormat(/* language= */ "ZH"); - Format textFormatUs = createTextFormat(/* language= */ "US"); - Format textFormatZh = createTextFormat(/* language= */ "ZH"); + Format videoFormatLow = createVideoFormat(/* bitrate= */ 200_000); + Format videoFormatHigh = createVideoFormat(/* bitrate= */ 800_000); + Format audioFormatEn = createAudioFormat(/* language= */ "en"); + Format audioFormatDe = createAudioFormat(/* language= */ "de"); + Format textFormatEn = createTextFormat(/* language= */ "en"); + Format textFormatDe = createTextFormat(/* language= */ "de"); - trackGroupAudioUs = new TrackGroup(audioFormatUs); - trackGroupAudioZh = new TrackGroup(audioFormatZh); - trackGroupTextUs = new TrackGroup(textFormatUs); - trackGroupTextZh = new TrackGroup(textFormatZh); + trackGroupVideoLow = new TrackGroup(videoFormatLow); + trackGroupVideoLowAndHigh = new TrackGroup(videoFormatLow, videoFormatHigh); + trackGroupAudioUs = new TrackGroup(audioFormatEn); + trackGroupAudioZh = new TrackGroup(audioFormatDe); + trackGroupTextUs = new TrackGroup(textFormatEn); + trackGroupTextZh = new TrackGroup(textFormatDe); TrackGroupArray trackGroupArrayAll = new TrackGroupArray( - TRACK_GROUP_VIDEO_BOTH, + trackGroupVideoLowAndHigh, trackGroupAudioUs, trackGroupAudioZh, trackGroupTextUs, trackGroupTextZh); TrackGroupArray trackGroupArraySingle = - new TrackGroupArray(TRACK_GROUP_VIDEO_SINGLE, trackGroupAudioUs); + new TrackGroupArray(trackGroupVideoLow, trackGroupAudioUs); trackGroupArrays = new TrackGroupArray[] {trackGroupArrayAll, trackGroupArraySingle}; testMediaItem = @@ -127,18 +130,14 @@ public class DownloadHelperTest { public void getManifest_returnsManifest() throws Exception { prepareDownloadHelper(downloadHelper); - Object manifest = downloadHelper.getManifest(); - - assertThat(manifest).isEqualTo(TEST_MANIFEST); + assertThat(downloadHelper.getManifest()).isEqualTo(TEST_MANIFEST); } @Test public void getPeriodCount_returnsPeriodCount() throws Exception { prepareDownloadHelper(downloadHelper); - int periodCount = downloadHelper.getPeriodCount(); - - assertThat(periodCount).isEqualTo(2); + assertThat(downloadHelper.getPeriodCount()).isEqualTo(2); } @Test @@ -175,7 +174,7 @@ public class DownloadHelperTest { assertThat(mappedTracks0.getTrackGroups(/* rendererIndex= */ 1).get(/* index= */ 1)) .isEqualTo(trackGroupAudioZh); assertThat(mappedTracks0.getTrackGroups(/* rendererIndex= */ 2).get(/* index= */ 0)) - .isEqualTo(TRACK_GROUP_VIDEO_BOTH); + .isEqualTo(trackGroupVideoLowAndHigh); assertThat(mappedTracks1.getRendererCount()).isEqualTo(3); assertThat(mappedTracks1.getRendererType(/* rendererIndex= */ 0)).isEqualTo(C.TRACK_TYPE_TEXT); @@ -187,7 +186,7 @@ public class DownloadHelperTest { assertThat(mappedTracks1.getTrackGroups(/* rendererIndex= */ 1).get(/* index= */ 0)) .isEqualTo(trackGroupAudioUs); assertThat(mappedTracks1.getTrackGroups(/* rendererIndex= */ 2).get(/* index= */ 0)) - .isEqualTo(TRACK_GROUP_VIDEO_SINGLE); + .isEqualTo(trackGroupVideoLow); } @Test @@ -209,11 +208,11 @@ public class DownloadHelperTest { assertSingleTrackSelectionEquals(selectedText0, trackGroupTextUs, 0); assertSingleTrackSelectionEquals(selectedAudio0, trackGroupAudioUs, 0); - assertSingleTrackSelectionEquals(selectedVideo0, TRACK_GROUP_VIDEO_BOTH, 1); + assertSingleTrackSelectionEquals(selectedVideo0, trackGroupVideoLowAndHigh, 1); assertThat(selectedText1).isEmpty(); assertSingleTrackSelectionEquals(selectedAudio1, trackGroupAudioUs, 0); - assertSingleTrackSelectionEquals(selectedVideo1, TRACK_GROUP_VIDEO_SINGLE, 0); + assertSingleTrackSelectionEquals(selectedVideo1, trackGroupVideoLow, 0); } @Test @@ -242,7 +241,7 @@ public class DownloadHelperTest { // Verify assertThat(selectedText1).isEmpty(); assertSingleTrackSelectionEquals(selectedAudio1, trackGroupAudioUs, 0); - assertSingleTrackSelectionEquals(selectedVideo1, TRACK_GROUP_VIDEO_SINGLE, 0); + assertSingleTrackSelectionEquals(selectedVideo1, trackGroupVideoLow, 0); } @Test @@ -250,9 +249,9 @@ public class DownloadHelperTest { throws Exception { prepareDownloadHelper(downloadHelper); DefaultTrackSelector.Parameters parameters = - new DefaultTrackSelector.ParametersBuilder(ApplicationProvider.getApplicationContext()) - .setPreferredAudioLanguage("ZH") - .setPreferredTextLanguage("ZH") + new DefaultTrackSelector.ParametersBuilder(getApplicationContext()) + .setPreferredAudioLanguage("de") + .setPreferredTextLanguage("de") .setRendererDisabled(/* rendererIndex= */ 2, true) .build(); @@ -277,7 +276,7 @@ public class DownloadHelperTest { assertThat(selectedText1).isEmpty(); assertSingleTrackSelectionEquals(selectedAudio1, trackGroupAudioUs, 0); - assertSingleTrackSelectionEquals(selectedVideo1, TRACK_GROUP_VIDEO_SINGLE, 0); + assertSingleTrackSelectionEquals(selectedVideo1, trackGroupVideoLow, 0); } @Test @@ -286,10 +285,10 @@ public class DownloadHelperTest { prepareDownloadHelper(downloadHelper); // Select parameters to require some merging of track groups because the new parameters add // all video tracks to initial video single track selection. - DefaultTrackSelector.Parameters parameters = - new DefaultTrackSelector.ParametersBuilder(ApplicationProvider.getApplicationContext()) - .setPreferredAudioLanguage("ZH") - .setPreferredTextLanguage("US") + TrackSelectionParameters parameters = + new TrackSelectionParameters.Builder(getApplicationContext()) + .setPreferredAudioLanguage("de") + .setPreferredTextLanguage("en") .build(); // Add only to one period selection to verify second period selection is untouched. @@ -311,11 +310,11 @@ public class DownloadHelperTest { assertThat(selectedAudio0).hasSize(2); assertTrackSelectionEquals(selectedAudio0.get(0), trackGroupAudioUs, 0); assertTrackSelectionEquals(selectedAudio0.get(1), trackGroupAudioZh, 0); - assertSingleTrackSelectionEquals(selectedVideo0, TRACK_GROUP_VIDEO_BOTH, 0, 1); + assertSingleTrackSelectionEquals(selectedVideo0, trackGroupVideoLowAndHigh, 0, 1); assertThat(selectedText1).isEmpty(); assertSingleTrackSelectionEquals(selectedAudio1, trackGroupAudioUs, 0); - assertSingleTrackSelectionEquals(selectedVideo1, TRACK_GROUP_VIDEO_SINGLE, 0); + assertSingleTrackSelectionEquals(selectedVideo1, trackGroupVideoLow, 0); } @Test @@ -326,7 +325,7 @@ public class DownloadHelperTest { downloadHelper.clearTrackSelections(/* periodIndex= */ 1); // Add a non-default language, and a non-existing language (which will select the default). - downloadHelper.addAudioLanguagesToSelection("ZH", "Klingonese"); + downloadHelper.addAudioLanguagesToSelection("de", "Klingonese"); List selectedText0 = downloadHelper.getTrackSelections(/* periodIndex= */ 0, /* rendererIndex= */ 0); List selectedAudio0 = @@ -360,7 +359,7 @@ public class DownloadHelperTest { // Add a non-default language, and a non-existing language (which will select the default). downloadHelper.addTextLanguagesToSelection( - /* selectUndeterminedTextLanguage= */ true, "ZH", "Klingonese"); + /* selectUndeterminedTextLanguage= */ true, "de", "Klingonese"); List selectedText0 = downloadHelper.getTrackSelections(/* periodIndex= */ 0, /* rendererIndex= */ 0); List selectedAudio0 = @@ -390,14 +389,13 @@ public class DownloadHelperTest { prepareDownloadHelper(downloadHelper); // Ensure we have track groups with multiple indices, renderers with multiple track groups and // also renderers without any track groups. - DefaultTrackSelector.Parameters parameters = - new DefaultTrackSelector.ParametersBuilder(ApplicationProvider.getApplicationContext()) - .setPreferredAudioLanguage("ZH") - .setPreferredTextLanguage("US") + TrackSelectionParameters parameters = + new TrackSelectionParameters.Builder(getApplicationContext()) + .setPreferredAudioLanguage("de") + .setPreferredTextLanguage("en") .build(); downloadHelper.addTrackSelection(/* periodIndex= */ 0, parameters); - byte[] data = new byte[10]; - Arrays.fill(data, (byte) 123); + byte[] data = TestUtil.buildTestData(10); DownloadRequest downloadRequest = downloadHelper.getDownloadRequest(data); @@ -408,13 +406,37 @@ public class DownloadHelperTest { assertThat(downloadRequest.data).isEqualTo(data); assertThat(downloadRequest.streamKeys) .containsExactly( - new StreamKey(/* periodIndex= */ 0, /* groupIndex= */ 0, /* trackIndex= */ 0), - new StreamKey(/* periodIndex= */ 0, /* groupIndex= */ 0, /* trackIndex= */ 1), - new StreamKey(/* periodIndex= */ 0, /* groupIndex= */ 1, /* trackIndex= */ 0), - new StreamKey(/* periodIndex= */ 0, /* groupIndex= */ 2, /* trackIndex= */ 0), - new StreamKey(/* periodIndex= */ 0, /* groupIndex= */ 3, /* trackIndex= */ 0), - new StreamKey(/* periodIndex= */ 1, /* groupIndex= */ 0, /* trackIndex= */ 0), - new StreamKey(/* periodIndex= */ 1, /* groupIndex= */ 1, /* trackIndex= */ 0)); + new StreamKey(/* periodIndex= */ 0, /* groupIndex= */ 0, /* streamIndex= */ 0), + new StreamKey(/* periodIndex= */ 0, /* groupIndex= */ 0, /* streamIndex= */ 1), + new StreamKey(/* periodIndex= */ 0, /* groupIndex= */ 1, /* streamIndex= */ 0), + new StreamKey(/* periodIndex= */ 0, /* groupIndex= */ 2, /* streamIndex= */ 0), + new StreamKey(/* periodIndex= */ 0, /* groupIndex= */ 3, /* streamIndex= */ 0), + new StreamKey(/* periodIndex= */ 1, /* groupIndex= */ 0, /* streamIndex= */ 0), + new StreamKey(/* periodIndex= */ 1, /* groupIndex= */ 1, /* streamIndex= */ 0)); + } + + @Test + public void getDownloadRequest_createsDownloadRequest_withMultipleOverridesOfSameType() + throws Exception { + prepareDownloadHelper(downloadHelper); + + TrackSelectionParameters parameters = + new TrackSelectionParameters.Builder(getApplicationContext()) + .addOverride(new TrackSelectionOverride(trackGroupAudioUs, /* trackIndex= */ 0)) + .addOverride(new TrackSelectionOverride(trackGroupAudioZh, /* trackIndex= */ 0)) + .setTrackTypeDisabled(C.TRACK_TYPE_VIDEO, /* disabled= */ true) + .setTrackTypeDisabled(C.TRACK_TYPE_TEXT, /* disabled= */ true) + .build(); + + downloadHelper.replaceTrackSelections(/* periodIndex= */ 0, parameters); + downloadHelper.clearTrackSelections(/* periodIndex= */ 1); + + DownloadRequest downloadRequest = downloadHelper.getDownloadRequest(/* data= */ null); + + assertThat(downloadRequest.streamKeys) + .containsExactly( + new StreamKey(/* periodIndex= */ 0, /* groupIndex= */ 1, /* streamIndex= */ 0), + new StreamKey(/* periodIndex= */ 0, /* groupIndex= */ 2, /* streamIndex= */ 0)); } private static void prepareDownloadHelper(DownloadHelper downloadHelper) throws Exception {