Téléchargement de contenu multimédia

ExoPlayer propose une fonctionnalité permettant de télécharger des contenus multimédias pour les lire hors connexion. Dans la plupart des cas, il est souhaitable que les téléchargements continuent même lorsque votre application est en arrière-plan. Pour ces cas d'utilisation, votre application doit sous-classer DownloadService et envoyer des commandes au service pour ajouter, supprimer et contrôler les téléchargements. Le diagramme suivant montre les principales classes impliquées.

Classes pour télécharger des contenus multimédias Les flèches indiquent le flux de données.

  • DownloadService: encapsule un DownloadManager et lui transfère des commandes. Le service permet à DownloadManager de continuer à s'exécuter même lorsque l'application est en arrière-plan.
  • DownloadManager: gère plusieurs téléchargements, charge (et stocke) leurs états à partir (et vers) un DownloadIndex, démarre et arrête les téléchargements en fonction de leurs exigences, telles que la connectivité réseau, etc. Pour télécharger le contenu, le gestionnaire lit généralement les données téléchargées à partir d'un HttpDataSource et les écrit dans un Cache.
  • DownloadIndex: conserve les états des téléchargements.

Créer un DownloadService

Pour créer un DownloadService, sous-classez-le et implémentez ses méthodes abstraites:

  • getDownloadManager(): renvoie le DownloadManager à utiliser.
  • getScheduler(): renvoie un Scheduler facultatif, qui peut redémarrer le service lorsque les conditions requises pour la progression des téléchargements en attente sont remplies. ExoPlayer fournit les implémentations suivantes :
    • PlatformScheduler, qui utilise JobScheduler (API minimale : 21). Consultez les javadocs de PlatformScheduler pour connaître les exigences concernant les autorisations d'application.
    • WorkManagerScheduler, qui utilise WorkManager.
  • getForegroundNotification(): renvoie une notification à afficher lorsque le service s'exécute au premier plan. Vous pouvez utiliser DownloadNotificationHelper.buildProgressNotification pour créer une notification dans le style par défaut.

Enfin, définissez le service dans votre fichier 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>

Pour obtenir un exemple concret, consultez DemoDownloadService et AndroidManifest.xml dans l'application de démonstration ExoPlayer.

Créer un DownloadManager

L'extrait de code suivant montre comment instancier un DownloadManager, qui peut être renvoyé par getDownloadManager() dans votre 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);

Pour obtenir un exemple concret, consultez DemoUtil dans l'application de démonstration.

Ajouter un téléchargement

Pour ajouter un téléchargement, créez un DownloadRequest et envoyez-le à votre DownloadService. Pour les flux adaptatifs, utilisez DownloadHelper pour créer un DownloadRequest. L'exemple suivant montre comment créer une requête de téléchargement:

Kotlin

val downloadRequest = DownloadRequest.Builder(contentId, contentUri).build()

Java

DownloadRequest downloadRequest = new DownloadRequest.Builder(contentId, contentUri).build();

Dans cet exemple, contentId est un identifiant unique du contenu. Dans les cas simples, le contentUri peut souvent être utilisé comme contentId. Toutefois, les applications sont libres d'utiliser le schéma d'ID qui convient le mieux à leur cas d'utilisation. DownloadRequest.Builder comporte également des setters facultatifs. Par exemple, setKeySetId et setData peuvent être utilisés pour définir respectivement les DRM et les données personnalisées que l'application souhaite associer au téléchargement. Le type MIME du contenu peut également être spécifié à l'aide de setMimeType, comme indice dans les cas où le type de contenu ne peut pas être déduit de contentUri.

Une fois créée, la requête peut être envoyée à DownloadService pour ajouter le téléchargement:

Kotlin

DownloadService.sendAddDownload(
  context,
  MyDownloadService::class.java,
  downloadRequest,
  /* foreground= */ false
)

Java

DownloadService.sendAddDownload(
    context, MyDownloadService.class, downloadRequest, /* foreground= */ false);

Dans cet exemple, MyDownloadService est la sous-classe DownloadService de l'application, et le paramètre foreground contrôle si le service doit être démarré au premier plan. Si votre application est déjà au premier plan, le paramètre foreground doit normalement être défini sur false, car DownloadService se place au premier plan s'il détermine qu'il a des tâches à effectuer.

