ExoPlayer offre la funzionalità di scaricare contenuti multimediali per la riproduzione offline. Nella maggior parte dei casi d'uso, è preferibile che i download continuino anche quando l'app è in background. Per questi casi d'uso, la tua app deve creare una sottoclasse di DownloadService e
inviare comandi al servizio per aggiungere, rimuovere e controllare i download. Il diagramma seguente mostra le classi principali coinvolte.
DownloadService: racchiude unDownloadManagere gli inoltra i comandi. Il servizio consente aDownloadManagerdi continuare a funzionare anche quando l'app è in background.DownloadManager: gestisce più download, caricando (e memorizzando) i relativi stati da (e verso) unDownloadIndex, avviando e interrompendo i download in base a requisiti quali la connettività di rete e così via. Per scaricare i contenuti, il gestore in genere legge i dati scaricati da unHttpDataSourcee li scrive in unCache.DownloadIndex: Mantiene gli stati dei download.
Creazione di un DownloadService
Per creare un DownloadService, crea una sottoclasse e implementa i relativi metodi astratti:
getDownloadManager(): restituisce ilDownloadManagerda utilizzare.getScheduler(): restituisce unSchedulerfacoltativo, che può riavviare il servizio quando vengono soddisfatti i requisiti necessari per l'avanzamento dei download in attesa. ExoPlayer fornisce le seguenti implementazioni:PlatformScheduler, che utilizza JobScheduler (l'API minima è 21). Consulta la documentazione Java di PlatformScheduler per i requisiti delle autorizzazioni app.WorkManagerScheduler, che utilizza WorkManager.
getForegroundNotification(): restituisce una notifica da visualizzare quando il servizio è in esecuzione in primo piano. Puoi utilizzareDownloadNotificationHelper.buildProgressNotificationper creare una notifica nello stile predefinito.
Infine, definisci il servizio nel file 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>
Per un esempio concreto, consulta DemoDownloadService e AndroidManifest.xml nell'app demo di ExoPlayer.
Creazione di un DownloadManager
Il seguente snippet di codice mostra come creare un'istanza di DownloadManager,
che può essere restituita da getDownloadManager() in 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);
Per un esempio concreto, vedi DemoUtil nell'app demo.
Aggiunta di un download
Per aggiungere un download, crea un DownloadRequest e invialo al tuo
DownloadService. Per gli stream adattivi, utilizza DownloadHelper per creare
un DownloadRequest. L'esempio seguente mostra come creare una richiesta di download:
Kotlin
val downloadRequest = DownloadRequest.Builder(contentId, contentUri).build()
Java
DownloadRequest downloadRequest = new DownloadRequest.Builder(contentId, contentUri).build();
In questo esempio, contentId è un identificatore univoco per i contenuti. Nei casi semplici, l'contentUri può spesso essere utilizzato come contentId, ma le app sono libere di utilizzare lo schema di ID più adatto al loro caso d'uso. DownloadRequest.Builder ha anche
alcuni setter facoltativi. Ad esempio, setKeySetId e setData possono essere utilizzati per
impostare DRM e dati personalizzati che l'app vuole associare al download,
rispettivamente. Il tipo MIME dei contenuti può essere specificato anche utilizzando setMimeType,
come suggerimento per i casi in cui il tipo di contenuti non può essere dedotto da contentUri.
Una volta creata, la richiesta può essere inviata a DownloadService per aggiungere il download:
Kotlin
DownloadService.sendAddDownload( context, MyDownloadService::class.java, downloadRequest, /* foreground= */ false )
Java
DownloadService.sendAddDownload( context, MyDownloadService.class, downloadRequest, /* foreground= */ false);
In questo esempio, MyDownloadService è la sottoclasse DownloadService dell'app e il
parametro foreground controlla se il servizio verrà avviato in primo piano. Se la tua app è già in primo piano, il parametro foreground
deve normalmente essere impostato su false perché DownloadService
si mette in primo piano se determina di dover svolgere un'attività.
Rimozione dei download…
Un download può essere rimosso inviando un comando di rimozione a DownloadService,
dove contentId identifica il download da rimuovere:
Kotlin
DownloadService.sendRemoveDownload( context, MyDownloadService::class.java, contentId, /* foreground= */ false )
Java
DownloadService.sendRemoveDownload( context, MyDownloadService.class, contentId, /* foreground= */ false);
Puoi anche rimuovere tutti i dati scaricati con
DownloadService.sendRemoveAllDownloads.
Avvio e interruzione dei download
Il download procederà solo se vengono soddisfatte quattro condizioni:
- Il download non ha un motivo di interruzione.
- I download non sono in pausa.
- I requisiti per l'avanzamento dei download sono soddisfatti. I requisiti possono specificare vincoli sui tipi di rete consentiti, nonché se il dispositivo deve essere inattivo o connesso a un caricabatterie.
- Non è stato superato il numero massimo di download paralleli.
Tutte queste condizioni possono essere controllate inviando comandi al tuo
DownloadService.
Impostare e cancellare i motivi di interruzione del download
È possibile impostare un motivo per l'interruzione di uno o tutti i download:
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 può essere qualsiasi valore diverso da zero (Download.STOP_REASON_NONE = 0 è
un valore speciale che indica che il download non viene interrotto). Le app che hanno
più motivi per interrompere i download possono utilizzare valori diversi per tenere traccia
del motivo per cui ogni download viene interrotto. L'impostazione e l'annullamento del motivo di interruzione per tutti i download funzionano allo stesso modo dell'impostazione e dell'annullamento del motivo di interruzione per un singolo download, tranne per il fatto che contentId deve essere impostato su null.
Quando un download ha un motivo di interruzione diverso da zero, si trova nello stato
Download.STATE_STOPPED. I motivi di arresto vengono mantenuti in DownloadIndex e quindi conservati se il processo dell'applicazione viene interrotto e successivamente riavviato.
Mettere in pausa e riprendere tutti i download
Tutti i download possono essere sospesi e ripresi come segue:
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);
Quando i download vengono sospesi, saranno nello stato Download.STATE_QUEUED.
A differenza dell'impostazione dei motivi di arresto, questo approccio non mantiene alcuna modifica di stato. Influisce solo sullo stato di runtime di DownloadManager.
Impostazione dei requisiti per l'avanzamento dei download
Requirements può essere utilizzato per specificare i vincoli che devono essere soddisfatti affinché i download possano procedere. I requisiti possono essere impostati chiamando DownloadManager.setRequirements() durante la creazione di DownloadManager, come nell'esempio sopra. Possono anche essere modificati dinamicamente inviando 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);
Quando un download non può procedere perché i requisiti non sono soddisfatti, sarà nello stato Download.STATE_QUEUED. Puoi interrogare i requisiti non soddisfatti con DownloadManager.getNotMetRequirements().
Impostazione del numero massimo di download paralleli
Il numero massimo di download paralleli può essere impostato chiamando
DownloadManager.setMaxParallelDownloads(). Normalmente questa operazione viene eseguita durante la creazione di DownloadManager, come nell'esempio sopra.
Quando un download non può procedere perché è già stato raggiunto il numero massimo di download paralleli, lo stato sarà Download.STATE_QUEUED.
Interrogazione dei download
È possibile interrogare il DownloadIndex di un DownloadManager per conoscere lo stato di tutti i download, compresi quelli completati o non riusciti. Il DownloadIndex può essere ottenuto chiamando DownloadManager.getDownloadIndex(). È quindi possibile ottenere un cursore che scorre tutti i download chiamando DownloadIndex.getDownloads(). In alternativa, è possibile interrogare lo stato di un singolo download chiamando DownloadIndex.getDownload().
DownloadManager fornisce anche DownloadManager.getCurrentDownloads(), che restituisce solo lo stato dei download correnti (ovvero non completati o non riusciti). Questo metodo è utile per aggiornare le notifiche e altri componenti dell'interfaccia utente che visualizzano l'avanzamento e lo stato dei download correnti.
Ascolto dei download
Puoi aggiungere un listener a DownloadManager per essere informato quando lo stato dei download correnti cambia:
Kotlin
downloadManager.addListener( object : DownloadManager.Listener { // Override methods of interest here. } )
Java
downloadManager.addListener( new DownloadManager.Listener() { // Override methods of interest here. });
Per un esempio concreto, vedi DownloadManagerListener nella classe DownloadTracker dell'app demo.
Riproduzione di contenuti scaricati
La riproduzione dei contenuti scaricati è simile a quella dei contenuti online, tranne per il fatto che
i dati vengono letti dal download Cache anziché dalla rete.
Per riprodurre i contenuti scaricati, crea un CacheDataSource.Factory utilizzando la stessa istanza Cache utilizzata per il download e inseriscila in DefaultMediaSourceFactory durante la creazione del player:
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();
Se la stessa istanza del player verrà utilizzata anche per riprodurre contenuti non scaricati,
CacheDataSource.Factory deve essere configurato come di sola lettura per evitare
di scaricare anche questi contenuti durante la riproduzione.
Una volta configurato il player con CacheDataSource.Factory, potrà
accedere ai contenuti scaricati per la riproduzione. Per riprodurre un download, è sufficiente
passare il MediaItem corrispondente al lettore. Un MediaItem
può essere ottenuto da un Download utilizzando Download.request.toMediaItem oppure
direttamente da un DownloadRequest utilizzando DownloadRequest.toMediaItem.
Configurazione di MediaSource
L'esempio precedente rende disponibile la cache dei download per la riproduzione di tutti i
MediaItem. Puoi anche rendere disponibile la cache di download per singole istanze di MediaSource, che possono essere passate direttamente al lettore:
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();
Scaricare e riprodurre flussi adattivi
Gli stream adattivi (ad es. DASH, SmoothStreaming e HLS) di solito contengono più tracce multimediali. Spesso ci sono più tracce che contengono gli stessi contenuti in diverse qualità (ad es. tracce video SD, HD e 4K). Potrebbero anche esserci più tracce dello stesso tipo contenenti contenuti diversi (ad es. più tracce audio in lingue diverse).
Per le riproduzioni in streaming, è possibile utilizzare un selettore di tracce per scegliere quali tracce riprodurre. Allo stesso modo, per il download, è possibile utilizzare un DownloadHelper per scegliere quali tracce scaricare. L'utilizzo tipico di un DownloadHelper
segue questi passaggi:
- Crea un
DownloadHelperutilizzando un'istanzaDownloadHelper.Factory. Preparare l'aiutante e attendere la chiamata.Kotlin
val downloadHelper = DownloadHelper.Factory() .setRenderersFactory(DefaultRenderersFactory(context)) .setDataSourceFactory(dataSourceFactory) .create(MediaItem.fromUri(contentUri)) downloadHelper.prepare(callback)
Java
DownloadHelper downloadHelper = new DownloadHelper.Factory() .setRenderersFactory(new DefaultRenderersFactory(context)) .setDataSourceFactory(dataSourceFactory) .create(MediaItem.fromUri(contentUri)); downloadHelper.prepare(callback);
- (Facoltativo) Esamina le tracce selezionate per impostazione predefinita utilizzando
getMappedTrackInfoegetTrackSelectionse apporta modifiche utilizzandoclearTrackSelections,replaceTrackSelectionseaddTrackSelection. - Crea un
DownloadRequestper le tracce selezionate chiamandogetDownloadRequest. La richiesta può essere inoltrata al tuoDownloadServiceper aggiungere il download, come descritto sopra. - Rilascia l'helper utilizzando
release().
La riproduzione dei contenuti adattivi scaricati richiede la configurazione del player e
il passaggio del MediaItem corrispondente, come descritto sopra.
Durante la creazione di MediaItem, MediaItem.localConfiguration.streamKeys deve essere impostato in modo che corrisponda a quelli in DownloadRequest, in modo che il lettore provi a riprodurre solo il sottoinsieme di tracce che sono state scaricate. L'utilizzo di
Download.request.toMediaItem e DownloadRequest.toMediaItem per creare
MediaItem si occuperà di questo per te.