Medien werden heruntergeladen

ExoPlayer bietet Funktionen zum Herunterladen von Medien zur Offlinewiedergabe. In den meisten Fällen ist es wünschenswert, dass Downloads auch dann fortgesetzt werden, wenn die App im Hintergrund ausgeführt wird. Für diese Anwendungsfälle sollte Ihre App DownloadService als Unterklasse haben und dem Dienst Befehle zum Hinzufügen, Entfernen und Steuern der Downloads senden. Das folgende Diagramm zeigt die beteiligten Hauptklassen.

Kurse zum Herunterladen von Medien Die Pfeilrichtungen geben den Datenfluss an.

  • DownloadService: Umschließt einen DownloadManager und leitet Befehle an ihn weiter. Der Dienst ermöglicht es, DownloadManager auch dann auszuführen, wenn die App im Hintergrund läuft.
  • DownloadManager: Verwaltet mehrere Downloads, lädt (und speichert) ihren Status von (und in) DownloadIndex und startet und beendet Downloads je nach Anforderungen wie der Netzwerkverbindung usw. Zum Herunterladen des Inhalts liest der Manager in der Regel die aus einem HttpDataSource heruntergeladenen Daten und schreibt sie in eine Cache.
  • DownloadIndex: Speichert den Status der Downloads.

DownloadService erstellen

Erstellen Sie eine Unterklasse für DownloadService und implementieren Sie die abstrakten Methoden:

  • getDownloadManager(): Gibt den zu verwendenden DownloadManager zurück.
  • getScheduler(): Gibt ein optionales Scheduler zurück, mit dem der Dienst neu gestartet werden kann, wenn die Anforderungen für ausstehende Downloads erfüllt sind. ExoPlayer bietet folgende Implementierungen:
    • PlatformScheduler, für die JobScheduler verwendet wird (Mindest-API 21). Informationen zu den Anforderungen an App-Berechtigungen finden Sie in den Javadocs von PlatformScheduler.
    • WorkManagerScheduler, für die WorkManager verwendet wird.
  • getForegroundNotification(): Gibt eine Benachrichtigung zurück, die angezeigt wird, wenn der Dienst im Vordergrund ausgeführt wird. Mit DownloadNotificationHelper.buildProgressNotification können Sie eine Benachrichtigung im Standardstil erstellen.

Definieren Sie abschließend den Dienst in der Datei 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>

Ein konkretes Beispiel findest du in der ExoPlayer-Demo-App unter DemoDownloadService und AndroidManifest.xml.

DownloadManager erstellen

Das folgende Code-Snippet zeigt, wie eine DownloadManager instanziiert wird, die von getDownloadManager() in Ihrem DownloadService zurückgegeben werden kann:

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

Ein konkretes Beispiel finden Sie in der Demo-App unter DemoUtil.

Download hinzufügen

Wenn du einen Download hinzufügen möchtest, erstelle einen DownloadRequest und sende ihn an deine DownloadService. Für adaptive Streams kannst du DownloadHelper verwenden, um eine DownloadRequest zu erstellen. Das folgende Beispiel zeigt, wie eine Downloadanfrage erstellt wird:

Kotlin

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

Java

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

In diesem Beispiel ist contentId eine eindeutige Kennung für die Inhalte. In einfachen Fällen kann der contentUri oft als contentId verwendet werden. Apps können jedoch das ID-Schema verwenden, das am besten zu ihrem Anwendungsfall passt. DownloadRequest.Builder hat auch einige optionale Setter. Beispielsweise können setKeySetId und setData verwendet werden, um die digitale Rechteverwaltung und benutzerdefinierte Daten festzulegen, die die App dem Download zuordnen möchte. Der MIME-Typ des Inhalts kann auch mit setMimeType angegeben werden, als Hinweis für Fälle, in denen der Inhaltstyp nicht aus contentUri abgeleitet werden kann.

Nach dem Erstellen kann die Anfrage an die DownloadService gesendet werden, um den Download hinzuzufügen:

Kotlin

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

Java

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

In diesem Beispiel ist MyDownloadService die DownloadService-Unterklasse der App und der Parameter foreground steuert, ob der Dienst im Vordergrund gestartet wird. Wenn sich Ihre App bereits im Vordergrund befindet, sollte der Parameter foreground normalerweise auf false gesetzt werden, da sich die DownloadService selbst in den Vordergrund stellt, wenn sie feststellt, dass sie etwas zu tun hat.

