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
* {@code endPositionMs} is larger than the duration of the media, then the download will
* cover to the end of the media.
* @throws IllegalStateException If the media item is of type DASH, HLS or SmoothStreaming.
*/
public DownloadRequest getDownloadRequest(
@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
* 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.
* @throws IllegalStateException If the media item is of type DASH, HLS or SmoothStreaming.
*/
public DownloadRequest getDownloadRequest(
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);
assertPreparedWithProgressiveSource();
assertPreparedWithMedia();
populateDownloadRequestBuilderWithDownloadRange(builder, startPositionMs, durationMs);
return builder.build();
}
@ -906,13 +901,22 @@ public final class DownloadHelper {
private void populateDownloadRequestBuilderWithDownloadRange(
DownloadRequest.Builder requestBuilder, long startPositionMs, long durationMs) {
assertPreparedWithProgressiveSource();
Timeline timeline = mediaPreparer.timeline;
if (mediaPreparer.mediaPeriods.length > 1) {
Log.w(TAG, "Partial download is only supported for single period.");
return;
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();
Timeline timeline = mediaPreparer.timeline;
Timeline.Window window = new Timeline.Window();
Timeline.Period period = new Timeline.Period();
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({
"trackGroupArrays",
"trackSelectionsByPeriodAndRenderer",

View File

@ -18,7 +18,6 @@ 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.junit.Assert.assertThrows;
import static org.robolectric.shadows.ShadowLooper.shadowMainLooper;
import android.content.Context;
@ -72,10 +71,15 @@ import org.junit.runner.RunWith;
public class DownloadHelperTest {
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 =
new FakeTimeline(
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 trackGroupVideoLowAndHigh;
@ -86,6 +90,7 @@ public class DownloadHelperTest {
private static TrackGroupArray[] trackGroupArrays;
private static MediaItem testMediaItem;
private RenderersFactory renderersFactory;
private DownloadHelper downloadHelper;
@BeforeClass
@ -124,7 +129,7 @@ public class DownloadHelperTest {
FakeRenderer videoRenderer = new FakeRenderer(C.TRACK_TYPE_VIDEO);
FakeRenderer audioRenderer = new FakeRenderer(C.TRACK_TYPE_AUDIO);
FakeRenderer textRenderer = new FakeRenderer(C.TRACK_TYPE_TEXT);
RenderersFactory renderersFactory =
renderersFactory =
(handler, videoListener, audioListener, metadata, text) ->
new Renderer[] {textRenderer, audioRenderer, videoRenderer};
@ -329,8 +334,8 @@ 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.
TrackSelectionParameters parameters =
new TrackSelectionParameters.Builder(getApplicationContext())
DefaultTrackSelector.Parameters parameters =
new DefaultTrackSelector.Parameters.Builder()
.setPreferredAudioLanguage("de")
.setPreferredTextLanguage("en")
.build();
@ -433,8 +438,8 @@ 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.
TrackSelectionParameters parameters =
new TrackSelectionParameters.Builder(getApplicationContext())
DefaultTrackSelector.Parameters parameters =
new DefaultTrackSelector.Parameters.Builder()
.setPreferredAudioLanguage("de")
.setPreferredTextLanguage("en")
.build();
@ -464,8 +469,8 @@ public class DownloadHelperTest {
throws Exception {
prepareDownloadHelper(downloadHelper);
TrackSelectionParameters parameters =
new TrackSelectionParameters.Builder(getApplicationContext())
DefaultTrackSelector.Parameters parameters =
new DefaultTrackSelector.Parameters.Builder()
.addOverride(new TrackSelectionOverride(trackGroupAudioUs, /* trackIndex= */ 0))
.addOverride(new TrackSelectionOverride(trackGroupAudioZh, /* trackIndex= */ 0))
.setTrackTypeDisabled(C.TRACK_TYPE_VIDEO, /* disabled= */ true)
@ -485,7 +490,7 @@ public class DownloadHelperTest {
@Test
public void
getDownloadRequest_createsDownloadRequestWithConcreteTimeRange_requestContainsConcreteByteRange()
getDownloadRequestForProgressive_withConcreteTimeRange_requestContainsConcreteByteRange()
throws Exception {
DownloadHelper downloadHelper =
new DownloadHelper.Factory()
@ -504,7 +509,7 @@ public class DownloadHelperTest {
@Test
public void
getDownloadRequest_createsDownloadRequestWithUnsetStartPosition_requestContainsConcreteByteRange()
getDownloadRequestForProgressive_withUnsetStartPosition_requestContainsConcreteByteRange()
throws Exception {
DownloadHelper downloadHelper =
new DownloadHelper.Factory()
@ -522,7 +527,8 @@ public class DownloadHelperTest {
}
@Test
public void getDownloadRequest_createsDownloadRequestWithUnsetLength_requestContainsUnsetLength()
public void
getDownloadRequestForProgressive_withUnsetDuration_requestContainsUnsetByteRangeLength()
throws Exception {
DownloadHelper downloadHelper =
new DownloadHelper.Factory()
@ -541,7 +547,7 @@ public class DownloadHelperTest {
@Test
public void
getDownloadRequest_createsDownloadRequestForTooShortStreamWithTimeRange_requestContainsUnsetLength()
getDownloadRequestForShortProgressive_withConcreteTimeRange_requestContainsUnsetByteRangeLength()
throws Exception {
DownloadHelper downloadHelper =
new DownloadHelper.Factory()
@ -559,8 +565,7 @@ public class DownloadHelperTest {
}
@Test
public void
getDownloadRequest_createsDownloadRequestWithoutTimeRange_requestContainsNullByteRange()
public void getDownloadRequestForProgressive_withoutRange_requestContainsNullByteRange()
throws Exception {
DownloadHelper downloadHelper =
new DownloadHelper.Factory()
@ -575,17 +580,155 @@ public class DownloadHelperTest {
@Test
public void
getDownloadRequest_createDownloadRequestWithTimeRangeForNonProgressiveStream_throwsIllegalStateException()
getDownloadRequestForNonProgressive_withConcreteTimeRange_requestContainsCorrectTimeRange()
throws Exception {
// We use this.downloadHelper as it was created with a TestMediaSource, thus the DownloadHelper
// will treat it as non-progressive.
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);
assertThrows(
IllegalStateException.class,
() ->
DownloadRequest downloadRequest =
downloadHelper.getDownloadRequest(
/* data= */ null, /* startPositionMs= */ 0, /* durationMs= */ 10000));
/* 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