H5 直播方案分析讲解
- 2020.05.25
 
直播的流程
在研究方案前我们先来了解下直播的大致流程:

视频流协议 HLS 与 RTMP
目前 WEB项目 上主流的视频直播方案有 HLS 和 RTMP,H5上目前以 HLS 为主(HLS存在延迟性问题,也可以借助 video.js 采用RTMP),PC 端则以 RTMP 为主实时性较好。
实际的直播和用户播放的直播会有 10 秒左右或者更高的延迟,如何降低这个延时提高体验的关键。
HTTP Live Streaming
HLS(HTTP Live Streaming)是一个基于HTTP的视频流协议,由Apple公司实现,Mac OS上的QuickTime、Safari以及iOS上的Safari都能很好的支持HLS,高版本Android也增加了对HLS的支持。一些常见的客户端如:MPlayerX、VLC也都支持HLS协议。
提供 HLS 的服务器需要做两件事:
编码: 以H.263格式对图像进行编码,以MP3或者HE-AAC对声音进行编码,最终打包到MPEG-2 TS(Transport Stream)容器之中。分割: 把编码好的TS文件等长切分成后缀为ts的小文件,并生成一个.m3u8的纯文本索引文件。
m3u8 文件
浏览器使用的是 m3u8 文件。
m3u8跟音频列表格式 m3u很像,可以简单的认为m3u8就是包含多个ts文件的播放列表(采用 utf-8 编码)。
播放流程
播放器会按顺序逐个播放,全部放完再请求一下 m3u8 文件,获得包含最新 ts 文件的播放列表继续播放,周而复始。整个直播过程就是依靠一个不断更新的 m3u8 和一堆小的 ts 文件组成,m3u8 必须动态更新,ts 可以走 CDN。一个典型的 m3u8 文件格式如下:
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-MEDIA-SEQUENCE:753
#EXT-X-TARGETDURATION:6
#EXTINF:6.099,
pili-live-rtmp.xxx.com_1708021247HmBHAG-1590389214780.ts
#EXTINF:6.050,
pili-live-rtmp.xxx.com_1708021247HmBHAG-1590389220884.ts
#EXTINF:6.096,
pili-live-rtmp.xxx.com_1708021247HmBHAG-1590389226932.ts
可以看到 HLS 协议本质还是一个个的 HTTP 请求 / 响应,所以适应性很好,不会受到防火墙影响。
WARNING
但它也有一个致命的弱点:延迟现象非常明显。如果每个 ts文件 按照 5 秒来切分,一个 m3u8 放 6 个 ts 索引,那么至少就会带来 30 秒的延迟。
如果减少每个 ts 的长度,减少 m3u8 中的索引数,延时确实会减少,但会带来更频繁的缓冲,对服务端的请求压力也会成倍增加。
苹果官方推荐的 ts 时长是10s,所以这样就会大该有30s的延迟。所以服务器接收流,转码,保存,切块,再分发给客户端,这里就延时的根本原因,所以只能根据实际情况找到一个折中的点。
对于支持 HLS 的浏览器来说,直接这样写就能播放了:
<video src=”./bipbopall.m3u8″ height=”300″ width=”400″  preload=”auto” autoplay=”autoplay” loop=”loop” webkit-playsinline=”true”></video>
TIP
HLS 在 PC 端仅支持safari浏览器,类似chrome浏览器使用HTML5 video
标签无法播放 m3u8 格式,需要其他插件辅助转化,可采用目前网上一些比较成熟的方案。
sewise-player([Flash和HTML5播放器]Flash播放m3u8文件)MediaElement([Flash和Sliverlight播放器] )videojs-contrib-hls(解码m3u8文件)jwplayer([Flash和HTML5播放器] 网页媒体播放器)videojs(可能会出现 跨域 问题,需要服务端的配合,让视频允许跨域)。
video.js方案部分代码展示
<!-- 引入的文件 -->
<link href="https://unpkg.com/video.js/dist/video-js.css" rel="stylesheet" />
<script src="https://unpkg.com/video.js/dist/video.js"></script>
<script src="https://unpkg.com/videojs-contrib-hls/dist/videojs-contrib-hls.js"></script>
<!-- html部分 -->
<video
  id="my_video_1"
  class="video-js vjs-default-skin"
  controls
  preload="auto"
  width="640"
  height="268"
  data-setup="{}"
>
  <source
    src="http://www.tony.com/hls/test.m3u8"
    type="application/x-mpegURL"
  />
