短视频作为内容重要的承载方式,是吸引用户的重点,短视频的内容与体验直接关系到用户是否愿意长时停留。因此,体验的优化就显得尤为重要。
跨页面续播
跨页面续播是除秒播外另一个可以从体感上增加用户体验的能力。由于一些业务场景需要在不同页面上播放同一个视频内容的场景,而这些场景页面切换往往是连续的,这就要求短视频的播放也是连续。这样才能使得体验上会有连贯性,让用户在进入沉浸式页面时,能流畅的过度,并无感知的继续播放,从而产生连续不间断的感受。
在优化前,盒马沉浸式短视频播放页面的体验与主流短视频 App 有明显差距。从卡片列表页面跳转视频沉浸式页面时,相同视频无法续播,影响用户观赏和体验。下面主要介绍盒马短视频从普通展示页进入沉浸式页面时跨页面续播能力和流畅的动画切换效果的实现过程。
环境
- 手机:Pixel 4
- os:Android 10
- 播放器:淘宝播放器
效果对比
首先我们来看一下盒马优化前后与主流短视频 App 的效果对比
问题分析
从对比可以看出,续播的关键在于视频流的复用以及页面转场动画。
◆ 视频流的复用
要解决流的复用,同时又要保证进入新的页面时可以立即播放,不产生声音和画面的顿挫,这里根据上一篇 《揭秘盒马鲜生 APP Android 短视频秒播优化方案》 的分析,必须要解决视频下载,加载解码的耗时。
- 根据 《揭秘盒马鲜生 APP Android 短视频秒播优化方案》 里讲到缓存原理,这里可以利用播放器播放同一个视频(注意统一 URL,盒马全部转为 H.265)来避免多次下载。
- 加载解码的耗时则需要播放器复用来解决。这里涉及到实现方案,可参照下一章的续播方案选型。
◆ 转场动画
转场动画能显著提高体感流畅度,但实现过程中需要考虑各种兼容问题。
续播方案选型
在优化前期,我们考虑了三种续播方案。
1. 播放器 View 跨页面传递
优点:思路简单,体验效果好。
缺点:业务侵入严重,不具通用性,播放器业务回调无法隔离,不利于续播放器管控。
2. 基于 Surface(View) 级别的全局播放器管理
优点:体验效果好,能扩展内存管控,侵入性低。
缺点:实现复杂,需要改写底层 HMVideoView 的封装逻辑;改造中易出现内存泄漏,较难排查。
3. 基于 MediaPlayer 级别的全局播放器管理
优点:无侵入,能扩展内存管控,实现快(可复用和扩展淘宝播放器底层 token 机制)
缺点:需要一定的改造,体验比方案 1、2 略差(声音有一瞬间的顿挫,不明显)
盒马最终选择 方案 3 ,这里方案 2 和 3 原理是相同的,没有明显的优劣之分,最终选择方案 3 是因为这是目前稳定性最高,成本最低的方法。后续的播放器续播、复用、管理的分析同样适用于方案 2。
播放器续播、复用和管理
业务上,我们需要实现续播,通过问题分析,我们已经知道,通过视频流的复用即可实现,而视频流的复用这里选择通过复用 MediaPlayer 实现(也可以复用 Surface+MediaPlayer)。
1 解耦播放器 View 与 MediaPlayer 层
将 MediaPlayer 从 TaobaoPlayerView 中拆解出来,通过 MediaPlayerManager 进行全局管理。全局管理后,所有的播放器的 MediaPlayer 都由 MediaPlayerManager 分配和控制。
各组建间关系
2 业务流程
确保业务流程中,只需要关心业务与 VideoView 之间的交互,底层播放器复用由 MediaPlayerManager 实现。
3 播放器复用(管理)原理
播放器复用是管理的一个子集,所以这里一起介绍。主要原来有以下几个原则:
- 全局播放器(MediaPlayer)控制最多创建 4 个;
- 超过 4 个播放器,创建第 5 个时,先销毁最少使用的播放器的 MediaPlayer;
- 每个播放器随机分配一个 token(时间戳 + 随机数),也可以开发者指定;
- 相同 token 的播放器,共享 MediaPlayer;
- 一个 MediaPlayer 同时只能被 1 个播放器 Surface 所绑定和持有;
- 存在相同 token 的播放器,当前播放器在销毁时,保留 MediaPlayer 实例;
- 已创建的播放器恢复播放,但 MediaPlayer 被其他后创建的播放器占用时,解绑 MediaPlayer 并重新绑定当前播放器。
4 场景模拟
场景一:APP 共创建 4 个及以内播放器。
场景二:创建超过 4 个播放器时。
场景三:新创建的播放器 token 已存在时,复用 MediaPlayer。
场景四:存在 token 与当前即将被销毁的播放器 token 一致时(或已被解除 MediaPlayer 的播放器播放时)。
逻辑流程图
从场景总结,MediaPlayer 主要提供 复用、恢复、销毁、驱逐(创建) 四个能力。
转场动画
目前转场动画有两个方案可选:
1. Android 自带的元素动画
优点:动画流畅顺滑,无需实现动画逻辑,由系统自己实现。
缺点:侵入严重,需要改写 Nav 层,在 View 复用的方案下有白屏和黑屏。
2. 自定义实现属性动画
优点:侵入小,只需要前置页极少的坐标信息,如果是 View 复用方案,甚至不需要前置页提供坐标信息;兼容性好,适用于各种播放器复用场景。
缺点:需要自己实现动画,有一定的闪烁感。
动画原理
- 前置页跳转到沉浸式,传递播放器坐标 Rect 信息;
- 沉浸式默认透明,并根据 Rect 坐标信息创建播放器(复用);
- 开始动画,将播放器 View 放大至正确位置,同时背景不透明度增加。
(注意:这里最后要将沉浸式页的主题设为不透明,否则前置页不会执行 onStop() 具体参考下一节,生命周期填坑。)
ps:返回动画同理,过程相反即可。
生命周期填坑
属性动画原理存在一个坑。
问题描述:
假设页面为 A->B,方案 3 要求 B 页面在动画过程中是全透明的。当 B 的 theme 中 windowIsTranslucent 为 true 时,A->B 过程 A 的生命周期无法走向 stop(即便 B 页面动画结束,完全遮盖 A 页面)。因此,A 的生命周期没有按照预期执行,一些需要 onStop 执行的场景下,业务就无法正常执行
B Ativity 的样式(注:示例代码):
<style name="MyTransparent" parent="xxxx">
<item name="android:windowFullscreen">false</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:colorBackgroundCacheHint">@null</item>
<item name="android:windowIsTranslucent">true</item>
<item name="android:background">@android:color/transparent</item>
<item name="android:windowAnimationStyle">@style/noAnimation</item>
</style>
解决方案:
- 进入动画结束时,通过反射调用 Activity 的 convertFromTranslucent 方法, 使 activity 不透明;
- 返回动画开始时,通过反射调用 Activity 的 convertToTranslucent 方法,使 activity 透明。
后续优化展望
关于多媒体的优化工作还有很多可以做。除了续播和沉浸式秒播等场景外,我们还可以:
- 对播放器的一般性场景进行秒播优化,如首页列表的卡片视频;
- 对播放器的全局实例管控,控制播放器创建数量,从而优化内存。
未优化:
操作:连续开启 30~50 个页面及播放器。
现象:内存飙升,手机发烫,影响手机正常使用。
优化后:
操作:每秒开启 1 个页面和播放器,连续开启 100 个。
现象:内存呈锯齿状正常上升,无明显飙升现象,软件运行正常。