在当今以媒体为中心的应用中,提供流畅、不间断的播放体验是打造愉悦用户体验的关键。用户希望视频能够即时开始播放,并且播放过程顺畅无卡顿。
核心挑战是延迟时间。传统上,视频播放器只有在用户选择要播放的内容后,才会开始执行连接、下载、解析、缓冲等操作。在当今的短视频环境中,这种被动应对的方式效率低下。解决方案是主动出击。我们需要预测用户接下来会观看什么内容,并提前准备好这些内容。这就是预加载的本质。
预加载的主要优势包括:
- 🚀 播放启动速度更快:视频已准备就绪,因此内容之间的切换更迅速,播放启动更即时。
- 📉 减少缓冲:通过主动加载数据,播放中断的可能性大大降低,例如因网络故障导致的中断。
- ✨ 带来更流畅的用户体验:更快的启动速度和更少的缓冲时间相结合,可为用户带来更流畅、更无缝的互动体验。
在这个由三部分组成的系列中,我们将介绍并深入探究 Media3 用于(预)加载组件的强大实用程序。
- 在第 1 部分中,我们将介绍基础知识:了解 Media3 中提供的不同预加载策略、启用 PreloadConfiguration 并设置 DefaultPreloadManager,从而使应用能够预加载内容。阅读完本博文后,您应该能够按照配置的排名和时长预加载和播放媒体文件。
- 在第 2 部分中,我们将深入探讨 DefaultPreloadManager 的更高级主题:使用监听器进行分析,探索可用于生产用途的最佳实践,例如滑动窗口模式以及 DefaultPreloadManager 和 ExoPlayer 的自定义共享组件。
- 在第 3 部分中,我们将深入探讨如何使用 DefaultPreloadManager 进行磁盘缓存。
预加载功能可以帮您解决这个问题!🦸♀️
预加载的核心理念很简单:在需要媒体内容之前加载它。当用户滑动到下一个视频时,该视频的前几个片段已经下载完毕,可以立即播放。
不妨将其想象成一家餐厅。忙碌的厨房不会等到订单来了才开始切洋葱。🧅 提前做好准备工作。预加载是视频播放器的准备工作。
启用后,如果用户在播放缓冲区到达下一项之前跳到下一项,预加载功能有助于最大限度地缩短加入延迟时间。系统会准备下一个窗口的第一个时段,并缓冲视频、音频和文本样本。预加载的片段随后会排入播放器队列,其中包含可立即使用的缓冲样本,并准备好馈送到编解码器进行渲染。
Media3 中有两个主要预加载 API,分别适用于不同的应用场景。选择合适的 API 是第一步。
1. 使用 PreloadConfiguration 预加载播放列表内容
这是一种简单的方法,适用于播放顺序可预测的线性、依序媒体(例如一系列剧集)。您可以使用 ExoPlayer 的播放列表 API 为播放器提供完整的媒体文件列表,并为播放器设置 PreloadConfiguration,然后播放器就会按照配置自动预加载序列中的后续媒体文件。当用户在播放缓冲区尚未重叠到下一项之前跳到下一项时,此 API 会尝试优化加入延迟时间。
只有在没有为正在进行的播放加载媒体时,才会开始预加载,这可以防止预加载与主要播放争夺带宽。
如果您仍不确定是否需要预加载,此 API 是一个不错的低成本试用选项!
player.preloadConfiguration =
PreloadConfiguration(/* targetPreloadDurationUs= */ 5_000_000L)通过上述 PreloadConfiguration,播放器会尝试为播放列表中的下一项预加载 5 秒的媒体内容。
选择启用后,您可以使用 PreloadConfiguration.DEFAULT 停用播放列表预加载,从而再次关闭播放列表预加载:
player.preloadConfiguration = PreloadConfiguration.DEFAULT
2. 使用 PreloadManager 预加载动态列表
对于动态界面(例如垂直信息流或轮播界面),其中“下一个”内容由用户互动决定,PreloadManager API 非常合适。这是 Media3 ExoPlayer 库中一个功能强大的全新独立组件,专门用于主动预加载。它管理着一系列潜在的 MediaSource,并根据它们与用户当前位置的接近程度来确定优先级,还提供了对预加载内容的精细控制,适用于短视频动态信息流等复杂场景。
设置 PreloadManager
DefaultPreloadManager 是 PreloadManager 的规范实现。
DefaultPreloadManager 的构建器可以构建 DefaultPreloadManager 以及将播放其预加载内容的任何 ExoPlayer 实例。如需创建 DefaultPreloadManager,您需要传递一个 TargetPreloadStatusControl,预加载管理器可以查询该控件,以确定要为某个项加载多少内容。我们将在下文中解释和定义 TargetPreloadStatusControl 的示例。
val preloadManagerBuilder = DefaultPreloadManager.Builder(context, targetPreloadStatusControl) val preloadManager = val preloadManagerBuilder.build() // Build ExoPlayer with DefaultPreloadManager.Builder val player = preloadManagerBuilder.buildExoPlayer()
必须为 ExoPlayer 和 DefaultPreloadManager 使用相同的构建器,以确保它们底层的组件能够正确共享。
就是这样!现在,您已有一个可以接收指令的管理器。
使用 TargetPreloadStatusControl 配置时长和排名
如果您想预加载 10 秒的视频,该怎么办?您可以提供媒体文件在轮播界面中的位置,DefaultPreloadManager 会根据用户当前播放的内容与待加载内容的接近程度,来确定待加载内容的加载优先级。
如果您想控制要预加载的媒体项的时长,可以通过返回的 DefaultPreloadManager.PreloadStatus 来指定。
例如,
- 项“A”的优先级最高,加载 5 秒视频。
- 内容“B”具有中等优先级,但当您加载到该内容时,请加载 3 秒的视频。
- 项“C”的优先级较低,仅加载轨道。
- “D”项的优先级更低,只需做好准备即可。
- 其他任何内容都距离较远,不预加载任何内容。
这种精细控制有助于您优化资源利用率,建议您采用这种方式来实现流畅播放。
import androidx.media3.exoplayer.DefaultPreloadManager.PreloadStatus
class MyTargetPreloadStatusControl(
currentPlayingIndex: Int = C.INDEX_UNSET
) : TargetPreloadStatusControl<Int,PreloadStatus> {
// The app is responsible for updating this based on UI state
override fun getTargetPreloadStatus(index: Int): PreloadStatus? {
val distance = index - currentPlayingIndex
// Adjacent items (Next): preload 5 seconds
if (distance == 1) {
// Return a PreloadStatus that is labelled by STAGE_SPECIFIED_RANGE_LOADED and suggest loading // 5000ms from the default start position
return PreloadStatus.specifiedRangeLoaded(5000L)
}
// Adjacent items (Previous): preload 3 seconds
else if (distance == -1) {
// Return a PreloadStatus that is labelled by STAGE_SPECIFIED_RANGE_LOADED //and suggest loading 3000ms from the default start position
return PreloadStatus.specifiedRangeLoaded(3000L)
}
// Items two positions away: just select tracks
else if (distance) == 2) {
// Return a PreloadStatus that is labelled by STAGE_TRACKS_SELECTED
return PreloadStatus.TRACKS_SELECTED
}
// Items four positions away: just select prepare
else if (abs(distance) <= 4) {
// Return a PreloadStatus that is labelled by STAGE_SOURCE_PREPARED
return PreloadStatus.SOURCE_PREPARED
}
// All other items are too far away
return null
}
}提示:PreloadManager 可以同时预加载上一个和下一个内容,而 PreloadConfiguration 只会预加载下一个内容。
管理预加载项
创建管理器后,您就可以开始告诉它要执行哪些任务。当用户滚动浏览信息流时,您需要识别即将播放的视频,并将其添加到管理器中。与 PreloadManager 的互动是界面与预加载引擎之间由状态驱动的对话。
1. 添加媒体文件
在填充 Feed 时,您必须告知管理器需要跟踪的媒体。如果您刚开始使用,可以添加要预加载的整个列表。之后,您可以根据需要继续向列表中添加单个商品。您可以完全控制预加载列表中的内容,这意味着您还必须管理从管理器中添加和移除的内容。
val initialMediaItems = pullMediaItemsFromService(/* count= */ 20)
for (index in 0 until initialMediaItems.size) {
preloadManager.add(
initialMediaItems.get(index),index)
)
}现在,管理器将开始在后台为此 MediaItem 提取数据。
添加后,告知管理器重新评估其新列表(提示某些内容已更改,例如添加/ 移除内容,或用户切换到播放新内容)。
preloadManager.invalidate()
2. 检索和播放内容
接下来是主播放逻辑。当用户决定播放该视频时,您无需创建新的 MediaSource。而是向 PreloadManager 请求其已准备好的实例。您可以使用 MediaItem 从 Preload Manager 中检索 MediaSource。
如果从 PreloadManager 检索到的项为 null,则表示 mediaItem 尚未预加载或添加到 PreloadManager,因此您选择直接设置 mediaItem。
// When a media item is about to display on the screen
val mediaSource = preloadManager.getMediaSource(mediaItem)
if (mediaSource!= null) {
player.setMediaSource(mediaSource)
} else {
// If mediaSource is null, that mediaItem hasn't been added yet.
// So, send it directly to the player.
player.setMediaItem(mediaItem)
}
player.prepare()
// When the media item is displaying at the center of the screen
player.play()通过准备从 PreloadManager 检索到的 MediaSource,您可以利用内存中已有的数据,从预加载无缝过渡到播放。这就是启动时间更短的原因。
3. 使当前索引与界面保持同步
由于我们的信息流 / 列表可能是动态的,因此请务必将当前播放索引通知 PreloadManager,以便它始终优先预加载最接近当前索引的内容。
preloadManager.setCurrentPlayingIndex(currentIndex) // Need to call invalidate() to update the priorities preloadManager.invalidate()
4. 移除项目
为了让管理器保持高效,您应移除它不再需要跟踪的项,例如远离用户当前位置的项。
// When an item is too far from the current playing index preloadManager.remove(mediaItem)
如果您需要一次性清除所有内容,可以调用 preloadManager.reset()。
5. 释放 Manager
当您不再需要 PreloadManager 时(例如,当界面被销毁时),必须将其释放以释放其资源。建议在释放 Player 资源的位置执行此操作。建议您在释放播放器之前先释放管理器,因为如果您不再需要预加载,播放器可以继续播放。
// In your Activity's onDestroy() or Composable's onDispose preloadManager.release()
演示时间
查看实际应用 👍
在下面的演示中,我们可以看到 PreloadManager 的影响:右侧的加载时间更快,而左侧显示的是现有体验。您还可以查看演示的代码示例。(额外福利:它还会显示每个视频的启动延迟时间)
后续步骤
第 1 部分到此结束!现在,您已经掌握了构建动态预加载系统所需的工具。您可以使用 PreloadConfiguration 在 ExoPlayer 中预加载播放列表中的下一项,也可以设置 DefaultPreloadManager、动态添加和移除内容、配置目标预加载状态,以及正确检索预加载的内容以进行播放。
在第 2 部分中,我们将深入探讨 DefaultPreloadManager。我们将探讨如何监听预加载事件,讨论使用滑动窗口避免内存问题等最佳实践,并深入了解 ExoPlayer 和 DefaultPreloadManager 的自定义共享组件。
您有任何反馈要分享吗?我们期待收到您的回复。
敬请期待,快去提升您的应用速度吧!🚀
-
产品资讯欢迎观看“使用 Media3 预加载媒体”三部曲系列的第二部分。本系列旨在指导您在 Android 应用中构建响应迅速、低延迟的媒体体验。
Mayuri Khinvasara Khabya • 阅读用时:9 分钟 -
产品资讯随着智能体开发不断加速,Android 工程领域正迈向一个令人振奋的新境界:从辅助性代码补全,跃升至智能体在您的指挥下执行端到端任务。您可以完全自主、灵活地选择要使用的模型和智能体。
Jason Buchel • 阅读用时:3 分钟 -
产品资讯今天,我们分享了如何将“不只是商店”的愿景推进到下一个阶段。从月活跃用户数超过 2.5 亿的“我”标签页,到连接超过 7 亿玩家资料的统一游戏平台,Google Play 始终致力于满足用户的各种需求。
Paul Feng • 阅读用时:3 分钟
每周通过电子邮件接收最新的 Android 开发洞见。