ExoPlayer fornisce funzionalità per 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 sottoclassificare DownloadService
e
inviare comandi al servizio per aggiungere, rimuovere e controllare i download. Il seguente diagramma mostra le principali classi coinvolte.
DownloadService
: racchiude unDownloadManager
e inoltra i comandi. Il servizio consente aDownloadManager
di rimanere in esecuzione anche quando l'app è in background.DownloadManager
: gestisce più download, il caricamento (e l'archiviazione) dei relativi stati da (e al) unDownloadIndex
, l'avvio e l'interruzione dei download in base a requisiti quali la connettività di rete e così via. Per scaricare i contenuti, in genere il gestore legge i dati scaricati da unHttpDataSource
e li scrive in unCache
.DownloadIndex
: mantiene invariati gli stati dei download.
Creazione di un DownloadService
Per creare un DownloadService
, crea una sottoclasse e implementa i relativi metodi astratti:
getDownloadManager()
: restituisce ilDownloadManager
da utilizzare.getScheduler()
: restituisce unScheduler
facoltativo, 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 (API minima 21). Per i requisiti di autorizzazione per le app, consulta la documentazione Java di PlatformScheduler.WorkManagerScheduler
, che utilizza WorkManager.
getForegroundNotification()
: restituisce una notifica da visualizzare quando il servizio è in esecuzione in primo piano. Puoi utilizzareDownloadNotificationHelper.buildProgressNotification
per 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 restituito 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 riportato di seguito 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 dei contenuti. Nei casi più semplici, spesso il valore contentUri
può 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 rispettivamente i dati DRM e personalizzati che l'app vuole associare al download. Il tipo MIME dei contenuti può essere specificato anche utilizzando setMimeType
,
come suggerimento per i casi in cui il tipo di contenuto 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, in genere il parametro foreground
dovrebbe essere impostato su false
perché DownloadService
si imposterà in primo piano se rileva che ha del lavoro da svolgere.
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
Un download avanza solo se sono 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 per i tipi di rete consentiti, nonché se il dispositivo deve essere inattivo o collegato a un caricabatterie.
- Il numero massimo di download in parallelo non viene superato.
Tutte queste condizioni possono essere controllate inviando comandi al tuo DownloadService
.
Impostazione e cancellazione dei motivi dell'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'eliminazione del motivo dell'interruzione per tutti i download funzionano come l'impostazione e l'eliminazione del motivo dell'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, sarà nello stato
Download.STATE_STOPPED
. I motivi dell'interruzione vengono mantenuti nel
DownloadIndex
e quindi vengono conservati se il processo di applicazione viene interrotto e
poi riavviato.
Sospensione e ripristino di tutti i download
Tutti i download possono essere messi in pausa e ripresi nel seguente modo:
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 sono in pausa, saranno nello stato Download.STATE_QUEUED
.
A differenza dell'impostazione dei motivi di interruzione, questo approccio non mantiene le modifiche dello stato. Influisce solo sullo stato del runtime di DownloadManager
.
Impostazione dei requisiti per l'avanzamento dei download
Requirements
può essere utilizzato per specificare i vincoli che devono essere soddisfatti per procedere con i download. 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 al 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ò essere eseguito perché i requisiti non sono soddisfatti, sarà nello stato Download.STATE_QUEUED
. Puoi eseguire una query sui requisiti
non soddisfatti con DownloadManager.getNotMetRequirements()
.
Impostare il numero massimo di download paralleli
Il numero massimo di download paralleli può essere impostato chiamando
DownloadManager.setMaxParallelDownloads()
. In genere questa operazione viene eseguita durante la creazione di DownloadManager
, come nell'esempio sopra.
Quando un download non può procedere perché il numero massimo di download paralleli è già in corso, sarà nello stato Download.STATE_QUEUED
.
Esecuzione di query sui download
È possibile eseguire query sul DownloadIndex
di un DownloadManager
per conoscere lo stato di tutti i download, inclusi quelli completati o non riusciti. DownloadIndex
può essere ottenuto chiamando DownloadManager.getDownloadIndex()
. Un cursore che
esegue l'iterazione su tutti i download può essere ottenuto chiamando
DownloadIndex.getDownloads()
. In alternativa, puoi eseguire query sullo stato di un
singolo download chiamando DownloadIndex.getDownload()
.
DownloadManager
fornisce anche DownloadManager.getCurrentDownloads()
, che restituisce solo lo stato dei download attuali (non completati o non riusciti). Questo metodo è utile per aggiornare le notifiche e altri componenti dell'interfaccia utente che mostrano l'avanzamento e lo stato dei download in corso.
Ascoltare i download
Puoi aggiungere un ascoltatore a DownloadManager
per ricevere una notifica quando lo stato dei download in corso 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, consulta DownloadManagerListener
nella classe DownloadTracker
dell'app di demo.
Riprodurre i contenuti scaricati
La riproduzione dei contenuti scaricati è simile alla riproduzione 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 iniettala 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
, avrà accesso ai contenuti scaricati per la riproduzione. Riprodurre un download è quindi
quanto basta passare il MediaItem
corrispondente al player. Un MediaItem
può essere ottenuto da un Download
utilizzando Download.request.toMediaItem
o
direttamente da un DownloadRequest
tramite DownloadRequest.toMediaItem
.
Configurazione di MediaSource
L'esempio precedente rende disponibile la cache di download per la riproduzione di tutti i MediaItem
. Puoi anche rendere disponibile la cache dei download per
singole istanze MediaSource
, che possono essere trasmesse direttamente al player:
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 stream adattivi
Gli stream adattivi (ad es. DASH, SmoothStreaming e HLS) di solito contengono più tracce media. Spesso esistono più tracce con gli stessi contenuti con qualità diverse (ad es. tracce video SD, HD e 4K). Potrebbero anche essere presenti 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 le tracce da scaricare. L'utilizzo tipico di un DownloadHelper
è il seguente:
- Crea un
DownloadHelper
utilizzando uno dei metodiDownloadHelper.forMediaItem
. Prepara l'assistente e attendi la chiamata.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);
- Se vuoi, ispeziona le tracce selezionate predefinite utilizzando
getMappedTrackInfo
egetTrackSelections
e apporta modifiche utilizzandoclearTrackSelections
,replaceTrackSelections
eaddTrackSelection
. - Crea un
DownloadRequest
per le tracce selezionate chiamandogetDownloadRequest
. La richiesta può essere passata al tuoDownloadService
per aggiungere il download, come descritto sopra. - Rilascia l'assistente utilizzando
release()
.
La riproduzione dei contenuti adattabili scaricati richiede la configurazione del player e la trasmissione del MediaItem
corrispondente, come descritto sopra.
Quando crei il file MediaItem
, MediaItem.localConfiguration.streamKeys
deve essere impostato in modo da corrispondere a quello nel file DownloadRequest
in modo che il player provi a riprodurre solo il sottoinsieme di tracce che sono state scaricate. L'utilizzo di
Download.request.toMediaItem
e DownloadRequest.toMediaItem
per creare
MediaItem
se ne occuperà per te.