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.
DownloadService
: Umschließt einenDownloadManager
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 einemHttpDataSource
heruntergeladenen Daten und schreibt sie in eineCache
.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 verwendendenDownloadManager
zurück.getScheduler()
: Gibt ein optionalesScheduler
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. MitDownloadNotificationHelper.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 MediaItem
s 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:
- Erstellen Sie eine
DownloadHelper
mit einer derDownloadHelper.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);
- Prüfen Sie optional die ausgewählten Standard-Tracks mit
getMappedTrackInfo
undgetTrackSelections
und nehmen Sie Anpassungen mitclearTrackSelections
,replaceTrackSelections
undaddTrackSelection
vor. - Erstelle eine
DownloadRequest
für die ausgewählten Titel, indem dugetDownloadRequest
aufrufst. Die Anfrage kann an deineDownloadService
übergeben werden, um den Download wie oben beschrieben hinzuzufügen. - 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.