DefaultDownloaderFactory.java
/*
* Copyright (C) 2018 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 android.util.SparseArray;
import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.MediaItem;
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;
/**
* Default {@link DownloaderFactory}, supporting creation of progressive, DASH, HLS and
* SmoothStreaming downloaders. Note that for the latter three, the corresponding library module
* must be built into the application.
*/
@UnstableApi
public class DefaultDownloaderFactory implements DownloaderFactory {
private static final SparseArray<Constructor<? extends Downloader>> CONSTRUCTORS =
createDownloaderConstructors();
private final CacheDataSource.Factory cacheDataSourceFactory;
private final Executor executor;
/**
* Creates an instance.
*
* @param cacheDataSourceFactory A {@link CacheDataSource.Factory} for the cache into which
* downloads will be written.
* @deprecated Use {@link #DefaultDownloaderFactory(CacheDataSource.Factory, Executor)}.
*/
@Deprecated
public DefaultDownloaderFactory(CacheDataSource.Factory cacheDataSourceFactory) {
this(cacheDataSourceFactory, /* executor= */ Runnable::run);
}
/**
* Creates an instance.
*
* @param cacheDataSourceFactory A {@link CacheDataSource.Factory} for the cache into which
* downloads will be written.
* @param executor An {@link Executor} used to download data. Passing {@code Runnable::run} will
* cause each download task to download data on its own thread. Passing an {@link Executor}
* that uses multiple threads will speed up download tasks that can be split into smaller
* parts for parallel execution.
*/
public DefaultDownloaderFactory(
CacheDataSource.Factory cacheDataSourceFactory, Executor executor) {
this.cacheDataSourceFactory = Assertions.checkNotNull(cacheDataSourceFactory);
this.executor = Assertions.checkNotNull(executor);
}
@Override
public Downloader createDownloader(DownloadRequest request) {
@C.ContentType
int contentType = Util.inferContentTypeForUriAndMimeType(request.uri, request.mimeType);
switch (contentType) {
case C.TYPE_DASH:
case C.TYPE_HLS:
case C.TYPE_SS:
return createDownloader(request, contentType);
case C.TYPE_OTHER:
return new ProgressiveDownloader(
new MediaItem.Builder()
.setUri(request.uri)
.setCustomCacheKey(request.customCacheKey)
.build(),
cacheDataSourceFactory,
executor);
default:
throw new IllegalArgumentException("Unsupported type: " + contentType);
}
}
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);
}
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);
}
}
private static SparseArray<Constructor<? extends Downloader>> createDownloaderConstructors() {
SparseArray<Constructor<? extends Downloader>> array = new SparseArray<>();
try {
array.put(
C.TYPE_DASH,
getDownloaderConstructor(
Class.forName("androidx.media3.exoplayer.dash.offline.DashDownloader")));
} catch (ClassNotFoundException e) {
// Expected if the app was built without the DASH module.
}
try {
array.put(
C.TYPE_HLS,
getDownloaderConstructor(
Class.forName("androidx.media3.exoplayer.hls.offline.HlsDownloader")));
} catch (ClassNotFoundException e) {
// Expected if the app was built without the HLS module.
}
try {
array.put(
C.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;
}
private static Constructor<? extends Downloader> getDownloaderConstructor(Class<?> clazz) {
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);
}
}
}