H5 视频播放与直播整理

2020/06/15

Video 视频播放

HTML5支持video和audio之前, 网页播放流媒体文件, 都是通过其它方法, 例如 activeX 插件 或者 flash 支持后,页面可以在web服务器上放置视频文件, 浏览器根据支持的视频格式请求相应的视频文件

标签规范

video 标签 :https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/video
HTMLMediaElement :https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLMediaElement

常用 option

  • src 视频地址
  • poster 视频封面,没有播放时显示的图片
  • preload 预加载
  • autoplay 自动播放
  • loop 循环播放
  • controls 浏览器自带的控制条
  • muted 静音播放

常用 属性 (HTMLMediaElement)

  • buffered 返回哪部分已被下载,会返回一个 TimeRanges 对象,(只读)
  • currentTime 当前播放时间,(只读)
  • duration 视频总长,(只读)
  • paused 是否处于暂停状态,(只读)
  • playbackRate 播放速度
  • volume 音量

常用 方法 (HTMLMediaElement)

  • pause() 暂停播放。
  • play() 开始播放。

常用 事件 (HTMLMediaElement)

  • canplay 有足够的可用数据可以播放媒体时触发
  • pause 当播放状态更改为暂停时发送
  • play 当播放状态更改为播放时发送
  • ratechange 播放速度变化时触发
  • timeupdate 当前播放事件变化时触发
  • volumechange 音量大小变化时触发

Tips

1. autoplay 属性限制

autoplay 属性在多种浏览器上有阻止策略,需要用户手动调整浏览器播放策略才能够实现自动播放。

解决办法:将视频设置为静音播放,并给予用户提示,参考刷新 bilibili 直播页面后的交互。

HTMLMediaElement.play().catch(async (err) => {
  if (err.name === 'NotAllowedError') {
    this.$refs.video.muted = true
    await this.$refs.video.play()
    this.$utils.message({ message: '您正在使用静音播放' })
  }
})

2. 视频下载原理

一个非常大的视频,video 标签是如何实现快速开始播放的?又是如何实现播放进度控制的?

通过 请求头中的 Range 字段控制数据加载范围 video-rage.png

首次加载 video 后会发送 一个 Range: bytes=0- 的请求,暂停时,在加载够一定数据量后停止数据加载。继续播放后当数据量不足时继续下载

当用户手动 seek 到一个未 buffer 的位置时,会发送一个 Range: bytes=seek位置- 的请求

具体参考 chrome 内核 BufferReader 源码

首先获取视频码率 -> 根据播放速度判断10s视频大小 -> 预加载 10s 的播放长度,并且最大不超过50MB

3. 自定义进度条时遇到的死循环问题

video 的 timeupdate 事件会在 视频播放进度发生改变时实时触发,在这个事件里 实时修改 data 里的 currentTime 和 timeProcess,进度条组件中 timeProgress 和 进度条位置是 双向绑定的,外部会 watch timeProgress 属性,根据 timeProgress 属性修改 video 播放进度,导致循环。

解决方式

  1. 不使用 watch 方式,单独监听 进度条的点击和拖动事件修改视频进度
  2. watch 时判断 element 组件的 dragging 属性,只有当进度条在被拖动时才去修改视频进度,之后再添加点击事件即可

4. 进度条预览、外部预览功能实现

进度条预览和 外部预览的原理是 后台会根据视频返回给前端一个预览雪碧图,前端根据进度百分比使用 background-position 属性调整显示的 图片样式。

B 站前端实现方式 video-preview-2.png

对应的雪碧图 video-preview.jpg

雪碧图由后台生成,生成方式可通过 javac 和 ffmpeg 相关 jar 包实现

5. “高能进度条”实现

什么是“高能进度条”? video-focus.png

实现方式可以理解为 后台返回一个 曲线数组,前端通过绘制 SVG 等方式实现

B 站前端实现方式 video-focus-2.png

最终生成的 svg 如下 video-focus-3.png

<path d="
      M 0 100
      L 0 80
      C 5.0  80.0, 5.0 75.5, 10.0 75.5
      C 15.0 75.5, 15.0 73.0, 20.0 73.0
      C 25.0 73.0, 25.0 70.9, 30.0 70.9
      C 35.0 70.9, 35.0 69.3, 40.0 69.3
      C 45.0 69.3, 45.0 66.2, 50.0 66.2
      C 55.0 66.2, 55.0 57.3, 60.0 57.3
      C 65.0 57.3, 65.0 15.8, 70.0 15.8
      C 75.0 15.8, 75.0 67.6, 80.0 67.6
      C 85.0 67.6, 85.0 64.0, 90.0 64.0
      C 95.0 64.0, 95.0 67.0, 100.0 67.0
      L 1000 100
      Z">
</path>

SVG 左上角为原点 x=0,y=0 B 站使用的是 3次贝塞尔曲线的方式实现的绘制, 其中 两个 clipPath 分别为曲线剪切和 已播放区域剪切,配合进度条可实现 已播放部分背景蓝色的需求。 video-focus-5.png

https://cubic-bezier.com/#0,0,1,1

对应点: 80 75.5 73 70.9 69.3 66.2 57.3 15.8 67.6 64 67

分别用 100 减后,对应图中后台返回的数据 video-focus-4.png 刚好符合对应规则:y = 0.00272727x + 20

因此我们可以非常容易地仿照 B 站规则生成 SVG,即取后台返回数组中最小的数字作为 80 最大的数字作为 0 来绘制 3次贝塞尔曲线即可

已播放部分变蓝这里涉及到 区间合并算法 问题,可简化为 给定[[1,5],[7,11],[15,16]] , 输入参数 [7,19] 获得合并后的结果 [[1,5],[7,19]]

类似于 leetCode 上的 有效括号问题, 首先合并数组,对每一项标记为开始或结尾,从大到小排序后,每遇到一个开始标记count++,每遇到一个结束标记 count– 当count为0的时候则生成一个区间

6. “弹幕功能”实现

使用 css animate 实现,包含 animation-play-state

遇到的问题有 Vue key 更新策略的问题,现象是 对数组同时进行 push 和 shift 操作后,尽管 v-for 设置了 key 属性,但是其动画仍被重置。

原因是 vue 是异步渲染的,先将数组push后实际并没有渲染,而是等 shift 也完成后一同进行渲染,导致 vue 实际上是使用 insertBefore 进行节点的移动,而不是先删除再添加的方式,而节点的移动会导致 动画重置。

解决方式是,在 push 之后,使用 $nextTick 包裹 shift 方法。

7. “防档弹幕”实现

css mask 背景遮罩

8. 使用 flv.js 中遇到的问题

https://developer.mozilla.org/zh-CN/docs/Web/API/Media_Source_Extensions_API

  • 页面离开后,尽管调用了 .destroy() flv 文件仍在加载: fork 后添加 AbortController 控制

  • 直播在暂停一段时间后 视频直播被断开:MSE 中 各个浏览器对 SourceBuffer 有大小限制,解决办法是新建一个自己的数组,用于暂存数据,判断只有当用户正在播放的时候 再将 数据输送到 SourceBuffer 中。/src/core/mse-controller.js :445

文章导航