ExoPlayer 提供下载媒体以供离线播放的功能。大多数
用例,最好即使您的应用处于
背景。对于这些用例,您的应用应创建 DownloadService
和
向该服务发送添加、删除和控制下载内容的命令。通过
下图显示了涉及的主要类。
DownloadService
:封装DownloadManager
并将命令转发给它。通过 服务可让DownloadManager
即使在应用处于运行状态时也保持运行 背景。DownloadManager
:管理多个下载内容,以及加载(和存储)其 状态包括从(和到)DownloadIndex
,根据 网络连接等要求要下载 则管理器通常会读取从HttpDataSource
,并将其写入Cache
。DownloadIndex
:保留下载状态。
创建 DownloadService
如需创建 DownloadService
,请将其子类化并实现其
抽象方法:
getDownloadManager()
:返回要使用的DownloadManager
。getScheduler()
:返回一个可选的Scheduler
,它可能会重启 服务。 ExoPlayer 提供以下实现: <ph type="x-smartling-placeholder">- </ph>
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 和自定义数据;
。内容的 MIME 类型也可以使用 setMimeType
指定,
,针对无法从 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.setRequirements()
(在创建 DownloadManager
时指定),如
请参阅上述示例。也可以通过发送命令
发送到 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
,以获取所有
包括已完成或已失败的下载内容。DownloadIndex
可通过调用 DownloadManager.getDownloadIndex()
获取。一个光标
然后调用
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
中读取的,而不是通过网络读取。
要播放已下载的内容,请使用以下代码创建 CacheDataSource.Factory
:
Cache
实例,并将其注入
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
传递给播放器一样简单。MediaItem
可以使用 Download.request.toMediaItem
从 Download
获取,或
从 DownloadRequest
直接使用 DownloadRequest.toMediaItem
。
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
。 - 通过调用以下方法来为所选轨道创建
DownloadRequest
:getDownloadRequest
。您可以将该请求传递给DownloadService
, 添加下载内容(如上所述)。 - 使用
release()
释放帮助程序。
若要播放已下载的自适应内容,您需要配置播放器和
传递相应的 MediaItem
,如上所述。
构建 MediaItem
时,MediaItem.localConfiguration.streamKeys
必须为
设置为与 DownloadRequest
中的请求相匹配,以便播放器仅尝试
播放已下载的部分曲目。使用
Download.request.toMediaItem
和 DownloadRequest.toMediaItem
,用于构建
MediaItem
将为您处理。