ExoPlayer fournit une fonctionnalité permettant de télécharger des contenus multimédias pour les lire hors connexion. Dans la plupart des cas d'utilisation, il est souhaitable que les téléchargements se poursuivent même lorsque votre application est exécutée 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 schéma suivant montre les principales classes impliquées.
DownloadService
: encapsule unDownloadManager
et lui transmet les commandes. Il permet àDownloadManager
de continuer à s'exécuter même lorsque l'application est en arrière-plan.DownloadManager
: permet de gérer plusieurs téléchargements, de charger (et de stocker) leurs états depuis (et vers) un élémentDownloadIndex
, ainsi que de démarrer et d'arrêter les téléchargements en fonction d'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'unHttpDataSource
et les écrit dans unCache
.DownloadIndex
: conserve les états des téléchargements.
Créer un service DownloadService
Pour créer un DownloadService
, sous-classez-le et implémentez ses méthodes abstraites:
getDownloadManager()
: renvoie leDownloadManager
à utiliser.getScheduler()
: renvoie unScheduler
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 (l'API minimale est 21). Consultez les javadocs PlatformScheduler pour connaître les exigences d'autorisations des applications.WorkManagerScheduler
, qui utilise WorkManager.
getForegroundNotification()
: renvoie une notification à afficher lorsque le service s'exécute au premier plan. Vous pouvez utiliserDownloadNotificationHelper.buildProgressNotification
pour créer une notification dans le style par défaut.
Enfin, définissez le service dans le 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>
Consultez DemoDownloadService
et AndroidManifest.xml
dans l'application de démonstration ExoPlayer pour obtenir un exemple concret.
Créer un gestionnaire de téléchargement
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 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
afin de 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 pour le contenu. Dans des cas simples, contentUri
peut souvent être utilisé comme contentId
, mais les applications sont libres d'utiliser le schéma d'ID le plus adapté à leur cas d'utilisation. DownloadRequest.Builder
comporte également des setters facultatifs. Par exemple, setKeySetId
et setData
peuvent être utilisés pour définir la DRM et les données personnalisées que l'application souhaite associer au téléchargement, respectivement. Le type MIME du contenu peut également être spécifié à l'aide de setMimeType
, afin d'indiquer 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. Le paramètre foreground
détermine si le service sera démarré au premier plan. Si votre application est déjà exécutée 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 du travail à 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 se déroule que si les quatre conditions suivantes sont remplies:
- Le téléchargement n'est associé à aucun motif d'arrêt.
- Les téléchargements ne sont pas suspendus.
- Les conditions requises pour la progression des téléchargements sont remplies. Les exigences peuvent spécifier des contraintes sur les types de réseaux autorisés et indiquer 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 effacer les motifs d'arrêt du téléchargement
Vous pouvez définir le motif de 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 qui indique que le téléchargement n'est pas arrêté). Les applications qui présentent plusieurs raisons d'arrêter les téléchargements peuvent utiliser différentes valeurs pour savoir pourquoi chaque téléchargement est arrêté. La définition et l'effacement du motif d'arrêt pour tous les téléchargements fonctionnent de la même manière que pour définir et effacer le motif d'arrêt d'un seul téléchargement, sauf que contentId
doit être défini sur null
.
Lorsqu'un motif d'arrêt est différent de zéro, il est à l'état Download.STATE_STOPPED
. Les motifs d'arrêt sont conservés dans DownloadIndex
. Ils sont donc conservés si le processus de l'application est arrêté, puis redémarré.
Suspendre et reprendre tous les téléchargements
Pour suspendre et reprendre tous les téléchargements, procédez 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 de motifs d'arrêt, cette approche ne conserve aucun changement d'état. Elle n'affecte 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 du DownloadManager
, comme dans l'exemple ci-dessus. Vous pouvez également les modifier 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 continuer, car les exigences ne sont pas remplies, il est à l'état Download.STATE_QUEUED
. Vous pouvez interroger les exigences non satisfaites 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 aurait normalement lieu lors de la création du DownloadManager
, comme dans l'exemple ci-dessus.
Lorsqu'un téléchargement ne peut pas continuer, car le nombre maximal de téléchargements parallèles est déjà en cours, il est à l'état Download.STATE_QUEUED
.
Interroger des téléchargements
Le DownloadIndex
d'un DownloadManager
peut être interrogé pour l'état de tous les téléchargements, y compris ceux qui ont été terminés ou qui ont échoué. Vous pouvez obtenir le 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 seul téléchargement 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 ayant échoué). 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.
Écoute des téléchargements
Vous pouvez ajouter un écouteur à DownloadManager
pour être informé lorsque l'état des téléchargements actuels change:
Kotlin
downloadManager.addListener( object : DownloadManager.Listener { // Override methods of interest here. } )
Java
downloadManager.addListener( new DownloadManager.Listener() { // Override methods of interest here. });
Consultez DownloadManagerListener
dans la classe DownloadTracker
de l'application de démonstration pour obtenir un exemple concret.
Lecture de contenus téléchargés
La lecture de contenu téléchargé est semblable à la lecture de contenu en ligne, sauf que les données sont lues à partir du Cache
de téléchargement et non via le réseau.
Pour lire le contenu téléchargé, créez un CacheDataSource.Factory
à l'aide de la même instance Cache
que celle 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 également de télécharger ce contenu pendant la lecture.
Une fois le lecteur configuré avec CacheDataSource.Factory
, il aura accès au contenu téléchargé pour la lecture. Pour lire un téléchargement, il suffit alors de transmettre le MediaItem
correspondant au joueur. Vous pouvez obtenir un MediaItem
à partir d'un Download
en utilisant Download.request.toMediaItem
, ou directement à partir d'un DownloadRequest
en utilisant DownloadRequest.toMediaItem
.
Configuration MediaSource
L'exemple précédent rend le cache de téléchargement disponible pour 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. Souvent, plusieurs pistes présentent le même contenu dans des qualités différentes (par exemple, des pistes vidéo SD, HD et 4K). Il peut également y avoir plusieurs pistes du même type avec un contenu différent (par exemple, plusieurs pistes audio dans différentes langues).
Pour les lectures en streaming, vous pouvez utiliser un sélecteur de piste afin de choisir les pistes à lire. De même, pour le téléchargement, vous pouvez utiliser un DownloadHelper
pour choisir les titres à télécharger. L'utilisation typique d'un DownloadHelper
s'effectue comme suit:
- Créez une
DownloadHelper
à l'aide de l'une des méthodesDownloadHelper.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);
- Vous pouvez également inspecter les pistes sélectionnées par défaut à l'aide de
getMappedTrackInfo
etgetTrackSelections
, puis effectuer des ajustements à l'aide declearTrackSelections
,replaceTrackSelections
etaddTrackSelection
. - Créez un
DownloadRequest
pour les pistes sélectionnées en appelantgetDownloadRequest
. La requête peut être transmise à votreDownloadService
pour ajouter le téléchargement, comme décrit ci-dessus. - Relâchez l'application auxiliaire à 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 qui ont été téléchargés. L'utilisation de Download.request.toMediaItem
et de DownloadRequest.toMediaItem
pour compiler MediaItem
se charge de cette tâche.