From cd828e5c10c65318235b7579c04e996c47197ff3 Mon Sep 17 00:00:00 2001 From: olly Date: Wed, 22 Apr 2020 16:16:37 +0100 Subject: [PATCH] Plumb an ExecutorService into Downloader implementations Issue: #5978 PiperOrigin-RevId: 307819608 --- library/core/proguard-rules.txt | 6 ++-- .../offline/DefaultDownloaderFactory.java | 31 +++++++++++++++++-- .../offline/ProgressiveDownloader.java | 21 +++++++++++++ .../exoplayer2/offline/SegmentDownloader.java | 11 ++++++- .../source/dash/offline/DashDownloader.java | 23 +++++++++++++- .../source/hls/offline/HlsDownloader.java | 24 +++++++++++++- .../smoothstreaming/offline/SsDownloader.java | 24 +++++++++++++- 7 files changed, 130 insertions(+), 10 deletions(-) diff --git a/library/core/proguard-rules.txt b/library/core/proguard-rules.txt index 72f2b27849..1014dc0de4 100644 --- a/library/core/proguard-rules.txt +++ b/library/core/proguard-rules.txt @@ -51,15 +51,15 @@ # Constructors accessed via reflection in DefaultDownloaderFactory -dontnote com.google.android.exoplayer2.source.dash.offline.DashDownloader -keepclassmembers class com.google.android.exoplayer2.source.dash.offline.DashDownloader { - (android.net.Uri, java.util.List, com.google.android.exoplayer2.upstream.cache.CacheDataSource.Factory); + (android.net.Uri, java.util.List, com.google.android.exoplayer2.upstream.cache.CacheDataSource.Factory, java.util.concurrent.ExecutorService); } -dontnote com.google.android.exoplayer2.source.hls.offline.HlsDownloader -keepclassmembers class com.google.android.exoplayer2.source.hls.offline.HlsDownloader { - (android.net.Uri, java.util.List, com.google.android.exoplayer2.upstream.cache.CacheDataSource.Factory); + (android.net.Uri, java.util.List, com.google.android.exoplayer2.upstream.cache.CacheDataSource.Factory, java.util.concurrent.ExecutorService); } -dontnote com.google.android.exoplayer2.source.smoothstreaming.offline.SsDownloader -keepclassmembers class com.google.android.exoplayer2.source.smoothstreaming.offline.SsDownloader { - (android.net.Uri, java.util.List, com.google.android.exoplayer2.upstream.cache.CacheDataSource.Factory); + (android.net.Uri, java.util.List, com.google.android.exoplayer2.upstream.cache.CacheDataSource.Factory, java.util.concurrent.ExecutorService); } # Constructors accessed via reflection in DefaultMediaSourceFactory and DownloadHelper diff --git a/library/core/src/main/java/com/google/android/exoplayer2/offline/DefaultDownloaderFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/offline/DefaultDownloaderFactory.java index 88699db804..619456a201 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/offline/DefaultDownloaderFactory.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/offline/DefaultDownloaderFactory.java @@ -20,6 +20,7 @@ import androidx.annotation.Nullable; import com.google.android.exoplayer2.upstream.cache.CacheDataSource; import java.lang.reflect.Constructor; import java.util.List; +import java.util.concurrent.ExecutorService; /** * Default {@link DownloaderFactory}, supporting creation of progressive, DASH, HLS and @@ -70,6 +71,7 @@ public class DefaultDownloaderFactory implements DownloaderFactory { } private final CacheDataSource.Factory cacheDataSourceFactory; + @Nullable private final ExecutorService executorService; /** * Creates an instance. @@ -78,7 +80,24 @@ public class DefaultDownloaderFactory implements DownloaderFactory { * downloads will be written. */ public DefaultDownloaderFactory(CacheDataSource.Factory cacheDataSourceFactory) { + this(cacheDataSourceFactory, /* executorService= */ null); + } + + /** + * Creates an instance. + * + * @param cacheDataSourceFactory A {@link CacheDataSource.Factory} for the cache into which + * downloads will be written. + * @param executorService An {@link ExecutorService} used to make requests for media being + * downloaded. Must not be a direct executor, but may be {@code null} if each download should + * make its requests directly on its own thread. Providing an {@link ExecutorService} that + * uses multiple threads will speed up download tasks that can be split into smaller parts for + * parallel execution. + */ + public DefaultDownloaderFactory( + CacheDataSource.Factory cacheDataSourceFactory, @Nullable ExecutorService executorService) { this.cacheDataSourceFactory = cacheDataSourceFactory; + this.executorService = executorService; } @Override @@ -86,7 +105,7 @@ public class DefaultDownloaderFactory implements DownloaderFactory { switch (request.type) { case DownloadRequest.TYPE_PROGRESSIVE: return new ProgressiveDownloader( - request.uri, request.customCacheKey, cacheDataSourceFactory); + request.uri, request.customCacheKey, cacheDataSourceFactory, executorService); case DownloadRequest.TYPE_DASH: return createDownloader(request, DASH_DOWNLOADER_CONSTRUCTOR); case DownloadRequest.TYPE_HLS: @@ -98,13 +117,18 @@ public class DefaultDownloaderFactory implements DownloaderFactory { } } + // Checker framework has no way of knowing whether arguments to newInstance can be null or not, + // and so opts to be conservative and assumes they cannot. In this case executorService can be + // nullable, so we suppress the warning. + @SuppressWarnings("nullness:argument.type.incompatible") private Downloader createDownloader( DownloadRequest request, @Nullable Constructor constructor) { if (constructor == null) { throw new IllegalStateException("Module missing for: " + request.type); } try { - return constructor.newInstance(request.uri, request.streamKeys, cacheDataSourceFactory); + return constructor.newInstance( + request.uri, request.streamKeys, cacheDataSourceFactory, executorService); } catch (Exception e) { throw new RuntimeException("Failed to instantiate downloader for: " + request.type, e); } @@ -115,7 +139,8 @@ public class DefaultDownloaderFactory implements DownloaderFactory { try { return clazz .asSubclass(Downloader.class) - .getConstructor(Uri.class, List.class, CacheDataSource.Factory.class); + .getConstructor( + Uri.class, List.class, CacheDataSource.Factory.class, ExecutorService.class); } catch (NoSuchMethodException e) { // The downloader is present, but the expected constructor is missing. throw new RuntimeException("Downloader constructor missing", e); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/offline/ProgressiveDownloader.java b/library/core/src/main/java/com/google/android/exoplayer2/offline/ProgressiveDownloader.java index 03e1094596..1bae4c3655 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/offline/ProgressiveDownloader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/offline/ProgressiveDownloader.java @@ -23,6 +23,7 @@ import com.google.android.exoplayer2.upstream.cache.CacheDataSource; import com.google.android.exoplayer2.upstream.cache.CacheUtil; import com.google.android.exoplayer2.util.PriorityTaskManager; import java.io.IOException; +import java.util.concurrent.ExecutorService; import java.util.concurrent.atomic.AtomicBoolean; /** A downloader for progressive media streams. */ @@ -43,6 +44,26 @@ public final class ProgressiveDownloader implements Downloader { */ public ProgressiveDownloader( Uri uri, @Nullable String customCacheKey, CacheDataSource.Factory cacheDataSourceFactory) { + this(uri, customCacheKey, cacheDataSourceFactory, /* executorService= */ null); + } + + /** + * @param uri Uri of the data to be downloaded. + * @param customCacheKey A custom key that uniquely identifies the original stream. Used for cache + * indexing. May be null. + * @param cacheDataSourceFactory A {@link CacheDataSource.Factory} for the cache into which the + * download will be written. + * @param executorService An {@link ExecutorService} used to make requests for the media being + * downloaded. Must not be a direct executor, but may be {@code null} if the requests should + * be made directly from the thread that calls {@link #download(ProgressListener)}. In the + * future, providing an {@link ExecutorService} that uses multiple threads may speed up the + * download by allowing parts of it to be executed in parallel. + */ + public ProgressiveDownloader( + Uri uri, + @Nullable String customCacheKey, + CacheDataSource.Factory cacheDataSourceFactory, + @Nullable ExecutorService executorService) { dataSpec = new DataSpec.Builder() .setUri(uri) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/offline/SegmentDownloader.java b/library/core/src/main/java/com/google/android/exoplayer2/offline/SegmentDownloader.java index 007fca5b7e..eba62686a6 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/offline/SegmentDownloader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/offline/SegmentDownloader.java @@ -32,6 +32,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; +import java.util.concurrent.ExecutorService; import java.util.concurrent.atomic.AtomicBoolean; /** @@ -77,9 +78,17 @@ public abstract class SegmentDownloader> impleme * If empty, all streams are downloaded. * @param cacheDataSourceFactory A {@link CacheDataSource.Factory} for the cache into which the * download will be written. + * @param executorService An {@link ExecutorService} used to make requests for the media being + * downloaded. Must not be a direct executor, but may be {@code null} if the requests should + * be made directly from the thread that calls {@link #download(ProgressListener)}. Providing + * an {@link ExecutorService} that uses multiple threads will speed up the download by + * allowing parts of it to be executed in parallel. */ public SegmentDownloader( - Uri manifestUri, List streamKeys, CacheDataSource.Factory cacheDataSourceFactory) { + Uri manifestUri, + List streamKeys, + CacheDataSource.Factory cacheDataSourceFactory, + @Nullable ExecutorService executorService) { this.manifestDataSpec = getCompressibleDataSpec(manifestUri); this.streamKeys = new ArrayList<>(streamKeys); this.dataSource = cacheDataSourceFactory.createDataSourceForDownloading(); diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/offline/DashDownloader.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/offline/DashDownloader.java index 29fc28ab8d..a694fea433 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/offline/DashDownloader.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/offline/DashDownloader.java @@ -38,6 +38,7 @@ import com.google.android.exoplayer2.upstream.cache.CacheDataSource; import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.ExecutorService; /** * A downloader for DASH streams. @@ -73,7 +74,27 @@ public final class DashDownloader extends SegmentDownloader { */ public DashDownloader( Uri manifestUri, List streamKeys, CacheDataSource.Factory cacheDataSourceFactory) { - super(manifestUri, streamKeys, cacheDataSourceFactory); + this(manifestUri, streamKeys, cacheDataSourceFactory, /* executorService= */ null); + } + + /** + * @param manifestUri The {@link Uri} of the manifest to be downloaded. + * @param streamKeys Keys defining which representations in the manifest should be selected for + * download. If empty, all representations are downloaded. + * @param cacheDataSourceFactory A {@link CacheDataSource.Factory} for the cache into which the + * download will be written. + * @param executorService An {@link ExecutorService} used to make requests for the media being + * downloaded. Must not be a direct executor, but may be {@code null} if the requests should + * be made directly from the thread that calls {@link #download(ProgressListener)}. Providing + * an {@link ExecutorService} that uses multiple threads will speed up the download by + * allowing parts of it to be executed in parallel. + */ + public DashDownloader( + Uri manifestUri, + List streamKeys, + CacheDataSource.Factory cacheDataSourceFactory, + @Nullable ExecutorService executorService) { + super(manifestUri, streamKeys, cacheDataSourceFactory, executorService); } @Override diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloader.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloader.java index 43a393398b..5d5bffbfdd 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloader.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloader.java @@ -16,6 +16,7 @@ package com.google.android.exoplayer2.source.hls.offline; import android.net.Uri; +import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.offline.SegmentDownloader; import com.google.android.exoplayer2.offline.StreamKey; @@ -32,6 +33,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.HashSet; import java.util.List; +import java.util.concurrent.ExecutorService; /** * A downloader for HLS streams. @@ -67,7 +69,27 @@ public final class HlsDownloader extends SegmentDownloader { */ public HlsDownloader( Uri playlistUri, List streamKeys, CacheDataSource.Factory cacheDataSourceFactory) { - super(playlistUri, streamKeys, cacheDataSourceFactory); + this(playlistUri, streamKeys, cacheDataSourceFactory, /* executorService= */ null); + } + + /** + * @param playlistUri The {@link Uri} of the playlist to be downloaded. + * @param streamKeys Keys defining which renditions in the playlist should be selected for + * download. If empty, all renditions are downloaded. + * @param cacheDataSourceFactory A {@link CacheDataSource.Factory} for the cache into which the + * download will be written. + * @param executorService An {@link ExecutorService} used to make requests for the media being + * downloaded. Must not be a direct executor, but may be {@code null} if the requests should + * be made directly from the thread that calls {@link #download(ProgressListener)}. Providing + * an {@link ExecutorService} that uses multiple threads will speed up the download by + * allowing parts of it to be executed in parallel. + */ + public HlsDownloader( + Uri playlistUri, + List streamKeys, + CacheDataSource.Factory cacheDataSourceFactory, + @Nullable ExecutorService executorService) { + super(playlistUri, streamKeys, cacheDataSourceFactory, executorService); } @Override diff --git a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/offline/SsDownloader.java b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/offline/SsDownloader.java index 0eb2cdafcb..64d3e43868 100644 --- a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/offline/SsDownloader.java +++ b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/offline/SsDownloader.java @@ -16,6 +16,7 @@ package com.google.android.exoplayer2.source.smoothstreaming.offline; import android.net.Uri; +import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.offline.SegmentDownloader; import com.google.android.exoplayer2.offline.StreamKey; @@ -30,6 +31,7 @@ import com.google.android.exoplayer2.upstream.cache.CacheDataSource; import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.ExecutorService; /** * A downloader for SmoothStreaming streams. @@ -66,7 +68,27 @@ public final class SsDownloader extends SegmentDownloader { */ public SsDownloader( Uri manifestUri, List streamKeys, CacheDataSource.Factory cacheDataSourceFactory) { - super(SsUtil.fixManifestUri(manifestUri), streamKeys, cacheDataSourceFactory); + this(manifestUri, streamKeys, cacheDataSourceFactory, /* executorService= */ null); + } + + /** + * @param manifestUri The {@link Uri} of the manifest to be downloaded. + * @param streamKeys Keys defining which streams in the manifest should be selected for download. + * If empty, all streams are downloaded. + * @param cacheDataSourceFactory A {@link CacheDataSource.Factory} for the cache into which the + * download will be written. + * @param executorService An {@link ExecutorService} used to make requests for the media being + * downloaded. Must not be a direct executor, but may be {@code null} if the requests should + * be made directly from the thread that calls {@link #download(ProgressListener)}. Providing + * an {@link ExecutorService} that uses multiple threads will speed up the download by + * allowing parts of it to be executed in parallel. + */ + public SsDownloader( + Uri manifestUri, + List streamKeys, + CacheDataSource.Factory cacheDataSourceFactory, + @Nullable ExecutorService executorService) { + super(SsUtil.fixManifestUri(manifestUri), streamKeys, cacheDataSourceFactory, executorService); } @Override