在当今以媒体为中心的应用中,提供流畅、不间断的播放体验是打造出色用户体验的关键。用户希望视频能够立即开始播放,并且流畅播放,不会出现暂停。
核心挑战是延迟。传统上,视频播放器只有在用户选择要播放的项后才会开始工作,即连接、下载、解析、缓冲。对于当今的短视频环境,这种被动方法速度较慢。解决方案是主动出击。我们需要预测用户接下来会观看什么内容,并提前准备好内容。这就是预加载的本质。
预加载的主要优势包括:
- 🚀 更快的播放开始速度: 视频已准备就绪,因此项之间的过渡更快,并且可以立即开始播放。
- 📉 减少缓冲: 通过主动加载数据,播放不太可能停滞,例如由于网络故障而停滞。
- ✨ 带来更流畅的用户体验: 更快的开始速度和更少的缓冲相结合,为用户创造了更流畅、更顺畅的互动体验。
在这个由三部分组成的系列中,我们将介绍并深入探讨 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 分钟
-
产品动态
每位开发者的 AI 工作流和需求都是独一无二的,因此能够选择 AI 如何帮助您进行开发非常重要。1 月,我们推出了在 Android Studio 中选择任何本地或远程 AI 模型来支持 AI 功能的功能
Matthew Warner • 阅读时间:2 分钟
-
产品动态
Android Studio Panda 3 现已稳定,可供您在生产环境中使用。此版本让您可以更好地控制和自定义 AI 支持的工作流,从而比以往更轻松地构建高质量的 Android 应用。
Matt Dyor • 阅读时间:3 分钟
随时了解最新动态
每周都会将最新的 Android 开发见解发送到您的收件箱 每周。