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.
DownloadService: Umschließt einenDownloadManagerund leitet Befehle an ihn weiter. Der Dienst ermöglicht es, dassDownloadManagerauch dann weiter ausgeführt wird, wenn die App im Hintergrund ist.DownloadManager: Verwaltet mehrere Downloads, lädt (und speichert) deren Status aus (und in) einemDownloadIndex, startet und beendet Downloads basierend auf Anforderungen wie Netzwerkverbindung usw. Zum Herunterladen der Inhalte liest der Manager die heruntergeladenen Daten normalerweise aus einemHttpDataSourceund schreibt sie in einCache.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 verwendendenDownloadManagerzurück.getScheduler(): Gibt einen optionalenSchedulerzurü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. MitDownloadNotificationHelper.buildProgressNotificationkö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:
- Erstellen Sie eine
DownloadHelpermit einerDownloadHelper.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);
- Optional können Sie die standardmäßig ausgewählten Tracks mit
getMappedTrackInfoundgetTrackSelectionsprüfen und mitclearTrackSelections,replaceTrackSelectionsundaddTrackSelectionanpassen. - Erstellen Sie ein
DownloadRequestfür die ausgewählten Tracks, indem SiegetDownloadRequestaufrufen. Die Anfrage kann an IhrenDownloadServiceweitergeleitet werden, um den Download hinzuzufügen, wie oben beschrieben. - 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.