Medien werden heruntergeladen

ExoPlayer bietet Funktionen zum Herunterladen von Medien für die Offlinewiedergabe. In den meisten Anwendungsfällen ist es wünschenswert, dass Downloads auch dann fortgesetzt werden, wenn Ihre App im Hintergrund ausgeführt wird. Für diese Anwendungsfälle sollte Ihre App die Klasse DownloadService unterteilen und Befehle an den Dienst senden, um Downloads hinzuzufügen, zu entfernen und zu steuern. Das folgende Diagramm zeigt die wichtigsten Klassen.

Klassen 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, dass DownloadManager auch dann weiter ausgeführt wird, wenn die App im Hintergrund ist.
  • DownloadManager: Verwaltet mehrere Downloads, lädt (und speichert) deren Status aus (und in) einem DownloadIndex, startet und beendet Downloads basierend auf Anforderungen wie Netzwerkverbindung usw. Zum Herunterladen der Inhalte liest der Manager die heruntergeladenen Daten normalerweise aus einem HttpDataSource und schreibt sie in ein Cache.
  • DownloadIndex: Speichert die Status der Downloads.

DownloadService erstellen

Um ein DownloadService zu erstellen, müssen Sie eine Unterklasse erstellen und die abstrakten Methoden implementieren:

  • getDownloadManager(): Gibt den zu verwendenden DownloadManager zurück.
  • getScheduler(): Gibt einen optionalen Scheduler zurück, mit dem der Dienst neu gestartet werden kann, wenn die Anforderungen für den Fortschritt ausstehender Downloads erfüllt sind. ExoPlayer bietet die folgenden Implementierungen:
    • PlatformScheduler, das JobScheduler verwendet (Mindest-API ist 21). Informationen zu den Berechtigungsanforderungen für Apps finden Sie in der PlatformScheduler-Javadocs.
    • WorkManagerScheduler, das WorkManager verwendet.
  • getForegroundNotification(): Gibt eine Benachrichtigung zurück, die angezeigt werden soll, wenn der Dienst im Vordergrund ausgeführt wird. Mit DownloadNotificationHelper.buildProgressNotification können Sie eine Benachrichtigung im Standardstil erstellen.

Definieren Sie den Dienst schließlich 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 finden Sie 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 Sie einen Download hinzufügen möchten, erstellen Sie eine DownloadRequest und senden Sie sie an Ihre DownloadService. Bei adaptiven Streams kann DownloadHelper dabei helfen, einen 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 die contentUri häufig als contentId verwendet werden. Apps können jedoch jedes ID-Schema verwenden, das am besten zu ihrem Anwendungsfall passt. DownloadRequest.Builder hat auch einige optionale Setter. Mit setKeySetId und setData können beispielsweise DRM und benutzerdefinierte Daten festgelegt werden, die die App mit dem Download verknüpfen 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.

Nachdem Sie die Anfrage erstellt haben, können Sie sie an DownloadService senden, 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 DownloadService selbst in den Vordergrund stellt, wenn es Aufgaben zu erledigen hat.

Downloads werden entfernt

Ein Download kann entfernt werden, indem ein Entfernungsbefehl an DownloadService gesendet wird, wobei contentId den zu entfernenden Download identifiziert:

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 wird nur fortgesetzt, wenn vier Bedingungen erfüllt sind:

  • Für den Download wurde kein Grund für das Anhalten angegeben.
  • Downloads werden nicht pausiert.
  • Die Voraussetzungen für den Fortschritt von Downloads sind erfüllt. In den Anforderungen können Einschränkungen für die zulässigen Netzwerktypen sowie Angaben dazu festgelegt werden, ob das Gerät im Leerlauf sein oder an ein Ladegerät angeschlossen sein soll.
  • Die maximale Anzahl paralleler Downloads wird nicht überschritten.

Alle diese Zustände können durch Senden von Befehlen an Ihren DownloadService gesteuert werden.

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

Sie können einen Grund dafür angeben, dass ein oder alle Downloads beendet werden:

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, der angibt, dass der Download nicht beendet wird). Apps, bei denen Downloads aus mehreren Gründen beendet werden können, können unterschiedliche Werte verwenden, um nachzuvollziehen, warum die einzelnen Downloads beendet wurden. Das Festlegen und Löschen des Stoppgrunds für alle Downloads funktioniert genauso wie das Festlegen und Löschen des Stoppgrunds für einen einzelnen Download, mit der Ausnahme, dass contentId auf null gesetzt werden sollte.

Wenn ein Download einen anderen als null lautenden Grund für das Beenden hat, befindet er sich im Status Download.STATE_STOPPED. Die Gründe für das Beenden werden in DownloadIndex gespeichert und bleiben daher erhalten, 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 werden, haben sie den Status Download.STATE_QUEUED. Im Gegensatz zum Festlegen von Gründen für das Beenden werden bei diesem Ansatz keine Statusänderungen beibehalten. Sie wirkt sich nur auf den Laufzeitstatus von DownloadManager aus.

