1

H5自带播放器

优势

  • 开箱即用,支持全屏等各功能

问题

PC上使用video可以正常播放,但是在移动上使用video标签会有诸多问题。可以参考H5-Video 能做的事&存在的坑

1. 各环境中Video UI不一致

image.png

2. 各种环境中API实现与支持程度的差异:

看看下面这一堆配置都是什么鬼?(本小节参考资料)

<video
  src="video.mp4" 
  controls
  poster="images.jpg"
  preload="auto" 
  webkit-playsinline="true" 
  playsinline="true"
  x-webkit-airplay="allow" 
  x5-video-player-type="h5"
  x5-video-player-fullscreen="true"
  x5-video-orientation="portraint"
  style="object-fit:fill">
</video> 

我们针对不同的环境,必须做一些符合它的要求的配置,以期望能尽量达成我们想要的目的。

  • 但是不得不说的是,标准API里的controls、poster、autoplay也都未必有效。
  • 自动播放在移动端浏览器上是不被允许的,可以尝试mute下自动播放是否支持
  • 前面说的通过source来设置不同媒体源的方案,部分浏览器也可能是不认识的。
  • 例如监听数据加载拿到视频数据的场景,canplaycanplaythrough在不同系统版本的设备上支持程度不同。一般我们会使用兼容性好的onloadedmetadata来获取视频的duration

    3. 事件交互行为不一致

    通过MediaEvents&API 检测,会发现各环境中播放进度变化、事件触发频率不同、部分事件触发时相应状态值未必可靠、部分场景缺少事件(全屏状态变化、系统播放器劫持)、seek时不一定触发play...

  • 例如ios部分环境监听canplaycanplaythrough(是否已缓冲了足够的数据可以流畅播放),当加载时是不会触发的,即使preload="auto"也没用,但在pc的chrome调试器下和android下,是会在加载阶段就触发,ios需要播放后才会触发。
  • 部分环境中视频从开始播到能展现画面会有短暂的黑屏(处理视频源数据的时间),为了避免这个黑屏,可以在视频上加个浮层或设置容器背景并在播放前隐藏video,然后监听相关事件,开始播放或认为有画面的时候再切换到Video界面。
  • 部分环境中在你没设置poster的时候点击播放时也会尝试加载poster,导致到播放这段时间会有一段时间黑屏

4. 媒体格式的支持无法保证

MP4 总体支持较好、M3U8在高版本移动环境较好、媒体点播直播中现存大量 flv 播放需求原生Video是不支持的。

手写移动端播放器

目标

  • 封面
  • 播放/暂停/重播
  • 拖动滚动条调节进度
  • 全屏与非全屏
  • 自定义按钮如分享

实现

第一步:H5适配模版

    <video
      :src="videoSrc"
      :poster="defaultPoster"
      preload="auto"
      @click.stop.prevent="handleVideoClick"
      class="video__content"
      playsinline="true"
      x5-playsinline="true"
      webkit-playsinline="true"
      x-webkit-airplay="allow"
      x5-video-player-type="h5"
      x5-video-player-fullscreen="true"
      x5-video-orientation="portraint"
    ></video>
  • preload: 属性规定在页面加载后载入视频。
  • controls: 默认会false,后面我们会自己实现控制条控制。
  • webkit-playsinline ,x5-playsinlineplaysinline: 视频播放时局域播放,不脱离文档流。
  • x5-video-player-type: 启用同层H5播放器,就是在视频全屏的时候,div可以呈现在视频层上,也是WeChat安卓版特有的属性
  • x5-video-orientation: 声明播放器支持的方向:landscape横屏, portraint竖屏
  • x5-video-player-fullscreen:全屏设置:true支持全屏播放,false不支持全屏播放。
  • object-fill: fill(会强制填充满播放器,视频比例可能会被改变)/contain(视频按照比例撑满播放,宽高不能同时撑满播放器)/...

第二步:poster

原理:在video上面放一个div容器,让这个容器填满video组件。通过设置div的background-image加载图片。

<template>
    <div
        class="video-poster"
        :style="style"
    >
    </div>
</template>
<script lang="ts">
import {
  Vue, Prop, Component
} from 'vue-property-decorator';

@Component({})
export default  class extends Vue {
  @Prop()
  public videoPoster?: string;

