仿抖音视频全屏播放&滑动切换

avatar
公众号:转转技术

1 前言

随着移动技术的快速迭代,数据流量费用的快速下降,视频、直播正成为全民的媒体盛宴,我司必然也不会缺席此次盛宴,这里讲述的是通过h5实现仿抖音视频全屏播放&滑动切换的效果,供我司直播鉴定回放视频使用。

2 实现效果

3 设计方案

  • 视频播放video标签
    video标签是HTML5新增的用于视频播放的标签,MDN对其介绍如下:

对于HTML <video> 元素 用于在HTML或者XHTML文档中嵌入媒体播放器,用于支持文档内的视频播放。

兼容性如下(来自Can I Use): 其在移动端较好的兼容性,成为目前我们的首选方案之一

  • 单视频缓冲
    关于video标签的preload属性: 此属性用于定义视频是否预加载。属性有三个可选择的值:nonemetadataauto

    • None:不进行预加载。使用此属性值,可能是页面制作者认为用户不期望此视频,或者减少HTTP请求。
    • Metadata:部分预加载。使用此属性值,代表页面制作者认为用户不期望此视频,但为用户提供一些元数据(包括尺寸,第一帧,曲目列表,持续时间等等)。
    • Auto:用户需要这个视频优先加载;换句话说就是提示:如果需要的话,可以下载整个视频,即使用户并不一定会用它。

但是在实际情况下,其实只预加载了一部分。它并没有自动进行全部视频内容的下载,这样的策略实际有利于节约用户宽带造成不必要的请求。

假如不设置,默认值就是浏览器定义的了 (即,不同浏览器会选择自己的默认值),即使规范建议设置为 metadata。

由于各个浏览器实现不同,有些浏览器是处于auto默认设置,在其处于auto设置下,如果页面内存在多个视频,会同时缓冲,造成资源浪费以及低端安卓机器的白屏和崩溃。

所以,为了尽量保证当前视频的快速、流畅播放,尽量保证仅有当前视频处于资源加载中。

  • 无限加载实现
    • 简单方案:使用列表进行无限加载,和实现无限下拉列表类似,实现简单,但是在无限加载情况必然会出现页面性能问题
    • 复杂方案:参考轮播图最后一页循环加载方案,使用三个大的节点,每次动画后进行隐式切换,示意图如下:

4 实现

4.1 模板层代码实现

代码由vue进行实现,目前使用上下排列的三个主要节点构成,上下放置视频封面等信息,中间放置实际视频信息,上下节点主要用于用户滑动视频时候预览视频封面等相关信息,在移动端通过监听touch相关事件进行切换实现,其主要代码如下:

<div
  :class="[isMove && 'wrap-animation']"
  :style="{ transform: `translateY(${translateY}px)`}"
  @touchstart="onTouchStart"
  @touchmove.prevent="onTouchMove"
  @touchend="onTouchEnd"
>
  <div><!-- 一些除开视频外的点赞信息等 --></div>
  <div><!-- 视频信息 --></div>
  <div><!-- 一些除开视频外的点赞信息等 --></div>
</div>

4.2 自动切换动画实现

  • js实现 PK CSS实现
    在用户触摸结束后,如果达到切换条件,则需要切换到下一个视频,需要切换动画,动画的实现主要有requestAnimationFrame/setTimeout等传统的方法实现,也有css3新增的transition/animation过渡效果和动画实现本实例中为了低端安卓机的流畅性,故采用的css过渡transition进行实现,通过isMove判断进行动画类wrap-animation的添加,动画类实现如下:
.wrap-animation {
  transition: transform .6s;
}
  • 是否切换视频判断
    由用户滑动距离&滑动速度决定,满足其一即可,主要实现是通过translateY参数在滑动开始和滑动进行中记录滑动距离,同时在滑动中实现页面拖拽跟随效果,以及使用startTime参数在滑动开始时的时间戳,滑动结束时候进行判断,如果需要进入下一个视频,则将通过isMove参数开启动画,然后通过修改translateY参数进行切换。
