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
获取。
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
中的相应设置匹配,以便播放器仅尝试播放已下载的部分曲目。使用 Download.request.toMediaItem
和 DownloadRequest.toMediaItem
构建 MediaItem
即可为您搞定。