</video>
var player = videojs("#my_video_1");
player.play();
// 视频播放
var myPlayer = videojs(
  "my_video_1<%=i%>",
  {
    bigPlayButton: true,
    textTrackDisplay: true,
    posterImage: true,
    errorDisplay: true,
    controlBar: true,
  },
  function() {
    //ready 加载
    var _that = this;
    this.on("loadedmetadata", function() {});
    this.on("ended", function() {});
    this.on("firstplay", function() {});
    this.on("loadstart", function() {
      //开始加载
    });
    this.on("loadeddata", function() {});
    this.on("seeking", function() {
      //正在去拿视频流的路上
    });
    this.on("seeked", function() {
      //已经拿到视频流,可以播放
    });
    this.on("waiting", function() {});
    this.on("pause", function() {});
    this.on("play", function() {});
  }
);
myPlayer.play(); //视频播放
Real Time Messaging Protocol
Real Time Messaging Protocol(简称 RTMP)是Macromedia开发的一套视频直播协议,现在属于Adobe。这套方案需要搭建专门的RTMP流媒体服务如Adobe Media Server,并且在浏览器中只能使用Flash实现播放器。它的实时性非常好,延迟很小,但无法支持移动端 WEB播放是它的硬伤。
虽然无法在iOS的H5页面播放,但是对于iOS原生应用是可以自己写解码去解析的, RTMP 延迟低、实时性较好。
浏览器端,HTML5 video 标签无法播放 RTMP 协议的视频,可以通过 video.js 来实现。
<link href=“http://vjs.zencdn.net/5.8.8/video-js.css” rel=“stylesheet”>
<video id=“example_video_1″ class=“video-js vjs-default-skin” controls preload=“auto” width=“640” height=“264” loop=“loop” webkit-playsinline>
<source src=“rtmp://10.14.221.17:1935/rtmplive/home” type='rtmp/flv'>
</video>
<script src=“http://vjs.zencdn.net/5.8.8/video.js”></script>
<script>
videojs.options.flash.swf = ‘video.swf’;
videojs(‘example_video_1′).ready(function() {
this.play();
});
</script>
视频流协议 HLS 与 RTMP 对比
| 方式 | 协议 | 原理 | 延时 | 优点 | 使用场景 | 
|---|---|---|---|---|---|
HLS |  短链接 HTTP | 集合一段时间数据生成 ts 切片文件更新m3u8文件 |  10s - 30s | 跨平台 | 移动端为主 | 
RTMP |  长链接 Tcp | 每个时刻的数据收到后立即发送 | 2s | 延时低、实时性好 | PC + 直播 + 实时性要求高 + 互动性强 | 
H5 录制视频
对于
H5视频录制,可以使用强大的webRTC (Web Real-Time Communication)是一个支持网页浏览器进行实时语音对话或视频对话的技术,缺点是只在PC的Chrome上支持较好,移动端支持不太理想。
使用 webRTC 录制视频基本流程是:
调用
window.navigator.webkitGetUserMedia();获取用户的 PC 摄像头视频数据。将获取到视频流数据转换成
window.webkitRTCPeerConnection(一种视频流数据格式)。利用
webscoket将视频流数据传输到服务端。
TIP
由于许多方法都要加上浏览器前缀,所以很多移动端的浏览器还不支持 webRTC,所以真正的视频录制还是要靠客户端(iOS,Android)来实现,效果会好一些。
iOS 采集(录制)音视频数据 OS
关于音视频采集录制,首先明确下面几个概念:
视频编码: 所谓视频编码就是指通过特定的压缩技术,将某个视频格式的文件转换成另一种视频格式文件的方式,我们使用的iPhone录制的视频,必须要经过编码,上传,解码,才能真正的在用户端的播放器里播放。编解码标准: 视频流传输中最为重要的编解码标准有国际电联的H.261、H.263、H.264,其中HLS协议支持H.264格式的编码。音频编码: 同视频编码类似,将原始的音频流按照一定的标准进行编码,上传,解码,同时在播放器里播放,当然音频也有许多编码标准,例如PCM 编码,WMA 编码,AAC 编码等等,这里我们HLS协议支持的音频编码方式是AAC 编码。
利用 iOS 上的摄像头,进行音视频的数据采集,主要分为以下几个步骤:
音视频的采集,利用
AVCaptureSession和AVCaptureDevice可以采集到原始的音视频数据流。对视频进行
H264编码,对音频进行AAC编码,在iOS中分别有已经封装好的编码库(x264编码、faac编码、ffmpeg编码)来实现对音视频的编码。对编码后的音、视频数据进行组装封包。
建立
RTMP连接并上推到服务端。

直播间状态分析
直播业务中常常会涉及到很多场景,比如说直播过程中主播离开了直播间,直播结束,当前推流断了等,建议可以按照以下场景切分不同业务类型。
- 页面初始化
 - 直播中
 - 主播暂时离开
 - 直播结束
 - 自己的直播间
 - 主播正赶来
 - 子账号
 - 回放
 - 预展中
 
H5 直播开发注意事项
Video标签兼容问题
比如: 在微信和 QQ 的内置浏览器里,播放视频会自动全屏,video标签也是永远浮在页面最上层,你根本控制不了。 浮在最上层不只是X5浏览器,还有些手机只带的浏览器。 视频源出现问题的表现,播放按钮的问题,都有不同。 因此需要我们对 video 标签的属性就行兼容处理:
<video
  id="video"
  style="object-fit:fill"
  autoplay
  webkit-playsinline
  playsinline
  x5-video-player-type="h5"
  x5-video-player-fullscreen="true"
  x5-video-orientation="portraint"
  src="video.mp4"
  poster="img.jpg"
