历史问题
前些年,WEB的视频直播都是由flash或者插件实现。但是插件的权限太大,浏览器不放心,于是把插件给咔擦了,剩下一个flash躲在角落里瑟瑟发抖。目前,flash也已经沦落到需要用户手动启用才能播放的地步。这么一来,用户体验肯定不行,客户该抱怨抱怨,该投诉投诉。
那么,H5直播成了未来的唯一一条路了。从此,WEB开发人员不得不扛上视频直播的担子。(呸,什么都要WEB开发人员做)
MSE
video元素让我们能够播放完整的视频文件,MSE(Media Source Extension)
则提供了底层控制视频流的方法。它提供了一个音视频播放轨道,可以由JS不停地向轨道添加实时的视频段,从而达到直播的效果。
MediaSource.isTypeSupported()
检测 MS 是否支持某个特定的编码和容器盒子
MediaSource.addSourceBuffer()
创建一个带有给定MIME类型的新的 SourceBuffer并添加到 MediaSource 的SourceBuffer 列表。它返回的sourceBuffer很重要,之后的视频信息都是往它身上扔。
MediaSource.removeSourceBuffer()
删除sourceBuffer。播放结束后就可以把它删了。
SourceBuffer.appendBuffer()
不停地往sourceBuffer上扔视频片段,直播就能达成了。基本上直播的核心就是这句话,剩下的就是appendBuffer的参数对不对的问题。如何生成这个参数才是H5直播的难点。
附上MDN上的例子,对着我的中文注释,应该很好理解
var video = document.querySelector('video');
var assetURL = 'frag_bunny.mp4'; //记住这个文件名,待会要考
var mimeCodec = 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"';
if ('MediaSource' in window && MediaSource.isTypeSupported(mimeCodec)) { //判断MS是否支持该文件类型
var mediaSource = new MediaSource;
//console.log(mediaSource.readyState); // closed
video.src = URL.createObjectURL(mediaSource); //video和MS建立连接
mediaSource.addEventListener('sourceopen', sourceOpen); //只有MS的状态变为open后才能对它进行处理
} else {
console.error('Unsupported MIME type or codec: ', mimeCodec);
}
function sourceOpen (_) {
//console.log(this.readyState); // open
var mediaSource = this;
var sourceBuffer = mediaSource.addSourceBuffer(mimeCodec); //创建sourceBuffer
fetchAB(assetURL, function (buf) {
sourceBuffer.addEventListener('updateend', function (_) {
mediaSource.endOfStream();
video.play();
//console.log(mediaSource.readyState); // ended
});
sourceBuffer.appendBuffer(buf); //核心。塞视频数据了。buf是获取到文件的二进制数据
});
}
function fetchAB (url, cb) { //获取文件二进制数据的
console.log(url);
var xhr = new XMLHttpRequest;
xhr.open('get', url);
xhr.responseType = 'arraybuffer';
xhr.onload = function () {
cb(xhr.response);
};
xhr.send();
}
有一天,你兴冲冲地找了个MP4文件,想试试上面那套代码,结果,无法播放视频。这是为什么?因为普通的MP4文件是不支持MSE播放的,只有Fragement MP4(FMP4)
才行。回过头看看,例子的文件名 frag_bunny。frag可以大致看出这个例子用的就是FMP4文件。
ArrayBuffer
目前的浏览器提供了ArrayBuffer
来处理二进制数据,它表示通用的、固定长度的原始二进制数据缓冲区。ArrayBuffer
不能直接操作,而是要通过TypedArray
对象或DataView
对象来操作。它们会将缓冲区中的数据表示为特定的格式,并通过这些格式来读写缓冲区的内容。
var buf = new ArrayBuffer(8);
console.log(buf.byteLength) //8
TypedArray
有如下几种类型:
Int8Array(); 8位有符号整数
Uint8Array(); 8位无符号整数
Uint8ClampedArray(); 8位无符号整型固定数组
Int16Array(); 16位有符号整数
Uint16Array(); 16位无符号整数
Int32Array(); 32位有符号整数
Uint32Array(); 32位无符号整数
Float32Array(); 32位浮点数
Float64Array(); 64位浮点数
var buffer = new ArrayBuffer(8);
var view8 = new Int8Array(buffer);
var view16 = new Int16Array(buffer);
var view32 = new Int32Array(buffer);
console.log(view8.byteLength,view16.byteLength,view32.byteLength); // 8 8 8
console.log(view8.length,view16.length,view32.length) // 8 4 2
byteLength表示字节数,因为view8,view16,view32都是来自同一份内存,所以字节数都为8。length表示数组的长度,Int16Array 一个元素占2个字节,所以数组长度为4。
TypedArray.prototype.subarray()
返回一个新的、基于相同 ArrayBuffer
、元素类型也相同的的 TypedArray
。开始的索引将会被包括,而结束的索引将不会被包括。
const uint8 = new Uint8Array([10, 20, 30, 40, 50]);
console.log(uint8.subarray(1, 3));
// Uint8Array [20, 30]
console.log(uint8.subarray(1));
// Uint8Array [20, 30, 40, 50]
TypedArray.prototype.set(Array,offset)
set() 方法用于从指定数组中读取值,并将其存储在类型化数组中。Array表示源数据,offset表示起始偏移量。
var buffer = new ArrayBuffer(8);
var uint8 = new Uint8Array(buffer);
uint8.set([1,2,3], 3);
console.log(uint8); // Uint8Array [ 0, 0, 0, 1, 2, 3, 0, 0 ]
Video
视频播放离不开video元素。
[element].currentTime
当前播放秒数。可以在代码层面上拖动视频进度条。
[element].duration
视频时长。直播的过程中duration在不停增大,因为video正在一直接收视频数据。网络稳定的情况下,duration-currentTime可以粗略的计算出视频延时。
理论上来说,currentTime设定的和duration差距越小,延时就越小。
其他的事件和基础方法请自行查阅。
到此,准备工作已就绪。
下篇讲解 如何把appendBuffer函数调用的参数凑出来。