  get style() {
    return `background-image:url(${this.videoPoster})`;
  }
}
</script>
<style lang="scss" scoped>
.video-poster{
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  z-index: 3; // 显示在video组件最上层
  background-repeat: no-repeat;
  background-size: 100% 100%; // 100%填充
  background-position: 50%;// 居中放置
}
</style>

第三步:实现滚动条进度控制

支持点击进度条调整进度和拖动圆点调整进度:放置一个滚动条容器,里面放一个实际进度区域和一个圆点元素。

currentTime决定进度条进度

活动的进度条长度 / 进度条总长度 = currentTime / duration

通过传入不同currentTime就可以实现不同的进度展示
在次基础上可以封装出包含播放/暂停等动作的全屏下的进度条和非全屏幕下的进度条。

进度条前进方案有两种:

  1. 活动进度条的长度100%,初始时将活动进度条向左偏移100%,设置超出容器是hidden。通过计算出的距离,使用translateX 控制移动。

    • 优点:前进非常流畅
    • 缺点:横竖屏切换的时候需要重新计算位置
  2. 通过活动进度条的长度 0-100% 来控制进度

    • 优点:自动适配横竖屏切换
    • 缺点:前进的时候有闪烁,性能越差越明显。

比较发现前进的时候有闪烁可以接受,使用了第二种方案。

  // active controls 样式
  public getWindowActiveMoveStyle(): Object {
    const percent = this.currentTime / this.duration;
    return {
      'width':`${Math.round(percent * 10000) / 100}%`,
      'background-color':`${this.activeControlBarColor}`
    };
  }
点击进度条,换算成currentTime
  // 点击进度条,换算成currentTime
  private getTouchedTime(e: TouchEvent): number {
    const clientRects = this.windowProgressEl.getClientRects() || []; // IOS>=6
    const touchedClientX = e.touches[0].clientX;
    const touchedClientY = e.touches[0].clientY;
    const { left = 1, top = 0, width = 0, height= 0 } = clientRects[0] || {};

    /** 根据是否全屏,计算方式也有区别:主要是旋转之后一些属性值发生了变化 */
    const unit = this.fullscreen ? height : width;
    const touchedClientUnit = this.fullscreen ? touchedClientY : touchedClientX;
    const positionUnit = this.fullscreen ? top : left;
    return (touchedClientUnit - positionUnit) / unit * this.duration;
  }

第四步:实现全屏播放

  • 全屏后画面大小设置:

    video{
      // object-fit: fill; 这个会填充满区域,有些问题
      object-fit: contain
    }
  • 非全屏下,播放画面大小受外层容器限制;全屏下如何使画面充满而不受外面容器限制

      z-index:100;
      width : 100vmax;
      height : 100vmin;
      transform-origin: top left;
      transform: rotate(90deg) translate(0,-100vmin);
      background-color: #000;

转屏之后换算成currentTime需要注意

  private getTouchedTime(e: TouchEvent): number {
   ...

    /** 根据是否全屏,计算方式也有区别:主要是旋转之后一些属性值发生了变化 */
    const unit = this.fullscreen ? height : width;
    const touchedClientUnit = this.fullscreen ? touchedClientY : touchedClientX;
    const positionUnit = this.fullscreen ? top : left;
    return (touchedClientUnit - positionUnit) / unit * this.duration;
  }

其他

  • 全局loading

      this.videoRef.addEventListener('waiting', () => {
        this.isLoading = true;
      }, false);
  • 断网处理

    public mounted(){
    // 网络状况处理
      window.addEventListener('offline', this.handleNetwork, false);
      window.addEventListener('online', this.handleNetwork, false);
    }
    private handleNetwork() {
      if (navigator.onLine) {
        this.isOnline = true;
      } else {
        this.isOnline = false;
      }
    }
细节处理
  • 边距问题

    • 返回按键和进度条/播放按钮 位置要考虑曲面屏
    • 进度圆圈要留padding,防止点不到
  • 整体需要蒙层

    • loading下需要一个蒙层,显示出loading 动画转起来
    • 播放器上各个图标(白色)、进度条下需要一个蒙层,防止视频内容是白色的时候显示不出来

参考资料:


specialCoder
2.2k 声望168 粉丝

前端 设计 摄影 文学