Downloads werden entfernt

Ein Download kann entfernt werden, indem ein Befehl zum Entfernen an DownloadService gesendet wird. Dabei wird mit contentId der zu entfernende Download angegeben:

Kotlin

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

Java

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

Sie können auch alle heruntergeladenen Daten mit DownloadService.sendRemoveAllDownloads entfernen.

Downloads starten und beenden

Ein Download kann nur fortgesetzt werden, wenn vier Bedingungen erfüllt sind:

  • Der Download hat keinen Grund für das Anhalten.
  • Downloads werden nicht pausiert.
  • Die Voraussetzungen für den Download sind erfüllt. In den Anforderungen können Einschränkungen für die zulässigen Netzwerktypen sowie für den Status des Geräts (inaktiv oder an ein Ladegerät angeschlossen) angegeben werden.
  • Die maximale Anzahl paralleler Downloads wird nicht überschritten.

Alle diese Bedingungen können durch Senden von Befehlen an Ihre DownloadService gesteuert werden.

Gründe für das Beenden von Downloads festlegen und löschen

Sie können einen Grund für das Beenden eines oder aller Downloads festlegen:

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 kann ein beliebiger Wert ungleich Null sein (Download.STOP_REASON_NONE = 0 ist ein spezieller Wert, was bedeutet, dass der Download nicht angehalten wird). Bei Anwendungen, die Downloads aus mehreren Gründen anhalten, können unterschiedliche Werte verwendet werden, um nachzuverfolgen, warum die einzelnen Downloads angehalten wurden. Das Festlegen und Löschen des Grunds für das Beenden aller Downloads funktioniert genauso wie das Festlegen und Löschen des Grunds für das Beenden eines einzelnen Downloads, mit der Ausnahme, dass contentId auf null gesetzt werden sollte.

Wenn ein Download einen nicht nullwertigen Beendigungsgrund hat, befindet er sich im Status Download.STATE_STOPPED. Gründe für Stopps bleiben im DownloadIndex erhalten und werden beibehalten, wenn der Anwendungsprozess beendet und später neu gestartet wird.

Alle Downloads pausieren und fortsetzen

Alle Downloads können so pausiert und fortgesetzt werden:

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

Wenn Downloads pausiert sind, haben sie den Status Download.STATE_QUEUED. Im Gegensatz zum Festlegen von Gründen für das Anhalten werden bei diesem Ansatz keine Statusänderungen beibehalten. Sie wirkt sich nur auf den Laufzeitstatus der DownloadManager aus.

Anforderungen für Downloads festlegen

Mit Requirements können Einschränkungen angegeben werden, die erfüllt sein müssen, damit Downloads fortgesetzt werden können. Die Anforderungen können festgelegt werden, indem beim Erstellen der DownloadManager DownloadManager.setRequirements() aufgerufen wird, wie im Beispiel oben. Sie können auch dynamisch geändert werden, indem ein Befehl an die DownloadService gesendet wird:

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

Wenn ein Download nicht fortgesetzt werden kann, weil die Anforderungen nicht erfüllt sind, hat er den Status Download.STATE_QUEUED. Sie können die nicht erfüllten Anforderungen mit DownloadManager.getNotMetRequirements() abfragen.

Maximale Anzahl paralleler Downloads festlegen

Die maximale Anzahl paralleler Downloads kann durch Aufrufen von DownloadManager.setMaxParallelDownloads() festgelegt werden. Normalerweise geschieht dies beim Erstellen des DownloadManager, wie im Beispiel oben.

Wenn ein Download nicht fortgesetzt werden kann, weil bereits die maximale Anzahl paralleler Downloads läuft, hat er den Status Download.STATE_QUEUED.

Downloads abfragen

Über die DownloadIndex eines DownloadManager kann der Status aller Downloads abgefragt werden, einschließlich der abgeschlossenen oder fehlgeschlagenen. DownloadIndex kann durch Aufrufen von DownloadManager.getDownloadIndex() abgerufen werden. Durch Aufrufen von DownloadIndex.getDownloads() kann dann ein Cursor abgerufen werden, der über alle Downloads iteriert. Alternativ kann der Status eines einzelnen Downloads durch Aufrufen von DownloadIndex.getDownload() abgefragt werden.

