Cómo descargar contenido multimedia

ExoPlayer proporciona funcionalidad para descargar contenido multimedia y reproducirlo sin conexión. En la mayoría casos de uso, te recomendamos que las descargas continúen incluso cuando tu app se encuentre en segundo plano. Para estos casos de uso, tu app debe crear una subclase de DownloadService y enviar comandos al servicio para agregar, quitar y controlar las descargas. El En el siguiente diagrama, se muestran las clases principales involucradas.

Clases para descargar contenido multimedia Las direcciones de las flechas indican el flujo de datos.

  • DownloadService: Une un DownloadManager y le reenvía comandos. El servicio permite que DownloadManager se siga ejecutando incluso cuando la app está en segundo plano.
  • DownloadManager: Administra varias descargas y carga (y almacena) sus estados desde (y hacia) un DownloadIndex, iniciar y detener descargas según según requisitos como la conectividad de red, etcétera. Para descargar la el administrador, por lo general, leerá los datos que se descargan de un HttpDataSource y escríbelo en un Cache.
  • DownloadIndex: Conserva los estados de las descargas.

Cómo crear un DownloadService

Para crear un elemento DownloadService, crea una subclase e implementa su métodos abstractos:

  • getDownloadManager(): Muestra el DownloadManager que se usará.
  • getScheduler(): Muestra un Scheduler opcional, que puede reiniciar el servicio cuando se cumplen los requisitos necesarios para que las descargas pendientes progresen. ExoPlayer proporciona estas implementaciones:
  • getForegroundNotification(): Muestra una notificación que se mostrará cuando servicio se ejecuta en primer plano. Puedes usar DownloadNotificationHelper.buildProgressNotification para crear un en 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 ExoPlayer de demostración de Google Cloud para dar 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 puede mostrar getDownloadManager() 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 archivo DownloadRequest y envíalo a tu DownloadService Para las transmisiones adaptables, usa DownloadHelper como ayuda compila un DownloadRequest. Lo siguiente En este 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 para el contenido. En casos simples, el contentUri suele usarse como contentId; sin embargo, las apps se pueden usar sin costo. el 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 lo siguiente: 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 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 descargar:

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 El parámetro foreground controla si el servicio se iniciará en el primer plano. Si tu app ya está en primer plano, foreground se debería establecer como false, ya que DownloadService se coloca en primer plano si determina que debe hacer algo.

Quitando descargas

Para quitar una descarga, se 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.
  • No se pausan las descargas.
  • Se cumplen los requisitos para que se desarrollen las descargas. Los requisitos pueden especificar restricciones sobre los tipos de red permitidos, así como si el dispositivo estar inactivo o conectado a un cargador.
  • No se supera la cantidad máxima de descargas paralelas.

Todas estas condiciones se pueden controlar enviando comandos a tu DownloadService

Cómo configurar y borrar motivos de detención de descargas

Puedes establecer el motivo por el que se detendrán 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, lo que significa que no se detiene la descarga). Apps que tienen varios motivos para detener las descargas pueden usar valores diferentes para realizar un seguimiento de por qué se detiene cada descarga. Establecer y borrar el motivo de detención para todos de descargas funciona de la misma manera que establecer y borrar el motivo de detención de una una sola descarga, con la excepción de que contentId debe establecerse en null.

Cuando el motivo de una descarga con un valor distinto de cero, aparecerá en Download.STATE_STOPPED. Los motivos de detención se mantienen en la DownloadIndex, y así se retienen si el proceso de la aplicación se cierra y más tarde.

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, este enfoque no conserva ningún estado cambios. Solo afecta el estado del tiempo de ejecución de DownloadManager.

Configura los requisitos para que las descargas progresen

Requirements se puede usar para especificar las restricciones que deben cumplirse para descargas para continuar. Para establecer los requisitos, llama a DownloadManager.setRequirements() cuando crees 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 puede continuar porque no se cumplen los requisitos, esta estarán en el estado Download.STATE_QUEUED. Puedes consultar los errores incompletos los requisitos con DownloadManager.getNotMetRequirements().

