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.
DownloadService
: Fügt bei einemDownloadManager
einen Umbruch ein und leitet Befehle an ihn weiter. Der Dienst sorgt dafür, dassDownloadManager
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) einemDownloadIndex
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 einemHttpDataSource
heruntergeladen werden, und schreibt sie in einCache
.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 verwendendenDownloadManager
zurück.getScheduler()
: Gibt ein optionalesScheduler
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önnenDownloadNotificationHelper.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 DownloadHelper
s sind folgende Schritte erforderlich:
- Erstellen Sie ein
DownloadHelper
mit einer derDownloadHelper.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);
- Optional können Sie die standardmäßig ausgewählten Tracks mit
getMappedTrackInfo
undgetTrackSelections
prüfen und mitclearTrackSelections
,replaceTrackSelections
undaddTrackSelection
Anpassungen vornehmen. - Erstellen Sie eine
DownloadRequest
für die ausgewählten Tracks, indem SiegetDownloadRequest
aufrufen. Die Anfrage kann an IhreDownloadService
weitergeleitet 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 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.