DownloadManager stellt auch DownloadManager.getCurrentDownloads() bereit, die nur den Status der aktuellen Downloads (d.h. nicht abgeschlossen oder fehlgeschlagene Downloads) zurückgeben. Diese Methode eignet sich zum Aktualisieren von Benachrichtigungen und anderen UI-Komponenten, die den Fortschritt und Status aktueller Downloads anzeigen.

Heruntergeladene Inhalte abspielen

Sie können DownloadManager einen Listener hinzufügen, der informiert wird, wenn sich der Status aktueller Downloads ändert:

Kotlin

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

Java

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

Ein konkretes Beispiel finden Sie unter DownloadManagerListener in der Klasse DownloadTracker der Demo-App.

Heruntergeladene Inhalte abspielen

Das Abspielen heruntergeladener Inhalte ähnelt dem Abspielen von Onlineinhalten, mit der Ausnahme, dass die Daten nicht über das Netzwerk, sondern aus dem DownloadCache gelesen werden.

Wenn du heruntergeladene Inhalte abspielen möchtest, erstelle eine CacheDataSource.Factory mit derselben Cache-Instanz, die für den Download verwendet wurde, und füge sie beim Erstellen des Players in DefaultMediaSourceFactory ein:

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

Wenn dieselbe Playerinstanz auch für die Wiedergabe nicht heruntergeladener Inhalte verwendet wird, sollte CacheDataSource.Factory als schreibgeschützt konfiguriert werden, damit diese Inhalte während der Wiedergabe nicht heruntergeladen werden.

Sobald der Player mit dem CacheDataSource.Factory konfiguriert wurde, hat er Zugriff auf die heruntergeladenen Inhalte zur Wiedergabe. Das Abspielen eines Downloads ist dann so einfach wie das Übergeben der entsprechenden MediaItem an den Player. Eine MediaItem kann aus einer Download mit Download.request.toMediaItem oder direkt aus einem DownloadRequest mit DownloadRequest.toMediaItem abgerufen werden.

MediaSource-Konfiguration

Im vorherigen Beispiel wird der Downloadcache für die Wiedergabe aller MediaItems verfügbar gemacht. Du kannst den Download-Cache auch für einzelne MediaSource-Instanzen verfügbar machen, die dann direkt an den Player übergeben werden können:

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

Adaptive Streams herunterladen und wiedergeben

Adaptive Streams (z.B. DASH, SmoothStreaming und HLS) enthalten normalerweise mehrere Medientracks. Es gibt oft mehrere Tracks mit demselben Inhalt in unterschiedlichen Qualitäten (z.B. SD-, HD- und 4K-Videotracks). Es kann auch mehrere Tracks desselben Typs mit unterschiedlichen Inhalten geben (z.B. mehrere Audiotracks in verschiedenen Sprachen).

Bei der Streamingwiedergabe kann über eine Titelauswahl festgelegt werden, welche Titel abgespielt werden. Beim Herunterladen können Sie mit einem DownloadHelper auswählen, welche Titel heruntergeladen werden sollen. Die typische Verwendung eines DownloadHelper erfolgt in diesen Schritten:

  1. Erstellen Sie eine DownloadHelper mit einer der DownloadHelper.forMediaItem-Methoden. Bereiten Sie den Helfer vor und warten Sie auf den Rückruf.

    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. Prüfen Sie optional die ausgewählten Standard-Tracks mit getMappedTrackInfo und getTrackSelections und nehmen Sie Anpassungen mit clearTrackSelections, replaceTrackSelections und addTrackSelection vor.
  3. Erstelle eine DownloadRequest für die ausgewählten Titel, indem du getDownloadRequest aufrufst. Die Anfrage kann an deine DownloadService übergeben werden, um den Download wie oben beschrieben hinzuzufügen.
  4. Geben Sie das Hilfsprogramm mit release() frei.

Für die Wiedergabe heruntergeladener adaptiver Inhalte muss der Player wie oben beschrieben konfiguriert und die entsprechende MediaItem übergeben werden.

Beim Erstellen der MediaItem muss MediaItem.localConfiguration.streamKeys so festgelegt werden, dass es mit den Werten in der DownloadRequest übereinstimmt, damit der Player nur versucht, die heruntergeladenen Titel abzuspielen. Wenn Sie Download.request.toMediaItem und DownloadRequest.toMediaItem zum Erstellen der MediaItem verwenden, wird das automatisch für Sie erledigt.