Configura la cantidad máxima de descargas paralelas

Se puede establecer la cantidad máxima de descargas paralelas DownloadManager.setMaxParallelDownloads() Normalmente, esto se haría creando el DownloadManager, como en el ejemplo anterior.

Cuando una descarga no puede continuar porque se alcanzó la cantidad máxima de descargas paralelas ya están en curso, estará en el estado Download.STATE_QUEUED.

Cómo consultar descargas

Se puede consultar el DownloadIndex de un DownloadManager para conocer el estado de todos descargas, incluidas las que se completaron o no se pudieron realizar. El DownloadIndex se puede obtener llamando a DownloadManager.getDownloadIndex(). Un cursor que las iteraciones se pueden obtener llamando DownloadIndex.getDownloads() De forma alternativa, el estado de una sola descarga se puede consultar llamando a DownloadIndex.getDownload().

DownloadManager también proporciona DownloadManager.getCurrentDownloads(), que Solo devuelve el estado de las descargas actuales (es decir, no completadas o fallidas). Esta es útil para actualizar las notificaciones y otros componentes de la IU que se muestran el progreso y el estado de las descargas actuales.

Escuchando descargas

Puedes agregar un objeto de escucha a DownloadManager para que se te notifique cuando el elemento esté activo. estado de cambio de las descargas:

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 demo para lo siguiente: un ejemplo concreto.

Cómo reproducir contenido descargado

La reproducción de contenido descargado es similar a la reproducción de contenido en línea, con la excepción de que los datos se leen desde la descarga Cache en lugar de a través de la red.

Para reproducir contenido descargado, crea un CacheDataSource.Factory con el mismo instancia de Cache que se usó para la descarga y, luego, inyéctala 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 también se usa la misma instancia del reproductor para reproducir contenido no descargado entonces, CacheDataSource.Factory debe configurarse como de solo lectura para evitar descargar ese contenido también durante la reproducción.

Una vez que el reproductor se haya configurado con el CacheDataSource.Factory, tener acceso al contenido descargado para su reproducción. Luego, se reproduce una descarga es tan simple como pasar el MediaItem correspondiente al reproductor. Un MediaItem Se puede obtener de un elemento Download con Download.request.toMediaItem. directamente desde un objeto DownloadRequest con DownloadRequest.toMediaItem.

Configuración de MediaSource

El ejemplo anterior hace que la caché de descarga esté disponible para la reproducción de todos 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. Suele haber varias pistas con el mismo contenido en calidades distintas (p.ej., pistas de video en SD, HD y 4K). También puede haber varias pistas del mismo tipo con contenido diferente (por ejemplo, varias pistas de audio en diferentes idiomas).

Para las reproducciones en streaming, se puede usar un selector de pistas para elegir cuál de los se reproducen las pistas. De manera similar, para la descarga, se puede usar un DownloadHelper para lo siguiente: elige cuál de las pistas quieres descargar. Uso típico de un DownloadHelper sigue estos pasos:

  1. Compila un DownloadHelper mediante una de las siguientes opciones: DownloadHelper.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);
    
  2. De manera opcional, inspecciona los segmentos seleccionados predeterminados con getMappedTrackInfo y getTrackSelections, y realiza ajustes con clearTrackSelections, replaceTrackSelections y addTrackSelection.
  3. Crea un DownloadRequest para los segmentos seleccionados llamando a getDownloadRequest La solicitud se puede pasar a tu DownloadService para agregar la descarga, como se describió anteriormente.
  4. Libera el asistente con release().

La reproducción de contenido adaptable descargado requiere configurar el reproductor y y pasar el MediaItem correspondiente, como se describió anteriormente.

Cuando compilas el MediaItem, MediaItem.localConfiguration.streamKeys debe ser para que coincida con los de DownloadRequest de modo que el jugador solo intente reproducir el subconjunto de pistas descargadas. Usando Download.request.toMediaItem y DownloadRequest.toMediaItem para compilar la MediaItem se encargará de esto por ti.