Suppression des téléchargements…

Vous pouvez supprimer un téléchargement en envoyant une commande de suppression à DownloadService, où contentId identifie le téléchargement à supprimer:

Kotlin

DownloadService.sendRemoveDownload(
  context,
  MyDownloadService::class.java,
  contentId,
  /* foreground= */ false
)

Java

DownloadService.sendRemoveDownload(
    context, MyDownloadService.class, contentId, /* foreground= */ false);

Vous pouvez également supprimer toutes les données téléchargées avec DownloadService.sendRemoveAllDownloads.

Démarrer et arrêter des téléchargements

Un téléchargement ne progresse que si quatre conditions sont remplies:

  • Le téléchargement n'a pas de motif d'arrêt.
  • Les téléchargements ne sont pas suspendus.
  • Les conditions requises pour que les téléchargements puissent progresser sont remplies. Les exigences peuvent spécifier des contraintes sur les types de réseaux autorisés, ainsi que si l'appareil doit être inactif ou connecté à un chargeur.
  • Le nombre maximal de téléchargements parallèles n'est pas dépassé.

Toutes ces conditions peuvent être contrôlées en envoyant des commandes à votre DownloadService.

Définir et supprimer les motifs d'arrêt des téléchargements

Vous pouvez définir un motif pour l'arrêt d'un ou de tous les téléchargements:

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 peut être n'importe quelle valeur non nulle (Download.STOP_REASON_NONE = 0 est une valeur spéciale indiquant que le téléchargement n'est pas arrêté). Les applications qui ont plusieurs raisons d'arrêter les téléchargements peuvent utiliser différentes valeurs pour déterminer la raison pour laquelle chaque téléchargement est arrêté. Définir et effacer la raison d'arrêt pour tous les téléchargements fonctionne de la même manière que définir et effacer la raison d'arrêt pour un seul téléchargement, sauf que contentId doit être défini sur null.

Lorsqu'un téléchargement a un motif d'arrêt non nul, il est dans l'état Download.STATE_STOPPED. Les motifs d'arrêt sont conservés dans DownloadIndex et sont donc conservés si le processus d'application est arrêté, puis redémarré.

Suspendre et reprendre tous les téléchargements

Vous pouvez suspendre et reprendre tous les téléchargements comme suit:

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);

Lorsque les téléchargements sont suspendus, ils sont à l'état Download.STATE_QUEUED. Contrairement à la définition des motifs d'arrêt, cette approche ne conserve aucun changement d'état. Il ne concerne que l'état d'exécution de DownloadManager.

Définir les conditions requises pour la progression des téléchargements

Requirements permet de spécifier les contraintes à respecter pour que les téléchargements se poursuivent. Les exigences peuvent être définies en appelant DownloadManager.setRequirements() lors de la création de DownloadManager, comme dans l'exemple ci-dessus. Ils peuvent également être modifiés de manière dynamique en envoyant une commande à 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);

Lorsqu'un téléchargement ne peut pas se poursuivre parce que les exigences ne sont pas remplies, il est à l'état Download.STATE_QUEUED. Vous pouvez interroger les exigences non remplies avec DownloadManager.getNotMetRequirements().

Définir le nombre maximal de téléchargements parallèles

Vous pouvez définir le nombre maximal de téléchargements parallèles en appelant DownloadManager.setMaxParallelDownloads(). Cela se fait généralement lors de la création de DownloadManager, comme dans l'exemple ci-dessus.

Lorsqu'un téléchargement ne peut pas être effectué, car le nombre maximal de téléchargements parallèles est déjà en cours, il est à l'état Download.STATE_QUEUED.

Interroger les téléchargements

Vous pouvez interroger l'DownloadIndex d'un DownloadManager pour connaître l'état de tous les téléchargements, y compris ceux qui ont abouti ou échoué. Vous pouvez obtenir DownloadIndex en appelant DownloadManager.getDownloadIndex(). Vous pouvez ensuite obtenir un curseur qui itère sur tous les téléchargements en appelant DownloadIndex.getDownloads(). Vous pouvez également interroger l'état d'un téléchargement individuel en appelant DownloadIndex.getDownload().

