视频在滑动列表中的异步缓存和播放

最近在Github上看到VideoPlayerManager这么一个项目,目的在是ListView和RecyclerView中播放小视频,模仿了Instagram中滑动到可见视频项时开始播放该视频,滑动至不可见时停止视频播放的功能

但是该项目存在几个问题:

  1. 快速上下滑动列表后,无法再播放视频,有时还会直接Crash

  2. 不支持网络视频的异步缓存

故在该项目的基础上进行了优化,并且支持网络视频的异步缓存

网络视频的异步缓存

视频的缓存其实跟图片缓存大致是一样的,现在图片缓存框架很多,但是根本原理都是网络下载+内存缓存+本地缓存这三大块组成。而视频的缓存只需要跳过内存缓存就可以了,当发视频文件未下载时就去下载并本地缓存,下次就直接从本地缓存读取视频文件信息,所以基于图片缓存框架不难实现视频文件的缓存功能。

这里我采用了Glide来实现视频缓存,Glide不仅支持图片缓存还支持对普通文件缓存,所以使用Glide可以很简单的就能实现视频文件的缓存

基于TextureView的视频播放控件

Android原生提供了一个视频播放控件 - VideoView,但VideoView是基于SurfaceView实现的,SurfaceView会单独一个窗口用来绘制,它不在View hierachy中,显示也不受View的属性控制,不能进行平移,缩放等变换,也难以放在ListView或者ScrollView中,一些View中的特性也无法使用。

为了弥补SurfaceView的不足,Android在4.0中加入了TextureView,它并没有创建一个单独的窗口用来绘制,这使得它可以像一般的View一样执行一些变换操作,设置透明度等,也很方便的放在其它ViewGroup中

所以要在ListView或者RecyclerView中播放视频,我们就需要实现基于TextureView的VideoView,实现代码参考ViewVideo就可以了

视频在滑动列表中的自动播放和停止

要实现视频的自动播放和停止,我们需要计算每个item中列表中的可见比。比如当某item可见比大于70%时,则该item视为可见的,激活视频播放。反之视为不可见,停止视频播放

这里简单说下实现原理,主要分为下面三步

  1. 在列表滑动时,判断滑动方向

  2. 根据滑动方向判断相邻的item是否视为可见,比如在下滑列表时,当前可见item的可见比在逐渐减小,而下一项的可见比在逐渐加大,当前item可见比低于70%时停止播放,下一项可见比大于70%时就开始播放

  3. 在快速滑动列表时,不检测item的变化(避免卡顿);在滑动停止时,查找当前可见item中可见比最大的item,如果该item和之前可见的item不一样时,则激活该item

列表中视频播放的性能问题

视频的播放主要使用了MediaPlayer,MediaPlayer的状态图如下所示:

state diagramstate diagram

从图中可以看出,视频在开始播放前需要首先通过setDataSource()进行初始化,然后通过prepare()或者prepareAsync()进行播放前准备工作,最后准备完成后通过start()操作才开始播放视频

其中prepare()操作是相当耗时的,这一步操作绝不应该在UI线程中调用,而prepareAsync()则是使用异步的方式调用,所以在list列表中播放视频应该使用prepareAsync()来准备视频

光靠prepareAsync()这一步,可不足以保证list滑动时每帧耗时不超过16ms,像setDataSource(),reset(),release()这些操作都是比较耗时的,虽然达不到引起ANR的程度,但是对于list滑动的流畅性却影响很大

解决方案

这里我采用了将MediaPlayer的全部操作都放在一个单独的线程中去处理,事件回调则通过ui Handler post回ui线程,这样就可以保证list滑动的流畅性

效果预览

代码具体的使用和详细实现方法都已放到Github上

项目地址:VideoListPlayer

欢迎大家拍砖

原文出处:blog.waynell.com