Allow setting time range for adaptive media in DownloadRequest

PiperOrigin-RevId: 731314103
This commit is contained in:
tianyifeng 2025-02-26 07:44:19 -08:00 committed by Copybara-Service
parent 275e7d3dbd
commit e4369b2317
2 changed files with 222 additions and 44 deletions

View File

@ -54,12 +54,14 @@ public final class DownloadRequest implements Parcelable {
@Nullable private String customCacheKey;
@Nullable private byte[] data;
@Nullable private ByteRange byteRange;
@Nullable private TimeRange timeRange;
/** Creates a new instance with the specified id and uri. */
public Builder(String id, Uri uri) {
this.id = id;
this.uri = uri;
this.byteRange = null;
this.timeRange = null;
}
/** Sets the {@link DownloadRequest#mimeType}. */
@ -112,6 +114,22 @@ public final class DownloadRequest implements Parcelable {
return this;
}
/**
* Sets the time range to be downloaded.
*
* <p>This will be ignored progressive downloads.
*
* @param startPositionUs The start position in microseconds that the download should start
* from.
* @param durationUs The duration in microseconds from the {@code startPositionUs} to be
* downloaded, or @link C#TIME_UNSET} if the media should be downloaded to the end.
*/
@CanIgnoreReturnValue
public Builder setTimeRange(long startPositionUs, long durationUs) {
this.timeRange = new TimeRange(startPositionUs, durationUs);
return this;
}
public DownloadRequest build() {
return new DownloadRequest(
id,
@ -121,7 +139,8 @@ public final class DownloadRequest implements Parcelable {
keySetId,
customCacheKey,
data,
byteRange);
byteRange,
timeRange);
}
}
@ -156,6 +175,9 @@ public final class DownloadRequest implements Parcelable {
/** The byte range to be downloaded. Must be null for DASH, HLS and SmoothStreaming downloads. */
@Nullable public final ByteRange byteRange;
/** The time range to be downloaded. Must be null progressive downloads. */
@Nullable public final TimeRange timeRange;
/**
* @param id See {@link #id}.
* @param uri See {@link #uri}.
@ -163,6 +185,8 @@ public final class DownloadRequest implements Parcelable {
* @param streamKeys See {@link #streamKeys}.
* @param customCacheKey See {@link #customCacheKey}.
* @param data See {@link #data}.
* @param byteRange See {@link #byteRange}.
* @param timeRange See {@link #timeRange}.
*/
private DownloadRequest(
String id,
@ -172,15 +196,18 @@ public final class DownloadRequest implements Parcelable {
@Nullable byte[] keySetId,
@Nullable String customCacheKey,
@Nullable byte[] data,
@Nullable ByteRange byteRange) {
@Nullable ByteRange byteRange,
@Nullable TimeRange timeRange) {
@C.ContentType int contentType = Util.inferContentTypeForUriAndMimeType(uri, mimeType);
if (contentType == C.CONTENT_TYPE_DASH
|| contentType == C.CONTENT_TYPE_HLS
|| contentType == C.CONTENT_TYPE_SS) {
checkArgument(customCacheKey == null, "customCacheKey must be null for type: " + contentType);
this.byteRange = null;
this.timeRange = timeRange;
} else {
this.byteRange = byteRange;
this.timeRange = null;
}
this.id = id;
this.uri = uri;
@ -207,6 +234,7 @@ public final class DownloadRequest implements Parcelable {
customCacheKey = in.readString();
data = castNonNull(in.createByteArray());
byteRange = in.readParcelable(ByteRange.class.getClassLoader());
timeRange = in.readParcelable(TimeRange.class.getClassLoader());
}
/**
@ -217,7 +245,7 @@ public final class DownloadRequest implements Parcelable {
*/
public DownloadRequest copyWithId(String id) {
return new DownloadRequest(
id, uri, mimeType, streamKeys, keySetId, customCacheKey, data, byteRange);
id, uri, mimeType, streamKeys, keySetId, customCacheKey, data, byteRange, timeRange);
}
/**
@ -228,7 +256,7 @@ public final class DownloadRequest implements Parcelable {
*/
public DownloadRequest copyWithKeySetId(@Nullable byte[] keySetId) {
return new DownloadRequest(
id, uri, mimeType, streamKeys, keySetId, customCacheKey, data, byteRange);
id, uri, mimeType, streamKeys, keySetId, customCacheKey, data, byteRange, timeRange);
}
/**
@ -265,7 +293,8 @@ public final class DownloadRequest implements Parcelable {
newRequest.keySetId,
newRequest.customCacheKey,
newRequest.data,
newRequest.byteRange);
newRequest.byteRange,
newRequest.timeRange);
}
/** Returns a {@link MediaItem} for the content defined by the request. */
@ -297,7 +326,8 @@ public final class DownloadRequest implements Parcelable {
&& Arrays.equals(keySetId, that.keySetId)
&& Objects.equals(customCacheKey, that.customCacheKey)
&& Arrays.equals(data, that.data)
&& Objects.equals(byteRange, that.byteRange);
&& Objects.equals(byteRange, that.byteRange)
&& Objects.equals(timeRange, that.timeRange);
}
@Override
@ -310,6 +340,7 @@ public final class DownloadRequest implements Parcelable {
result = 31 * result + (customCacheKey != null ? customCacheKey.hashCode() : 0);
result = 31 * result + Arrays.hashCode(data);
result = 31 * result + (byteRange != null ? byteRange.hashCode() : 0);
result = 31 * result + (timeRange != null ? timeRange.hashCode() : 0);
return result;
}
@ -333,6 +364,7 @@ public final class DownloadRequest implements Parcelable {
dest.writeString(customCacheKey);
dest.writeByteArray(data);
dest.writeParcelable(byteRange, /* parcelableFlags= */ 0);
dest.writeParcelable(timeRange, /* parcelableFlags= */ 0);
}
public static final Parcelable.Creator<DownloadRequest> CREATOR =
@ -410,4 +442,65 @@ public final class DownloadRequest implements Parcelable {
}
};
}
/** Defines the time range. */
public static final class TimeRange implements Parcelable {
/** The start position of the time range, in microseconds. */
public final long startPositionUs;
/** The duration of the time range, in microseconds. */
public final long durationUs;
/* package */ TimeRange(long startPositionUs, long durationUs) {
checkArgument(durationUs >= 0 || durationUs == C.TIME_UNSET);
this.startPositionUs = startPositionUs;
this.durationUs = durationUs;
}
/* package */ TimeRange(Parcel in) {
this(in.readLong(), in.readLong());
}
@Override
public boolean equals(@Nullable Object o) {
if (!(o instanceof TimeRange)) {
return false;
}
TimeRange that = (TimeRange) o;
return startPositionUs == that.startPositionUs && durationUs == that.durationUs;
}
@Override
public int hashCode() {
int result = 31 * (int) startPositionUs;
result = 31 * result + (int) durationUs;
return result;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(startPositionUs);
dest.writeLong(durationUs);
}
public static final Parcelable.Creator<TimeRange> CREATOR =
new Parcelable.Creator<TimeRange>() {
@Override
public TimeRange createFromParcel(Parcel in) {
return new TimeRange(in);
}
@Override
public TimeRange[] newArray(int size) {
return new TimeRange[size];
}
};
}
}

