Download di contenuti multimediali

ExoPlayer fornisce funzionalità per scaricare contenuti multimediali per la riproduzione offline. Nella maggior parte dei casi, per i casi d'uso, è consigliabile che i download continuino anche quando l'app è sfondo. Per questi casi d'uso, la tua app deve avere la sottoclasse DownloadService e inviare comandi al servizio per aggiungere, rimuovere e controllare i download. La il seguente diagramma mostra le principali classi coinvolte.

Classi per il download di contenuti multimediali. Le direzioni delle frecce indicano il flusso di dati.

  • DownloadService: aggrega i comandi DownloadManager e gli inoltra i comandi. La consente a DownloadManager di rimanere in esecuzione anche quando l'app è in sullo sfondo.
  • DownloadManager: gestisce più download, carica (e archivia) i relativi stati da (e a) un DownloadIndex, avviando e interrompendo i download in base su requisiti come la connettività di rete e così via. Per scaricare contenuti, generalmente l'amministratore legge i dati scaricati da un HttpDataSource e scriverlo in un Cache.
  • DownloadIndex: mantiene gli stati dei download.

Creazione di un DownloadService

Per creare una sottoclasse DownloadService, devi implementarla e implementarne metodi astratti:

  • getDownloadManager(): restituisce il valore DownloadManager da utilizzare.
  • getScheduler(): restituisce un Scheduler facoltativo, che può riavviare la quando vengono soddisfatti i requisiti necessari per l'avanzamento dei download in attesa. ExoPlayer fornisce le seguenti implementazioni:
    • PlatformScheduler, che utilizza JobScheduler (l'API min è 21). Consulta la documentazione Java di PlatformScheduler per i requisiti di autorizzazione delle app.
    • WorkManagerScheduler, che utilizza WorkManager.
  • getForegroundNotification(): restituisce una notifica da visualizzare quando è in esecuzione in primo piano. Puoi utilizzare DownloadNotificationHelper.buildProgressNotification per creare un in 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>

Vedi DemoDownloadService e AndroidManifest.xml in ExoPlayer un'app demo per un esempio concreto.

Creazione di un DownloadManager

Il seguente snippet di codice mostra come creare un'istanza di un DownloadManager, che possono essere restituiti 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, usa DownloadHelper per facilitare per creare un DownloadRequest. Le seguenti un esempio 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. In casi semplici, Spesso è possibile utilizzare contentUri come contentId, ma le app sono senza costi a seconda del caso d'uso. DownloadRequest.Builder ha anche alcuni setter facoltativi. Ad esempio, setKeySetId e setData possono essere utilizzati per Impostare i dati DRM e personalizzati che l'app desidera 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 contenuto non può essere dedotto da contentUri.

Una volta creata, la richiesta può essere inviata a DownloadService per aggiungere 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 consente di stabilire se il servizio verrà avviato nel in primo piano. Se la tua app è già in primo piano, viene restituito foreground dovrebbe in genere essere impostato su false perché il parametro DownloadService si pone in primo piano se stabilisce che ha del lavoro da fare.

Rimozione dei download…

Un download può essere rimosso inviando un comando di rimozione all'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 procede solo se vengono soddisfatte quattro condizioni:

  • Non è presente un motivo per l'interruzione del download.
  • I download non sono in pausa.
  • I requisiti per l'avanzamento dei download sono soddisfatti. I requisiti possono specificare ai tipi di rete consentiti e al fatto che il dispositivo debba quando è inattivo o collegato a un caricabatterie.
  • Il numero massimo di download paralleli non è stato 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 con più motivi per interrompere i download possono utilizzare valori diversi per tenere traccia sul motivo per cui ogni download viene interrotto. Impostazione e cancellazione del motivo dell'interruzione per tutti download funziona come l'impostazione e la cancellazione del motivo dell'interruzione di un download singolo, tranne per il fatto che contentId deve essere impostato su null.

Quando un download ha un motivo di interruzione diverso da zero, verrà Download.STATE_STOPPED. I motivi dell'interruzione sono persistenti nel DownloadIndex e quindi vengono conservati se il processo di richiesta viene interrotto in seguito.

Sospensione e ripristino di tutti i download

Tutti i download possono essere messi in pausa 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 sono in pausa, saranno nello stato Download.STATE_QUEUED. A differenza dell'impostazione dei motivi dell'interruzione, questo approccio non conserva alcuno stato. modifiche. Influisce solo sullo stato del runtime di DownloadManager.

Impostazione dei requisiti per l'avanzamento dei download

Puoi utilizzare Requirements per specificare i vincoli che devono essere soddisfatti download per continuare. I requisiti possono essere impostati chiamando DownloadManager.setRequirements() durante la creazione di DownloadManager, ad esempio l'esempio sopra. Possono anche essere modificate in modo dinamico 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ò continuare perché i requisiti non sono soddisfatti, sarà nello stato Download.STATE_QUEUED. Puoi eseguire query sulle requisiti 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 quando creando DownloadManager, come nell'esempio sopra.

Quando non è possibile procedere con un download perché è stato raggiunto il numero massimo di download paralleli sono già in corso, sarà nello stato Download.STATE_QUEUED.

Esecuzione di query sui download

È possibile eseguire una query su DownloadIndex di un DownloadManager per conoscere lo stato di tutte download, inclusi quelli completati o non riusciti. DownloadIndex che puoi ottenere chiamando DownloadManager.getDownloadIndex(). Un cursore che è possibile ottenere iterazioni di tutti i download richiamando DownloadIndex.getDownloads(). In alternativa, lo stato di un singolo download per eseguire query chiamando DownloadIndex.getDownload().

DownloadManager fornisce anche DownloadManager.getCurrentDownloads(), restituisce solo lo stato dei download attuali (non completati o non riusciti). Questo è utile per aggiornare le notifiche e altri componenti UI che mostrano l'avanzamento e lo stato dei download correnti.

Ascolto dei download in corso...

Puoi aggiungere un listener a DownloadManager per ricevere una notifica quando è corrente dei download cambia stato:

Kotlin

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

Java

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

Guarda DownloadManagerListener nel corso DownloadTracker dell'app demo per un esempio concreto.

Riproduzione dei contenuti scaricati

La riproduzione dei contenuti scaricati è simile alla riproduzione dei contenuti online, ad eccezione del fatto che i dati vengono letti dal download Cache anziché tramite la rete.

Per riprodurre i contenuti scaricati, crea un CacheDataSource.Factory utilizzando lo stesso Cache istanza 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 verrà utilizzata anche la stessa istanza di player per riprodurre contenuti non scaricati il CacheDataSource.Factory deve essere configurato come di sola lettura per evitare scaricare i contenuti durante la riproduzione.

Una volta configurato il player con CacheDataSource.Factory, hanno accesso ai contenuti scaricati per la riproduzione. La riproduzione di un download corrisponde quindi è sufficiente passare il valore MediaItem corrispondente al giocatore. MediaItem può essere ottenuto da un Download utilizzando Download.request.toMediaItem, oppure direttamente da DownloadRequest tramite DownloadRequest.toMediaItem.

Configurazione MediaSource

L'esempio precedente rende disponibile la cache dei download per la riproduzione di tutti MediaItem Puoi anche rendere disponibile la cache di download per singole istanze di 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 gli stream adattivi

Gli stream adattivi (ad es. DASH, smoothStreaming e HLS) di solito contengono più tracce multimediali. Spesso esistono più tracce con gli stessi contenuti diverse qualità (ad es. tracce video SD, HD e 4K). Potrebbero esserci anche tracce multiple dello stesso tipo con contenuti diversi (ad es. più tracce tracce audio in lingue diverse).

Per le riproduzioni in streaming, è possibile usare un selettore di traccia per scegliere quale vengono riprodotte tracce. Analogamente, per il download è possibile usare un DownloadHelper scegli le tracce da scaricare. Utilizzo tipico di DownloadHelper procedi nel seguente modo:

  1. Crea un DownloadHelper usando uno degli DownloadHelper.forMediaItem di machine learning. Prepara l'assistente e attendi che venga richiamato.

    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. Se vuoi, controlla le tracce selezionate per impostazione predefinita utilizzando getMappedTrackInfo e getTrackSelections e apporta modifiche utilizzando clearTrackSelections, replaceTrackSelections e addTrackSelection.
  3. Crea un DownloadRequest per le tracce selezionate chiamando getDownloadRequest. La richiesta può essere trasmessa al tuo DownloadService per aggiungere il download, come descritto sopra.
  4. Rilascia l'assistente utilizzando release().

La riproduzione dei contenuti adattivi scaricati richiede la configurazione del player e passando il valore MediaItem corrispondente, come descritto sopra.

Quando si crea MediaItem, MediaItem.localConfiguration.streamKeys deve essere impostata in modo che corrisponda a quelle in DownloadRequest in modo che il giocatore provi soltanto riprodurre il sottoinsieme delle tracce scaricate. Utilizzo Download.request.toMediaItem e DownloadRequest.toMediaItem per creare MediaItem se ne occuperà al posto tuo.