mirror of
https://github.com/androidx/media.git
synced 2025-04-29 22:36:54 +08:00
Add Factory for SegmentDownloader implementations
PiperOrigin-RevId: 745530254
This commit is contained in:
parent
c8a3361cc8
commit
eae886fe28
@ -76,6 +76,7 @@
|
||||
correspondingly.
|
||||
* Add `DownloadHelper.Factory` with which the static
|
||||
`DownloadHelper.forMediaItem()` methods are replaced.
|
||||
* Add `Factory` for `SegmentDownloader` implementations.
|
||||
* OkHttp extension:
|
||||
* Cronet extension:
|
||||
* RTMP extension:
|
||||
|
@ -39,17 +39,17 @@
|
||||
}
|
||||
|
||||
# Constructors accessed via reflection in DefaultDownloaderFactory
|
||||
-dontnote androidx.media3.exoplayer.dash.offline.DashDownloader
|
||||
-keepclassmembers class androidx.media3.exoplayer.dash.offline.DashDownloader {
|
||||
<init>(androidx.media3.common.MediaItem, androidx.media3.datasource.cache.CacheDataSource$Factory, java.util.concurrent.Executor);
|
||||
-dontnote androidx.media3.exoplayer.dash.offline.DashDownloader$Factory
|
||||
-keepclassmembers class androidx.media3.exoplayer.dash.offline.DashDownloader$Factory {
|
||||
<init>(androidx.media3.datasource.cache.CacheDataSource$Factory);
|
||||
}
|
||||
-dontnote androidx.media3.exoplayer.hls.offline.HlsDownloader
|
||||
-keepclassmembers class androidx.media3.exoplayer.hls.offline.HlsDownloader {
|
||||
<init>(androidx.media3.common.MediaItem, androidx.media3.datasource.cache.CacheDataSource$Factory, java.util.concurrent.Executor);
|
||||
-dontnote androidx.media3.exoplayer.hls.offline.HlsDownloader$Factory
|
||||
-keepclassmembers class androidx.media3.exoplayer.hls.offline.HlsDownloader$Factory {
|
||||
<init>(androidx.media3.datasource.cache.CacheDataSource$Factory);
|
||||
}
|
||||
-dontnote androidx.media3.exoplayer.smoothstreaming.offline.SsDownloader
|
||||
-keepclassmembers class androidx.media3.exoplayer.smoothstreaming.offline.SsDownloader {
|
||||
<init>(androidx.media3.common.MediaItem, androidx.media3.datasource.cache.CacheDataSource$Factory, java.util.concurrent.Executor);
|
||||
-dontnote androidx.media3.exoplayer.smoothstreaming.offline.SsDownloader$Factory
|
||||
-keepclassmembers class androidx.media3.exoplayer.smoothstreaming.offline.SsDownloader$Factory {
|
||||
<init>(androidx.media3.datasource.cache.CacheDataSource$Factory);
|
||||
}
|
||||
|
||||
# Constructors accessed via reflection in DefaultMediaSourceFactory
|
||||
|
@ -15,6 +15,8 @@
|
||||
*/
|
||||
package androidx.media3.exoplayer.offline;
|
||||
|
||||
import static androidx.media3.common.util.Util.contains;
|
||||
|
||||
import android.util.SparseArray;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.media3.common.C;
|
||||
@ -23,7 +25,6 @@ import androidx.media3.common.util.Assertions;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.common.util.Util;
|
||||
import androidx.media3.datasource.cache.CacheDataSource;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
@ -34,11 +35,9 @@ import java.util.concurrent.Executor;
|
||||
@UnstableApi
|
||||
public class DefaultDownloaderFactory implements DownloaderFactory {
|
||||
|
||||
private static final SparseArray<Constructor<? extends Downloader>> CONSTRUCTORS =
|
||||
createDownloaderConstructors();
|
||||
|
||||
private final CacheDataSource.Factory cacheDataSourceFactory;
|
||||
private final Executor executor;
|
||||
private final SparseArray<SegmentDownloaderFactory> segmentDownloaderFactories;
|
||||
|
||||
/**
|
||||
* Creates an instance.
|
||||
@ -66,6 +65,7 @@ public class DefaultDownloaderFactory implements DownloaderFactory {
|
||||
CacheDataSource.Factory cacheDataSourceFactory, Executor executor) {
|
||||
this.cacheDataSourceFactory = Assertions.checkNotNull(cacheDataSourceFactory);
|
||||
this.executor = Assertions.checkNotNull(executor);
|
||||
this.segmentDownloaderFactories = new SparseArray<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -76,7 +76,7 @@ public class DefaultDownloaderFactory implements DownloaderFactory {
|
||||
case C.CONTENT_TYPE_DASH:
|
||||
case C.CONTENT_TYPE_HLS:
|
||||
case C.CONTENT_TYPE_SS:
|
||||
return createDownloader(request, contentType);
|
||||
return createSegmentDownloader(request, contentType);
|
||||
case C.CONTENT_TYPE_OTHER:
|
||||
@Nullable DownloadRequest.ByteRange byteRange = request.byteRange;
|
||||
return new ProgressiveDownloader(
|
||||
@ -93,64 +93,78 @@ public class DefaultDownloaderFactory implements DownloaderFactory {
|
||||
}
|
||||
}
|
||||
|
||||
private Downloader createDownloader(DownloadRequest request, @C.ContentType int contentType) {
|
||||
@Nullable Constructor<? extends Downloader> constructor = CONSTRUCTORS.get(contentType);
|
||||
if (constructor == null) {
|
||||
throw new IllegalStateException("Module missing for content type " + contentType);
|
||||
}
|
||||
private Downloader createSegmentDownloader(
|
||||
DownloadRequest request, @C.ContentType int contentType) {
|
||||
SegmentDownloaderFactory downloaderFactory =
|
||||
getSegmentDownloaderFactory(contentType, cacheDataSourceFactory);
|
||||
MediaItem mediaItem =
|
||||
new MediaItem.Builder()
|
||||
.setUri(request.uri)
|
||||
.setStreamKeys(request.streamKeys)
|
||||
.setCustomCacheKey(request.customCacheKey)
|
||||
.build();
|
||||
try {
|
||||
return constructor.newInstance(mediaItem, cacheDataSourceFactory, executor);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException(
|
||||
"Failed to instantiate downloader for content type " + contentType, e);
|
||||
}
|
||||
return downloaderFactory.setExecutor(executor).create(mediaItem);
|
||||
}
|
||||
|
||||
// LINT.IfChange
|
||||
private static SparseArray<Constructor<? extends Downloader>> createDownloaderConstructors() {
|
||||
SparseArray<Constructor<? extends Downloader>> array = new SparseArray<>();
|
||||
try {
|
||||
array.put(
|
||||
C.CONTENT_TYPE_DASH,
|
||||
getDownloaderConstructor(
|
||||
Class.forName("androidx.media3.exoplayer.dash.offline.DashDownloader")));
|
||||
} catch (ClassNotFoundException e) {
|
||||
// Expected if the app was built without the DASH module.
|
||||
private SegmentDownloaderFactory getSegmentDownloaderFactory(
|
||||
@C.ContentType int contentType, CacheDataSource.Factory cacheDataSourceFactory) {
|
||||
if (contains(segmentDownloaderFactories, contentType)) {
|
||||
return segmentDownloaderFactories.get(contentType);
|
||||
}
|
||||
|
||||
SegmentDownloaderFactory downloaderFactory;
|
||||
try {
|
||||
array.put(
|
||||
C.CONTENT_TYPE_HLS,
|
||||
getDownloaderConstructor(
|
||||
Class.forName("androidx.media3.exoplayer.hls.offline.HlsDownloader")));
|
||||
downloaderFactory = loadSegmentDownloaderFactory(contentType, cacheDataSourceFactory);
|
||||
} catch (ClassNotFoundException e) {
|
||||
// Expected if the app was built without the HLS module.
|
||||
throw new IllegalStateException("Module missing for content type " + contentType, e);
|
||||
}
|
||||
try {
|
||||
array.put(
|
||||
C.CONTENT_TYPE_SS,
|
||||
getDownloaderConstructor(
|
||||
Class.forName("androidx.media3.exoplayer.smoothstreaming.offline.SsDownloader")));
|
||||
} catch (ClassNotFoundException e) {
|
||||
// Expected if the app was built without the SmoothStreaming module.
|
||||
}
|
||||
return array;
|
||||
return downloaderFactory;
|
||||
}
|
||||
|
||||
private static Constructor<? extends Downloader> getDownloaderConstructor(Class<?> clazz) {
|
||||
private SegmentDownloaderFactory loadSegmentDownloaderFactory(
|
||||
@C.ContentType int contentType, CacheDataSource.Factory cacheDataSourceFactory)
|
||||
throws ClassNotFoundException {
|
||||
SegmentDownloaderFactory factory;
|
||||
switch (contentType) {
|
||||
case C.CONTENT_TYPE_DASH:
|
||||
factory =
|
||||
createSegmentDownloaderFactory(
|
||||
Class.forName("androidx.media3.exoplayer.dash.offline.DashDownloader$Factory")
|
||||
.asSubclass(SegmentDownloaderFactory.class),
|
||||
cacheDataSourceFactory);
|
||||
break;
|
||||
case C.CONTENT_TYPE_HLS:
|
||||
factory =
|
||||
createSegmentDownloaderFactory(
|
||||
Class.forName("androidx.media3.exoplayer.hls.offline.HlsDownloader$Factory")
|
||||
.asSubclass(SegmentDownloaderFactory.class),
|
||||
cacheDataSourceFactory);
|
||||
break;
|
||||
case C.CONTENT_TYPE_SS:
|
||||
factory =
|
||||
createSegmentDownloaderFactory(
|
||||
Class.forName(
|
||||
"androidx.media3.exoplayer.smoothstreaming.offline.SsDownloader$Factory")
|
||||
.asSubclass(SegmentDownloaderFactory.class),
|
||||
cacheDataSourceFactory);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported type: " + contentType);
|
||||
}
|
||||
segmentDownloaderFactories.put(contentType, factory);
|
||||
return factory;
|
||||
}
|
||||
|
||||
private static SegmentDownloaderFactory createSegmentDownloaderFactory(
|
||||
Class<? extends SegmentDownloaderFactory> clazz,
|
||||
CacheDataSource.Factory cacheDataSourceFactory) {
|
||||
try {
|
||||
return clazz
|
||||
.asSubclass(Downloader.class)
|
||||
.getConstructor(MediaItem.class, CacheDataSource.Factory.class, Executor.class);
|
||||
} catch (NoSuchMethodException e) {
|
||||
// The downloader is present, but the expected constructor is missing.
|
||||
throw new IllegalStateException("Downloader constructor missing", e);
|
||||
.getConstructor(CacheDataSource.Factory.class)
|
||||
.newInstance(cacheDataSourceFactory);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Downloader factory missing", e);
|
||||
}
|
||||
}
|
||||
// LINT.ThenChange(../../../../../../../proguard-rules.txt)
|
||||
|
@ -38,6 +38,7 @@ import androidx.media3.datasource.cache.CacheWriter;
|
||||
import androidx.media3.datasource.cache.ContentMetadata;
|
||||
import androidx.media3.exoplayer.upstream.ParsingLoadable;
|
||||
import androidx.media3.exoplayer.upstream.ParsingLoadable.Parser;
|
||||
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
@ -56,6 +57,37 @@ import java.util.concurrent.Executor;
|
||||
@UnstableApi
|
||||
public abstract class SegmentDownloader<M extends FilterableManifest<M>> implements Downloader {
|
||||
|
||||
/** A base class of the factory of the concrete extension of {@link SegmentDownloader}. */
|
||||
protected abstract static class BaseFactory<M extends FilterableManifest<M>>
|
||||
implements SegmentDownloaderFactory {
|
||||
|
||||
protected final CacheDataSource.Factory cacheDataSourceFactory;
|
||||
protected Parser<M> manifestParser;
|
||||
protected Executor executor;
|
||||
protected long maxMergedSegmentStartTimeDiffMs;
|
||||
|
||||
public BaseFactory(CacheDataSource.Factory cacheDataSourceFactory, Parser<M> manifestParser) {
|
||||
this.cacheDataSourceFactory = cacheDataSourceFactory;
|
||||
this.manifestParser = manifestParser;
|
||||
this.executor = Runnable::run;
|
||||
this.maxMergedSegmentStartTimeDiffMs = DEFAULT_MAX_MERGED_SEGMENT_START_TIME_DIFF_MS;
|
||||
}
|
||||
|
||||
@Override
|
||||
@CanIgnoreReturnValue
|
||||
public BaseFactory<M> setExecutor(Executor executor) {
|
||||
this.executor = executor;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
@CanIgnoreReturnValue
|
||||
public BaseFactory<M> setMaxMergedSegmentStartTimeDiffMs(long maxMergedSegmentStartTimeDiffMs) {
|
||||
this.maxMergedSegmentStartTimeDiffMs = maxMergedSegmentStartTimeDiffMs;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/** Smallest unit of content to be downloaded. */
|
||||
protected static class Segment implements Comparable<Segment> {
|
||||
|
||||
@ -103,19 +135,7 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M>> impleme
|
||||
|
||||
private volatile boolean isCanceled;
|
||||
|
||||
/**
|
||||
* @param mediaItem The {@link MediaItem} to be downloaded.
|
||||
* @param manifestParser A parser for manifests belonging to the media 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.
|
||||
* Providing an {@link Executor} that uses multiple threads will speed up the download by
|
||||
* allowing parts of it to be executed in parallel.
|
||||
* @param maxMergedSegmentStartTimeDiffMs The maximum difference of the start time of two
|
||||
* segments, up to which the segments (of the same URI) should be merged into a single
|
||||
* download segment, in milliseconds.
|
||||
*/
|
||||
public SegmentDownloader(
|
||||
protected SegmentDownloader(
|
||||
MediaItem mediaItem,
|
||||
Parser<M> manifestParser,
|
||||
CacheDataSource.Factory cacheDataSourceFactory,
|
||||
@ -414,12 +434,14 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M>> impleme
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("FutureReturnValueIgnored")
|
||||
private void removeActiveRunnable(RunnableFutureTask<?, ?> runnable) {
|
||||
synchronized (activeRunnables) {
|
||||
activeRunnables.remove(runnable);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("FutureReturnValueIgnored")
|
||||
private void removeActiveRunnable(int index) {
|
||||
synchronized (activeRunnables) {
|
||||
activeRunnables.remove(index);
|
||||
|
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2025 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package androidx.media3.exoplayer.offline;
|
||||
|
||||
import androidx.media3.common.MediaItem;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/** A factory to create {@linkplain SegmentDownloader segment downloaders}. */
|
||||
@UnstableApi
|
||||
/* package */ interface SegmentDownloaderFactory {
|
||||
|
||||
/**
|
||||
* Sets the {@link Executor} used to make requests for the media being downloaded. Providing an
|
||||
* {@link Executor} that uses multiple threads will speed up the download by allowing parts of it
|
||||
* to be executed in parallel.
|
||||
*/
|
||||
SegmentDownloaderFactory setExecutor(Executor executor);
|
||||
|
||||
/**
|
||||
* Sets the maximum difference of the start time of two segments, up to which the segments (of the
|
||||
* same URI) should be merged into a single download segment, in milliseconds.
|
||||
*/
|
||||
SegmentDownloaderFactory setMaxMergedSegmentStartTimeDiffMs(long maxMergedSegmentStartTimeDiffMs);
|
||||
|
||||
/**
|
||||
* Creates the segment downloader.
|
||||
*
|
||||
* @param mediaItem The {@link MediaItem} to be downloaded.
|
||||
*/
|
||||
SegmentDownloader<?> create(MediaItem mediaItem);
|
||||
}
|
@ -41,6 +41,7 @@ import androidx.media3.exoplayer.offline.SegmentDownloader;
|
||||
import androidx.media3.exoplayer.upstream.ParsingLoadable.Parser;
|
||||
import androidx.media3.extractor.ChunkIndex;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@ -60,12 +61,11 @@ import java.util.concurrent.Executor;
|
||||
* // Create a downloader for the first representation of the first adaptation set of the first
|
||||
* // period.
|
||||
* DashDownloader dashDownloader =
|
||||
* new DashDownloader(
|
||||
* new MediaItem.Builder()
|
||||
* .setUri(manifestUrl)
|
||||
* .setStreamKeys(Collections.singletonList(new StreamKey(0, 0, 0)))
|
||||
* .build(),
|
||||
* cacheDataSourceFactory);
|
||||
* new DashDownloader.Factory(cacheDataSourceFactory)
|
||||
* .create(new MediaItem.Builder()
|
||||
* .setUri(manifestUrl)
|
||||
* .setStreamKeys(ImmutableList.of(new StreamKey(0, 0, 0)))
|
||||
* .build());
|
||||
* // Perform the download.
|
||||
* dashDownloader.download(progressListener);
|
||||
* // Use the downloaded data for playback.
|
||||
@ -76,29 +76,81 @@ import java.util.concurrent.Executor;
|
||||
@UnstableApi
|
||||
public final class DashDownloader extends SegmentDownloader<DashManifest> {
|
||||
|
||||
/** A factory for {@linkplain DashDownloader DASH downloaders}. */
|
||||
public static final class Factory extends BaseFactory<DashManifest> {
|
||||
|
||||
/**
|
||||
* Creates a factory for {@link DashDownloader}.
|
||||
*
|
||||
* @param cacheDataSourceFactory A {@link CacheDataSource.Factory} for the cache into which the
|
||||
* download will be written.
|
||||
*/
|
||||
public Factory(CacheDataSource.Factory cacheDataSourceFactory) {
|
||||
super(cacheDataSourceFactory, new DashManifestParser());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a parser for DASH manifests.
|
||||
*
|
||||
* @return This factory, for convenience.
|
||||
*/
|
||||
@CanIgnoreReturnValue
|
||||
public Factory setManifestParser(DashManifestParser manifestParser) {
|
||||
this.manifestParser = manifestParser;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link Executor} used to make requests for the media being downloaded. Providing an
|
||||
* {@link Executor} that uses multiple threads will speed up the download by allowing parts of
|
||||
* it to be executed in parallel.
|
||||
*
|
||||
* @return This factory, for convenience.
|
||||
*/
|
||||
@Override
|
||||
@CanIgnoreReturnValue
|
||||
public Factory setExecutor(Executor executor) {
|
||||
return (Factory) super.setExecutor(executor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum difference of the start time of two segments, up to which the segments (of
|
||||
* the same URI) should be merged into a single download segment, in milliseconds.
|
||||
*
|
||||
* @return This factory, for convenience.
|
||||
*/
|
||||
@Override
|
||||
@CanIgnoreReturnValue
|
||||
public Factory setMaxMergedSegmentStartTimeDiffMs(long maxMergedSegmentStartTimeDiffMs) {
|
||||
return (Factory) super.setMaxMergedSegmentStartTimeDiffMs(maxMergedSegmentStartTimeDiffMs);
|
||||
}
|
||||
|
||||
/** Creates {@linkplain DashDownloader DASH downloaders}. */
|
||||
@Override
|
||||
public DashDownloader create(MediaItem mediaItem) {
|
||||
return new DashDownloader(
|
||||
mediaItem,
|
||||
manifestParser,
|
||||
cacheDataSourceFactory,
|
||||
executor,
|
||||
maxMergedSegmentStartTimeDiffMs);
|
||||
}
|
||||
}
|
||||
|
||||
private final BaseUrlExclusionList baseUrlExclusionList;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param mediaItem The {@link MediaItem} to be downloaded.
|
||||
* @param cacheDataSourceFactory A {@link CacheDataSource.Factory} for the cache into which the
|
||||
* download will be written.
|
||||
* @deprecated Use {@link DashDownloader.Factory#create(MediaItem)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public DashDownloader(MediaItem mediaItem, CacheDataSource.Factory cacheDataSourceFactory) {
|
||||
this(mediaItem, cacheDataSourceFactory, Runnable::run);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param mediaItem The {@link MediaItem} 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.
|
||||
* Providing an {@link Executor} that uses multiple threads will speed up the download by
|
||||
* allowing parts of it to be executed in parallel.
|
||||
* @deprecated Use {@link DashDownloader.Factory#create(MediaItem)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public DashDownloader(
|
||||
MediaItem mediaItem, CacheDataSource.Factory cacheDataSourceFactory, Executor executor) {
|
||||
this(
|
||||
@ -123,7 +175,7 @@ public final class DashDownloader extends SegmentDownloader<DashManifest> {
|
||||
* segments, up to which the segments (of the same URI) should be merged into a single
|
||||
* download segment, in milliseconds.
|
||||
*/
|
||||
public DashDownloader(
|
||||
private DashDownloader(
|
||||
MediaItem mediaItem,
|
||||
Parser<DashManifest> manifestParser,
|
||||
CacheDataSource.Factory cacheDataSourceFactory,
|
||||
|
@ -340,9 +340,8 @@ public class DashDownloaderTest {
|
||||
new CacheDataSource.Factory()
|
||||
.setCache(cache)
|
||||
.setUpstreamDataSourceFactory(upstreamDataSourceFactory);
|
||||
return new DashDownloader(
|
||||
new MediaItem.Builder().setUri(TEST_MPD_URI).setStreamKeys(keysList(keys)).build(),
|
||||
cacheDataSourceFactory);
|
||||
return new DashDownloader.Factory(cacheDataSourceFactory)
|
||||
.create(new MediaItem.Builder().setUri(TEST_MPD_URI).setStreamKeys(keysList(keys)).build());
|
||||
}
|
||||
|
||||
private static ArrayList<StreamKey> keysList(StreamKey... keys) {
|
||||
|
@ -29,6 +29,7 @@ import androidx.media3.exoplayer.hls.playlist.HlsPlaylist;
|
||||
import androidx.media3.exoplayer.hls.playlist.HlsPlaylistParser;
|
||||
import androidx.media3.exoplayer.offline.SegmentDownloader;
|
||||
import androidx.media3.exoplayer.upstream.ParsingLoadable.Parser;
|
||||
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
@ -48,14 +49,13 @@ import java.util.concurrent.Executor;
|
||||
* .setUpstreamDataSourceFactory(new DefaultHttpDataSource.Factory());
|
||||
* // Create a downloader for the first variant in a multivariant playlist.
|
||||
* HlsDownloader hlsDownloader =
|
||||
* new HlsDownloader(
|
||||
* new MediaItem.Builder()
|
||||
* new HlsDownloader.Factory(cacheDataSourceFactory)
|
||||
* .create(new MediaItem.Builder()
|
||||
* .setUri(playlistUri)
|
||||
* .setStreamKeys(
|
||||
* Collections.singletonList(
|
||||
* new StreamKey(HlsMultivariantPlaylist.GROUP_INDEX_VARIANT, 0)))
|
||||
* .build(),
|
||||
* Collections.singletonList();
|
||||
* .setStreamKeys(
|
||||
* ImmutableList.of(
|
||||
* new StreamKey(HlsMultivariantPlaylist.GROUP_INDEX_VARIANT, 0)))
|
||||
* .build());
|
||||
* // Perform the download.
|
||||
* hlsDownloader.download(progressListener);
|
||||
* // Use the downloaded data for playback.
|
||||
@ -66,27 +66,79 @@ import java.util.concurrent.Executor;
|
||||
@UnstableApi
|
||||
public final class HlsDownloader extends SegmentDownloader<HlsPlaylist> {
|
||||
|
||||
/** A factory for {@linkplain HlsDownloader HLS downloaders}. */
|
||||
public static final class Factory extends BaseFactory<HlsPlaylist> {
|
||||
|
||||
/**
|
||||
* Creates a factory for {@link HlsDownloader}.
|
||||
*
|
||||
* @param cacheDataSourceFactory A {@link CacheDataSource.Factory} for the cache into which the
|
||||
* download will be written.
|
||||
*/
|
||||
public Factory(CacheDataSource.Factory cacheDataSourceFactory) {
|
||||
super(cacheDataSourceFactory, new HlsPlaylistParser());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a parser for HLS playlists.
|
||||
*
|
||||
* @return This factory, for convenience.
|
||||
*/
|
||||
@CanIgnoreReturnValue
|
||||
public Factory setManifestParser(HlsPlaylistParser manifestParser) {
|
||||
this.manifestParser = manifestParser;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link Executor} used to make requests for the media being downloaded. Providing an
|
||||
* {@link Executor} that uses multiple threads will speed up the download by allowing parts of
|
||||
* it to be executed in parallel.
|
||||
*
|
||||
* @return This factory, for convenience.
|
||||
*/
|
||||
@Override
|
||||
@CanIgnoreReturnValue
|
||||
public Factory setExecutor(Executor executor) {
|
||||
return (Factory) super.setExecutor(executor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum difference of the start time of two segments, up to which the segments (of
|
||||
* the same URI) should be merged into a single download segment, in milliseconds.
|
||||
*
|
||||
* @return This factory, for convenience.
|
||||
*/
|
||||
@Override
|
||||
@CanIgnoreReturnValue
|
||||
public Factory setMaxMergedSegmentStartTimeDiffMs(long maxMergedSegmentStartTimeDiffMs) {
|
||||
return (Factory) super.setMaxMergedSegmentStartTimeDiffMs(maxMergedSegmentStartTimeDiffMs);
|
||||
}
|
||||
|
||||
/** Creates {@linkplain HlsDownloader HLS downloaders}. */
|
||||
@Override
|
||||
public HlsDownloader create(MediaItem mediaItem) {
|
||||
return new HlsDownloader(
|
||||
mediaItem,
|
||||
manifestParser,
|
||||
cacheDataSourceFactory,
|
||||
executor,
|
||||
maxMergedSegmentStartTimeDiffMs);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param mediaItem The {@link MediaItem} to be downloaded.
|
||||
* @param cacheDataSourceFactory A {@link CacheDataSource.Factory} for the cache into which the
|
||||
* download will be written.
|
||||
* @deprecated Use {@link HlsDownloader.Factory#create(MediaItem)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public HlsDownloader(MediaItem mediaItem, CacheDataSource.Factory cacheDataSourceFactory) {
|
||||
this(mediaItem, cacheDataSourceFactory, Runnable::run);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param mediaItem The {@link MediaItem} 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.
|
||||
* Providing an {@link Executor} that uses multiple threads will speed up the download by
|
||||
* allowing parts of it to be executed in parallel.
|
||||
* @deprecated Use {@link HlsDownloader.Factory#create(MediaItem)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public HlsDownloader(
|
||||
MediaItem mediaItem, CacheDataSource.Factory cacheDataSourceFactory, Executor executor) {
|
||||
this(
|
||||
@ -111,7 +163,7 @@ public final class HlsDownloader extends SegmentDownloader<HlsPlaylist> {
|
||||
* segments, up to which the segments (of the same URI) should be merged into a single
|
||||
* download segment, in milliseconds.
|
||||
*/
|
||||
public HlsDownloader(
|
||||
private HlsDownloader(
|
||||
MediaItem mediaItem,
|
||||
Parser<HlsPlaylist> manifestParser,
|
||||
CacheDataSource.Factory cacheDataSourceFactory,
|
||||
|
@ -223,9 +223,8 @@ public class HlsDownloaderTest {
|
||||
new CacheDataSource.Factory()
|
||||
.setCache(cache)
|
||||
.setUpstreamDataSourceFactory(new FakeDataSource.Factory().setFakeDataSet(fakeDataSet));
|
||||
return new HlsDownloader(
|
||||
new MediaItem.Builder().setUri(mediaPlaylistUri).setStreamKeys(keys).build(),
|
||||
cacheDataSourceFactory);
|
||||
return new HlsDownloader.Factory(cacheDataSourceFactory)
|
||||
.create(new MediaItem.Builder().setUri(mediaPlaylistUri).setStreamKeys(keys).build());
|
||||
}
|
||||
|
||||
private static ArrayList<StreamKey> getKeys(int... variantIndices) {
|
||||
|
@ -28,6 +28,7 @@ import androidx.media3.exoplayer.smoothstreaming.manifest.SsManifest;
|
||||
import androidx.media3.exoplayer.smoothstreaming.manifest.SsManifest.StreamElement;
|
||||
import androidx.media3.exoplayer.smoothstreaming.manifest.SsManifestParser;
|
||||
import androidx.media3.exoplayer.upstream.ParsingLoadable.Parser;
|
||||
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
@ -45,12 +46,11 @@ import java.util.concurrent.Executor;
|
||||
* .setUpstreamDataSourceFactory(new DefaultHttpDataSource.Factory());
|
||||
* // Create a downloader for the first track of the first stream element.
|
||||
* SsDownloader ssDownloader =
|
||||
* new SsDownloader(
|
||||
* new MediaItem.Builder()
|
||||
* .setUri(manifestUri)
|
||||
* .setStreamKeys(Collections.singletonList(new StreamKey(0, 0)))
|
||||
* .build(),
|
||||
* cacheDataSourceFactory);
|
||||
* new SsDownloader.Factory(cacheDataSourceFactory)
|
||||
* .create(new MediaItem.Builder()
|
||||
* .setUri(manifestUri)
|
||||
* .setStreamKeys(ImmutableList.of(new StreamKey(0, 0)))
|
||||
* .build());
|
||||
* // Perform the download.
|
||||
* ssDownloader.download(progressListener);
|
||||
* // Use the downloaded data for playback.
|
||||
@ -61,27 +61,84 @@ import java.util.concurrent.Executor;
|
||||
@UnstableApi
|
||||
public final class SsDownloader extends SegmentDownloader<SsManifest> {
|
||||
|
||||
/** A factory for {@linkplain SsDownloader SmoothStreaming downloaders}. */
|
||||
public static final class Factory extends BaseFactory<SsManifest> {
|
||||
|
||||
/**
|
||||
* Creates a factory for {@link SsDownloader}.
|
||||
*
|
||||
* @param cacheDataSourceFactory A {@link CacheDataSource.Factory} for the cache into which the
|
||||
* download will be written.
|
||||
*/
|
||||
public Factory(CacheDataSource.Factory cacheDataSourceFactory) {
|
||||
super(cacheDataSourceFactory, new SsManifestParser());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a parser for SmoothStreaming manifests.
|
||||
*
|
||||
* @return This factory, for convenience.
|
||||
*/
|
||||
@CanIgnoreReturnValue
|
||||
public Factory setManifestParser(SsManifestParser manifestParser) {
|
||||
this.manifestParser = manifestParser;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link Executor} used to make requests for the media being downloaded. Providing an
|
||||
* {@link Executor} that uses multiple threads will speed up the download by allowing parts of
|
||||
* it to be executed in parallel.
|
||||
*
|
||||
* @return This factory, for convenience.
|
||||
*/
|
||||
@Override
|
||||
@CanIgnoreReturnValue
|
||||
public Factory setExecutor(Executor executor) {
|
||||
return (Factory) super.setExecutor(executor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum difference of the start time of two segments, up to which the segments (of
|
||||
* the same URI) should be merged into a single download segment, in milliseconds.
|
||||
*
|
||||
* @return This factory, for convenience.
|
||||
*/
|
||||
@Override
|
||||
@CanIgnoreReturnValue
|
||||
public Factory setMaxMergedSegmentStartTimeDiffMs(long maxMergedSegmentStartTimeDiffMs) {
|
||||
return (Factory) super.setMaxMergedSegmentStartTimeDiffMs(maxMergedSegmentStartTimeDiffMs);
|
||||
}
|
||||
|
||||
/** Creates {@linkplain SsDownloader SmoothStreaming downloaders}. */
|
||||
@Override
|
||||
public SsDownloader create(MediaItem mediaItem) {
|
||||
return new SsDownloader(
|
||||
mediaItem
|
||||
.buildUpon()
|
||||
.setUri(
|
||||
Util.fixSmoothStreamingIsmManifestUri(
|
||||
checkNotNull(mediaItem.localConfiguration).uri))
|
||||
.build(),
|
||||
manifestParser,
|
||||
cacheDataSourceFactory,
|
||||
executor,
|
||||
maxMergedSegmentStartTimeDiffMs);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance.
|
||||
*
|
||||
* @param mediaItem The {@link MediaItem} to be downloaded.
|
||||
* @param cacheDataSourceFactory A {@link CacheDataSource.Factory} for the cache into which the
|
||||
* download will be written.
|
||||
* @deprecated Use {@link SsDownloader.Factory#create(MediaItem)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public SsDownloader(MediaItem mediaItem, CacheDataSource.Factory cacheDataSourceFactory) {
|
||||
this(mediaItem, cacheDataSourceFactory, Runnable::run);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance.
|
||||
*
|
||||
* @param mediaItem The {@link MediaItem} 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.
|
||||
* Providing an {@link Executor} that uses multiple threads will speed up the download by
|
||||
* allowing parts of it to be executed in parallel.
|
||||
* @deprecated Use {@link SsDownloader.Factory#create(MediaItem)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public SsDownloader(
|
||||
MediaItem mediaItem, CacheDataSource.Factory cacheDataSourceFactory, Executor executor) {
|
||||
this(
|
||||
@ -111,7 +168,7 @@ public final class SsDownloader extends SegmentDownloader<SsManifest> {
|
||||
* segments, up to which the segments (of the same URI) should be merged into a single
|
||||
* download segment, in milliseconds.
|
||||
*/
|
||||
public SsDownloader(
|
||||
private SsDownloader(
|
||||
MediaItem mediaItem,
|
||||
Parser<SsManifest> manifestParser,
|
||||
CacheDataSource.Factory cacheDataSourceFactory,
|
||||
|
@ -126,8 +126,7 @@ public final class DashDownloadTest {
|
||||
new CacheDataSource.Factory()
|
||||
.setCache(cache)
|
||||
.setUpstreamDataSourceFactory(httpDataSourceFactory);
|
||||
return new DashDownloader(
|
||||
new MediaItem.Builder().setUri(MANIFEST_URI).setStreamKeys(keys).build(),
|
||||
cacheDataSourceFactory);
|
||||
return new DashDownloader.Factory(cacheDataSourceFactory)
|
||||
.create(new MediaItem.Builder().setUri(MANIFEST_URI).setStreamKeys(keys).build());
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user