View File

@ -32,20 +32,45 @@ import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
public class DownloadRequestTest {
private Uri uri1;
private Uri uri2;
private Uri progressiveUri1;
private Uri progressiveUri2;
private Uri adaptiveUri1;
private Uri adaptiveUri2;
@Before
public void setUp() {
uri1 = Uri.parse("http://test/1.uri");
uri2 = Uri.parse("http://test/2.uri");
progressiveUri1 = Uri.parse("http://test/1.uri");
progressiveUri2 = Uri.parse("http://test/2.uri");
adaptiveUri1 = Uri.parse("http://test/1.m3u8");
adaptiveUri2 = Uri.parse("http://test/2.m3u8");
}
@Test
public void createRequestForProgressiveStream_ignoreTimeRangeField() {
DownloadRequest downloadRequest =
new DownloadRequest.Builder(/* id= */ "id1", progressiveUri1)
.setTimeRange(/* startPositionUs= */ 0, /* durationUs= */ 10_000)
.build();
assertThat(downloadRequest.timeRange).isNull();
}
@Test
public void createRequestForAdaptiveStream_ignoreByteRangeField() {
DownloadRequest downloadRequest =
new DownloadRequest.Builder(/* id= */ "id1", adaptiveUri1)
.setByteRange(/* offset= */ 0, /* length= */ 10)
.build();
assertThat(downloadRequest.byteRange).isNull();
}
@Test
public void mergeRequests_withDifferentIds_fails() {
DownloadRequest request1 = new DownloadRequest.Builder(/* id= */ "id1", uri1).build();
DownloadRequest request2 = new DownloadRequest.Builder(/* id= */ "id2", uri2).build();
DownloadRequest request1 =
new DownloadRequest.Builder(/* id= */ "id1", progressiveUri1).build();
DownloadRequest request2 =
new DownloadRequest.Builder(/* id= */ "id2", progressiveUri2).build();
try {
request1.copyWithMergedRequest(request2);
@ -57,7 +82,7 @@ public class DownloadRequestTest {
@Test
public void mergeRequest_withSameRequest() {
DownloadRequest request1 = createRequest(uri1, new StreamKey(0, 0, 0));
DownloadRequest request1 = createRequest(progressiveUri1, new StreamKey(0, 0, 0));
DownloadRequest mergedRequest = request1.copyWithMergedRequest(request1);
assertEqual(request1, mergedRequest);
@ -65,8 +90,8 @@ public class DownloadRequestTest {
@Test
public void mergeRequests_withEmptyStreamKeys() {
DownloadRequest request1 = createRequest(uri1, new StreamKey(0, 0, 0));
DownloadRequest request2 = createRequest(uri1);
DownloadRequest request1 = createRequest(progressiveUri1, new StreamKey(0, 0, 0));
DownloadRequest request2 = createRequest(progressiveUri1);
// If either of the requests have empty streamKeys, the merge should have empty streamKeys.
DownloadRequest mergedRequest = request1.copyWithMergedRequest(request2);
@ -81,8 +106,8 @@ public class DownloadRequestTest {
StreamKey streamKey1 = new StreamKey(0, 1, 2);
StreamKey streamKey2 = new StreamKey(3, 4, 5);
StreamKey streamKey3 = new StreamKey(6, 7, 8);
DownloadRequest request1 = createRequest(uri1, streamKey1, streamKey2);
DownloadRequest request2 = createRequest(uri1, streamKey2, streamKey3);
DownloadRequest request1 = createRequest(progressiveUri1, streamKey1, streamKey2);
DownloadRequest request2 = createRequest(progressiveUri1, streamKey2, streamKey3);
// Merged streamKeys should be in their original order without duplicates.
DownloadRequest mergedRequest = request1.copyWithMergedRequest(request2);
@ -100,23 +125,23 @@ public class DownloadRequestTest {
byte[] data2 = new byte[] {9, 10, 11};
DownloadRequest request1 =
new DownloadRequest.Builder(/* id= */ "id1", uri1)
new DownloadRequest.Builder(/* id= */ "id1", progressiveUri1)
.setKeySetId(keySetId1)
.setCustomCacheKey("key1")
.setData(data1)
.setByteRange(/* offset= */ 0, /* length= */ 10)
.build();
DownloadRequest request2 =
new DownloadRequest.Builder(/* id= */ "id1", uri2)
new DownloadRequest.Builder(/* id= */ "id1", progressiveUri2)
.setKeySetId(keySetId2)
.setCustomCacheKey("key2")
.setData(data2)
.setByteRange(/* offset= */ 10, /* length= */ 20)
.build();
// uri, keySetId, customCacheKey and data should be from the request being merged.
// uri, keySetId, customCacheKey, data and byteRange should be from the request being merged.
DownloadRequest mergedRequest = request1.copyWithMergedRequest(request2);
assertThat(mergedRequest.uri).isEqualTo(uri2);
assertThat(mergedRequest.uri).isEqualTo(progressiveUri2);
assertThat(mergedRequest.keySetId).isEqualTo(keySetId2);
assertThat(mergedRequest.customCacheKey).isEqualTo("key2");
assertThat(mergedRequest.data).isEqualTo(data2);
@ -124,12 +149,40 @@ public class DownloadRequestTest {
assertThat(mergedRequest.byteRange.length).isEqualTo(20);
mergedRequest = request2.copyWithMergedRequest(request1);
assertThat(mergedRequest.uri).isEqualTo(uri1);
assertThat(mergedRequest.uri).isEqualTo(progressiveUri1);
assertThat(mergedRequest.keySetId).isEqualTo(keySetId1);
assertThat(mergedRequest.customCacheKey).isEqualTo("key1");
assertThat(mergedRequest.data).isEqualTo(data1);
assertThat(mergedRequest.byteRange.offset).isEqualTo(0);
assertThat(mergedRequest.byteRange.length).isEqualTo(10);
DownloadRequest adaptiveRequest1 =
new DownloadRequest.Builder(/* id= */ "id1", adaptiveUri1)
.setKeySetId(keySetId1)
.setData(data1)
.setTimeRange(/* startPositionUs= */ 0, /* durationUs= */ 10_000)
.build();
DownloadRequest adaptiveRequest2 =
new DownloadRequest.Builder(/* id= */ "id1", adaptiveUri2)
.setKeySetId(keySetId2)
.setData(data2)
.setTimeRange(/* startPositionUs= */ 10_000, /* durationUs= */ 20_000)
.build();
// uri, keySetId, data and timeRange should be from the request being merged.
mergedRequest = adaptiveRequest1.copyWithMergedRequest(adaptiveRequest2);
assertThat(mergedRequest.uri).isEqualTo(adaptiveUri2);
assertThat(mergedRequest.keySetId).isEqualTo(keySetId2);
assertThat(mergedRequest.data).isEqualTo(data2);
assertThat(mergedRequest.timeRange.startPositionUs).isEqualTo(10_000);
assertThat(mergedRequest.timeRange.durationUs).isEqualTo(20_000);
mergedRequest = adaptiveRequest2.copyWithMergedRequest(adaptiveRequest1);
assertThat(mergedRequest.uri).isEqualTo(adaptiveUri1);
assertThat(mergedRequest.keySetId).isEqualTo(keySetId1);
assertThat(mergedRequest.data).isEqualTo(data1);
assertThat(mergedRequest.timeRange.startPositionUs).isEqualTo(0);
assertThat(mergedRequest.timeRange.durationUs).isEqualTo(10_000);
}
@Test
@ -158,53 +211,78 @@ public class DownloadRequestTest {
@SuppressWarnings("EqualsWithItself")
@Test
public void equals() {
DownloadRequest request1 = createRequest(uri1);
DownloadRequest request1 = createRequest(progressiveUri1);
assertThat(request1.equals(request1)).isTrue();
DownloadRequest request2 = createRequest(uri1);
DownloadRequest request3 = createRequest(uri1);
DownloadRequest request2 = createRequest(progressiveUri1);
DownloadRequest request3 = createRequest(progressiveUri1);
assertEqual(request2, request3);
DownloadRequest request4 = createRequest(uri1);
DownloadRequest request5 = createRequest(uri1, new StreamKey(0, 0, 0));
DownloadRequest request4 = createRequest(progressiveUri1);
DownloadRequest request5 = createRequest(progressiveUri1, new StreamKey(0, 0, 0));
assertNotEqual(request4, request5);
DownloadRequest request6 = createRequest(uri1, new StreamKey(0, 1, 1));
DownloadRequest request7 = createRequest(uri1, new StreamKey(0, 0, 0));
DownloadRequest request6 = createRequest(progressiveUri1, new StreamKey(0, 1, 1));
DownloadRequest request7 = createRequest(progressiveUri1, new StreamKey(0, 0, 0));
assertNotEqual(request6, request7);
DownloadRequest request8 = createRequest(uri1);
DownloadRequest request9 = createRequest(uri2);
DownloadRequest request8 = createRequest(progressiveUri1);
DownloadRequest request9 = createRequest(progressiveUri2);
assertNotEqual(request8, request9);
DownloadRequest request10 = createRequest(uri1, new StreamKey(0, 0, 0), new StreamKey(0, 1, 1));
DownloadRequest request11 = createRequest(uri1, new StreamKey(0, 1, 1), new StreamKey(0, 0, 0));
DownloadRequest request10 =
createRequest(progressiveUri1, new StreamKey(0, 0, 0), new StreamKey(0, 1, 1));
DownloadRequest request11 =
createRequest(progressiveUri1, new StreamKey(0, 1, 1), new StreamKey(0, 0, 0));
assertEqual(request10, request11);
DownloadRequest request12 = createRequest(uri1, new StreamKey(0, 0, 0));
DownloadRequest request13 = createRequest(uri1, new StreamKey(0, 1, 1), new StreamKey(0, 0, 0));
DownloadRequest request12 = createRequest(progressiveUri1, new StreamKey(0, 0, 0));
DownloadRequest request13 =
createRequest(progressiveUri1, new StreamKey(0, 1, 1), new StreamKey(0, 0, 0));
assertNotEqual(request12, request13);
DownloadRequest request14 = createRequest(uri1);
DownloadRequest request15 = createRequest(uri1);
DownloadRequest request14 = createRequest(progressiveUri1);
DownloadRequest request15 = createRequest(progressiveUri1);
assertEqual(request14, request15);
DownloadRequest request16 = createRequest(uri1);
DownloadRequest request16 = createRequest(progressiveUri1);
DownloadRequest request17 =
createRequest(uri1, /* byteRangeOffset= */ 0, /* byteRangeLength= */ 20);
createRequest(progressiveUri1, /* byteRangeOffset= */ 0, /* byteRangeLength= */ 20);
assertNotEqual(request16, request17);
DownloadRequest request18 =
createRequest(uri1, /* byteRangeOffset= */ 0, /* byteRangeLength= */ 20);
createRequest(progressiveUri1, /* byteRangeOffset= */ 0, /* byteRangeLength= */ 20);
DownloadRequest request19 =
createRequest(uri1, /* byteRangeOffset= */ 0, /* byteRangeLength= */ 20);
createRequest(progressiveUri1, /* byteRangeOffset= */ 0, /* byteRangeLength= */ 20);
assertEqual(request18, request19);
DownloadRequest request20 =
createRequest(uri1, /* byteRangeOffset= */ 0, /* byteRangeLength= */ 10);
createRequest(progressiveUri1, /* byteRangeOffset= */ 0, /* byteRangeLength= */ 10);
DownloadRequest request21 =
createRequest(uri1, /* byteRangeOffset= */ 0, /* byteRangeLength= */ 20);
createRequest(progressiveUri1, /* byteRangeOffset= */ 0, /* byteRangeLength= */ 20);
assertNotEqual(request20, request21);
DownloadRequest request22 = createRequest(adaptiveUri1);
DownloadRequest request23 =
createRequestWithTimeRange(
adaptiveUri1, /* startPositionUs= */ 0, /* durationUs= */ 20_000);
assertNotEqual(request22, request23);
DownloadRequest request24 =
createRequestWithTimeRange(
adaptiveUri1, /* startPositionUs= */ 0, /* durationUs= */ 20_000);
DownloadRequest request25 =
createRequestWithTimeRange(
adaptiveUri1, /* startPositionUs= */ 0, /* durationUs= */ 20_000);
assertEqual(request24, request25);
DownloadRequest request26 =
createRequestWithTimeRange(
adaptiveUri1, /* startPositionUs= */ 0, /* durationUs= */ 10_000);
DownloadRequest request27 =
createRequestWithTimeRange(
adaptiveUri1, /* startPositionUs= */ 0, /* durationUs= */ 20_000);
assertNotEqual(request26, request27);
}
private static void assertNotEqual(DownloadRequest request1, DownloadRequest request2) {
@ -228,4 +306,11 @@ public class DownloadRequestTest {
.setByteRange(byteRangeOffset, byteRangeLength)
.build();
}
private static DownloadRequest createRequestWithTimeRange(
Uri uri, long startPositionUs, long durationUs) {
return new DownloadRequest.Builder(uri.toString(), uri)
.setTimeRange(startPositionUs, durationUs)
.build();
}
}