Media Source Extensions
Media Source API,正式称为Media Source Extensions (MSE),提供了基于web且不依赖插件播放流媒体的功能。使用MSE,可以通过JavaScript创建媒体流MediaSource,并使用和元素播放。
MSE可以让我们使用一个 MediaSource 对象来替代通常的单个媒体文件的 src 值给HTMLMediaElement元素。
MSE 让我们能够根据内容获取的大小和频率,或是内存占用详情(例如什么时候缓存被回收),进行更加精准地控制。
MSE为可扩展API构建自适应码率播放器的出现奠定了基础,例如DASH 、 HLS播放器。
使用createObjectURL将MediaSource和video标签连接起来
var mediaSource = new MediaSource;
//console.log(mediaSource.readyState); // closed
video.src = URL.createObjectURL(mediaSource);
mediaSource.addEventListener('sourceopen', sourceOpen);
function sourceOpen(){
URL.revokeObjectURL(video.src)
}
创建MediaSource对象,通过URL.createObjectURL()将MediaSource对象转换为url,然后赋值给媒体元素的src。MS对象的sourceopen事件触发时,代表MS对象和媒体元素两者已经连接在一起,URL.createObjectURL创建的url就没有作用可以销毁。
Media Source API
支持事件:
- sourceopen 绑定到媒体元素后开始触发
- sourceclosed 未绑定到媒体元素后开始触发
- sourceended 所有数据接收完成后触发
对应的属性mediaSource.readyState:
- open MSE 实例,已经绑定到了媒体元素上,等待接受数据或者正在接受数据
- closed MSE 实例未绑定到了媒体元素上。MS刚创建时就是该状态。
- ended MSE 实例,已经绑定到了媒体元素上, 并且所有数据都已经接受到了。当endOfStream()执行完成,会变为该状态。
设置编码类型mime 字符串,创建SourceBuffer
function sourceOpen(e) {
URL.revokeObjectURL(videoMp4.src);
var mime = 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"';
// e.target refers to the mediaSource instance.
// Store it in a variable so it can be used in a closure.
var mediaSource = e.target;
// addSourceBuffer只能在readyState为open时调用
var sourceBuffer = mediaSource.addSourceBuffer(mime);
// Fetch and process the video.
}
首先,前面的 video/mp4 代表这是一段 mp4 格式封装的视频,同理也存在类似 video/webm、audio/mpeg、audio/mp4 这样的 mime 格式。一般情况下,可以通过 canPlayType 这个方法来判断浏览器是否支持当前格式。
后面的这一段 codecs="…" 比较特别,以逗号相隔,分为两段:
-
第一段,‘avc1.42E01E’,即它用于告诉浏览器关于视频编解码的一些重要信息,诸如编码方式、分辨率、帧率、码率以及对解码器解码能力的要求。在这个例子中,**‘avc1’ **代表视频采用 H.264 编码,随后是一个分隔点,之后是 3 个两位的十六进制的数,这 3 个十六进制数分别代表:
AVCProfileIndication(42)
profile_compability(E0)
AVCLevelIndication(1E)
第一个用于标识 H.264 的 profile,后两个用于标识视频对于解码器的要求,只是浏览器用于判断自身的解码能力能否满足需求,所以不需要和视频完全对应,更高也是可以的。
对于一个 mp4 视频,可以使用 mp4file 这样的命令行工具来查看这些参数:mp4file --dump xxx.mp4 -
codecs 的第二段 ‘mp4a.40.2’,这一段信息是关于音频部分的,代表视频的音频部分采用了 AAC LC 标准:‘mp4a’ 代表此视频的音频部分采用 MPEG-4 压缩编码。随后是一个分隔点,和一个十六进制数(40),这是 ObjectTypeIndication,40 对应的是 Audio ISO/IEC 14496-3 标准。然后又是一个分隔点,和一个十进制数2,这是 MPEG-4 Audio Object Type,是一种 H.264 视频中常用的音频编码规范。
https://tools.ietf.org/html/rfc6381
SourceBuffer简介
SourceBuffer 是由 mediaSource 创建,并直接和 HTMLMediaElement 接触。简单来说,它就是一个流的容器,里面提供的 append(),remove() 来进行流的操作,它可以包含一个或者多个 media segments。
1 添加/移除 buffer
1.1 appendBuffer
appendBuffer可以动态地向 MediaSource 中添加视频/音频片段(对于一个 MediaSource,可以同时存在多个 SourceBuffer)
如果视频很长,存在多个chunk 的话,就需要不停地向 SourceBuffer 中加入新的 chunk。这里就需要注意一个问题了,即 appendBuffer 是异步执行的,在完成前,不能 append 新的 chunk,而是应该监听 SourceBuffer 上的 updateend 事件,确定空闲后,再加入新的 chunk:
sourceBuffer.addEventListener('updateend', () => {
// 这个时候才能加入新 chunk
// 先设定新chunk加入的位置,比如第20秒处
sourceBuffer.timestampOffset = 20
// 然后加入
sourceBuffer.appendBuffer(newBuffer)
}
当然,并不是所有的 Buffer 都能随便添加给指定的 SB,这里面是需要条件和相关顺序的。
- 该 buffer,必须满足 MIME 限定的类型
- 该 buffer,必须包含 initialization segments(IS) 和 media segments(MS)
在添加 Buffer 的时候,你需要了解你所采用的 mode 是哪种类型,sequence 或者 segments。这两种是完全两种不同的添加方式。
-
segments:这种方式是直接根据 MP4 文件中的 pts 来决定播放的位置和顺序,它的添加方式极其简单,只需要判断 updating === false,然后,直接通过 appendBuffer 添加即可。
-
sequence:
如果你是采用这种方式进行添加 Buffer 进行播放的话,那么你也就没必要了解 FMP4 格式,而是了解 MP4 格式。因为,该模式下,SB 是根据具体添加的位置来进行播放的。所以,如果你是 FMP4 的话,有可能就有点不适合了。针对 sequence 来说,每段 buffer 都必须有自己本身的指定时长,每段 buffer 不需要参考的 baseDts,即,他们直接可以毫无关联。那 sequence 具体怎么操作呢?
简单来说,在每一次添加过后,都需要根据指定 SB 上的 timestampOffset。该属性,是用来控制具体 Buffer 的播放时长和位置的。
if (!sb.updating) {
let MS = this._mergeBuffer(media.tmpBuffer);
sb.appendBuffer(MS); // ****
sb.timestampOffset += lib.duration; // ****
media.tmpBuffer = [];
}
1.2 remove
在 SB 中,简单的来说,就是移除指定的时间范围内 buffer。需要用到的 API 为:
remove(double start, unrestricted double end);
具体的步骤为:
找到具体需要移除的 segment。
得到其开始(start)的时间戳(以 s 为单位)
得到其结束(end)的时间戳(以 s 为单位)
此时,updating 为 true,表明正在移除
完成之后,出发 updateend 事件
1.3 abort
清空当前 SB 中所有的 segment,常用的场景为:当用户移动进度条,而此时 fetch 已经获取前一次的 media segments,那么可以使用 abort 放弃该操作,转而请求新的 media segments
2 属性
2.1 mode
上面说过,SB(SourceBuffer) 里面存储的是 media segments(就是你每次通过 append 添加进去的流片段)。SB.mode 有两种格式:
segments: 乱序排放。通过 timestamps 来标识其具体播放的顺序。比如:20s的 buffer,30s 的 buffer 等。表示 A/V 的播放时根据你视频播放流中的 pts 来决定,该模式也是最常使用的。因为音视频播放中,最重要的就是 pts 的排序。因为,pts 可以决定播放的时长和顺序,如果一旦 A/V 的 pts 错开,有可能就会造成 A/V sync drift。
sequence: 按序排放。通过 appendBuffer 的顺序来决定每个 mode 添加的顺序。timestamps 根据 sequence 自动产生。还需要注意,在播放的同时,你需要告诉 SB这段 segment 有多长,也就是该段 Buffer 的实际偏移量。而该段偏移量就是由 timestampOffset 决定的。整个过程用代码描述一下就是:
sb.appendBuffer(media.segment);
sb.timestampOffset += media.duration;
SB.mode的默认值是根据传进来的media segments决定的,当 media segments 天生自带 timestamps,那么 mode 就为 segments ,否则为 sequence。所以,一般情况下,我们是不用管它的值。不过,你可以在后面,将 segments 设置为 sequence 这个是没毛病的。反之,将 sequence 设置为 segments 就有问题了。代码如下:
sb.appendBuffer(media.segment);
sb.timestampOffset += media.duration;
如果你想手动更改 mode 也是可以的,不过需要注意几个先决条件:
对应的 SB.updating 必须为 false.
如果该 parent MS 处于 ended 状态,则会手动将 MS readyState 变为 open 的状态。
2.2 buffered
返回一个 TimeRange 对象。用来表示当前被存储在 SB 中的 buffer。
2.3 updating
返回 Boolean,表示当前 SB 是否正在被更新。例如: appendBuffer(), appendStream(), remove() 调用时。
3. 事件
在 SB 中,相关事件触发包括:
updatestart: 当 updating 由 false 变为 true。
update:当 append()/remove() 方法被成功调用完成时,updating 由 true 变为 false。
updateend: append()/remove() 已经结束
error: 在 append() 过程中发生错误,updating 由 true 变为 false。
abort: 当 append()/remove() 过程中,使用 abort() 方法废弃时,会触发。此时,updating 由 true 变为 false。
注意上面有两个事件比较类似:update 和 updateend。都是表示处理的结束,不同的是,update 比 updateend 先触发。
MediaSource简介
1. isTypeSupported
isTypeSupported是静态方法,主要是用来检测 MS 是否支持某个特定的编码和容器盒子。例如:
MediaSource.isTypeSupported('video/mp4; codecs="avc1.42E01E, mp4a.40.2"')
2. addSourceBuffer
用来返回一个具体的视频流 SB,接受一个 mimeType 表示该流的编码格式
3. removeSourceBuffer
removeSourceBuffer用来移除某个 sourceBuffer。比如当前流已经结束,那么你就没必要再保留当前 SB 来占用空间,可以直接移除。示例代码为:
mediaSource.removeSourceBuffer(sourceBuffer);
4. endOfStream
用来表示接受的视频流的停止,注意,这里并不是断开,相当于只是下好了一部分视频,然后你可以进行播放。此时,MS 的状态变为:ended。例如:
var mediaSource = this;
var sourceBuffer = mediaSource.addSourceBuffer(mimeCodec);
fetchAB(asssourceopenetURL, function (buf) {
sourceBuffer.addEventListener('updateend', function (_) {
mediaSource.endOfStream(); // 结束当前的接受
video.play(); // 可以播放当前获得的流
});
sourceBuffer.appendBuffer(buf);
});
5.sourceBuffers
sourceBuffers 是 MS 实例上的一个属性,它返回的是一个 SourceBufferList 的对象,里面可以获取当前 MS 上挂载的所有 SB。不过,只有当 MS 为 open 状态的时候,它才可以访问。具体使用为:
var mediaSource = this;
var sourceBuffer = mediaSource.addSourceBuffer(mimeCodec);
fetchAB(assetURL, function (buf) {
sourceBuffer.addEventListener('updateend', function (_) {
mediaSource.endOfStream(); // 结束当前的接受
video.play(); // 可以播放当前获得的流
});
sourceBuffer.appendBuffer(buf);
});
6. activeSourceBuffers
sourceBuffers 是 MS 实例上的一个属性,它返回的是一个 SourceBufferList 的对象,里面可以获取当前 MS 上挂载的所有 SB。不过,只有当 MS 为 open 状态的时候,它才可以访问
7. sourceclose事件触发条件
sourceclose 是在 media 元素和 MS 断开的时候,才会触发。那这个怎么断开呢?难道直接将 media 的元素的 src 直接设置为 null 就 OK 了吗?MS 会这么简单么?实际上并不,如果要手动触发 sourceclose 事件的话,则需要下列步骤:
将 readyState 设置为 closed
将 MS.duration 设置为 NaN
移除 activeSourceBuffers 上的所有 Buffer
触发 activeSourceBuffers 的 removesourcebuffer 事件
移除 sourceBuffers 上的 SourceBuffer。
触发 sourceBuffers 的 removesourcebuffer 事件
触发 MediaSource 的 sourceclose 事件