前置知识
流媒体是指多种内容形式(文本、音频、视频、图片、动画)等的组合,所谓流媒体就是指源源不断的由提供者产生,并持续的被终端用户接受、展示的多媒体;说到媒体内容自然需要谈到媒体的封装格式和编码格式,总结来说就是原视频文件通过编码格式来压缩文件大小,再通过封装压缩音视频、字幕组合到一个容器中;
现在主流的流媒体点播/直播技术,都会将视频进行切片,而video标签的src只可以挂载整个MP4资源,无法逐个的加载视频分段;因此就出现了MSE(Media source Extenstion:媒体资源拓展API)这是一套可以不断的把音视频二进制数据塞给video标签播放的API,即通过MSE拓展来帮助浏览器识别并处理TS文件,将其变为原来可以支持的媒体容器格式;
hls.js浅析
hls实际会先通过ajax请求m3u8文件,然后会读取到文件的分片列表,及视频的编码格式、时长等,随后会按照顺序去对分片进行请求二进制的文件,然后借助MSE将Buffer内容进行合流,然后组成一个可以播放的媒体资源文件;
浅析MSE
MSE提供了实现无插件且基于Web流媒体的功能,使用MSE媒体流可以通过JS进行创建,且通过使用
Audio
和video
进行播放;MSE使我们可以把通常的单个媒体文件的src值替换成引用MediaSource对象(一个包含即将播放的媒体文件的准备状态等信息的容器),以及引用多个SourceBuffer对象(代表多个组成整个串流的不同媒体块)的元素,MSE可以让我们根据内容获取的大小和评率或是内存占用情况进行更精准的控制
video src中的blob:url浅析
常常会看到video src中不是普通的视频地址,而是Blob url,它允许通过Blob对象下载二进制数据,有了Blob URL就可以往video标签中动态的塞入二进制数据,实现一些功能如切换清晰度、视频源实现无缝切换;
判断浏览器是否支持MSE及MIME格式能否被客户端录制
js
// 判断浏览器是否支持MSE
if('MediaSource' in window){
//
}
// MIME格式能否被客户端录制
var canRecord = MediaRecorder.isTypeSupported(mimeType)
- MSE中的DASH(Dynamic Adaptive Streaming over HTTP) DASH是基于HTTP的动态自适应的比特率流技术,使用的传输协议是TCP(有些老的客户端直播会采用UDP协议直播, 例如YY, 齐齐视频等). 和HLS, HDS技术类似, 都是把视频分割成一小段一小段, 通过HTTP协议进行传输,客户端得到之后进行播放,不同的是MPEG-DASH支持MPEG-2 TS、MP4等多种格式, 可以将视频按照多种编码切割, 下载下来的媒体格式既可以是ts文件也可以是mp4文件, 所以当客户端加载视频时, 按照当前的网速和支持的编码加载相应的视频片段进行播放;
- 是一个规范了自适应内容应当如何被获取的协议
- DASH 的两个最常见的用例涉及"点播"或"直播"观看内容。点播功能让开发者有时间把媒体文件转码出多种不同的分辨率质量。
- 实时处理内容会引入由转码和播发带来的延迟。因此 DASH 并不适用于类似 WebRTC 的即时通讯。但它可以支持比 WebRTC 更多的客户端连接
- 主要API
new MediaSource()
-
MediaSource对象表示
HTMLMediaElement
元素的一个媒体数据源。它会记录源的readyState
和一个可以添加媒体数据去展示的sourceBuffer对象的列表 -
支持的事件
- sourceopen 绑定到媒体元素后开始触发
- sourceclosed 未绑定到媒体元素后开始触发
- sourceended 所有数据接收完成后触发
-
属性
- activeSourceBuffers
var myActiveSourceBuffers = mediaSource.activeSourceBuffers();
- duration:获取或设置当前媒体展示的时长
jsmediaSource.duration = 5.5; // 5.5 seconds var myDuration = mediaSource.duration;
- readyState
- closed: 当前源并未附着到一个 media 元素上。
- open: 当前源已附着到一个 media 元素并准备好接收 SourceBuffer 对象。
- ended: 当前源已附着到一个 media 元素,但流已被MediaSource.endOfStream()结束。
- activeSourceBuffers
-
方法
-
isTypeSupported
- 检测MSE是否支持某个特定的编码和容器盒子
jsconst mimeType = 'video/mp4;codecs=avc1.420028'; mediaSource.isTypeSupported(mimeType)
-
MediaSource.addSourceBuffer()
- 会根据给定的MIME类型创建一个新的SourceBuffer对象,然后将其追加到MediaSource的SourceBuffer列表中
- 用来返回一个具体的视频流
media segments
,接收一个mimeType表示该流的编码格式
jsconst mimeType = 'video/mp4;codecs=avc1.420028'; // avc1.42c01f avc1.42801e avc1.640028 avc1.420028 var sourceBuffer = mediaSource.addSourceBuffer(mimeType);
removeSourceBuffer
- 用来移除某个SourceBuffer,如当流已经结束,就需要释放空间,直接移除缓存的上一个SourceBuffer
MediaSource.endOfStream()
- 表示流的结束。结束情况如下
- 发生错误时
- 重置视频
- 结束流传输
MediaSource.setLiveSeekableRange()
- 表示流的结束。结束情况如下
-
-
- MSE可以实现的功能
- 更精确地加载缓存控制,不像单纯的src,缓存完全取决于浏览器标准。MSE配合range请求可以实现更精细的缓存控制,本质上来说都归结于MSE对音视频的加载实现了
分段切片
的控制,每一个切片的大小、加载机制、内容都可以由开发者来灵活控制; - 自适应拼接,如广告的插入
- 时光平移(Time shifting)
- 控制性能和下载大小
- 更精确地加载缓存控制,不像单纯的src,缓存完全取决于浏览器标准。MSE配合range请求可以实现更精细的缓存控制,本质上来说都归结于MSE对音视频的加载实现了
深入分析MSE
内部逻辑
MSE内部可以创建一系列的sourcebuffer,一般是一个音频Buffer,一个视频Buffer。将MSE做成blob url之后绑定给video的src。然后就可以通过appendBuffer往video里追加音视频数据了。
sourcebuffer简介
sourcebuffer是由MediaSource创建,并直接和HTMLMediaElement接触。简单来说就是一个流的容器,内部有
append()
、remove()来进行流的操作,它可以包含一个或多个media segments(每次通过append添加进去的流片段);
SourceBuffer对象提供了一系列的接口,其中的appendBuffer
方法可以动态的向MediaSource中添加视频/音频片段(对于一个MediaSource可以同时存在多个SourceBuffer),但是appendBuffer
是异步执行的,在完成前,不能append新的chunk,需要监听SourceBuffer
上的updateend
事件,确定空闲后,再加入新的chunk;
js
sourceBuffer.addEventListener('updateend', () => {
// 这个时候才能加入新 chunk
// 先设定新chunk加入的位置,比如第20秒处
sourceBuffer.timestampOffset = 20
// 然后加入
sourceBuffer.append(newBuffer)
}
基于MSE方式的Web播放器结构浅析
在浏览器层面,主要使用video标签、MSE、XHR和UI构成;
- 播放器解析流程
- 由Manager驱动加载视频的playlist
- playlist:(如HLS中的m3u8、dash中的MPD、FLV虽然不是playlist概念但是原理上差别不大,都是为了拿到视频的一个个的分片地址)
- 通过数据服务加载一个个的分片
- 通过transmuxer(转封装器)将分片的封装格式如TS拆开(demux),把连原始的音视频数据解出来,再重新打包成fmp4(remux)
- 最后通过MSE API喂给video标签里,让video去播放
- 总结
- 转封装:即将video不支持的封装格式转码成video所支持的封装格式
- 如何驱动整个播放进行:即决定何时下载下一个分片,何时需要转码插入到video的Buffer里
- 由Manager驱动加载视频的playlist
普通MP4文件和Frament mp4浅析
对于普通的MP4文件,整个MP4文件的Meta数据都在文件头,所有媒体数据为一整块。当文件较大时,mate数据就比较大,而播放器在播放MP4文件时必须下载全部的Meta数据才可以开始播放,这就意味着用户的缓冲时间将因为MP4文件的存储结构而延长;目前较常用的是将大的MP4文件切成物理分离的多段,使得每段的meta都比较小,从而减小缓冲大小;但是这种逻辑存在兼容性问题(后续也是阔以忽略滴,影响不大)(Frament mp4),是一个流式的封装格式,更适合在网络上进行流式传输,而不需要依赖文件头的metadata;
-
URL.createObjectURL(blob/file/MediaSource)浅析
- 该方法会对二进制数据生成一个URL,这个URL可以放置于任何通常可以放置URL的地方,如img的src属性,
- 📢:每调用一次该方法,就会得到一个不一样的URL,且该URL存在时间问题,等同于网页存在的时间,一旦刷新或卸载就会失效
- 也可以通过
URL.revokeObjectURL(url)
进行失效处理
jsvar blob = new Blob(["Hello hanzichi"]); var a = document.createElement("a"); a.href = window.URL.createObjectURL(blob); a.download = "a.txt"; a.textContent = "Download"; document.body.appendChild(a);
URL.createObjectURL
将MediaSource和video关联起来
JSvar mediaSource = new MediaSource; //console.log(mediaSource.readyState); // closed video.src = URL.createObjectURL(mediaSource); mediaSource.addEventListener('sourceopen', sourceOpen); function sourceOpen(){ // 为了节省空间 在底层流和video链接成功后需要将其移除掉 URL.revokeObjectURL(vidElement.src) }