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
还有一些可选的设置器。例如,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
获取。
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.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
将为您处理这个问题。