Medien werden heruntergeladen

ExoPlayer bietet eine Funktion zum Herunterladen von Medien für die Offline-Wiedergabe. In den meisten Anwendungsfällen sollten Downloads auch dann fortgesetzt werden, wenn die App im Hintergrund ausgeführt wird. Für diese Anwendungsfälle sollte Ihre Anwendung eine abgeleitete Klasse von DownloadService sein und Befehle zum Hinzufügen, Entfernen und Steuern der Downloads an den Dienst senden. Das folgende Diagramm zeigt die beteiligten Hauptklassen.

Klassen zum Herunterladen von Medien. Die Pfeilrichtungen zeigen den Datenfluss an.

  • DownloadService: Fügt bei einem DownloadManager einen Umbruch ein und leitet Befehle an ihn weiter. Der Dienst sorgt dafür, dass DownloadManager auch dann weiter ausgeführt wird, wenn die Anwendung im Hintergrund ausgeführt wird.
  • DownloadManager: Verwaltet mehrere Downloads, lädt (und speichert) ihren Status von (und zu) einem DownloadIndex und startet und beendet Downloads je nach Anforderungen wie der Netzwerkverbindung. Zum Herunterladen des Inhalts liest der Manager in der Regel die Daten, die aus einem HttpDataSource heruntergeladen werden, und schreibt sie in ein Cache.
  • DownloadIndex: Der Status der Downloads wird beibehalten.

DownloadService erstellen

Um einen DownloadService zu erstellen, erstellen Sie eine abgeleitete Klasse und implementieren Sie seine abstrakten Methoden:

  • getDownloadManager(): gibt den zu verwendenden DownloadManager zurück.
  • getScheduler(): Gibt ein optionales Scheduler zurück, das den Dienst neu starten kann, wenn die Anforderungen für ausstehende Downloads erfüllt sind. ExoPlayer bietet folgende Implementierungen:
    • PlatformScheduler mit JobScheduler (mindestens 21 API). Informationen zu den Anforderungen für App-Berechtigungen finden Sie in der PlatformScheduler-Javadocs.
    • WorkManagerScheduler verwendet WorkManager.
  • getForegroundNotification(): Gibt eine Benachrichtigung zurück, die angezeigt werden soll, wenn der Dienst im Vordergrund ausgeführt wird. Sie können DownloadNotificationHelper.buildProgressNotification verwenden, um eine Benachrichtigung im Standardstil zu 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.

Erstellen eines DownloadManagers

Das folgende Code-Snippet zeigt, wie ein DownloadManager instanziiert wird, das von getDownloadManager() in Ihrer 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 Ihr DownloadService. Für adaptive Streams verwenden Sie DownloadHelper, um eine DownloadRequest zu erstellen. Das folgende Beispiel zeigt, wie Sie eine Downloadanfrage erstellen:

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 den Inhalt. In einfachen Fällen kann contentUri häufig als contentId verwendet werden. Apps können jedoch das ID-Schema verwenden, das für ihren Anwendungsfall am besten geeignet ist. 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. Dies ist ein Hinweis für den Fall, dass der Inhaltstyp nicht aus contentUri abgeleitet werden kann.

Nach der Erstellung kann die Anfrage an den 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 abgeleitete DownloadService-Klasse der Anwendung und der Parameter foreground steuert, ob der Dienst im Vordergrund gestartet wird. Wenn Ihre App bereits im Vordergrund ausgeführt wird, sollte der Parameter foreground normalerweise auf false festgelegt sein, weil sich DownloadService selbst im Vordergrund befindet, wenn er erkennt, dass er Arbeit zu erledigen hat.

Downloads werden entfernt

Um einen Download zu entfernen, senden Sie einen Befehl zum Entfernen an DownloadService, 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 ausgeführt, wenn vier Bedingungen erfüllt sind:

  • Der Download hat keinen Grund, den Download zu stoppen.
  • Downloads werden nicht pausiert.
  • Die Anforderungen für den Fortschritt von Downloads sind erfüllt. Mit den Anforderungen können Einschränkungen für die zulässigen Netzwerktypen festgelegt werden. Außerdem kann angegeben werden, ob das Gerät inaktiv sein oder an ein Ladegerät angeschlossen sein soll.
  • Die maximale Anzahl paralleler Downloads wurde nicht überschritten.

Alle diese Bedingungen 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 für das Anhalten eines oder aller Downloads angeben:

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 bedeutet, dass der Download nicht angehalten wird). Anwendungen, die aus mehreren Gründen Downloads stoppen, können unterschiedliche Werte verwenden, um nachzuverfolgen, warum ein Download gestoppt wird. 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 Stoppgrund ungleich null hat, erhält er den Status Download.STATE_STOPPED. Gründe für Stopps bleiben im DownloadIndex erhalten und bleiben erhalten, wenn der Anwendungsprozess beendet und später neu gestartet wird.

Alle Downloads anhalten und fortsetzen

