如何实现兼容 PC 和微信 H5 的全屏播放小视频

对于这个问题,其实网上已经有一些比较好的实践,但有时候并不明白为何这样配置;若是你想要知道一个弱小,可怜又无助的 <video> 如何变成丰满健壮的视频播放器,那就往下看吧!css

需求

来问H5 医生端(PC & 微信)问题详情页面:html

  • 支持点击播放按钮唤起播放器播放视频
  • 播放过程当中,支持暂停视频、关闭视频、拖动进度条调整视频进度
  • 不区分是否处于 WiFi 环境下,需手动点击播放

踩坑之路

第一阶段

方案

  1. 使用HTML5中 <video> 标签进行视频播放
  2. 仅设置 src 属性

  1. 未播放时 DOM 宽高样式正常;但无封面,并未截取视频第一帧做为封面
  2. 点击播放后会调起系统默认播放器进行全屏播放,播放器初始界面五花八门
  3. 安卓视频播放退出后 DOM 样式错乱,会无视 css 使用视频源宽高显示
  4. 安卓在视频播放完后会追加视频推荐,而且白名单申请入口已经关闭

第二阶段

方案

  1. 增长 poster 属性,值为后端根据视频第一帧生成的封面地址
  2. 增长遮罩层及图标样式模拟播放器初始界面
  3. <video> 标签使用 <div> 容器包裹,并设置 display: none隐藏,用户点击封面时调用 videoplay() 方法进行播放
  4. 腾讯大佬强制显示视频推荐,因此暂时不作处理

  1. 隐藏 <video> 元素后微信中调用play()无反应
  2. PC 端只有声音没有图像。这是由于 PC 端不会打开专门的播放器,只会在 DOM 节点处直接播放,此时 DOM 节点未设置显示区域
  3. 只是要播放视频,响应的是 video.play() 方法,并不表明已经开始播放(会有一段缓冲过程),用户会误觉得点击无效

第三阶段

方案

  1. display: none; 或者 width: 0; height: 0; 方式隐藏视频时,元素处于未激活状态,不响应 play() 方法,因此咱们设置宽高为1px
  2. 设置 flag 判断环境,若在 PC 环境中,播放后将视频容器拓展为全屏大小,并增长关闭按钮,点击后暂停视频并移除拓展样式
  3. 点击封面时增长一个 loading 效果,PC 环境在视频播放时取消;微信环境则在视频暂停时取消。(在 iOS 中 play() 方法会触发播放事件,但播放器此时并未打开,而全屏播放中暂停视频并不会退出播放器。因此咱们能够在视频暂停时取消 loading);

  1. iPad 及 windowsPC 版微信中一样不会新开播放器,而是直接在video标签处播放,致使有声音无图像;iPad 中经过修改 PC 的判断条件能够解决
  2. 有些安卓设备没法播放,须要安装 QQ 浏览器或 QQ 视频播放插件才能够,不过仍有一部分用户没法安装该插件或安装后仍是无效

第四阶段:“最佳实践”

此时再使用原生 video 标签事件和属性,已经没办法进一步突破以上的这些坑,解决千差万别的兼容性问题了。所以,咱们参考了其余的方案实现了预期的效果。vue

方案

PS:项目是基于Vue & scss,但该功能能够不依赖这些实现web

  1. 使用各种兼容属性以及 x5 内核浏览器的扩展属性 webkit-playsinline, playsinline, x-webkit-airplay, x5-video-player-type, x5-video-player-fullscreen, x5-video-orientation 等解决不一样类型设备的播放差别;
  2. 设置 flag 代表是否正在播放或正在全屏状态中