Anforderungen für den Fortschritt von Downloads festlegen

Mit Requirements können Sie Einschränkungen angeben, die erfüllt sein müssen, damit Downloads fortgesetzt werden. Die Anforderungen können beim Erstellen des DownloadManager durch Aufrufen von DownloadManager.setRequirements() festgelegt werden, wie im Beispiel oben. Sie können auch dynamisch geändert werden, indem ein Befehl an 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.

Festlegen der maximalen Anzahl paralleler Downloads

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

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

Downloads abfragen

Der DownloadIndex eines DownloadManager kann für den Status aller Downloads abgefragt werden, einschließlich der Downloads, die abgeschlossen oder fehlgeschlagen sind. Die DownloadIndex kann durch Aufrufen von DownloadManager.getDownloadIndex() abgerufen werden. Ein Cursor, der alle Downloads durchläuft, kann dann durch Aufrufen von DownloadIndex.getDownloads() abgerufen werden. Alternativ kann der Status eines einzelnen Downloads durch Aufrufen von DownloadIndex.getDownload() abgefragt werden.

DownloadManager bietet außerdem DownloadManager.getCurrentDownloads(), welches den Status aktueller (d.h. noch nicht abgeschlossener oder fehlgeschlagener) Downloads zurückgibt. Diese Methode ist nützlich, um Benachrichtigungen und andere UI-Komponenten zu aktualisieren, in denen der Fortschritt und der Status aktueller Downloads angezeigt werden.

Downloads anhören

Sie können einen Listener zu DownloadManager hinzufügen, um benachrichtigt zu werden, wenn sich der Status der aktuellen 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. Der Unterschied besteht darin, dass die Daten aus dem Download Cache und nicht über das Netzwerk gelesen werden.

Wenn Sie heruntergeladene Inhalte abspielen möchten, erstellen Sie ein CacheDataSource.Factory mit derselben Cache-Instanz, die zum Herunterladen verwendet wurde, und fügen Sie es 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 Player-Instanz auch zum Abspielen von nicht heruntergeladenen Inhalten verwendet wird, sollte CacheDataSource.Factory als schreibgeschützt konfiguriert werden, um zu vermeiden, dass diese Inhalte während der Wiedergabe ebenfalls heruntergeladen werden.

Sobald der Player mit dem CacheDataSource.Factory konfiguriert wurde, kann er auf die heruntergeladenen Inhalte zugreifen und sie wiedergeben. Um einen Download abzuspielen, musst du dem Player nur die entsprechende MediaItem übergeben. Ein MediaItem kann mit Download.request.toMediaItem von einem Download oder mit DownloadRequest.toMediaItem direkt von einem DownloadRequest abgerufen werden.

MediaSource-Konfiguration

Im vorherigen Beispiel wird der Downloadcache für die Wiedergabe aller MediaItems verfügbar gemacht. Sie können den Downloadcache auch für einzelne MediaSource-Instanzen verfügbar machen, die 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 abspielen

Adaptive Streams (z.B. DASH, SmoothStreaming und HLS) enthalten normalerweise mehrere Mediatracks. Häufig gibt es mehrere Tracks, die denselben Inhalt in unterschiedlichen Qualitäten enthalten (z.B. SD-, HD- und 4K-Videotracks). Es kann auch mehrere Tracks desselben Typs mit unterschiedlichen Inhalten geben (z.B. mehrere Audio-Tracks in verschiedenen Sprachen).

Bei Streaming-Wiedergaben kann mit einer Titelauswahl festgelegt werden, welche der Tracks wiedergegeben werden. Beim Herunterladen kann mit einem DownloadHelper ausgewählt werden, welche Titel heruntergeladen werden. Die typische Verwendung eines DownloadHelper folgt diesen Schritten:

  1. Erstellen Sie eine DownloadHelper mit einer DownloadHelper.Factory-Instanz. Bereiten Sie den Helfer vor und warten Sie auf den Callback.

    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);
  2. Optional können Sie die standardmäßig ausgewählten Tracks mit getMappedTrackInfo und getTrackSelections prüfen und mit clearTrackSelections, replaceTrackSelections und addTrackSelection anpassen.
  3. Erstellen Sie ein DownloadRequest für die ausgewählten Tracks, indem Sie getDownloadRequest aufrufen. Die Anfrage kann an Ihren DownloadService weitergeleitet werden, um den Download hinzuzufügen, wie oben beschrieben.
  4. Stellen Sie die Hilfsmethode mit release() bereit.

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

Beim Erstellen von MediaItem muss MediaItem.localConfiguration.streamKeys so festgelegt werden, dass es mit den Werten in DownloadRequest übereinstimmt. So versucht der Player nur, die heruntergeladenen Tracks abzuspielen. Wenn Sie Download.request.toMediaItem und DownloadRequest.toMediaItem verwenden, um die MediaItem zu erstellen, wird dies automatisch für Sie erledigt.