onTouchEnd () {
  if (this.isMove || this.translateY == 0) return
  // 计算滑动速度
  const speed = Math.abs(this.translateY / (Date.now() - this.startTime));
  // 判断移动距离和滑动速度是否达到界限 如果达到界限
  if (Math.abs(this.translateY) > this.maxY || speed > this.maxSpeed) {
    // 开启切换动画 
    this.isMove = true;
    this.translateY = this.translateY < 0 ? -this.clientHeight : this.clientHeight;
    // 动画结束处理 去除动画参数,进行隐式界面切换
    setTimeout(() => {
      // 关闭切换动画 切换数据
      this.isMove = false
      this.videoIndex = this.translateY < 0 ? this.videoIndex + 1 : this.videoIndex - 1;
      this.translateY = 0;
      // 判断获取推荐视频列表信息
      // 切换视频等操作
    }, 500)
  } else {
    // 恢复原位
  }
}

在动画结束后的下一帧,去除动画,进行隐式界面数据切换,如此重复,达到无限加载的效果。同时在判断动画结束时间这块,本实例使用了setTimeout实现,该操作是不准确的,建议使用transitionend事件进行实现。

5 各类问题

在实现的时候的各种问题,欢迎吐槽

5.1 视频全屏

据MDN介绍:

使用提供的API,让一个元素与其子元素,可以占据整个屏幕,并在此期间,从屏幕上隐藏所有的浏览器用户界面以及其他应用。

总的来说,使用全屏的方式有两个,一个是模拟全屏,一个是web原生的。模拟全屏的好处是可以自定义相关控件,以达到统一多端样式的目的,固然需要复杂一些;原生全屏相对比较方便,处理起来会比较轻松,缺点是全屏后,几乎不能做什么干预。因此采用模拟全屏

5.1.1 防止iOS上默认全屏播放

在iOS上播放视频将会默认使用系统全屏进行播放,几乎不能做什么干预,因此需要禁止该能力,采取模拟全屏播放。在ios10及以后的版本,可以通过给video标签加playsinline属性防止iOS默认全屏播放,ios9之前加webkit-playsinline属性,如果要兼容,则把两个属性都加上。

5.2 视频自动播放

在进入页面后自动播放视频能够极大的提升用户体验。该功能主要由video元素autoplay属性实现,其在MDN上的提示如下:

使用备注:

  • autoplay 属性优先于 preload 假如用户想自动播放视频,那么很明显浏览器需要下载视频。同时设置autoplay 和 preload属性在规范里是允许的。
  • 规范没有强制浏览器去遵循该属性的值;这仅仅只是个提示。

由于没有强制浏览器去遵循该属性的值,所以在移动端,有些浏览器支持添加autoplay属性后自动播放,有些设置 autoplaymuted属性也能自动播放,比如IOS 10+、Chrome。

但是,经过实践,在安卓客户端,多数时候都是不能实现自动播放,经过多方调研,web端无法处理,最终求助客户端,通过修改webview容器相关参数,配合添加autoplay属性实现自动播放,其处理如下:

webView.getSettings().setMediaPlaybackRequiresUserGesture(false);

5.3 play方法错误排查

当调用视频标签的play方法时候,如果不支持播放,将会报错,且无法使用try catch捕获,是因为videoplay() 方法会返回一个Promise对象,如果播放失败,可以通过返回的Promise catch到相关错误信息,这对我们来说至关重要,当出现js调用播放失败的时候,我们可以对用户进行友好引导,同时上报相关错误信息以及机型,在千奇百怪的安卓机型兼容上显得尤其重要。

6 思考

连续滑动流畅性
由于该方案需要在每一次切换完成后的下一帧进行一次隐式数据修改,所以是不支持不间断连续滑动的,是否有更好的方案呢?

欢迎大家在评论区提出自己的想法!