ExoPlayer bietet Funktionen zum Herunterladen von Medien für die Offlinewiedergabe. In den meisten Anwendungsfällen ist es wünschenswert, dass Downloads fortgesetzt werden, auch wenn sich Ihre App im Hintergrund befindet. Für diese Anwendungsfälle sollte Ihre App die Klasse DownloadService ableiten 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 esDownloadManager, auch dann weiter ausgeführt zu werden, wenn die App im Hintergrund ist.DownloadManager: Verwaltet mehrere Downloads, lädt (und speichert) deren Status aus (und in) einemDownloadIndexund startet und stoppt Downloads basierend auf Anforderungen wie der Netzwerkverbindung. Zum Herunterladen der Inhalte liest der Manager die heruntergeladenen Daten normalerweise aus einemHttpDataSourceund schreibt sie in einCache.DownloadIndex: Behält den Status der Downloads bei.
DownloadService erstellen
Um eine DownloadService zu erstellen, leiten Sie sie ab und implementieren Sie ihre abstrakten Methoden:
getDownloadManager(): Gibt den zu verwendendenDownloadManagerzurück.getScheduler(): Gibt optional einSchedulerzurü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, dasJobSchedulerverwendet (Mindest-API-Level ist 21). Weitere Informationen zu den Anforderungen an App-Berechtigungen finden Sie in derPlatformScheduler-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 abschließend 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 ein DownloadRequest und senden Sie es 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. Die contentUri kann oft 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 MIME-Typ 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 Anforderungen 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 inaktiv sein oder an ein Ladegerät angeschlossen sein soll.
- Die maximale Anzahl paralleler Downloads wird nicht überschritten.
Alle diese Bedingungen können durch Senden von Befehlen an dein 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 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 festgelegt werden sollte.
Wenn ein Download einen Grund für das Beenden ungleich null 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 Beendigungsgrunds 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.
Maximale Anzahl paralleler Downloads festlegen
Die maximale Anzahl paralleler Downloads kann durch Aufrufen 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 auch DownloadManager.getCurrentDownloads(), das nur den Status aktueller (d.h. 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 für DownloadManager hinzufügen, um benachrichtigt zu werden, 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. 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 die heruntergeladenen Inhalte wiedergeben. Wenn Sie einen Download abspielen möchten, übergeben Sie die entsprechende MediaItem an den Player. Ein MediaItem kann über ein Download mit Download.request.toMediaItem oder direkt über ein DownloadRequest mit DownloadRequest.toMediaItem 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 wiedergeben
Adaptive Streams (z.B. DASH, SmoothStreaming und HLS) enthalten normalerweise mehrere Mediatracks. Oft 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 von DownloadHelper umfasst die folgenden Schritte:
- Erstellen Sie eine
DownloadHelpermit einerDownloadHelper.Factory-Instanz. Bereiten Sie den Helfer vor und warten Sie auf den 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.
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);
Für die Wiedergabe heruntergeladener adaptiver Inhalte muss der Player konfiguriert und das 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 das automatisch erledigt.