ExoPlayer proporciona funcionalidad para descargar contenido multimedia y reproducirlo sin conexión. En la mayoría de los casos de uso, es conveniente que las descargas continúen incluso cuando la app está en segundo plano. En estos casos de uso, tu app debe crear una subclase de DownloadService
y enviar comandos al servicio para agregar, quitar y controlar las descargas. En el siguiente diagrama, se muestran las clases principales que participan.
DownloadService
: Une unDownloadManager
y le reenvía comandos. El servicio permite queDownloadManager
siga ejecutándose incluso cuando la app está en segundo plano.DownloadManager
: Administra varias descargas y carga (y almacena) sus estados desde (y hasta) unDownloadIndex
e inicia y detén las descargas según requisitos como la conectividad de red, etcétera. Para descargar el contenido, el administrador suele leer los datos que se descargan de unHttpDataSource
y escribirlos en unCache
.DownloadIndex
: Conserva los estados de las descargas.
Cómo crear un DownloadService
Para crear un DownloadService
, crea una subclase y, luego, implementa sus métodos abstractos:
getDownloadManager()
: Muestra elDownloadManager
que se usará.getScheduler()
: Muestra unScheduler
opcional, que puede reiniciar el servicio cuando se cumplen los requisitos necesarios para que las descargas pendientes progresen. ExoPlayer proporciona estas implementaciones:PlatformScheduler
, que usa JobScheduler (la API mínima es 21) Consulta los javadocs de PlatformScheduler para conocer los requisitos de permisos de la app.WorkManagerScheduler
, que usa WorkManager.
getForegroundNotification()
: Muestra una notificación que se mostrará cuando el servicio se ejecute en primer plano. Puedes usarDownloadNotificationHelper.buildProgressNotification
para crear una notificación con el estilo predeterminado.
Por último, define el servicio en tu archivo AndroidManifest.xml
:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC"/>
<application>
<service android:name="com.myapp.MyDownloadService"
android:exported="false"
android:foregroundServiceType="dataSync">
<!-- This is needed for Scheduler -->
<intent-filter>
<action android:name="androidx.media3.exoplayer.downloadService.action.RESTART"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</service>
</application>
Consulta DemoDownloadService
y AndroidManifest.xml
en la app de demo de ExoPlayer para ver un ejemplo concreto.
Cómo crear un DownloadManager
En el siguiente fragmento de código, se muestra cómo crear una instancia de DownloadManager
, que getDownloadManager()
puede mostrar en tu DownloadService
:
Kotlin
// Note: This should be a singleton in your app. val databaseProvider = StandaloneDatabaseProvider(context) // A download cache should not evict media, so should use a NoopCacheEvictor. val downloadCache = SimpleCache(downloadDirectory, NoOpCacheEvictor(), databaseProvider) // Create a factory for reading the data from the network. val dataSourceFactory = DefaultHttpDataSource.Factory() // Choose an executor for downloading data. Using Runnable::run will cause each download task to // download data on its own thread. Passing an executor that uses multiple threads will speed up // download tasks that can be split into smaller parts for parallel execution. Applications that // already have an executor for background downloads may wish to reuse their existing executor. val downloadExecutor = Executor(Runnable::run) // Create the download manager. val downloadManager = DownloadManager(context, databaseProvider, downloadCache, dataSourceFactory, downloadExecutor) // Optionally, properties can be assigned to configure the download manager. downloadManager.requirements = requirements downloadManager.maxParallelDownloads = 3
Java
// Note: This should be a singleton in your app. databaseProvider = new StandaloneDatabaseProvider(context); // A download cache should not evict media, so should use a NoopCacheEvictor. downloadCache = new SimpleCache(downloadDirectory, new NoOpCacheEvictor(), databaseProvider); // Create a factory for reading the data from the network. dataSourceFactory = new DefaultHttpDataSource.Factory(); // Choose an executor for downloading data. Using Runnable::run will cause each download task to // download data on its own thread. Passing an executor that uses multiple threads will speed up // download tasks that can be split into smaller parts for parallel execution. Applications that // already have an executor for background downloads may wish to reuse their existing executor. Executor downloadExecutor = Runnable::run; // Create the download manager. downloadManager = new DownloadManager( context, databaseProvider, downloadCache, dataSourceFactory, downloadExecutor); // Optionally, setters can be called to configure the download manager. downloadManager.setRequirements(requirements); downloadManager.setMaxParallelDownloads(3);
Consulta DemoUtil
en la app de demo para ver un ejemplo concreto.
Cómo agregar una descarga
Para agregar una descarga, crea un DownloadRequest
y envíalo a tu DownloadService
. Para las transmisiones adaptables, usa DownloadHelper
para ayudar a compilar un DownloadRequest
. En el siguiente ejemplo, se muestra cómo crear una solicitud de descarga:
Kotlin
val downloadRequest = DownloadRequest.Builder(contentId, contentUri).build()
Java
DownloadRequest downloadRequest = new DownloadRequest.Builder(contentId, contentUri).build();
En este ejemplo, contentId
es un identificador único del contenido. En casos simples, el contentUri
a menudo se puede usar como contentId
. Sin embargo, las apps pueden usar cualquier esquema de ID que mejor se adapte a su caso de uso. DownloadRequest.Builder
también tiene algunos métodos set opcionales. Por ejemplo, setKeySetId
y setData
se pueden usar para configurar la DRM y los datos personalizados que la app desea asociar con la descarga, respectivamente. El tipo de MIME del contenido también se puede especificar con setMimeType
, como una sugerencia para los casos en los que el tipo de contenido no se puede inferir de contentUri
.
Una vez creada, la solicitud se puede enviar a DownloadService
para agregar la descarga:
Kotlin
DownloadService.sendAddDownload( context, MyDownloadService::class.java, downloadRequest, /* foreground= */ false )
Java
DownloadService.sendAddDownload( context, MyDownloadService.class, downloadRequest, /* foreground= */ false);
En este ejemplo, MyDownloadService
es la subclase DownloadService
de la app, y el parámetro foreground
controla si el servicio se iniciará en primer plano. Si tu app ya está en primer plano, el parámetro foreground
, por lo general, debe establecerse en false
, ya que DownloadService
se colocará en primer plano si determina que tiene trabajo por hacer.
Quitando descargas
Para quitar una descarga, envía un comando de eliminación a DownloadService
,
donde contentId
identifica la descarga que se quitará:
Kotlin
DownloadService.sendRemoveDownload( context, MyDownloadService::class.java, contentId, /* foreground= */ false )
Java
DownloadService.sendRemoveDownload( context, MyDownloadService.class, contentId, /* foreground= */ false);
También puedes quitar todos los datos descargados con DownloadService.sendRemoveAllDownloads
.
Inicia y detén descargas
Una descarga solo avanzará si se cumplen cuatro condiciones:
- La descarga no tiene un motivo para detenerla.
- Las descargas no se detienen.
- Se cumplen los requisitos para que se desarrollen las descargas. En los requisitos, se pueden especificar restricciones en los tipos de red permitidos y si el dispositivo debe estar inactivo o conectado a un cargador.
- No se supera la cantidad máxima de descargas en paralelo.
Todas estas condiciones se pueden controlar enviando comandos a tu DownloadService
.
Cómo configurar y borrar motivos de detención de descargas
Puedes establecer un motivo para que se detengan una o todas las descargas:
Kotlin
// Set the stop reason for a single download. DownloadService.sendSetStopReason( context, MyDownloadService::class.java, contentId, stopReason, /* foreground= */ false ) // Clear the stop reason for a single download. DownloadService.sendSetStopReason( context, MyDownloadService::class.java, contentId, Download.STOP_REASON_NONE, /* foreground= */ false )
Java
// Set the stop reason for a single download. DownloadService.sendSetStopReason( context, MyDownloadService.class, contentId, stopReason, /* foreground= */ false); // Clear the stop reason for a single download. DownloadService.sendSetStopReason( context, MyDownloadService.class, contentId, Download.STOP_REASON_NONE, /* foreground= */ false);
stopReason
puede ser cualquier valor distinto de cero (Download.STOP_REASON_NONE = 0
es un valor especial que significa que la descarga no se detiene). Las apps que tienen varios motivos para detener las descargas pueden usar diferentes valores para hacer un seguimiento de por qué se detiene cada descarga. La configuración y la limpieza del motivo de detención de todas las descargas funcionan de la misma manera que la configuración y la limpieza del motivo de detención de una sola descarga, excepto que contentId
debe establecerse en null
.
Cuando una descarga tenga un motivo de detención distinto de cero, estará en el
estado Download.STATE_STOPPED
. Los motivos de detención se conservan en DownloadIndex
, por lo que se retienen si se finaliza el proceso de la aplicación y se reinicia más adelante.
Cómo detener y reanudar todas las descargas
Todas las descargas se pueden pausar y reanudar de la siguiente manera:
Kotlin
// Pause all downloads. DownloadService.sendPauseDownloads( context, MyDownloadService::class.java, /* foreground= */ false ) // Resume all downloads. DownloadService.sendResumeDownloads( context, MyDownloadService::class.java, /* foreground= */ false )
Java
// Pause all downloads. DownloadService.sendPauseDownloads(context, MyDownloadService.class, /* foreground= */ false); // Resume all downloads. DownloadService.sendResumeDownloads(context, MyDownloadService.class, /* foreground= */ false);
Cuando se detengan las descargas, tendrán el estado Download.STATE_QUEUED
.
A diferencia de los motivos de detención de configuración, este enfoque no conserva ningún cambio de estado. Solo afecta el estado del entorno de ejecución de DownloadManager
.
Cómo configurar los requisitos para que las descargas progresen
Se puede usar Requirements
para especificar las restricciones que se deben cumplir para que se completen las descargas. Para establecer los requisitos, llama a DownloadManager.setRequirements()
cuando crees el DownloadManager
, como en el ejemplo anterior. También se pueden cambiar de forma dinámica si se envía un comando a DownloadService
:
Kotlin
// Set the download requirements. DownloadService.sendSetRequirements( context, MyDownloadService::class.java, requirements, /* foreground= */ false)
Java
// Set the download requirements. DownloadService.sendSetRequirements( context, MyDownloadService.class, requirements, /* foreground= */ false);
Cuando una descarga no pueda continuar porque no se cumplen los requisitos, estará en el estado Download.STATE_QUEUED
. Puedes consultar los requisitos que no se cumplieron con DownloadManager.getNotMetRequirements()
.
Cómo establecer la cantidad máxima de descargas paralelas
Para establecer la cantidad máxima de descargas en paralelo, llama a DownloadManager.setMaxParallelDownloads()
. Por lo general, esto se hace cuando se crea el DownloadManager
, como en el ejemplo anterior.
Si una descarga no puede continuar porque la cantidad máxima de descargas paralelas ya está en curso, se mostrará el estado Download.STATE_QUEUED
.
Cómo consultar descargas
Se puede consultar el DownloadIndex
de un DownloadManager
para conocer el estado de todas las descargas, incluidas las que se completaron o que fallaron. Para obtener el DownloadIndex
, llama a DownloadManager.getDownloadIndex()
. Luego, se puede obtener un cursor que itere sobre todas las descargas llamando a DownloadIndex.getDownloads()
. Como alternativa, puedes llamar a DownloadIndex.getDownload()
para consultar el estado de una sola descarga.
DownloadManager
también proporciona DownloadManager.getCurrentDownloads()
, que muestra solo el estado de las descargas actuales (es decir, que no se completaron o fallaron). Este método es útil para actualizar notificaciones y otros componentes de la IU que muestran el progreso y el estado de las descargas actuales.
Cómo escuchar descargas
Puedes agregar un objeto de escucha a DownloadManager
para que se te informe cuando las descargas
actuales cambien de estado:
Kotlin
downloadManager.addListener( object : DownloadManager.Listener { // Override methods of interest here. } )
Java
downloadManager.addListener( new DownloadManager.Listener() { // Override methods of interest here. });
Consulta DownloadManagerListener
en la clase DownloadTracker
de la app de demostración para ver un ejemplo concreto.
Cómo reproducir contenido descargado
Reproducir contenido descargado es similar a reproducir contenido en línea, excepto que los datos se leen desde el Cache
de descarga en lugar de hacerlo a través de la red.
Para reproducir contenido descargado, crea un CacheDataSource.Factory
con la misma instancia de Cache
que se usó para la descarga y, luego, insértalo en DefaultMediaSourceFactory
cuando compiles el reproductor:
Kotlin
// Create a read-only cache data source factory using the download cache. val cacheDataSourceFactory: DataSource.Factory = CacheDataSource.Factory() .setCache(downloadCache) .setUpstreamDataSourceFactory(httpDataSourceFactory) .setCacheWriteDataSinkFactory(null) // Disable writing. val player = ExoPlayer.Builder(context) .setMediaSourceFactory( DefaultMediaSourceFactory(context).setDataSourceFactory(cacheDataSourceFactory) ) .build()
Java
// Create a read-only cache data source factory using the download cache. DataSource.Factory cacheDataSourceFactory = new CacheDataSource.Factory() .setCache(downloadCache) .setUpstreamDataSourceFactory(httpDataSourceFactory) .setCacheWriteDataSinkFactory(null); // Disable writing. ExoPlayer player = new ExoPlayer.Builder(context) .setMediaSourceFactory( new DefaultMediaSourceFactory(context).setDataSourceFactory(cacheDataSourceFactory)) .build();
Si la misma instancia del reproductor también se usará para reproducir contenido no descargado, CacheDataSource.Factory
se debe configurar como de solo lectura para evitar que también se descargue ese contenido durante la reproducción.
Una vez que se haya configurado el reproductor con CacheDataSource.Factory
, este tendrá acceso al contenido descargado para su reproducción. Reproducir una descarga es tan simple como pasar el MediaItem
correspondiente al reproductor. Se puede obtener un MediaItem
de un Download
con Download.request.toMediaItem
, o directamente de un DownloadRequest
con DownloadRequest.toMediaItem
.
Configuración de MediaSource
En el ejemplo anterior, la caché de descarga está disponible para la reproducción de todos los MediaItem
. También puedes hacer que la caché de descarga esté disponible para instancias individuales de MediaSource
, que se pueden pasar directamente al reproductor:
Kotlin
val mediaSource = ProgressiveMediaSource.Factory(cacheDataSourceFactory) .createMediaSource(MediaItem.fromUri(contentUri)) player.setMediaSource(mediaSource) player.prepare()
Java
ProgressiveMediaSource mediaSource = new ProgressiveMediaSource.Factory(cacheDataSourceFactory) .createMediaSource(MediaItem.fromUri(contentUri)); player.setMediaSource(mediaSource); player.prepare();
Cómo descargar y reproducir transmisiones adaptables
Las transmisiones adaptables (p.ej., DASH, SmoothStreaming y HLS) suelen contener varias pistas multimedia. A menudo, hay varias pistas que contienen el mismo contenido en diferentes calidades (p.ej., pistas de video en SD, HD y 4K). También puede haber varias pistas del mismo tipo que contengan contenido diferente (p.ej., varias pistas de audio en diferentes idiomas).
Para las reproducciones de transmisión, se puede usar un selector de pistas para elegir cuáles se reproducen. Del mismo modo, durante la descarga, se puede usar un DownloadHelper
para elegir cuál de las pistas se descargarán. El uso típico de una DownloadHelper
sigue estos pasos:
- Compila un
DownloadHelper
con uno de los métodosDownloadHelper.forMediaItem
. Prepara el asistente y espera la devolución de llamada.Kotlin
val downloadHelper = DownloadHelper.forMediaItem( context, MediaItem.fromUri(contentUri), DefaultRenderersFactory(context), dataSourceFactory ) downloadHelper.prepare(callback)
Java
DownloadHelper downloadHelper = DownloadHelper.forMediaItem( context, MediaItem.fromUri(contentUri), new DefaultRenderersFactory(context), dataSourceFactory); downloadHelper.prepare(callback);
- De manera opcional, inspecciona las pistas seleccionadas de forma predeterminada con
getMappedTrackInfo
ygetTrackSelections
, y realiza ajustes conclearTrackSelections
,replaceTrackSelections
yaddTrackSelection
. - Llama a
getDownloadRequest
para crear unDownloadRequest
para las pistas seleccionadas. La solicitud se puede pasar a tuDownloadService
para agregar la descarga, como se describió anteriormente. - Libera el asistente con
release()
.
La reproducción de contenido adaptable descargado requiere configurar el reproductor y pasar el MediaItem
correspondiente, como se describió anteriormente.
Cuando se compila MediaItem
, MediaItem.localConfiguration.streamKeys
se debe configurar para que coincida con los de DownloadRequest
, de modo que el reproductor solo intente reproducir el subconjunto de pistas que se descargaron. Usa Download.request.toMediaItem
y DownloadRequest.toMediaItem
para compilar MediaItem
para todo por ti.