/></video>
<!-- object-fit: fill   视频内容充满整个video容器
poster:"img.jpg" 视频封面
autoplay: 自动播放
    auto - 当页面加载后载入整个视频
    meta - 当页面加载后只载入元数据
    none - 当页面加载后不载入视频
muted:当设置该属性后,音频输出为静音
playsinline: iOS10 之后Safari中新增 用于解决早期视频层级最高问题 采用视频内联播放
webkit-playsinline:   iOS10之前 解决在Safari和一些安卓的一些浏览器下播放视频的时候,不能在h5页面中播放视频,系统会自动接管视频的问题
x5-video-player-type="h5-page" :  启用x5内核H5播放器 解决安卓同层级播放
x5-video-player-fullscreen="true"  全屏设置。ture和false的设置会导致布局上的不一样
x5-video-orientation="portraint" :声明播放器支持的方向,可选值landscape 横屏,portraint竖屏。
                                   默认值portraint。无论是直播还是全屏H5一般都是竖屏播放,
                                   但是这个属性需要x5-video-player-type开启H5模式 -->
// 通过事件监听全屏情况
document
  .getElementById("video")
  .addEventListener("x5videoexitfullscreen", function() {
    alert("exit fullscreen");
  });
document
  .getElementById("video")
  .addEventListener("x5videoenterfullscreen", function() {
    alert("enter fullscreen");
  });
Video推流监听问题
推流会有一些不可控的情况,主播关闭摄像头,推送断流等,客户端断网。 这个时候在 H5 端的表现就是卡住,一旦卡住之后,就算推流又重新开始了,video 依然会卡在那里,不会有任何重新播放的样子。 如果推流重新开始,用户自己点击控制条的暂停,再点击播放,又可以正常播放了。
可我们不可能让用户一直点,因为你也不知道推流什么时候重新开始,或者什么时候不再是断网状态。 通过点击控制条的暂停,再点击播放便可以播放的规律,我们可以自己检查当前的状态,再用 JS 控制 video 暂停,再播放。
const video = document.getElementById("video");
video.pause();
video.play();
如何检查当前是卡住的状态呢?这里需要用到timeupdate事件。一旦用户是播放状态,监听timeupdate,通过对比currentTime轮询着来检查当前是否卡住。
var checkTime = null;
var checkLastTime = null;
var check = setInterval(function() {
  if (checkTime != null) {
    if (video.paused) {
      //如果是暂停状态
    }
    if (checkTime == checkLastTime || (checkTime == 0 && checkLastTime == 0)) {
      if (!video.paused) {
        //如果是暂停状态,就忽略
        Message.tip("主播暂时"); // 提示一下用户
        video.pause();
        video.src = video.src; // 重置src,否则ios不会再次播放
        video.play();
      }
    }
  }
  checkLastTime = checkTime;
}, 10000);
video.addEventListener("timeupdate", function(e) {
  //每次play()都会触发一次timeupdate,所以需要加个条件判断
  if (checkTime != checkLastTime) hideShowTip(); //隐藏上面 主播 离开的提示
  checkTime = e.target.currentTime;
});
但是这样仍然会有一些问题,比如当前检测到视频卡住了,JS 控制重新播放,而当前还是没有获取到推流的话。 浏览器会先 loading 获取视频,最后会 x 显示加载失败。 我们的检查会轮询执行,所以我们可以在视频正常播放前使用一些提示类组件。
自动播放兼容问题
但是在很多移动浏览器里,都是要求用户的真实操作来(touchend、click、doubleclick 或 keydown 事件等标准的事件)触发调用video.play(),才能自动播放影音视频。
dom.addEventListener("click", function() {
  video.play();
});
在微信里 IOS 支持自动播放,安卓目前暂不支持(经过实测发现黑鲨手机上可以播放)。
window.wx &&
  wx.ready &&
  wx.ready(() => {
    wx.getNetworkType({
      success: (res) => {
        video.play();
      },
    });
  });
多个 source 之间的播放问题
有的时候我们可能会在<audio>、<video>之间放置多个<source>标签。
<audio>
  <source src =“ http://example.com/mysong.aac”>
  <source src="“" mysong.oga” />
</audio>
TIP
当我们提供了多个 source 的时候,浏览器会遍历 source 列表并播放它可以播放的第一个源。
如何良好的兼容回退
不支持 HTML5 的浏览器会忽略<audio> 和 <video> 标记,而支持 HTML5 的浏览器会忽略开始和结束标记之间除<source>标记以外的所有内容。因此为旧版浏览器指定后备行为很容易。只需将后备 HTML 放在开始或结束<audio> 或 <video> 标记之间(在任何<source>标记之后)即可。