<!-- 视频容器 -->
<div class="video" :class="{'full-screen': isFullScreen}">
  <!-- 视频主体 -->
  <div class="video-content">
    <video :controls="isFullScreen" :style="isFullScreen ? {} : img.style" :class="img.isVertical ? 'vertical-img' : 'horizontal-img'" :src="img.url" preload="metadata" :poster="img.preview_pic_url" :ref="`video${img.id}`" webkit-playsinline="true" playsinline="true" x-webkit-airplay="allow" x5-video-player-type="h5" x5-video-player-fullscreen="true" x5-video-orientation="portraint">
      抱歉,您的浏览器不支持内嵌视频!
    </video>
  </div>
  <!-- 遮罩层,显示播放按钮;仅在待播放状态显示 -->
  <div v-if="!isFullScreen" class="video-mask">
    <div>
      <img src="~images/play.png" />
    </div>
  </div>
  <!-- 全屏控制按钮;仅在非安卓高版本内核中显示 -->
  <div v-else-if="!inHighTBS" class="video-controls">
    <span class="video-controls-close" @click.stop="handleVideoControls('close')">
      &times;
    </span>
  </div>
</div>
复制代码

属性说明windows

  1. controls: 经过 flag 设置仅在播放时出现,避免初始播放状态不一样
  2. style:对待播放 dom 进行绝对定位计算视频偏移量;视频显示区为正方形窗口,所以要横向及纵向视频显示区都在正中间
  3. class:设定横向视频高度或纵向视频宽度
  4. src:视频源
  5. preload:值为预加载但不阻塞;每一个问题最多仅有一个视频,保证用户点击播放后当即响应,而且不阻塞其余图片附件的渲染
  6. poster:封面地址
  7. ref:在vue中获取并操做 video 元素
  8. webkit-playsinline:IOS 10中设置有效,视频播放时局域播放,不脱离文档流;能够保证播放界面与PC端一致
  9. playsinline:IOS 微信浏览器支持小窗内播放,和上一个属性一块儿食用可兼容几乎全部IOS设备
  10. x5-video-player-type:启用H5同层播放器,是 wechat 安卓版特性
  11. x5-video-player-fullscreen:视频播放时将会进入到全屏模式,若不设置仍是会新开播放器,但尺寸为原始视口大小(视频未播放前)
  12. x5-video-orientation:控制横竖屏
/* 外层还有其余已定位容器 */
  .video {
    position: absolute;
    top: 0;
    bottom: 0;
    right: 0;
    left: 0;
    transition: all 0.3s;
    background-color: rgba(0, 0, 0, 0.5);
    &.full-screen {
      position: fixed;
      z-index: 99;
      .video-content {
        width: 100%;
        height: 100%;
        video {
          position: initial;
          &.vertical-img {
            height: 100%;
            width: auto;
            margin: 0 auto;
          }
          &.horizontal-img {
            width: 100%;
            height: auto;
            max-height: 100%;
          }
        }
      }
    }
    &-mask {
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      display: flex;
      align-items: center;
      background-color: rgba(0, 0, 0, 0.5);
      div {
        width: 100%;
        text-align: center;
        img {
          width: 30%;
          position: inherit;
        }
      }
    }
    &-controls {
      position: absolute;
      right: 5%;
      top: 5%;
      display: flex;
      align-items: center;
      z-index: 1;
      &-close {
        width: 50px;
        height: 50px;
        line-height: 50px;
        color: rgba(255, 255, 255, 0.7);
        background: rgba(0, 0, 0, 0.3);
        text-align: center;
        border-radius: 50%;
        font-size: 2rem;
        cursor: pointer;
        transition: all 0.3s;
        &:hover {
          background: rgba(0, 0, 0, 0.5);
          color: #fff;
        }
      }
    }
    &-content {
      max-width: 768px; // 限制PC端不超过768px;如要PC全屏可不作设置
      margin: 0 auto;
      display: flex;
      align-items: center;
      video {
        display: block;
        position: absolute;
        object-fit: fill;
      }
    }
  }
  .horizontal-img {
    height: 100%;
    top: 0;
  }
  .vertical-img {
    width: 100%;
    left: 0;
  }
复制代码

小问题

在安卓微信中,就算加上了上面的属性,还会出现上下有黑边,不能全屏。解决给<video>加上object-fit: fill;的样式便可。后端