DownloadManager fournit également DownloadManager.getCurrentDownloads(), qui renvoie uniquement l'état des téléchargements en cours (c'est-à-dire non terminés ou échoués). Cette méthode est utile pour mettre à jour les notifications et d'autres composants d'interface utilisateur qui affichent la progression et l'état des téléchargements en cours.

Écouter des téléchargements

Vous pouvez ajouter un écouteur à DownloadManager pour être informé lorsque l'état des téléchargements en cours change:

Kotlin

downloadManager.addListener(
  object : DownloadManager.Listener { // Override methods of interest here.
  }
)

Java

downloadManager.addListener(
    new DownloadManager.Listener() {
      // Override methods of interest here.
    });

Pour un exemple concret, consultez DownloadManagerListener dans la classe DownloadTracker de l'application de démonstration.

Lire du contenu téléchargé

La lecture de contenus téléchargés est semblable à la lecture de contenus en ligne, à la différence que les données sont lues à partir du Cache de téléchargement plutôt que sur le réseau.

Pour lire le contenu téléchargé, créez un CacheDataSource.Factory à l'aide de la même instance Cache utilisée pour le téléchargement, puis injectez-le dans DefaultMediaSourceFactory lors de la création du lecteur:

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 même instance de lecteur est également utilisée pour lire du contenu non téléchargé, CacheDataSource.Factory doit être configuré en lecture seule pour éviter de télécharger ce contenu également pendant la lecture.

Une fois le lecteur configuré avec le CacheDataSource.Factory, il aura accès au contenu téléchargé pour le lire. Pour lire un téléchargement, il vous suffit de transmettre le MediaItem correspondant au lecteur. Vous pouvez obtenir un MediaItem à partir d'un Download à l'aide de Download.request.toMediaItem ou directement à partir d'un DownloadRequest à l'aide de DownloadRequest.toMediaItem.

Configuration de MediaSource

L'exemple précédent met le cache de téléchargement à la disposition de la lecture de tous les MediaItem. Vous pouvez également rendre le cache de téléchargement disponible pour des instances MediaSource individuelles, qui peuvent être transmises directement au joueur:

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();

Télécharger et lire des flux adaptatifs

Les flux adaptatifs (par exemple, DASH, SmoothStreaming et HLS) contiennent généralement plusieurs pistes multimédias. Il existe souvent plusieurs pistes contenant le même contenu dans différentes qualités (par exemple, des pistes vidéo SD, HD et 4K). Il peut également y avoir plusieurs pistes du même type contenant du contenu différent (par exemple, plusieurs pistes audio dans différentes langues).

Pour les lectures en streaming, un sélecteur de titres permet de choisir les titres à lire. De même, pour le téléchargement, un DownloadHelper peut être utilisé pour choisir les pistes à télécharger. L'utilisation typique d'un DownloadHelper se déroule comme suit:

  1. Créez un DownloadHelper à l'aide de l'une des méthodes DownloadHelper.forMediaItem. Préparez l'application auxiliaire et attendez le rappel.

    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. Si vous le souhaitez, inspectez les canaux sélectionnés par défaut à l'aide de getMappedTrackInfo et getTrackSelections, puis effectuez des ajustements à l'aide de clearTrackSelections, replaceTrackSelections et addTrackSelection.
  3. Créez un DownloadRequest pour les pistes sélectionnées en appelant getDownloadRequest. La requête peut être transmise à votre DownloadService pour ajouter le téléchargement, comme décrit ci-dessus.
  4. Libérez l'outil d'aide à l'aide de release().

La lecture du contenu adaptatif téléchargé nécessite de configurer le lecteur et de transmettre le MediaItem correspondant, comme décrit ci-dessus.

Lors de la création de MediaItem, MediaItem.localConfiguration.streamKeys doit être défini pour correspondre à ceux de DownloadRequest afin que le lecteur ne tente de lire que le sous-ensemble de titres téléchargés. Utilisez Download.request.toMediaItem et DownloadRequest.toMediaItem pour créer MediaItem.