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>
标记之后)即可。