在当今以媒体为中心的应用中,提供流畅、不间断的播放体验是打造出色用户体验的关键。用户希望视频能够立即开始播放,并且流畅播放,不会出现暂停。
核心挑战是延迟。传统上,视频播放器只有在用户选择要播放的内容后才会开始工作,即连接、下载、解析、缓冲。对于当今的短视频环境,这种被动方法速度较慢。解决方案是主动出击。我们需要预测用户接下来会观看什么内容,并提前准备好内容。这就是预加载的本质。
预加载的主要优势包括:
- 🚀 更快的播放开始速度: 视频已准备就绪,因此内容项之间的切换速度更快,并且可以立即开始播放。
- 📉 减少缓冲: 通过主动加载数据,播放不太可能停滞,例如由于网络故障而导致停滞。
- ✨ 带来更流畅的用户体验: 更快的开始速度和更少的缓冲相结合,为用户创造了更流畅、更顺畅的互动体验。
在这个由三部分组成的系列中,我们将介绍并深入探讨 Media3 的强大实用程序,用于(预)加载组件。
- 在第 1 部分中,我们将介绍基础知识:了解 Media3 中提供的不同预加载策略、启用 PreloadConfiguration 并设置 DefaultPreloadManager,使您的应用能够预加载内容项。阅读完这篇博文后,您应该能够使用配置的排名和时长预加载和播放媒体内容项。
- 在 第 2 部分 中,我们将介绍 DefaultPreloadManager 的更高级主题:使用监听器进行分析、探索可用于生产用途的最佳实践,例如滑动窗口模式以及 DefaultPreloadManager 和 ExoPlayer 的自定义共享组件。
- 在第 3 部分中,我们将深入探讨使用 DefaultPreloadManager 进行磁盘缓存。
预加载来助你脱困!🦸♀️
预加载背后的核心理念很简单:在需要媒体内容之前加载它。当用户滑动到下一个视频时,视频的第一个片段已经下载并可用,可以立即播放。
不妨将其想象成一家餐厅。繁忙的厨房不会等到接到订单才开始切洋葱。🧅 他们会提前做好准备工作。预加载就是为视频播放器做准备工作。
启用后,当用户在播放缓冲区到达下一个内容项之前跳到下一个内容项时,预加载有助于最大限度地缩短加入延迟时间。系统会准备好下一个窗口的第一个周期,并缓冲视频、音频和文本样本。预加载的周期随后会排入播放器队列,缓冲的样本会立即可用,并准备好馈送到编解码器以进行渲染。
在 Media3 中,有两个主要的预加载 API,每个 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 预加载动态列表
对于动态界面(例如垂直 Feed 或轮播界面),其中“下一个”内容项由用户互动决定,PreloadManager API 是合适的选择。这是 Media3 ExoPlayer 库中的一个功能强大的全新独立组件,专门用于主动预加载。它管理着一系列潜在的 MediaSource,根据与用户当前位置的接近程度对它们进行优先级排序,并提供对要预加载的内容的精细 控制,适用于复杂的场景,例如短视频的动态 Feed。
设置 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 只会预加载下一个内容项。
管理预加载的内容项
创建管理器后,您可以开始告知它要处理的内容。当用户滚动浏览 Feed 时,您将识别即将播放的视频并将其添加到管理器中。与 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 从预加载管理器中检索 MediaSource。
如果从 PreloadManager 检索到的内容项为 null,则表示 mediaItem 尚未预加载或添加到 PreloadMamager,因此您选择直接设置 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. 使当前索引与界面保持同步
由于我们的 Feed / 列表可能是动态的,因此务必将当前播放索引通知 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. 释放管理器
如果您不再需要 PreloadManager(例如,当界面被销毁时),则必须释放它以释放其资源。一个不错的做法是在您已经释放播放器资源的位置执行此操作。建议您在播放器之前释放管理器,因为如果您不再需要任何预加载,播放器可以继续播放。
// 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 分钟阅读时间
-
产品动态
在今年的 Google I/O 大会上,我们讨论了不断发展的业务模式,该模式提供了更多选择,并为您的应用和内容在应用商店内外被发现提供了新方式。我们还推出了先进的工具和分析洞见,这些工具和分析洞见将帮助您以更低的复杂性扩展业务。
Paul Feng • 6 分钟阅读时间
-
产品动态
我们很高兴地宣布,Android XR 现已正式支持 Unreal Engine 和 Godot。我们还推出了旨在提高您的工作效率并启用新的 XR 功能的新工具:Android XR Engine Hub 和 Android XR Interaction Framework。
Luke Hopkins • 4 分钟阅读时间
随时了解最新动态
每周都会将最新的 Android 开发洞见发送到您的收件箱 每周。