ExoPlayer 提供下载媒体以供离线播放的功能。大多数情况下,即使应用在后台运行,下载也应该继续进行。对于这些用例,您的应用程序应该继承 DownloadService 并向服务发送命令以添加、删除和控制下载。下图显示了涉及的主要类。
DownloadService: 包装一个DownloadManager并将命令转发给它。即使应用程序在后台运行,该服务也能让DownloadManager继续运行。DownloadManager: 管理多个下载,从DownloadIndex加载(和存储)它们的下载状态,根据网络连接等要求启动和停止下载。为了下载内容,管理器通常会从HttpDataSource读取正在下载的数据,并将其写入Cache。DownloadIndex: 保存下载状态。
创建 DownloadService
如需创建 DownloadService,请对其进行子类化并实现其抽象方法:
getDownloadManager(): 返回要使用的DownloadManager。getScheduler():返回一个可选的Scheduler,当满足待下载项取得进展所需的要求时,该可选的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,它可以由 getDownloadManager() 在你的 DownloadService 中返回:
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 也有一些可选的设置器。例如,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 发送删除命令来删除下载内容,其中 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(),它仅返回当前(即未完成或失败的)下载状态。此方法可用于更新显示当前下载进度和状态的通知和其他 UI 组件。
收听下载内容
您可以向 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)通常包含多个媒体轨道。通常,多个轨道包含相同的内容,但质量各不相同(例如 SD、HD 和 4K 视频轨道)。也可能存在多个相同类型的音轨,但内容不同(例如,多个不同语言的音轨)。
对于流媒体播放,可以使用曲目选择器来选择要播放的曲目。同样,对于下载,可以使用 DownloadHelper 来选择要下载哪些曲目。DownloadHelper 的典型用法如下:
- 使用
DownloadHelper.Factory实例构建DownloadHelper。准备好辅助函数并等待回调。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);
- (可选)使用
getMappedTrackInfo和getTrackSelections检查默认选定的轨道,并使用clearTrackSelections、replaceTrackSelections和addTrackSelection进行调整。 - 通过调用
getDownloadRequest为选定的曲目创建DownloadRequest。可以按照上述说明将请求传递给您的DownloadService以添加下载。 - 使用
release()释放辅助程序。
播放下载的自适应内容需要配置播放器并传递相应的 MediaItem,如上所述。
构建 MediaItem 时,必须将 MediaItem.localConfiguration.streamKeys 设置为与 DownloadRequest 中的值一致,以便播放器仅尝试播放已下载的轨道子集。使用 Download.request.toMediaItem 和 DownloadRequest.toMediaItem 构建 MediaItem 将为您处理此问题。