ExoPlayer 提供了下载媒体内容以供离线播放的功能。在大多数用例中,即使应用在后台运行,也希望下载继续进行。对于这些用例,您的应用应对 DownloadService
进行子类化,并向服务发送命令以添加、移除和控制下载内容。下图显示了涉及的主要类。
DownloadService
:封装DownloadManager
并将命令转发给它。该服务允许DownloadManager
在应用在后台运行时继续运行。DownloadManager
:管理多个下载,从DownloadIndex
加载(和存储)其状态,根据网络连接等要求启动和停止下载,等等。如需下载内容,管理器通常会从HttpDataSource
读取要下载的数据,并将其写入Cache
。DownloadIndex
:保留下载状态。
创建 DownloadService
如需创建 DownloadService
,请对其进行子类化并实现其抽象方法:
getDownloadManager()
:返回要使用的DownloadManager
。getScheduler()
:返回一个可选的Scheduler
,当满足待处理下载继续所需的要求时,可以重启服务。ExoPlayer 提供了以下实现:PlatformScheduler
,它使用 JobScheduler(最低 API 为 21)。如需了解应用权限要求,请参阅 PlatformScheduler javadocs。WorkManagerScheduler
,使用 WorkManager。
getForegroundNotification()
:返回在服务在前台运行时显示的通知。您可以使用DownloadNotificationHelper.buildProgressNotification
创建默认样式的通知。
最后,在 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>
如需查看具体示例,请参阅 ExoPlayer 演示版应用中的 DemoDownloadService
和 AndroidManifest.xml
。
创建 DownloadManager
以下代码段演示了如何实例化 DownloadManager
,该实例可由 DownloadService
中的 getDownloadManager()
返回:
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);
如需查看具体示例,请参阅演示版应用中的 DemoUtil
。
添加下载内容
如需添加下载内容,请创建一个 DownloadRequest
并将其发送到您的 DownloadService
。对于自适应串流,请使用 DownloadHelper
帮助构建 DownloadRequest
。以下示例展示了如何创建下载请求:
Kotlin
val downloadRequest = DownloadRequest.Builder(contentId, contentUri).build()
Java
DownloadRequest downloadRequest = new DownloadRequest.Builder(contentId, contentUri).build();
在此示例中,contentId
是内容的唯一标识符。在简单的情况下,contentUri
通常可以用作 contentId
,但应用可以自由使用最适合其用例的任何 ID 方案。DownloadRequest.Builder
还有一些可选的 setter。例如,setKeySetId
和 setData
可分别用于设置应用希望与下载内容相关联的 DRM 和自定义数据。您还可以使用 setMimeType
指定内容的 MIME 类型,以便在无法从 contentUri
推断出内容类型的情况下提供提示。
创建完成后,可以将请求发送到 DownloadService
以添加下载内容:
Kotlin
DownloadService.sendAddDownload( context, MyDownloadService::class.java, downloadRequest, /* foreground= */ false )
Java
DownloadService.sendAddDownload( context, MyDownloadService.class, downloadRequest, /* foreground= */ false);
在此示例中,MyDownloadService
是应用的 DownloadService
子类,foreground
参数用于控制是否在前台启动服务。如果您的应用已处于前台,则 foreground
参数通常应设置为 false
,因为 DownloadService
会在确定自己有工作要做时将自己置于前台。
正在移除下载内容
如需移除下载内容,请向 DownloadService
发送 remove 命令,其中 contentId
标识要移除的下载内容:
Kotlin
DownloadService.sendRemoveDownload( context, MyDownloadService::class.java, contentId, /* foreground= */ false )
Java
DownloadService.sendRemoveDownload( context, MyDownloadService.class, contentId, /* foreground= */ false);
您还可以使用 DownloadService.sendRemoveAllDownloads
移除所有下载的数据。
开始和停止下载
只有在满足以下四个条件时,系统才会开始下载:
- 下载没有停止原因。
- 下载未暂停。
- 满足下载进度的要求。这些要求可以规定对允许的网络类型的限制条件,以及设备应该处于空闲状态还是连接到充电器。
- 并行下载次数未超出上限。
您可以通过向 DownloadService
发送命令来控制所有这些条件。
设置和清除下载停止原因
您可以为一个或所有下载设置停止原因:
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
可以是任何非零值(Download.STOP_REASON_NONE = 0
是一个特殊值,表示下载不会停止)。如果应用因多种原因停止下载,则可以使用不同的值来跟踪每次下载停止的原因。为所有下载设置和清除停止原因的操作与为单个下载设置和清除停止原因的操作相同,但 contentId
应设置为 null
。
如果下载的停止原因不为零,则下载将处于 Download.STATE_STOPPED
状态。停止原因会保留在 DownloadIndex
中,因此,如果应用进程被终止并稍后重启,这些原因也会保留。
暂停和恢复所有下载
您可以按以下方式暂停和恢复所有下载:
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);
下载暂停后,它们将处于 Download.STATE_QUEUED
状态。
与设置停止原因不同,此方法不会保留任何状态更改。它只会影响 DownloadManager
的运行时状态。
设置下载进度所需满足的要求
Requirements
可用于指定必须满足的约束条件,以便下载继续进行。您可以在创建 DownloadManager
时通过调用 DownloadManager.setRequirements()
来设置这些要求,如上文的示例所示。您还可以通过向 DownloadService
发送命令来动态更改它们:
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);
如果下载因不符合要求而无法继续,则会处于 Download.STATE_QUEUED
状态。您可以使用 DownloadManager.getNotMetRequirements()
查询不满足的要求。
设置并行下载的数量上限
您可以通过调用 DownloadManager.setMaxParallelDownloads()
来设置并行下载的数量上限。通常,这会在创建 DownloadManager
时完成,如上文中的示例所示。
如果下载无法继续,因为并行下载的数量已达到上限,则下载将处于 Download.STATE_QUEUED
状态。
查询下载内容
您可以查询 DownloadManager
的 DownloadIndex
,了解所有下载(包括已完成或失败的下载)的状态。可通过调用 DownloadManager.getDownloadIndex()
获取 DownloadIndex
。然后,您可以通过调用 DownloadIndex.getDownloads()
来获取用于迭代所有下载内容的游标。或者,您也可以通过调用 DownloadIndex.getDownload()
来查询单个下载的状态。
DownloadManager
还提供 DownloadManager.getCurrentDownloads()
,该方法仅返回当前(即未完成或未成功)下载的状态。此方法对于更新显示当前下载进度和状态的通知以及其他界面组件非常有用。
收听下载内容
您可以向 DownloadManager
添加一个监听器,以便在当前下载状态发生变化时收到通知:
Kotlin
downloadManager.addListener( object : DownloadManager.Listener { // Override methods of interest here. } )
Java
downloadManager.addListener( new DownloadManager.Listener() { // Override methods of interest here. });
如需查看具体示例,请参阅演示版应用的 DownloadTracker
类中的 DownloadManagerListener
。
播放已下载的内容
播放已下载的内容与播放在线内容类似,只是数据是从下载内容 Cache
读取的,而不是通过网络读取。
如需播放已下载的内容,请使用用于下载内容的同一 Cache
实例创建 CacheDataSource.Factory
,并在构建播放器时将其注入 DefaultMediaSourceFactory
:
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();
如果同一播放器实例还将用于播放未下载的内容,则应将 CacheDataSource.Factory
配置为只读,以避免在播放期间也下载这些内容。
使用 CacheDataSource.Factory
配置播放器后,播放器将能够访问已下载的内容以进行播放。然后,播放下载内容就很简单了,只需将相应的 MediaItem
传递给播放器即可。您可以使用 Download.request.toMediaItem
从 Download
获取 MediaItem
,也可以直接使用 DownloadRequest.toMediaItem
从 DownloadRequest
获取 MediaItem
。
MediaSource 配置
上述示例会使下载缓存可用于播放所有 MediaItem
。您还可以将下载缓存提供给各个 MediaSource
实例,这些实例可以直接传递给播放器:
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();
下载和播放自适应串流
自适应串流(例如 DASH、SmoothStreaming 和 HLS)通常包含多个媒体轨道。通常,多个轨道会包含不同质量(例如标清、高清和 4K 视频轨道)的相同内容。同一类型的轨道也可能包含不同的内容(例如,多条不同语言的音轨)。
对于在线播放,您可以使用曲目选择器来选择要播放的曲目。同样,对于下载操作,您可以使用 DownloadHelper
来选择要下载的曲目。DownloadHelper
的典型用法如下:
- 使用
DownloadHelper.forMediaItem
方法之一构建DownloadHelper
。准备帮助程序并等待回调。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);
- (可选)使用
getMappedTrackInfo
和getTrackSelections
检查默认选定的轨道,并使用clearTrackSelections
、replaceTrackSelections
和addTrackSelection
进行调整。 - 通过调用
getDownloadRequest
为所选轨道创建DownloadRequest
。您可以将请求传递给DownloadService
以添加下载内容,如上所述。 - 使用
release()
释放帮助程序。
如需播放已下载的自适应内容,需要配置播放器并传递相应的 MediaItem
,如上所述。
构建 MediaItem
时,必须将 MediaItem.localConfiguration.streamKeys
设置为与 DownloadRequest
中的 MediaItem.localConfiguration.streamKeys
匹配,以便播放器仅尝试播放已下载的曲目的子集。使用 Download.request.toMediaItem
和 DownloadRequest.toMediaItem
构建 MediaItem
即可实现这一点。