So können Sie alle Downloads pausieren und fortsetzen:

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 Haltestellengründen 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 Einschränkungen angegeben werden, die erfüllt sein müssen, damit Downloads fortgesetzt werden. Die Anforderungen können durch Aufrufen von DownloadManager.setRequirements() beim Erstellen des DownloadManager festgelegt werden, wie im obigen Beispiel gezeigt. 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, erhält er den Status Download.STATE_QUEUED. Die nicht erfüllten Anforderungen können Sie mit DownloadManager.getNotMetRequirements() abfragen.

Maximale Anzahl paralleler Downloads festlegen

Die maximale Anzahl paralleler Downloads kann durch Aufrufen von DownloadManager.setMaxParallelDownloads() festgelegt werden. Dies wird normalerweise beim Erstellen des DownloadManager-Objekts gemacht, wie im obigen Beispiel gezeigt.

Wenn ein Download nicht fortgesetzt werden kann, weil die maximale Anzahl paralleler Downloads bereits 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 abgeschlossener oder fehlgeschlagener Downloads. Die DownloadIndex kann durch Aufrufen von DownloadManager.getDownloadIndex() abgerufen werden. Ein Cursor, der über alle Downloads iteriert, kann dann durch Aufrufen von DownloadIndex.getDownloads() abgerufen werden. Alternativ kann der Status eines einzelnen Downloads durch Aufrufen von DownloadIndex.getDownload() abgefragt werden.

DownloadManager stellt außerdem DownloadManager.getCurrentDownloads() bereit, das nur den Status aktueller (d.h. nicht abgeschlossener oder fehlgeschlagener) Downloads zurückgibt. Diese Methode eignet sich zum Aktualisieren von Benachrichtigungen und anderen UI-Komponenten, die den Fortschritt und Status aktueller Downloads anzeigen.

Downloads anhören

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 werden abgespielt

Das Abspielen heruntergeladener Inhalte ähnelt der Wiedergabe von Onlineinhalten, mit der Ausnahme, dass die Daten aus dem Download-Cache und nicht über das Netzwerk gelesen werden.

Zum Abspielen heruntergeladener Inhalte erstellen Sie eine CacheDataSource.Factory mit derselben Cache-Instanz, die für den Download verwendet wurde, und fügen Sie 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 Player-Instanz auch zum Abspielen von nicht heruntergeladenen Inhalten verwendet wird, sollte CacheDataSource.Factory als schreibgeschützt konfiguriert werden, um zu verhindern, dass diese Inhalte auch während der Wiedergabe heruntergeladen werden.

Sobald der Player mit CacheDataSource.Factory konfiguriert wurde, kann er zur Wiedergabe auf die heruntergeladenen Inhalte zugreifen. Zum Abspielen eines Downloads muss lediglich das entsprechende MediaItem-Element an den Player übergeben werden. Ein MediaItem kann mit Download.request.toMediaItem aus einem Download oder mit DownloadRequest.toMediaItem direkt aus einem DownloadRequest abgerufen werden.

MediaSource-Konfiguration

Durch das vorherige Beispiel wird der Download-Cache für die Wiedergabe aller MediaItem-Werte verfügbar gemacht. Du kannst den Download-Cache 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 wiedergeben

Adaptive Streams (z.B. DASH, SmoothStreaming und HLS) enthalten normalerweise mehrere Medienspuren. 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 Audiotracks in verschiedenen Sprachen).

Bei Streaming-Wiedergaben kann mit der Titelauswahl festgelegt werden, welche Titel abgespielt werden. Ebenso kann beim Herunterladen mithilfe eines DownloadHelper-Elements ausgewählt werden, welche der Tracks heruntergeladen werden sollen. Für die übliche Verwendung eines DownloadHelpers sind folgende Schritte erforderlich:

  1. Erstellen Sie ein DownloadHelper mit einer der DownloadHelper.forMediaItem-Methoden. Bereiten Sie das Hilfsprogramm vor und warten Sie auf den Callback.

    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. Optional können Sie die standardmäßig ausgewählten Tracks mit getMappedTrackInfo und getTrackSelections prüfen und mit clearTrackSelections, replaceTrackSelections und addTrackSelection Anpassungen vornehmen.
  3. Erstellen Sie eine DownloadRequest für die ausgewählten Tracks, indem Sie getDownloadRequest aufrufen. Die Anfrage kann an Ihre DownloadService weitergeleitet 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 konfiguriert und das entsprechende MediaItem-Element wie oben beschrieben übergeben werden.

Beim Erstellen von MediaItem muss MediaItem.localConfiguration.streamKeys so festgelegt werden, dass er mit dem Wert im DownloadRequest übereinstimmt, damit der Player versucht, nur die Teilmenge der heruntergeladenen Titel wiederzugeben. Wenn Sie Download.request.toMediaItem und DownloadRequest.toMediaItem zum Erstellen des MediaItem verwenden, wird das für Sie erledigt.