在当今以媒体为中心的应用中,提供流畅、不间断的播放体验是打造出色用户体验的关键。用户希望视频能够立即开始播放,并且流畅播放,不会出现暂停。
核心挑战是延迟时间。传统上,视频播放器仅在用户选择要播放的内容后才开始工作,即连接、下载、解析、缓冲。这种被动式方法在当今的短视频背景下显得过于缓慢。解决方案是积极主动。我们需要预测用户接下来会观看什么内容,并提前准备好相应内容。这就是预加载的本质。
预加载的主要优势包括:
- 🚀 更快的播放开始速度:视频已准备就绪,因此项目之间的过渡更快,开始播放也更及时。
- 📉 减少缓冲:通过主动加载数据,播放不太可能因网络问题等原因而停滞。
- ✨ 带来更流畅的用户体验:更快的启动速度和更少的缓冲时间相结合,可为用户带来更流畅、更顺畅的互动体验。
在这个分为三部分的系列中,我们将介绍并深入探讨 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 分钟
-
产品资讯
Android Studio Panda 4 现已是稳定版,可在生产环境中使用。此版本带来了规划模式、后续编辑预测等功能,让您能够比以往更轻松地构建高质量的 Android 应用。
Matt Dyor • 阅读用时:5 分钟
-
产品资讯
如果您是 Android 开发者,并希望在应用中实现创新的 AI 功能,那么我们最近推出的强大新更新将能满足您的需求。
Thomas Ezan • 阅读用时:3 分钟
随时了解最新动态
每周通过电子邮件接收最新的 Android 开发洞见。