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
This commit is contained in:
olly 2022-03-04 17:06:26 +00:00 committed by Ian Baker
parent 0316c03319
commit 0206622370
2 changed files with 105 additions and 62 deletions

View file

@ -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);

View file

@ -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<ExoTrackSelection> selectedText0 =
downloadHelper.getTrackSelections(/* periodIndex= */ 0, /* rendererIndex= */ 0);
List<ExoTrackSelection> 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<ExoTrackSelection> selectedText0 =
downloadHelper.getTrackSelections(/* periodIndex= */ 0, /* rendererIndex= */ 0);
List<ExoTrackSelection> 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 {