为了给用户带来多媒体方面的体验福利,一刻也不能停,现在要提升主要视频应用全屏播放的观看体验。

“提升主要视频应用全屏播放的观看体验”老板撂下一句话后拂袖而去,剩下的自己体会。经过人工智能的大脑快速处理,提取了几个比较关键的技术点。

1.视频应用,如何判断到视频应用在工作?

2.全屏播放,如何判断视频应用在播放且全屏幕播放?

3.主要视频应用,不是所有的视频应用如何区分?

4.隐藏的比较深的点,体验效果的生命周期,某个视频应用在全屏播放的时候观看体验得到增强,言外之意,非全屏播放的时候要恢复之前的状态,比如视频从全屏切换到非全屏,视频退出,视频进入后台,视频被杀死,视频崩溃等异常情况?

5.如何避免快速切换有效果/无效果/有效果,带来的视觉上的冲击?

逐个分析突破

1.视频应用,如何判断到视频应用在工作?

这些视频应用基本上都是第三方的比如腾讯视频,优酷,爱奇艺等,全部由第三方开发商实现,不受控制,需要从系统入手,对播放比较熟悉的话首先想到的是这些播放器会使用到一些系统控件进行播放,显示等。比如MediaPlayer,MediaCodec,SurfaceView,TextureView等,MediaPlayer第三方很少用基本都用自己的框架,MediaCodec只有在硬解码的时候才会用,SurfaceView或TextureView显示控件基本都会用,很少能绕过去,从这两个view入手,基本可以hack住想要的视频应用,而且虽然android有很多视频显示的控件,但基本上都是继承自这两个view。

clipboard.png

clipboard.png

通过这两个控件就可以hack到应用里了。

2.全屏播放,如何判断视频应用在播放且全屏幕播放?

首先定义下什么是全屏播放呢,一定是横屏吗,像腾讯,优酷等的电视剧电影综艺等,不一定,比如快手,抖音一类的短视频很多都是竖屏的全屏。
其次我们可以从SurfaceView和TextureVIew里拿到Surface的大小,也就是视频区域的宽高。
这样我们通过一个简单的算法就可以判断是否是全屏了。
video height > screen height || video width > screen height

clipboard.png

3.主要视频应用,不是所有的视频应用如何区分?

通过1,2操作后所有的视频应用在跑到相应逻辑后都会使能体验效果,比如在系统设置里有一些user guide视频的播放,比如多任务栏里一些动画的播放等,这些都不是我们想要的,也会给用户带来困惑造成不确定感。
没有别的办法只能通过名单来控制了,由于hack在app的java层,可以很容易的得到当前进程的包名,所以方案是通过包名过滤特定的名单。
比如。
//video apps
"com.tencent.qqlive.player.meizu","com.youku.phone.player.meizu", "com.meizu.media.video", "com.qiyi.video","tv.pps.mobile", "com.tencent.qqlive", "com.youku.phone","com.letv.android.client", "com.pplive.androidphone", "com.storm.smart","com.funshion.video.mobile", "com.cmcc.cmvideo", "com.mobile.videonews.li.video","com.sohu.sohuvideo", "com.baidu.video", “com.hunantv.imgo.activity",“tv.danmaku.bili",
//short videos
"com.flyme.videoclips", "com.smile.gifmaker", "com.ss.android.ugc.aweme","com.ss.android.ugc.live", "com.ss.android.article.video", "com.hupu.games","com.kuaiest.video", “com.tencent.weishi”,
//live videos
"com.duowan.mobile", "air.tv.douyu.android", "com.duowan.kiwi","com.panda.videoliveplatform", "com.huajiao", “com.tencent.now"