修改上线后之前报Bug的用户纷纷反馈好了,没问题,但...浏览器

还报Bug?

有一个用户反馈使用锤子的坚果Pro点击视频无反应,找来同型号测试机一番骚操做后...愣是没复现!缓存

以后经过和用户不断沟通发现该设备上竟然未使用X5内核浏览器(使用微信打开debugtbs.qq.com可调试X5内核,未安装会有提示)微信

所以在下面一个安卓兼容性事件判断上报错了,使用try { } catch (e) {}包一下,一样能够正常播放,但这是的播放效果已没法统一。dom

安卓事件兼容
// 高版本微信安卓环境下会自动加上返回按钮而且点击触发退出全屏事件
// 需作未使用X5内核容错处理
inHighTBS() {
  if (inAndroid) {
      try {
         const [, currentTbsVersion] = window.navigator.userAgent.match(/TBS\/(\d+)/);

        return currentTbsVersion > '036900';
      } catch() {
          return false;
      }
  } else {
    return false;
  }
}

// 安卓环境中会启用同层H5播放器,跳转新窗口,所以监听x5videoexitfullscreen事件可获取状态
// https://x5.tencent.com/tbs/guide/video.html
this.inHighTBS && vDom.addEventListener('x5videoexitfullscreen', () => {
  this.isFullScreen = false;
});
复制代码

附录

video原生支持事件

const mediaProperties = [
  'loadstart',  // 在媒体开始加载时触发。
  'progress',   // 告知媒体相关部分的下载进度时周期性地触发。有关媒体当前已下载总计的信息能够在元素的buffered属性中获取到。
  'suspend',    // 在媒体资源加载终止时触发,这多是由于下载已完成或由于其余缘由暂停。
  'abort',  // 在播放被终止时触发,例如, 当播放中的视频从新开始播放时会触发这个事件。
  'error',  // 在发生错误时触发。元素的error属性会包含更多信息。参阅Error handling得到详细信息。
  'emptied',    // 媒体被清空(初始化)时触发。
  'stalled',    // 在尝试获取媒体数据,但数据不可用时触发。
  'loadedmetadata', // 媒体的元数据已经加载完毕,如今全部的属性包含了它们应有的有效信息。
  'loadeddata', // 媒体的第一帧已经加载完毕。
  'canplay',    // 在媒体数据已经有足够的数据(至少播放数帧)可供播放时触发。这个事件对应CAN_PLAY的readyState。
  'canplaythrough', // 在媒体的readyState变为CAN_PLAY_THROUGH时触发,代表媒体能够在保持当前的下载速度的状况下不被中断地播放完毕。注意:手动设置currentTime会使得firefox触发一次canplaythrough事件,其余浏览器或许不会如此。
  'playing',    // 在媒体开始播放时触发(不管是初次播放、在暂停后恢复、或是在结束后从新开始)。
  'waiting',    // 在一个待执行的操做(如回放)因等待另外一个操做(如跳跃或下载)被延迟时触发。
  'seeking',    // 在跳跃操做开始时触发。
  'seeked', // 在跳跃操做完成时触发。
  'ended',  // 播放结束时触发。
  'durationchange', // 元信息已载入或已改变,代表媒体的长度发生了改变。例如,在媒体已被加载足够的长度从而得知总长度时会触发这个事件。
  'timeupdate', // 元素的currentTime属性表示的时间已经改变。
  'play',   // 在媒体回放被暂停后再次开始时触发。即,在一次暂停事件后恢复媒体回放。
  'pause',  // 播放暂停时触发。
  'ratechange', // 在回放速率变化时触发。
  'resize',
  'volumechange',   // 在音频音量改变时触发(既能够是volume属性改变,也能够是muted属性改变).。
  'mozaudioavailable'   // 当音频数据缓存并交给音频层处理时
 ];

mediaProperties.forEach(item => {
  vDom.addEventListener(item, e => console.log(item));
});
复制代码

参考:

做者:丁香医生团队 顾重

相关文章
相关标签/搜索