mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Propagate download byte range to ProgressiveDownloader
PiperOrigin-RevId: 729484044
This commit is contained in:
parent
378e70e15f
commit
d35fccef59
@ -35,6 +35,13 @@
|
||||
locked in case the data source throws an `Exception` other than
|
||||
`IOException`
|
||||
([#9760](https://github.com/google/ExoPlayer/issues/9760)).
|
||||
* Add partial download support for progressive streams. Apps can prepare a
|
||||
progressive stream with `DownloadHelper`, and request a
|
||||
`DownloadRequest` from the helper with specifying the time-based media
|
||||
start and end positions that the download should cover. The returned
|
||||
`DownloadRequest` carries the resolved byte range, with which a
|
||||
`ProgressiveDownloader` can be created and download the content
|
||||
correspondingly.
|
||||
* OkHttp Extension:
|
||||
* Cronet Extension:
|
||||
* RTMP Extension:
|
||||
|
@ -78,13 +78,16 @@ public class DefaultDownloaderFactory implements DownloaderFactory {
|
||||
case C.CONTENT_TYPE_SS:
|
||||
return createDownloader(request, contentType);
|
||||
case C.CONTENT_TYPE_OTHER:
|
||||
@Nullable DownloadRequest.ByteRange byteRange = request.byteRange;
|
||||
return new ProgressiveDownloader(
|
||||
new MediaItem.Builder()
|
||||
.setUri(request.uri)
|
||||
.setCustomCacheKey(request.customCacheKey)
|
||||
.build(),
|
||||
cacheDataSourceFactory,
|
||||
executor);
|
||||
executor,
|
||||
(byteRange != null) ? byteRange.offset : 0,
|
||||
(byteRange != null) ? byteRange.length : C.LENGTH_UNSET);
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported type: " + contentType);
|
||||
}
|
||||
|
@ -15,9 +15,11 @@
|
||||
*/
|
||||
package androidx.media3.exoplayer.offline;
|
||||
|
||||
import static androidx.annotation.VisibleForTesting.PRIVATE;
|
||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.MediaItem;
|
||||
import androidx.media3.common.PriorityTaskManager;
|
||||
@ -39,7 +41,10 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
public final class ProgressiveDownloader implements Downloader {
|
||||
|
||||
private final Executor executor;
|
||||
private final DataSpec dataSpec;
|
||||
|
||||
@VisibleForTesting(otherwise = PRIVATE)
|
||||
/* package */ final DataSpec dataSpec;
|
||||
|
||||
private final CacheDataSource dataSource;
|
||||
private final CacheWriter cacheWriter;
|
||||
@Nullable private final PriorityTaskManager priorityTaskManager;
|
||||
@ -57,7 +62,26 @@ public final class ProgressiveDownloader implements Downloader {
|
||||
*/
|
||||
public ProgressiveDownloader(
|
||||
MediaItem mediaItem, CacheDataSource.Factory cacheDataSourceFactory) {
|
||||
this(mediaItem, cacheDataSourceFactory, Runnable::run);
|
||||
this(mediaItem, cacheDataSourceFactory, /* executor= */ Runnable::run);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param mediaItem The media item with a uri to the stream to be downloaded.
|
||||
* @param cacheDataSourceFactory A {@link CacheDataSource.Factory} for the cache into which the
|
||||
* download will be written.
|
||||
* @param position The position of the {@link DataSpec} from which the {@link
|
||||
* ProgressiveDownloader} downloads.
|
||||
* @param length The length of the {@link DataSpec} for which the {@link ProgressiveDownloader}
|
||||
* downloads.
|
||||
*/
|
||||
public ProgressiveDownloader(
|
||||
MediaItem mediaItem,
|
||||
CacheDataSource.Factory cacheDataSourceFactory,
|
||||
long position,
|
||||
long length) {
|
||||
this(mediaItem, cacheDataSourceFactory, /* executor= */ Runnable::run, position, length);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -72,6 +96,34 @@ public final class ProgressiveDownloader implements Downloader {
|
||||
*/
|
||||
public ProgressiveDownloader(
|
||||
MediaItem mediaItem, CacheDataSource.Factory cacheDataSourceFactory, Executor executor) {
|
||||
this(
|
||||
mediaItem,
|
||||
cacheDataSourceFactory,
|
||||
executor,
|
||||
/* position= */ 0,
|
||||
/* length= */ C.LENGTH_UNSET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param mediaItem The media item with a uri to the stream to be downloaded.
|
||||
* @param cacheDataSourceFactory A {@link CacheDataSource.Factory} for the cache into which the
|
||||
* download will be written.
|
||||
* @param executor An {@link Executor} used to make requests for the media being downloaded. In
|
||||
* the future, providing an {@link Executor} that uses multiple threads may speed up the
|
||||
* download by allowing parts of it to be executed in parallel.
|
||||
* @param position The position of the {@link DataSpec} from which the {@link
|
||||
* ProgressiveDownloader} downloads.
|
||||
* @param length The length of the {@link DataSpec} for which the {@link ProgressiveDownloader}
|
||||
* downloads.
|
||||
*/
|
||||
public ProgressiveDownloader(
|
||||
MediaItem mediaItem,
|
||||
CacheDataSource.Factory cacheDataSourceFactory,
|
||||
Executor executor,
|
||||
long position,
|
||||
long length) {
|
||||
this.executor = Assertions.checkNotNull(executor);
|
||||
Assertions.checkNotNull(mediaItem.localConfiguration);
|
||||
dataSpec =
|
||||
@ -79,6 +131,8 @@ public final class ProgressiveDownloader implements Downloader {
|
||||
.setUri(mediaItem.localConfiguration.uri)
|
||||
.setKey(mediaItem.localConfiguration.customCacheKey)
|
||||
.setFlags(DataSpec.FLAG_ALLOW_CACHE_FRAGMENTATION)
|
||||
.setPosition(position)
|
||||
.setLength(length)
|
||||
.build();
|
||||
dataSource = cacheDataSourceFactory.createDataSourceForDownloading();
|
||||
@SuppressWarnings("nullness:methodref.receiver.bound")
|
||||
|
@ -18,6 +18,7 @@ package androidx.media3.exoplayer.offline;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.net.Uri;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.datasource.PlaceholderDataSource;
|
||||
import androidx.media3.datasource.cache.Cache;
|
||||
import androidx.media3.datasource.cache.CacheDataSource;
|
||||
@ -31,7 +32,28 @@ import org.mockito.Mockito;
|
||||
public final class DefaultDownloaderFactoryTest {
|
||||
|
||||
@Test
|
||||
public void createProgressiveDownloader() throws Exception {
|
||||
public void createProgressiveDownloader_downloadRequestWithByteRange() throws Exception {
|
||||
CacheDataSource.Factory cacheDataSourceFactory =
|
||||
new CacheDataSource.Factory()
|
||||
.setCache(Mockito.mock(Cache.class))
|
||||
.setUpstreamDataSourceFactory(PlaceholderDataSource.FACTORY);
|
||||
DownloaderFactory factory =
|
||||
new DefaultDownloaderFactory(cacheDataSourceFactory, /* executor= */ Runnable::run);
|
||||
|
||||
Downloader downloader =
|
||||
factory.createDownloader(
|
||||
new DownloadRequest.Builder(/* id= */ "id", Uri.parse("https://www.test.com/download"))
|
||||
.setByteRange(/* offset= */ 10, /* length= */ 20)
|
||||
.build());
|
||||
|
||||
assertThat(downloader).isInstanceOf(ProgressiveDownloader.class);
|
||||
ProgressiveDownloader progressiveDownloader = (ProgressiveDownloader) downloader;
|
||||
assertThat(progressiveDownloader.dataSpec.position).isEqualTo(10);
|
||||
assertThat(progressiveDownloader.dataSpec.length).isEqualTo(20);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createProgressiveDownloader_downloadRequestWithoutByteRange() throws Exception {
|
||||
CacheDataSource.Factory cacheDataSourceFactory =
|
||||
new CacheDataSource.Factory()
|
||||
.setCache(Mockito.mock(Cache.class))
|
||||
@ -43,6 +65,10 @@ public final class DefaultDownloaderFactoryTest {
|
||||
factory.createDownloader(
|
||||
new DownloadRequest.Builder(/* id= */ "id", Uri.parse("https://www.test.com/download"))
|
||||
.build());
|
||||
|
||||
assertThat(downloader).isInstanceOf(ProgressiveDownloader.class);
|
||||
ProgressiveDownloader progressiveDownloader = (ProgressiveDownloader) downloader;
|
||||
assertThat(progressiveDownloader.dataSpec.position).isEqualTo(0);
|
||||
assertThat(progressiveDownloader.dataSpec.length).isEqualTo(C.LENGTH_UNSET);
|
||||
}
|
||||
}
|
||||
|
@ -68,6 +68,27 @@ public class ProgressiveDownloaderTest {
|
||||
Util.recursiveDelete(testDir);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void download_withNonDefaultByteRange_succeeds() throws Exception {
|
||||
Uri uri = Uri.parse("test:///test.mp4");
|
||||
FakeDataSet data = new FakeDataSet();
|
||||
data.newData(uri).appendReadData(1024);
|
||||
DataSource.Factory upstreamDataSource = new FakeDataSource.Factory().setFakeDataSet(data);
|
||||
MediaItem mediaItem = MediaItem.fromUri(uri);
|
||||
CacheDataSource.Factory cacheDataSourceFactory =
|
||||
new CacheDataSource.Factory()
|
||||
.setCache(downloadCache)
|
||||
.setUpstreamDataSourceFactory(upstreamDataSource);
|
||||
ProgressiveDownloader downloader =
|
||||
new ProgressiveDownloader(
|
||||
mediaItem, cacheDataSourceFactory, /* position= */ 0, /* length= */ 100);
|
||||
TestProgressListener progressListener = new TestProgressListener();
|
||||
|
||||
downloader.download(progressListener);
|
||||
|
||||
assertThat(progressListener.bytesDownloaded).isEqualTo(100);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void download_afterReadFailure_succeeds() throws Exception {
|
||||
Uri uri = Uri.parse("test:///test.mp4");
|
||||
|
Loading…
x
Reference in New Issue
Block a user