Media Source 系列 - 使用 Media Source Extensions 播放视频
终于有时间写关于 Media Source Extensions (后面简称 MSE) 。Media Source Extensions 是在 2016年成为推荐的的 Html5 API。
This specification extends HTMLMediaElement [HTML51] to allow JavaScript to generate media streams for playback. Allowing JavaScript to generate streams facilitates a variety of use cases like adaptive streaming and time shifting live streams.
草案简单明了的指出这个 API 设计的目的:
允许 JavaScript 来生成看到播放的流媒体扩展了 HTMLMediaElement 对象。允许 JavaScript 来生成流促进了很多用途,如可自 适应的流和可进行时间变换的直播流
W3C 草图案例
透过概念,实际上我们就可以理解它所适应的场景,比如播放流文件(m3u8) 或者 我们现在比较火的直播领域都能够使用它。
MSE 在使用的时候我们还需要关心它浏览器支持情况,很不幸,在 IE 上,我们依旧要考虑兼容性的问题。
初步使用
图 Google Developer -
Media Source Extensions
MSE 处理的三要素大概如上图所示:
- Video / Audio 这些媒体元素
MediaSource
的实例对象- 需要请求下来的 MediaSource.SourceBuffer 资源
我们看下基本的代码:
var videoMp4 = document.querySelector('.js-player-mp4');
if (window.MediaSource) {
var mediaSource = new MediaSource();
videoMp4.src = URL.createObjectURL(mediaSource);
mediaSource.addEventListener('sourceopen', sourceOpen);
} else {
console.log("The Media Source Extensions API is not supported.")
}
function sourceOpen(e) {
URL.revokeObjectURL(videoMp4.src);
// 设置 媒体的编码类型
var mime = 'video/webm; codecs="vorbis, vp8"';
var mediaSource = e.target;
var sourceBuffer = mediaSource.addSourceBuffer(mime);
var videoUrl = './video/avegers3.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();
videoMp4.play().then(function() {
}).catch(function(err) {
log('.js-log-mp4', err)
});
}
});
sourceBuffer.appendBuffer(arrayBuffer);
});
}
这是 Google Developers 里的一个实现。大致代码细节就是:
var videoMp4 = document.querySelector('.js-player-mp4');
if (window.MediaSource) {
var mediaSource = new MediaSource();
videoMp4.src = URL.createObjectURL(mediaSource);
// mediaSource.addEventListener('sourceopen', sourceOpen);
} else {
console.log("The Media Source Extensions API is not supported.")
}
我们前面提到过,我们在使用 MSE 的时候需要三个要素,其中,我们需要实例化 MediaSource
并且使用 URL.createObjectURL
会创建一个 DOMString
,其中包含一个表示参数中给出的对象的URL。这个 URL 的生命周期和创建它的窗口中的 document 绑定。这个新的 URL 对象表示指定的 File 对象或 Blob 对象或者 Media Stream;这个时候 媒体元素已经了,Media Source 对象也已经有了,而且它们之间已经关联起来了。
注意,这个时候实际上我们还是不能播放的,很明显,我们没有具体的媒体资源,即我们播出视频的对象文件。但是在引入开始之前,我们还需要了解 MSE 本身实例化后的状态,我们可以通过属性 readyState
获取:
open
MSE 实例,已经绑定到了媒体元素上,等待接受数据或者正在接受数据closed
MSE 实例未绑定到了媒体元素上ended
MSE 实例,已经绑定到了媒体元素上, 并且所有数据都已经接受到了
直接使用属性访问状态,对性能不是很好,MSE 还支持了具体的事件,
- sourceopen 绑定到媒体元素后开始触发
- sourceclosed 未绑定到媒体元素后开始触发
- sourceended 所有数据接收完成后触发
创建 SourceBuffer
前面提到了三要素的前面两点,但是具体的数据需要我们接下来的代码来实现了;
function sourceOpen(e) {
URL.revokeObjectURL(videoMp4.src);
var mime = 'video/webm; codecs="opus, vp9"';
// e.target refers to the mediaSource instance.
// Store it in a variable so it can be used in a closure.
var mediaSource = e.target;
var sourceBuffer = mediaSource.addSourceBuffer(mime);
// Fetch and process the video.
}
在 MSE 中 SourceBuffer
在 MSE 实例和媒体元素之前穿梭,SourceBuffer 必须指定你需要加载资源的媒体 MIME 类型;
我们通过 addSourceBuffer()
方法来进行添加,代码中,我们在指定 MIME 类型的时候还包含了,两种 [codec](https://en.wikipedia.org/wiki/Video_codec)
,它表示音频和视频的编码格式;MSE 版本1 的草案中没有强制要求同时含有 mine 类型和 编码格式;一些浏览器可能不用那么完整,但是 Chrome 需要,而且必须和视频格式匹配,如果不匹配你可能会遇见这样的错误:
Failed to load because no supported source was found
你可以借助一些软件来查看视频的这些信息,比如MediaInfo
请求媒体资源
前面的代码我们并没有看到具体需要播放什么内容,我们需要借助 XHR 来请求我们要播放的视频。
var videoUrl = './video/avegers3.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();
videoMp4.play().then(function() {
}).catch(function(err) {
log('.js-log-mp4', err)
});
}
});
sourceBuffer.appendBuffer(arrayBuffer);
});
}
我们可以留意到,在请求完毕后我们返回的是response.arrayBuffer()
在 MSE 中我们返回的对象数据必须是 buffer 对象才能够使用,要不然媒体是不能正常播放的。
这段代码虽然短,但是在实际的案列场景中我们可以看到这一块逻辑确实最为复杂的,它需要处理对 流文件,不同的清晰度的文件的请求判断和拉取,这一块关于请求 HLS 或者 DASH后面的文章会写到,这里不展开;
调用endOfStream()
在数据请求完成后,我们需要调用 endOfStream()
。它会改变 MediaSource.readyState
为 ended
并且触发 sourceended
事件。
if (!sourceBuffer.updating && mediaSource.readyState === 'open') {
mediaSource.endOfStream();
}
小结
文章简单介绍了 MSE 的基本使用和一些基本的 API ,后面会连着写几篇关于 Media Source 在实际应用场景中的使用。文章代码: