Enable DownloadHelper to create DownloadRequest with timeRange

PiperOrigin-RevId: 748722156
This commit is contained in:
tianyifeng 2025-04-17 10:11:55 -07:00 committed by Copybara-Service
parent c4c3e5e0c8
commit 7f6ddef502
2 changed files with 202 additions and 36 deletions

View File

@ -830,7 +830,6 @@ public final class DownloadHelper {
* to, or {@link C#TIME_UNSET} if the download should cover to the end of the media. If the * to, or {@link C#TIME_UNSET} if the download should cover to the end of the media. If the
* {@code endPositionMs} is larger than the duration of the media, then the download will * {@code endPositionMs} is larger than the duration of the media, then the download will
* cover to the end of the media. * cover to the end of the media.
* @throws IllegalStateException If the media item is of type DASH, HLS or SmoothStreaming.
*/ */
public DownloadRequest getDownloadRequest( public DownloadRequest getDownloadRequest(
@Nullable byte[] data, long startPositionMs, long durationMs) { @Nullable byte[] data, long startPositionMs, long durationMs) {
@ -863,15 +862,11 @@ public final class DownloadHelper {
* {@link C#TIME_UNSET} if the download should cover to the end of the media. If the end * {@link C#TIME_UNSET} if the download should cover to the end of the media. If the end
* position resolved from {@code startPositionMs} and {@code durationMs} is beyond the * position resolved from {@code startPositionMs} and {@code durationMs} is beyond the
* duration of the media, then the download will just cover to the end of the media. * duration of the media, then the download will just cover to the end of the media.
* @throws IllegalStateException If the media item is of type DASH, HLS or SmoothStreaming.
*/ */
public DownloadRequest getDownloadRequest( public DownloadRequest getDownloadRequest(
String id, @Nullable byte[] data, long startPositionMs, long durationMs) { String id, @Nullable byte[] data, long startPositionMs, long durationMs) {
checkState(
mode == MODE_PREPARE_PROGRESSIVE_SOURCE,
"Partial download is only supported for progressive streams");
DownloadRequest.Builder builder = getDownloadRequestBuilder(id, data); DownloadRequest.Builder builder = getDownloadRequestBuilder(id, data);
assertPreparedWithProgressiveSource(); assertPreparedWithMedia();
populateDownloadRequestBuilderWithDownloadRange(builder, startPositionMs, durationMs); populateDownloadRequestBuilderWithDownloadRange(builder, startPositionMs, durationMs);
return builder.build(); return builder.build();
} }
@ -906,13 +901,22 @@ public final class DownloadHelper {
private void populateDownloadRequestBuilderWithDownloadRange( private void populateDownloadRequestBuilderWithDownloadRange(
DownloadRequest.Builder requestBuilder, long startPositionMs, long durationMs) { DownloadRequest.Builder requestBuilder, long startPositionMs, long durationMs) {
switch (mode) {
case MODE_PREPARE_PROGRESSIVE_SOURCE:
populateDownloadRequestBuilderWithByteRange(requestBuilder, startPositionMs, durationMs);
break;
case MODE_PREPARE_NON_PROGRESSIVE_SOURCE_AND_SELECT_TRACKS:
populateDownloadRequestBuilderWithTimeRange(requestBuilder, startPositionMs, durationMs);
break;
default:
break;
}
}
private void populateDownloadRequestBuilderWithByteRange(
DownloadRequest.Builder requestBuilder, long startPositionMs, long durationMs) {
assertPreparedWithProgressiveSource(); assertPreparedWithProgressiveSource();
Timeline timeline = mediaPreparer.timeline; Timeline timeline = mediaPreparer.timeline;
if (mediaPreparer.mediaPeriods.length > 1) {
Log.w(TAG, "Partial download is only supported for single period.");
return;
}
Timeline.Window window = new Timeline.Window(); Timeline.Window window = new Timeline.Window();
Timeline.Period period = new Timeline.Period(); Timeline.Period period = new Timeline.Period();
long periodStartPositionUs = long periodStartPositionUs =
@ -957,6 +961,25 @@ public final class DownloadHelper {
} }
} }
private void populateDownloadRequestBuilderWithTimeRange(
DownloadRequest.Builder requestBuilder, long startPositionMs, long durationMs) {
assertPreparedWithNonProgressiveSourceAndTracksSelected();
Timeline timeline = mediaPreparer.timeline;
Timeline.Window window = timeline.getWindow(0, new Timeline.Window());
long startPositionUs =
startPositionMs == C.TIME_UNSET
? window.getDefaultPositionUs()
: Util.msToUs(startPositionMs);
long windowDurationUs = window.getDurationUs();
long durationUs = durationMs == C.TIME_UNSET ? windowDurationUs : Util.msToUs(durationMs);
if (windowDurationUs != C.TIME_UNSET) {
startPositionUs = min(startPositionUs, windowDurationUs);
durationUs = min(durationUs, windowDurationUs - startPositionUs);
}
requestBuilder.setTimeRange(startPositionUs, durationUs);
}
@RequiresNonNull({ @RequiresNonNull({
"trackGroupArrays", "trackGroupArrays",
"trackSelectionsByPeriodAndRenderer", "trackSelectionsByPeriodAndRenderer",

View File

@ -18,7 +18,6 @@ package androidx.media3.exoplayer.offline;
import static androidx.test.core.app.ApplicationProvider.getApplicationContext; import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.junit.Assert.assertThrows;
import static org.robolectric.shadows.ShadowLooper.shadowMainLooper; import static org.robolectric.shadows.ShadowLooper.shadowMainLooper;
import android.content.Context; import android.content.Context;
@ -72,10 +71,15 @@ import org.junit.runner.RunWith;
public class DownloadHelperTest { public class DownloadHelperTest {
private static final Object TEST_MANIFEST = new Object(); private static final Object TEST_MANIFEST = new Object();
private static final long TEST_WINDOW_DEFAULT_POSITION_US = C.MICROS_PER_SECOND;
private static final Timeline TEST_TIMELINE = private static final Timeline TEST_TIMELINE =
new FakeTimeline( new FakeTimeline(
new Object[] {TEST_MANIFEST}, new Object[] {TEST_MANIFEST},
new TimelineWindowDefinition(/* periodCount= */ 2, /* id= */ new Object())); new TimelineWindowDefinition.Builder()
.setPeriodCount(2)
.setDefaultPositionUs(TEST_WINDOW_DEFAULT_POSITION_US)
.build());
private static TrackGroup trackGroupVideoLow; private static TrackGroup trackGroupVideoLow;
private static TrackGroup trackGroupVideoLowAndHigh; private static TrackGroup trackGroupVideoLowAndHigh;
@ -86,6 +90,7 @@ public class DownloadHelperTest {
private static TrackGroupArray[] trackGroupArrays; private static TrackGroupArray[] trackGroupArrays;
private static MediaItem testMediaItem; private static MediaItem testMediaItem;
private RenderersFactory renderersFactory;
private DownloadHelper downloadHelper; private DownloadHelper downloadHelper;
@BeforeClass @BeforeClass
@ -124,7 +129,7 @@ public class DownloadHelperTest {
FakeRenderer videoRenderer = new FakeRenderer(C.TRACK_TYPE_VIDEO); FakeRenderer videoRenderer = new FakeRenderer(C.TRACK_TYPE_VIDEO);
FakeRenderer audioRenderer = new FakeRenderer(C.TRACK_TYPE_AUDIO); FakeRenderer audioRenderer = new FakeRenderer(C.TRACK_TYPE_AUDIO);
FakeRenderer textRenderer = new FakeRenderer(C.TRACK_TYPE_TEXT); FakeRenderer textRenderer = new FakeRenderer(C.TRACK_TYPE_TEXT);
RenderersFactory renderersFactory = renderersFactory =
(handler, videoListener, audioListener, metadata, text) -> (handler, videoListener, audioListener, metadata, text) ->
new Renderer[] {textRenderer, audioRenderer, videoRenderer}; new Renderer[] {textRenderer, audioRenderer, videoRenderer};
@ -329,8 +334,8 @@ public class DownloadHelperTest {
prepareDownloadHelper(downloadHelper); prepareDownloadHelper(downloadHelper);
// Select parameters to require some merging of track groups because the new parameters add // Select parameters to require some merging of track groups because the new parameters add
// all video tracks to initial video single track selection. // all video tracks to initial video single track selection.
TrackSelectionParameters parameters = DefaultTrackSelector.Parameters parameters =
new TrackSelectionParameters.Builder(getApplicationContext()) new DefaultTrackSelector.Parameters.Builder()
.setPreferredAudioLanguage("de") .setPreferredAudioLanguage("de")
.setPreferredTextLanguage("en") .setPreferredTextLanguage("en")
.build(); .build();
@ -433,8 +438,8 @@ public class DownloadHelperTest {
prepareDownloadHelper(downloadHelper); prepareDownloadHelper(downloadHelper);
// Ensure we have track groups with multiple indices, renderers with multiple track groups and // Ensure we have track groups with multiple indices, renderers with multiple track groups and
// also renderers without any track groups. // also renderers without any track groups.
TrackSelectionParameters parameters = DefaultTrackSelector.Parameters parameters =
new TrackSelectionParameters.Builder(getApplicationContext()) new DefaultTrackSelector.Parameters.Builder()
.setPreferredAudioLanguage("de") .setPreferredAudioLanguage("de")
.setPreferredTextLanguage("en") .setPreferredTextLanguage("en")
.build(); .build();
@ -464,8 +469,8 @@ public class DownloadHelperTest {
throws Exception { throws Exception {
prepareDownloadHelper(downloadHelper); prepareDownloadHelper(downloadHelper);
TrackSelectionParameters parameters = DefaultTrackSelector.Parameters parameters =
new TrackSelectionParameters.Builder(getApplicationContext()) new DefaultTrackSelector.Parameters.Builder()
.addOverride(new TrackSelectionOverride(trackGroupAudioUs, /* trackIndex= */ 0)) .addOverride(new TrackSelectionOverride(trackGroupAudioUs, /* trackIndex= */ 0))
.addOverride(new TrackSelectionOverride(trackGroupAudioZh, /* trackIndex= */ 0)) .addOverride(new TrackSelectionOverride(trackGroupAudioZh, /* trackIndex= */ 0))
.setTrackTypeDisabled(C.TRACK_TYPE_VIDEO, /* disabled= */ true) .setTrackTypeDisabled(C.TRACK_TYPE_VIDEO, /* disabled= */ true)
@ -485,7 +490,7 @@ public class DownloadHelperTest {
@Test @Test
public void public void
getDownloadRequest_createsDownloadRequestWithConcreteTimeRange_requestContainsConcreteByteRange() getDownloadRequestForProgressive_withConcreteTimeRange_requestContainsConcreteByteRange()
throws Exception { throws Exception {
DownloadHelper downloadHelper = DownloadHelper downloadHelper =
new DownloadHelper.Factory() new DownloadHelper.Factory()
@ -504,7 +509,7 @@ public class DownloadHelperTest {
@Test @Test
public void public void
getDownloadRequest_createsDownloadRequestWithUnsetStartPosition_requestContainsConcreteByteRange() getDownloadRequestForProgressive_withUnsetStartPosition_requestContainsConcreteByteRange()
throws Exception { throws Exception {
DownloadHelper downloadHelper = DownloadHelper downloadHelper =
new DownloadHelper.Factory() new DownloadHelper.Factory()
@ -522,8 +527,9 @@ public class DownloadHelperTest {
} }
@Test @Test
public void getDownloadRequest_createsDownloadRequestWithUnsetLength_requestContainsUnsetLength() public void
throws Exception { getDownloadRequestForProgressive_withUnsetDuration_requestContainsUnsetByteRangeLength()
throws Exception {
DownloadHelper downloadHelper = DownloadHelper downloadHelper =
new DownloadHelper.Factory() new DownloadHelper.Factory()
.setDataSourceFactory(new DefaultDataSource.Factory(getApplicationContext())) .setDataSourceFactory(new DefaultDataSource.Factory(getApplicationContext()))
@ -541,7 +547,7 @@ public class DownloadHelperTest {
@Test @Test
public void public void
getDownloadRequest_createsDownloadRequestForTooShortStreamWithTimeRange_requestContainsUnsetLength() getDownloadRequestForShortProgressive_withConcreteTimeRange_requestContainsUnsetByteRangeLength()
throws Exception { throws Exception {
DownloadHelper downloadHelper = DownloadHelper downloadHelper =
new DownloadHelper.Factory() new DownloadHelper.Factory()
@ -559,9 +565,8 @@ public class DownloadHelperTest {
} }
@Test @Test
public void public void getDownloadRequestForProgressive_withoutRange_requestContainsNullByteRange()
getDownloadRequest_createsDownloadRequestWithoutTimeRange_requestContainsNullByteRange() throws Exception {
throws Exception {
DownloadHelper downloadHelper = DownloadHelper downloadHelper =
new DownloadHelper.Factory() new DownloadHelper.Factory()
.setDataSourceFactory(new DefaultDataSource.Factory(getApplicationContext())) .setDataSourceFactory(new DefaultDataSource.Factory(getApplicationContext()))
@ -575,17 +580,155 @@ public class DownloadHelperTest {
@Test @Test
public void public void
getDownloadRequest_createDownloadRequestWithTimeRangeForNonProgressiveStream_throwsIllegalStateException() getDownloadRequestForNonProgressive_withConcreteTimeRange_requestContainsCorrectTimeRange()
throws Exception { throws Exception {
// We use this.downloadHelper as it was created with a TestMediaSource, thus the DownloadHelper DownloadHelper downloadHelper =
// will treat it as non-progressive. new DownloadHelper(
new MediaItem.Builder()
.setUri("http://test.uri")
.setMimeType(MimeTypes.APPLICATION_M3U8)
.build(),
new TestMediaSource(),
DownloadHelper.DEFAULT_TRACK_SELECTOR_PARAMETERS,
new DefaultRendererCapabilitiesList.Factory(renderersFactory)
.createRendererCapabilitiesList());
prepareDownloadHelper(downloadHelper); prepareDownloadHelper(downloadHelper);
assertThrows( DownloadRequest downloadRequest =
IllegalStateException.class, downloadHelper.getDownloadRequest(
() -> /* data= */ null, /* startPositionMs= */ 0, /* durationMs= */ 10000);
downloadHelper.getDownloadRequest(
/* data= */ null, /* startPositionMs= */ 0, /* durationMs= */ 10000)); assertThat(downloadRequest.timeRange).isNotNull();
assertThat(downloadRequest.timeRange.startPositionUs).isEqualTo(0);
assertThat(downloadRequest.timeRange.durationUs).isEqualTo(10000000);
}
@Test
public void
getDownloadRequestForNonProgressive_withUnsetStartPosition_requestContainsCorrectTimeRange()
throws Exception {
DownloadHelper downloadHelper =
new DownloadHelper(
new MediaItem.Builder()
.setUri("http://test.uri")
.setMimeType(MimeTypes.APPLICATION_M3U8)
.build(),
new TestMediaSource(),
DownloadHelper.DEFAULT_TRACK_SELECTOR_PARAMETERS,
new DefaultRendererCapabilitiesList.Factory(renderersFactory)
.createRendererCapabilitiesList());
prepareDownloadHelper(downloadHelper);
DownloadRequest downloadRequest =
downloadHelper.getDownloadRequest(
/* data= */ null, /* startPositionMs= */ C.TIME_UNSET, /* durationMs= */ 5000);
assertThat(downloadRequest.timeRange).isNotNull();
// The startPositionUs is set to window.defaultPositionUs.
Timeline.Window window = TEST_TIMELINE.getWindow(0, new Timeline.Window());
assertThat(downloadRequest.timeRange.startPositionUs).isEqualTo(window.defaultPositionUs);
assertThat(downloadRequest.timeRange.durationUs).isEqualTo(5000000);
}
@Test
public void
getDownloadRequestForNonProgressive_withStartPositionExceedingWindowDuration_requestContainsCorrectTimeRange()
throws Exception {
DownloadHelper downloadHelper =
new DownloadHelper(
new MediaItem.Builder()
.setUri("http://test.uri")
.setMimeType(MimeTypes.APPLICATION_M3U8)
.build(),
new TestMediaSource(),
DownloadHelper.DEFAULT_TRACK_SELECTOR_PARAMETERS,
new DefaultRendererCapabilitiesList.Factory(renderersFactory)
.createRendererCapabilitiesList());
prepareDownloadHelper(downloadHelper);
Timeline.Window window = TEST_TIMELINE.getWindow(0, new Timeline.Window());
DownloadRequest downloadRequest =
downloadHelper.getDownloadRequest(
/* data= */ null,
/* startPositionMs= */ window.durationUs + 100,
/* durationMs= */ C.TIME_UNSET);
assertThat(downloadRequest.timeRange).isNotNull();
// The startPositionUs is set to window.durationUs.
assertThat(downloadRequest.timeRange.startPositionUs).isEqualTo(window.durationUs);
assertThat(downloadRequest.timeRange.durationUs).isEqualTo(0);
}
@Test
public void
getDownloadRequestForNonProgressive_withUnsetDuration_requestContainsCorrectTimeRange()
throws Exception {
DownloadHelper downloadHelper =
new DownloadHelper(
new MediaItem.Builder()
.setUri("http://test.uri")
.setMimeType(MimeTypes.APPLICATION_M3U8)
.build(),
new TestMediaSource(),
DownloadHelper.DEFAULT_TRACK_SELECTOR_PARAMETERS,
new DefaultRendererCapabilitiesList.Factory(renderersFactory)
.createRendererCapabilitiesList());
prepareDownloadHelper(downloadHelper);
DownloadRequest downloadRequest =
downloadHelper.getDownloadRequest(
/* data= */ null, /* startPositionMs= */ 10, /* durationMs= */ C.TIME_UNSET);
assertThat(downloadRequest.timeRange).isNotNull();
assertThat(downloadRequest.timeRange.startPositionUs).isEqualTo(10_000);
Timeline.Window window = TEST_TIMELINE.getWindow(0, new Timeline.Window());
assertThat(downloadRequest.timeRange.durationUs).isEqualTo(window.durationUs - 10_000);
}
@Test
public void
getDownloadRequestForNonProgressive_withDurationExceedingWindowDuration_requestContainsCorrectTimeRange()
throws Exception {
DownloadHelper downloadHelper =
new DownloadHelper(
new MediaItem.Builder()
.setUri("http://test.uri")
.setMimeType(MimeTypes.APPLICATION_M3U8)
.build(),
new TestMediaSource(),
DownloadHelper.DEFAULT_TRACK_SELECTOR_PARAMETERS,
new DefaultRendererCapabilitiesList.Factory(renderersFactory)
.createRendererCapabilitiesList());
prepareDownloadHelper(downloadHelper);
Timeline.Window window = TEST_TIMELINE.getWindow(0, new Timeline.Window());
DownloadRequest downloadRequest =
downloadHelper.getDownloadRequest(
/* data= */ null, /* startPositionMs= */ 0, /* durationMs= */ window.durationUs + 100);
assertThat(downloadRequest.timeRange).isNotNull();
assertThat(downloadRequest.timeRange.startPositionUs).isEqualTo(0);
assertThat(downloadRequest.timeRange.durationUs).isEqualTo(window.durationUs);
}
@Test
public void getDownloadRequestForNonProgressive_withoutRange_requestContainsNullTimeRange()
throws Exception {
DownloadHelper downloadHelper =
new DownloadHelper(
new MediaItem.Builder()
.setUri("http://test.uri")
.setMimeType(MimeTypes.APPLICATION_M3U8)
.build(),
new TestMediaSource(),
DownloadHelper.DEFAULT_TRACK_SELECTOR_PARAMETERS,
new DefaultRendererCapabilitiesList.Factory(renderersFactory)
.createRendererCapabilitiesList());
prepareDownloadHelper(downloadHelper);
DownloadRequest downloadRequest = downloadHelper.getDownloadRequest(/* data= */ null);
assertThat(downloadRequest.timeRange).isNull();
} }
// https://github.com/androidx/media/issues/1224 // https://github.com/androidx/media/issues/1224