那名单做成静态的还是在线的比较好?如何条件允许的话做成在线的当然最好。原理比较简单把这个配置文件txt也好jason也好csv也好放在你的域名的某个目录下,通过http或https下载就可以了。
具体到这里最好采用jason或者csv的格式,或者其他比较好操作的格式,方便管理。
比如
Jason:
{“bussiness":{"bussiness1":{"package_name":"["com.meizu","com.tencent"]"}}}
Csv:
Name1,name2,name3,name4,name5,name6,…

然后配置你的服务就可以了,需要更新的时候刷新下cdn。

这里有个比较特殊的地方,在终端如何去管理这个名单,由于下载更新比较耗时而且为了省时且不频繁访问服务器需要在本地管理备份,但是hack到别人的应用里,所以不能在hack的代码里处理,这个时候需要一个service,而且是system service,它是一个单独的进程开机运行,用来管理一些唯一的资源,比如这个白名单的下载更新本地管理,效果模式的管理等等。
然后在hack里实现client与之通信,具体如何实现一个system service这里不表,后边由于生命周期管理的需要也会需要这个service的,后边再讨论。
service取名VideoService,有点像Android AudioService的意思它是用来管理audio相关逻辑的可以通过AudioManager访问,android没有VideoService,这里添加VideoService后也可以扩展到其他video相关逻辑的场景,同理设计了可以通过VideoManager访问的机制。

4.隐藏的比较深的点,体验效果的生命周期,某个视频应用在全屏播放的时候观看体验得到增强,言外之意,非全屏播放的时候要恢复之前的状态,比如视频从全屏切换到非全屏,视频切换,视频退出,视频进入后台,视频被杀死,视频崩溃等异常情况?

说的直白一点,需要效果的时候一定要有效果,不需要的时候千万不能有效果。这里就涉及到用户的交互了。
在介绍体验效果的生命周期之前,是不是有个疑问,效果的打开关闭时在哪里做的,是怎么做的?
打开关闭时在SurfaceView和TextureView里做的,利用了它们的生命周期。
SurfaceView的生命周期, surfaceCreated, surfaceChanged, surfaceDestroyed
TextureView的生命周期,onSurfaceTextureAvaliable, onSurfaceTextureSizeChanged, onSurfaceTextureDestroyed, onSurfaceTextureUpdated
通过view生命周期的创建,更新,销毁以及包含的宽高信息,来做到相应的打开,关闭效果。
比如SurfaceView,surfaceCreated的时候,如果判断是全屏,就打开它,用户从全屏切换到了小窗口触发了surfaceChanged,判断是否全屏,不是就关闭它,或者用户退出视频触发了surfaceDestroyed,就关闭它。

这两个view的生命周期和用户场景交互式密切相关的,正是由于这种机制,实现了体验效果的生命周期的管理,除了刚才说的全屏/小窗口切换,视频退出场景,还有几种比较典型的场景介绍下。以surfaceview为例

(1)视频在全屏幕状态,直接退到桌面;然后再点击app进入全屏
触发surfaceDestroyed,关闭效果;触发surfaceCreated, surfaceChanged,判断是否全屏,是打开效果
(2)调出多任务栏
触发surfaceDestroyed,关闭效果

clipboard.png

clipboard.png

(3)在设置里杀死视频
如果要进入的设置里去杀死视频,需要先回到桌面进入设置或通过多任务进入设置,无论回到桌面还是进入多任务,都会触发surfaceDestroyed关闭效果,所以在设置里杀死视频,体验效果是关闭状态。
(4)在多任务里杀死视频
同理,进入多任务,会触发surfaceDestroyed关闭效果,所以在多任务杀死视频,体验效果是关闭状态。
(5)视频进入后台后,被一些省电或内存管理机制杀掉
同理,视频先进入后台,会触发surfaceDestroyed关闭效果,所以视频进入后台后,被一些省电或内存管理机制杀掉,体验效果是关闭状态。
(6)正在全屏播放时,视频崩溃或用被kill
体验效果不能正常关闭,因为这个时候之前的生命周期不能正常走到。
需要为这种情况单独设计一种机制,本身设计是c/s结构的,所以很容易想到利用binder die来监听client die。不同的是根据binder die的规则,视频app也要作为一个binder的service。
在VideoManager里实现,然后VideoManager运行在视频app的进程。
private final Binder mICallback = new Binder();
然后通过binder调用将这个mICallback对象传到VideoService,在VideoService里实现IBinder.DeathRecipient
mICallback.linkToDeath(this, 0)
当die发生的时候binderDied()回调会被执行,可以在这个回调里关闭效果,完成这种情况下生命周期的设计。

5.如何避免快速切换有效果/无效果/有效果,带来的视觉上的冲击?

这里也涉及到用户的交互,怎么理解呢?可以看两个比较典型的场景
(1)浏览短视频的时候,比如抖音,从第一个开始快速滑动到第n个,这个中间可能发生了很多次打开/关闭效果,在很短的时间内,不停地切换效果会造成用户视觉上的体验不是很好
(2)另外一种不停地切换效果的情况是,频繁全屏/小窗切换
这种快速切换带来的频繁打开/关闭应该尽量避免。
所以在VideoService里设计了一个worker thread来专门串行处理各个client发过来请求,如果关闭打开关闭在很短的时间内发生,可以选择忽略。

clipboard.png

最终方案大致如下

clipboard.png


WalkerXu
95 声望29 粉丝