图片来源:bz.zzzmh.cn/前端
本文做者:王永亮git
在网易云音乐 8.0 改版中,接到一个播放中的视频能够点击「小窗」按钮收起到 mini 播放条中继续播放的需求,刚接到这个需求时心里是崩溃的,要知道网易云音乐的 mini 播放条是一个可能会出如今 App 中的任何 Activity 上的 View,在不一样 Activity 之间跳转时,如何能保证视频能够从一个 Activity “无缝”转移到另外一个 Activity 呢?github
通常简单的视频播放功能我会使用系统自带的 VideoView,只需几行代码就可让视频播放起来,系统自带的 VideoView 继承自 SurfaceView,而且将 MediaPlayer 的具体调用,包括 Surface 和 MediaPlayer 的绑定封装在里面,这样封装的优点是简单易用,可是也存在一些问题,SurfaceView 和 MediaPlayer 彻底绑定在一块儿,一个 MediaPlayer 只能对应一个 SurfaceView,而小窗播放想作到的是 MediaPlayer 和 SurfaceView 能够一对多,在页面切换时 MediaPlayer 能够绑定新的 SurfaceView,就像一台电脑对应多个显示器。咱们的视频播放框架很好的解决了这个问题,以下图所示:缓存
因为 App 中有一些对视频作动画的场景,因此框架中使用的是 TextureView,TextureView 和 MediaPlayer 使用 AIDL 进行通讯,以下图所示:markdown
从上面两图能够看出,视频播放框架中把全部的 MediaPlayer 放到了一个单独的 Video 进程的缓存池中来管理,正在使用的放在 Active 的池子中,闲置在 idle 池子中,闲置的 MediaPlayer 超过上限时会被回收,启动新页面时 VideoView 能够从 Video 进程的池子中获取闲置的 MediaPlayer,其余进程中的 VideoView 经过 AIDL 同 Video 进程中的 MediaPlayer 通讯。架构
这种架构使不一样 Activity 中的 VideoView 能够很方便的替换其绑定的 MediaPlayer,因为播放能力都在 MediaPlayer 中,因此在 MediaPlayer 同 TextureView 解绑时并不会致使播放的中断,新页面启动时,只要将正在播放的 MediaPlayer 同 TextureView 从新绑定,新的页面就能马上展现播放中的画面了。实际上视频播放框架最先并非服务于无缝播放的场景,设计最先是出于如下缘由:app
因此综合以上需求咱们设计了这套 MediaPlayer 和 TextureView 隔离的方案,若是是比较简单场景也能够考虑使用单例持有 MediaPlayer,因为这套方案已经很好的将 MediaPlayer 和 TextureView 隔离,因此咱们只须要经过给 MediaPlayer 池子增长一些获取被复用播放器的方法就能够很容易的支持 VideoView 和 MediaPlayer的换绑,从得到无缝播放的效果了。框架
具体的换绑 MediaPlayer 流程以下图:ide
在原有 Activity 中,若是播放器是要被复用的,咱们会将播放器的惟一 id 和正在播放的资源 id 保存在一个全局位置,以此做为播放器可复用的标志。在新页面启动时,新页面的 VideoView 被建立,在新页面中会调用 VideoView 的 setDataSource 设置要播放的内容,setDataSource 会根据当前播放的内容和保存的全局播放器 id 在播放器池子中从新找到原来正在播放的播放器,并将 Surface 经过 AIDL 发送给被复用的 MediaPlayer 从新绑定,这样在不打断当前播放的状况下,视频播放的画面就无缝被转移到新的 Activity 中了。其中要注意的一个知识点是Surface自己就是支持跨进程传递的:函数
public class Surface implements Parcelable
复制代码
另外这个方案中使用 MediaPlayer 对象的 hashCode 做为播放器的惟一 id,若是使用这个方案,你们也能够结合本身的状况设计惟一id。
换绑方案的核心是 MediaPlayer 同 VideoView 的从新绑定,从新绑定只须要作到下面两步:
这个方案基本上能知足绝大部分的无缝播放需求,不过也并不是没有缺点,这个方案主要有如下几个问题:
终实现效果以下图:
在换绑方案以外,网易云音乐中也有一些其余的无缝播放方案实现,首先介绍一种实现比较简单,也是在网易云中比较早使用的一种方案,“假”页面切换方案,由名字能够知道,这种方案不是真正的在 Activity 之间进行跳转,而是利用 TextureView 能够像普通 View 同样移动、作动画的特性,利用过渡动画,让效果看起来像是从一个页面跳转到了另外一个页面,效果以下图所示:
在网易云音乐的视频 Feed 流中,视频播放时,点击热区能够在不暂停播放的状况下“展开”到播放详情页,具体的实现方法是将视频播放的 View 放在 Fragment 中,Fragment 的 Container 放在整个 ViewTree 的最顶层,点击播放时,将视频播放 Fragment 移动到须要展现视频的位置并开始播放,须要点击进入详情页时,只须要对视频播放的 Fragment 作平移和缩放动画,在视频播放的 Fragment 下方再添加评论等其余的 Fragment。这里能够参考 Android 原生的 VideoView 的封装思想来实现:
public class VideoView extends SurfaceView
implements MediaPlayerControl, SubtitleController.Anchor {
复制代码
参考 VideoView 源码能够将 SurfaceView 替换为 TextureView ,再对应处理下 onSurfaceTextureAvailable 等回调便可。这种方案应用仍是比较普遍的,好比京东、淘宝等的商品详情的介绍视频。这种方案虽然简单可是局限也比较大,只能解决在同一个 Activity 中的场景,若是需求是在不一样 Activity 中无缝播放切换这个方案就没法知足了。
实现跨 Activity 场景无缝播放的另外一个方案是打开新的页面时,在新的页面中使用新播放器从新打开资源,并根据原来保存的进度从新 seek 后再续播,这种方案其实并不能保证真正的“无缝”播放,毕竟 Activity 启动也要消耗一两百毫秒的时间,不过这个方案最大的优点是一些老逻辑进行不多的更改就能够支持无缝播放功能,好比在一些不是很重要的页面中,视频播放功能可能已经存在而且播放逻辑耦合了很重的业务逻辑,这时 seek 方案就比较合适了。
这个方案虽然简单可是也有一些须要注意的地方:
关键帧被称为 I 帧,能够被看作是一帧没有压缩过的画面,解码的时候无需依赖其余帧,关键帧之间还存在 B 帧和 P 帧这样的压缩帧,须要依赖其余帧才能解码出完整的画面,两个关键帧之间的间隔被称为一个 GOP,在 GOP 内的帧系统播放器是没办法直接 seek 的。
View 跨 Activity 复用是指手动使用 ApplicationContext 建立须要被复用的 View,而且使用单例 Manager 持有该 View,添加删除可复用 View 能够统一在 Activity 生命周期函数中实现,示例代码以下:
object Manager : ActivityLifecycleCallbacks
override fun onActivityStarted(activity: Activity) {
...
removePlayerBarFromWindow(activity)
addPlayerBarToWindow(activity)
}
override fun onActivityPaused(activity: Activity) {
...
if (activity.isFinishing && getMiniPlayerBarParentContext() == activity) {
removePlayerBarFromWindow(activity, true)
}
}
复制代码
private fun getPlayerBar(activityBase: Activity): MiniPlayerBar {
synchronized(this) {
if (miniPlayerBar == null) {
miniPlayerBar = MiniPlayerBar(activityBase.applicationContext)
}
...
return miniPlayerBar!!
}
}
复制代码
理论上这是一种更加灵活的方案,使用 Application 做为 View 的 Context 也不用担忧泄漏问题,不过因为在此次小窗的需求中涉及到老的页面和新页面的播放器复用,在不少场景下并非一个统一的播放View,因此没有采用这种方案,不过这个方案在网易云音乐的音街 App 的 mini 播放条上已经被使用,有兴趣的小伙伴也能够尝试下。
以上是网易云音乐中一些无缝播放的方案的总结,主要介绍了一下网易云音乐中几种无缝播放能力的实现思路,给你们方案选型作参考,若是有其余的方案也欢迎交流。网易云音乐中的方案是从简单到复杂逐渐演进而来,随着需求不断迭代变成今天的样子,我的理解设计方案时不用过度的追求大而全,适合当前场景的才是最好的,好的架构不只要靠好的设计,也要靠不断的改进优化。
本文发布自 网易云音乐大前端团队,文章未经受权禁止任何形式的转载。咱们常年招收前端、iOS、Android,若是你准备换工做,又刚好喜欢云音乐,那就加入咱们 grp.music-fe(at)corp.netease.com!