本文作者:ivweb villainthr
接 《 全面进阶 H5 直播(上)》
在没有 MSE 出现之前,前端对 video 的操作,仅仅局限在对视频文件的操作,而并不能对视频流做任何相关的操作。现在 MSE 提供了一系列的接口,使开发者可以直接提供 media stream。
那 MSE 是如何完成视频流的加载和播放呢?
这可以参考 google 的 MSE 简介
var vidElement = document.querySelector('video');
if (window.MediaSource) {
var mediaSource = new MediaSource();
vidElement.src = URL.createObjectURL(mediaSource);
mediaSource.addEventListener('sourceopen', sourceOpen);
} else {
console.log("The Media Source Extensions API is not supported.")
}
function sourceOpen(e) {
URL.revokeObjectURL(vidElement.src);
var mime = 'video/webm; codecs="opus, vp9"';
var mediaSource = e.target;
var sourceBuffer = mediaSource.addSourceBuffer(mime);
var videoUrl = 'droid.webm';
fetch(videoUrl)
.then(function(response) {
return response.arrayBuffer();
})
.then(function(arrayBuffer) {
sourceBuffer.addEventListener('updateend', function(e) {
if (!sourceBuffer.updating && mediaSource.readyState === 'open') {
mediaSource.endOfStream();
}
});
sourceBuffer.appendBuffer(arrayBuffer);
});
}
可以从上面的代码看出,一套完整的执行代码,不仅需要使用 MSE 而且,还有一下这些相关的 API。
我们简单讲解一下上面的流程。根据 google 的阐述,整个过程可以为:
而中间传递的数据都是通过 Buffer
的形式来进行传递的。
中间有个需要注意的点,MS 的实例通过 URL.createObjectURL()
创建的 url 并不会同步连接到 video.src。换句话说,URL.createObjectURL()
只是将底层的流(MS)和 video.src 连接中间者,一旦两者连接到一起之后,该对象就没用了。
那么什么时候 MS 才会和 video.src 连接到一起呢?
创建实例都是同步的,但是底层流和 video.src 的连接时异步的。MS 提供了一个sourceopen
事件给我们进行这项异步处理。一旦连接到一起之后,该 URL object 就没用了,处于内存节省的目的,可以使用 URL.revokeObjectURL(vidElement.src)
销毁指定的
URL object。
mediaSource.addEventListener('sourceopen', sourceOpen);
function sourceOpen(){
URL.revokeObjectURL(vidElement.src)
}
MS 提供了我们对底层音视频流的处理,那一开始我们怎么决定以何种格式进行编解码呢?
这里,可以使用addSourceBuffer(mime)
来设置相关的编码器:
var mime = 'video/webm; codecs="opus, vp9"';
var sourceBuffer = mediaSource.addSourceBuffer(mime);
然后通过,异步拉取相关的音视频流:
fetch(url)
.then(res=>{
return res.arrayBuffer();
})
.then(buffer=>{
sourceBuffer.appendBuffer(buffer);
})
如果视频已经传完了,而相关的 Buffer 还在占用内存,这时候,就需要我们显示的中断当前的 Buffer 内容。那么最终我们的异步处理结果变为:
fetch(url)
.then(res=>{
return res.arrayBuffer();
})
.then(function(arrayBuffer) {
sourceBuffer.addEventListener('updateend', function(e) {
// 是否有持续更新的流
if (!sourceBuffer.updating && mediaSource.readyState === 'open') {
// 没有,则中断连接
mediaSource.endOfStream();
}
});
sourceBuffer.appendBuffer(arrayBuffer);
});
上面我们大致了解了一下关于 Media Source Extensions 的大致流程,但里面的细节我们还没有细讲。接下来,我们来具体看一下 MSE 一篮子的生态技术包含哪些内容。首先是,MediaSource
MS(MediaSource) 可以理解为多个视频流的管理工具。以前,我们只能下载一个清晰度的流,并且不能平滑切换低画质或者高画质的流,而现在我们可以利用 MS 实现这里特性。我们先来简单了解一下他的 API。
创建一个 MS:
var mediaSource = new MediaSource();
该是用来返回一个具体的视频流,接受一个 mimeType 表示该流的编码格式。例如:
var mimeType = 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"';
var sourceBuffer = mediaSource.addSourceBuffer(mimeType);
sourceBuffer 是直接和视频流有交集的 API。例如:
function sourceOpen (_) {
var mediaSource = this;
var sourceBuffer = mediaSource.addSourceBuffer(mimeCodec);
fetchAB(assetURL, function (buf) {
sourceBuffer.addEventListener('updateend', function (_) {
mediaSource.endOfStream();
video.play();
});
// 通过 fetch 添加视频 Buffer
sourceBuffer.appendBuffer(buf);
});
};
它通过appendBuffer
直接添加视频流,实现播放。不过,在使用 addSourceBuffer
创建之前,还需要保证当前浏览器是否支持该编码格式。
用来移除某个 sourceBuffer。移除也主要是考虑性能原因,将不需要的流移除以节省相应的空间,格式为:
mediaSource.removeSourceBuffer(sourceBuffer);
用来表示接受的视频流的停止,注意,这里并不是断开,相当于只是下好了一部分视频,然后你可以进行播放。此时,MS 的状态变为:ended
。例如:
var mediaSource = this;
var sourceBuffer = mediaSource.addSourceBuffer(mimeCodec);
fetchAB(assetURL, function (buf) {
sourceBuffer.addEventListener('updateend', function (_) {
mediaSource.endOfStream(); // 结束当前的接受
video.play(); // 可以播放当前获得的流
});
sourceBuffer.appendBuffer(buf);
});
该是用来检测当前浏览器是否支持指定视频格式的解码。格式为:
var isItSupported = mediaSource.isTypeSupported(mimeType); // 返回值为 Boolean
mimeType 可以为 type 或者 type + codec。
例如:
// 不同的浏览器支持不一样,不过基本的类型都支持。
MediaSource.isTypeSupported('audio/mp3'); // false,这里应该为 audio/mpeg
MediaSource.isTypeSupported('video/mp4'); // true
MediaSource.isTypeSupported('video/mp4; codecs="avc1.4D4028, mp4a.40.2"'); // true
这里有一份具体的 mimeType 参考列表。
当 MS 从创建开始,都会自带一个readyState
属性,用来表示其当前打开的状态。MS 有三个状态:
var mediaSource = new MediaSource; mediaSource.readyState; // 默认为 closed
当由 closed 变为 open 状态时,需要监听 sourceopen 事件。
video.src = URL.createObjectURL(mediaSource); mediaSource.addEventListener('sourceopen', sourceOpen);
MS 针对这几个状态变化,提供了相关的事件:sourceopen
,sourceended
,sourceclose
。MS 还提供了其他的监听事件 sourceopen,sourceended,sourceclose,updatestart,update,updateend,error,abort,addsourcebuffer,removesourcebuffer. 这里主要选了比较重要的,其他的可以参考官方文档。
比较常用的属性有: duration,readyState。
mediaSource.duration = 5.5; // 设置媒体流播放的时间
var myDuration = mediaSource.duration; // 获得媒体流开始播放的时间
在实际应用中为:
sourceBuffer.addEventListener('updateend', function (_) { mediaSource.endOfStream(); mediaSource.duration = 120; // 设置当前流播放的时间 video.play(); });
closed
,open
,ended
。
var mediaSource = new MediaSource; //此时的 mediaSource.readyState 状态为 closed
以及:
sourceBuffer.addEventListener('updateend', function (_) { mediaSource.endOfStream(); // 调用该方法后结果为:ended video.play(); });
除了上面两个属性外,还有 sourceBuffers
,activeSourceBuffers
这两个属性。用来返回通过 addSourceBuffer()
创建的 SourceBuffer 数组。这没啥过多的难度。接下来我们就来看一下靠底层的sourceBuffer
。
SourceBuffer 是由mediaSource
创建,并直接和 HTMLMediaElement
接触。简单来说,它就是一个流的容器,里面提供的 append()
,remove()
来进行流的操作,它可以包含一个或者多个 media segments
。同样,接下来,我们再来看一下该构造函数上的基本属性和内容。
前面说过 sourceBuffer 主要是一个用来存放流的容器,那么,它是怎么存放的,它存放的内容是啥,有没有顺序等等。这些都是 sourceBuffer 最最根本的问题。OK,接下来,我们来看一下的它的基本架构有些啥。
参考 W3C,可以基本了解到里面的内容为:
interface SourceBuffer : EventTarget {
attribute AppendMode mode;
readonly attribute boolean updating;
readonly attribute TimeRanges buffered;
attribute double timestampOffset;
readonly attribute AudioTrackList audioTracks;
readonly attribute VideoTrackList videoTracks;
readonly attribute TextTrackList textTracks;
attribute double appendWindowStart;
attribute unrestricted double appendWindowEnd;
attribute EventHandler onupdatestart;
attribute EventHandler onupdate;
attribute EventHandler onupdateend;
attribute EventHandler onerror;
attribute EventHandler onabort;
void appendBuffer(BufferSource data);
void abort();
void remove(double start, unrestricted double end);
};
上面这些属性决定了其 sourceBuffer 整个基础。
首先是 mode
。上面说过,SB(SourceBuffer) 里面存储的是 media segments(就是你每次通过 append 添加进去的流片段)。SB.mode 有两种格式:
timestamps
来标识其具体播放的顺序。比如:20s的 buffer,30s 的 buffer 等。appendBuffer
的顺序来决定每个 mode 添加的顺序。timestamps
根据 sequence 自动产生。那么上面两个哪个是默认值呢?
看情况,讲真,没骗你。
当 media segments
天生自带timestamps
,那么 mode
就为 segments
,否则为 sequence
。所以,一般情况下,我们是不用管它的值。不过,你可以在后面,将 segments
设置为 sequence
这个是没毛病的。反之,将 sequence
设置为 segments
就有问题了。
var bufferMode = sourceBuffer.mode;
if (bufferMode == 'segments') {
sourceBuffer.mode = 'sequence';
}
然后另外两个就是 buffered 和 updating。
另外还有一些其他的相关属性,比如 textTracks,timestampOffset,trackDefaults,这里就不多说了。实际上,SB 是一个事件驱动的对象,一些常见的处理,都是在具体的事件中完成的。那么它又有哪些事件呢?
在 SB 中,相关事件触发包括:
abort()
方法废弃时,会触发。此时,updating 由 true 变为 false。注意上面有两个事件比较类似:update
和 updateend
。都是表示处理的结束,不同的是,update 比 updateend 先触发。
sourceBuffer.addEventListener('updateend', function (e) {
// 当指定的 buffer 加载完后,就可以开始播放
mediaSource.endOfStream();
video.play();
});
SB 处理流的方法就是 +/- : appendBuffer, remove。另外还有一个中断处理函数 abort()
。
response.arrayBuffer();
来获取的。abort()
。有一个业务场景是,当用户移动进度条,而,此时 fetch 已经获取前一次的 media segments,那么可以使用 abort
放弃该操作,转而请求新的 media segments。具体可以参考:abort 使用上面主要介绍了处理音视频流需要用的 Web 技术,后面章节,我们接入实战,具体来讲一下,如何做到使用 MSE 进行 remux 和 demux。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。