android 多播放器实现列表播放视频秒开

1,101 阅读6分钟

前言

做短视频也有一年半了,谈不上很专业,但是也算踩了不少坑,不管是总结还是什么,就当这一年多以来的经验记录吧。

效果

先上个录屏看看效果,图1,抖音的录屏,图2,自己完成的第一版效果(不得不说,细节上还是跟抖音有差距)

device-2022-04-19-142233.gif device-2022-04-19-141844.gif

图1(抖音)                                           图2(优化前效果)

看上面的效果,可以看出抖音和我们的产品有比较大的差距,抖音在滑动时基本无缝播放了,而图2这边在滑动之后会明显的有停顿感,大概150ms左右的区别,而产品想要的可能就是把这150ms给干掉,基于此,只能继续做优化了,想要极致的体验就得一点点细节进行分析了。

先贴一篇参考文章# 揭秘盒马鲜生 Android 短视频秒播优化方案

参考上面的文章,基本实现思路跟我自己的整体上差不多,关键的地方在于盒马使用了多播放器+播放器首帧,而我这边为了防止性能问题,用的单播放器+封面隐藏,这里就是关键的点了。

有了上面的这些引子,再来讲述下面的内容相信大家就有点兴趣了.

列表方案

现有的视频列表的实现方式有如下几种,如果有更好的欢迎留言

  1. recyclerView+PagerSnapHelper
  • 优点:低端机上性能较好,可以控制滑动速率和动画
  • 缺点:生命周期需要自己控制,手指按住屏幕的情况可以一直往下滑动,容易出现bug
  1. viewpager2+fragment
  • 优点:API齐全,上手速度快
  • 缺点:低端机上反复加载和释放fragment会有卡顿,性能低
  1. Viewpager2+ RecyclerView.Adapter
  • 优点:相比方案2能好,相比方案1无法控制滑动速率和动画
  • 缺点:生命周期由自己控制

4 自定义viewpager

  • 据说淘宝是这个方案,未具体研究
  • TikTok是自定义的viewpager,在滑动控制上可以达到完美的效果

附带一篇文章讲解多种方案的实现以及差别 仿抖音上下滑动方案

对低端机性能和播放秒开进行综合评估之后,最终采用了Vwpager2+RecyclerView.Adapter+多播放器的方案,目前市面上已知的同类型短视频app,除了tiktok在性能和秒开都做的较好之外,其他的如抖音,虾皮,快手等都存在一定的问题,要么就是秒开效果不好,要么就是低端机滑动时明显卡顿,下面贴2个帧率图对比下效果,手机配置为4G RAM,64G ROM,8核(这个配置在非洲还是中高端了)

device-2022-04-20-194140.gif device-2022-04-20-194532.gif

虾皮                                        TikTok

各类问题解决

过程讲解起来比较麻烦,也要画较多的图和排版,为了方便,采取自问自答的形式来讲述吧

1. 如何做到视频秒开处理

  • 老方案:

    采用一张封面覆盖在surfaceView上面,视频首帧加载的时候,隐藏封面,显示播放,这种方案就是我上面优化前的效果图,一定会有那么一点停顿,无法做到百分之百的无缝播放,特别是首帧和第二帧动作差别较大的时候,效果上十分明显

  • 优化方案 exoPlayer+多播放器+提前prepare

    说下核心思路吧,首先是多播放器,目前我采用的是3个,也就是当前屏幕,当前屏幕上的,当前屏幕下的,用以控制上下滑动视频,为了节省内存开销,使用到了recyclerView的holder管理机制,自动销毁和复用,在adapter的onBindViewHolder()方法去创建播放器,在adapter的onViewRecycled()方法中释放和销毁播放器,这里存在一个反复创建和销毁的过程,所以内存开销必然会比较大,在低端机上可能会导致卡顿,这个是我目前的方案。


重点1: 滑动过程中播放,想要做到抖音等app的无缝秒开的效果,必须要在滑动中就开始播放,否则滑动完成之后,就会存在上面说的100-150ms的停顿,代码如下,在ViewPager2的监听方法里面播放视频:

override fun onPageScrollStateChanged(state: Int) {

      if (state == ViewPager2.SCROLL_STATE_IDLE) {
          if (currentSelectedPosition != 0) {
              playPosition(currentSelectedPosition)
          }
      }
  }


重点2: 在adapter的onBindViewHolder创建播放器的时候需要prepare资源,在滑动完成,代码如下:

override fun onBindViewHolder(
  context: Context,
  holder: MultiBaseAdapter.BindingViewHolder,
  layerBinding: VideoLayerVideoBinding?,
  position: Int,
  multiItem: MultiItem, payload: Payload?,
) {
playerManager?.prepare(videoModel!!)
}

2.视频如何自适应屏幕

要么以宽度来适配,要么以高度来适配,视频宽高比和屏幕宽高比相除,取一个铺满全屏的比例将视频进行缩放或者裁减即可。

3.秒开是否需要对数据源做处理

这个相当于是补充项吧,要做到秒开,数据源有如下处理:
1>视频源尽量小,让服务端把数据转码成h264、h265(这个有专利费问题),列表中所有的视频要统一编码格式,因为播放器在处理不同编码的时候内部要进行编码转换,会消耗性能和时间,相同格式的视频可以让解码器复用.
2>预加载,在播放视频的时候,可以根据当前网速对后面要播放的视频提前预加载一部分,比如3-5s
3>下发不同分辨率的视频,让服务端下发不同分辨率的视频,不同分辨率的视频,码率和大小都不同,在弱网情况下,可以选低分辨率的视频进行预加载和播放,这样不管是下载和解码速度都会快很多。
4>转码的时候视频的moov头要在前面,这样就不需要下载完整的视频文件才能播放,只有部分视频文件也可以播放,对于边播边下载来说也可以较好的实现效果。

4.surfaceView在RecyclerView中导致滑动时,遮住了上层的点赞控件.