先看效果
http://yangchaojie.top/plugin/dash-playerjavascript
若是你点了上面的地址。你就会发现,视频要加载几秒时间才能播放,虽然我已经使用的流媒体形式(下面会讲)。主要缘由仍是服务器带宽不够,像我这样我的服务器只有1M的速度,一个小短片15M的话就要加载十几秒才能开始播放。
像youku,B站 这种视频网站,虽然带宽高,但访问的人多,视频也更大,不会蠢蠢的放的静态MP4地址上去,等加载完2G的妇联4,妇联5都快出来了。html
扯了这么多,进入正题,怎么解决大视频加载问题。html5
系统 linux 浏览器 firefox (`必定要使用火狐,先放弃一下chrome`) 编辑器 vscode
有业务经验的人确定知道,文件体积大就分割嘛,就像分片上传同样,浏览器不能一次上传太大文件那就分割上传。视频文件大就分段加载。java
来看下youku 上的video.src 上绑定的啥node
Blob URL(参考W3C,官方名称)或Object-URL(参考MDN和方法名称)与Blob或File对象一块儿使用。linux
Blob URL只能由浏览器在内部生成。URL.createObjectURL()
将建立一个特殊的 File
对象、Blob
对象或者 MediaSource
对的引用,git
直接使用 input 选择本地视频便可github
<input id="upload" type="file" /> <video id="preview" src=""></video> <script>const upload = document.querySelector("#upload"); const preview = document.querySelector("#preview"); upload.onchange = function () { const file = upload.files[0]; //File对象 const src = URL.createObjectURL(file); // 此时video.scr上的地址就不在是文件路径,而是指向一块Blob对象(存储视频二进制数据对象的地址) preview.src = src; }; </script>
Blob(Binary Large Object) 对象表示一个不可变、原始数据的类文件对象。Blob
对象表明了一段二进制数据。其它操做二进制数据的接口都是创建在此对象的基础之上。ajax
File对象其实继承自Blob对象,并提供了提供了name , lastModifiedDate, size ,type 等基础元数据。
因此他们互相转换很容易, File=>Blob
XMLHttpRequest
第二版XHR2
容许服务器返回二进制数据,建立blob对象的引用更适合加载网络视频chrome
function ajax(url, cb) { const xhr = new XMLHttpRequest(); xhr.open("get", url); xhr.responseType = "blob"; // "text"-字符串 "blob"-Blob对象 "arraybuffer"-ArrayBuffer对象 xhr.onload = function () { cb(xhr.response); }; xhr.send(); } ajax('video.mp4', function (res) { const src = URL.createObjectURL(res); video.src = src; })
用调试工具查看视频标签的src属性已经变成一个Blob URL,表面上看起来是否是和各大视频网站形式一致了,可是考虑一个问题,这种形式要等到请求彻底部视频数据才能播放,依然面临大视频加载缓慢的问题。
答案应该就在对MediaSource
的引用上。
对MediaSource的引用
MediaSource包含在Media Source Extensions (MSE)标准中。
MSE解决的问题:现有架构过于简单,只能知足一次播放整个曲目的须要,没法实现拆分/合并数个缓冲文件。
MSE内容:MSE 使咱们能够把一般的单个媒体文件的 src
值替换成引用 MediaSource
对象(一个包含即将播放的媒体文件的准备状态等信息的容器),以及引用多个 SourceBuffer
对象(表明多个组成整个串流的不一样媒体块)的元素。MSE 让咱们可以根据内容获取的大小和频率,或是内存占用详情(例如何时缓存被回收),进行更加精准地控制。 它是基于它可扩展的 API 创建自适应比特率流客户端(例如DASH 或 HLS 的客户端)的基础。
简单的说就是先让video 加载 MediaSource
对象,但MediaSource
对象没有视频具体内容,内容被分割在SourceBuffer 对象中,可能有音频SourceBuffer,视频SourceBuffer,字幕SourceBuffer,
看到buffer就有谱了,能够向缓存区一点点写入video数据SourceBuffer.appendBuffer(source)
source
一个 BufferSource
对象(ArrayBufferView
或 ArrayBuffer
),存储了你要添加到 SourceBuffer 中去的媒体片断数据。
值得注意的是这里使用的是追加ArrayBuffer
xhr.responseType 应该等于 "arraybuffer"
ArrayBuffer
对象也表明储存二进制数据的一段内存,
Blob与ArrayBuffer的区别是,除了原始字节之外它还提供了mime type做为元数据,Blob和ArrayBuffer之间能够进行转换。
阅读文档,能够很快写出一个加载示例
我这里已经对视频作好了分割,后面会讲如何分割,下面chunk-stream0 表明240分辨率,若是感受加载快能够换成chunk-stream1 表明480分辨率,甚至chunk-stream2 表明1280分辨率,相应init-stream0 也要变化
<video width="400" controls autoplay="autoplay"></video> <script> const video = document.querySelector('video'); //视频资源存放路径,假设下面有5个分段视频 video1.mp4 ~ video5.mp4,第一个段为初始化视频init.mp4 const assetURL = "http://yangchaojie.top/allow_origin/mpd/"; //视频格式和编码信息,主要为判断浏览器是否支持视频格式,但若是信息和视频不符可能会报错 const mimeCodec = 'video/mp4'; if ('MediaSource' in window) { const mediaSource = new MediaSource(); video.pause(); video.src = URL.createObjectURL(mediaSource); //将video与MediaSource绑定,此处生成一个Blob URL mediaSource.addEventListener('sourceopen', sourceOpen); //能够理解为容器打开 } else { //浏览器不支持该视频格式 console.error('Unsupported MIME type or codec: ', mimeCodec); } function sourceOpen() { const mediaSource = this; const sourceBuffer = mediaSource.addSourceBuffer(mimeCodec); let i = 1; function getNextVideo(url) { //ajax代码实现翻看上文,数据请求类型为arraybuffer ajax(url, function (buf) { //往容器中添加请求到的数据,不会影响当下的视频播放。 sourceBuffer.appendBuffer(new Uint8Array(buf)); }); } //每次appendBuffer数据更新完以后就会触发 sourceBuffer.addEventListener("updateend", function () { if (i === 1) { //第一个初始化视频加载完就开始播放 video.play(); } if (i < 12) { //一段视频加载完成后,请求下一段视频 getNextVideo(`${assetURL}/chunk-stream0-000${String(i).padStart(2, 0)}.m4s`); } if (i === 12) { //所有视频片断加载完关闭容器 mediaSource.endOfStream(); URL.revokeObjectURL(video.src); //Blob URL已经使用并加载,不须要再次使用的话能够释放掉。 } i++; }); //加载初始视频 getNextVideo(`${assetURL}/init-stream0.m4s`); }; function ajax(url, cb) { const xhr = new XMLHttpRequest(); xhr.open("get", url); xhr.responseType = "arraybuffer"; // "text"-字符串 "blob"-Blob对象 "arraybuffer"-ArrayBuffer对象 xhr.onload = function () { cb(xhr.response); }; xhr.send(); } </script>
查看控制台 已经不断的请求分割的片断
但你确定发现没有音频信息,对的,你音响坏了,
这里咱们只加载了视频信息,并无加载音频,因此咱们还要加载音频
<video width="400" controls autoplay="autoplay"></video> <script> const video = document.querySelector('video'); //视频资源存放路径,假设下面有5个分段视频 video1.mp4 ~ video5.mp4,第一个段为初始化视频init.mp4 const assetURL = "http://yangchaojie.top/allow_origin/mpd/"; //视频格式和编码信息,主要为判断浏览器是否支持视频格式,但若是信息和视频不符可能会报错 const mimeCodec = 'video/mp4'; if ('MediaSource' in window) { const mediaSource = new MediaSource(); video.pause(); video.src = URL.createObjectURL(mediaSource); //将video与MediaSource绑定,此处生成一个Blob URL mediaSource.addEventListener('sourceopen', sourceOpen); //能够理解为容器打开 } else { //浏览器不支持该视频格式 console.error('Unsupported MIME type or codec: ', mimeCodec); } function appendVideo(mediaSource) { const sourceBuffer = mediaSource.addSourceBuffer('video/mp4'); let i = 1; function getNextVideo(url) { //ajax代码实现翻看上文,数据请求类型为arraybuffer ajax(url, function (buf) { //往容器中添加请求到的数据,不会影响当下的视频播放。 sourceBuffer.appendBuffer(new Uint8Array(buf)); }); } //每次appendBuffer数据更新完以后就会触发 sourceBuffer.addEventListener("updateend", function () { if (i === 1) { //第一个初始化视频加载完就开始播放 video.play(); } if (i < 12) { //一段视频加载完成后,请求下一段视频 getNextVideo(`${assetURL}/chunk-stream0-000${String(i).padStart(2, 0)}.m4s`); } if (i === 12) { //所有视频片断加载完关闭容器 mediaSource.endOfStream(); URL.revokeObjectURL(video.src); //Blob URL已经使用并加载,不须要再次使用的话能够释放掉。 } i++; }); //加载初始视频 getNextVideo(`${assetURL}/init-stream0.m4s`); } function appendAudio(mediaSource) { const sourceBuffer = mediaSource.addSourceBuffer('audio/mp4'); let i = 1; function getNextVideo(url) { //ajax代码实现翻看上文,数据请求类型为arraybuffer ajax(url, function (buf) { //往容器中添加请求到的数据,不会影响当下的视频播放。 sourceBuffer.appendBuffer(new Uint8Array(buf)); }); } //每次appendBuffer数据更新完以后就会触发 sourceBuffer.addEventListener("updateend", function () { if (i === 1) { //第一个初始化视频加载完就开始播放 video.play(); } if (i < 12) { //一段视频加载完成后,请求下一段视频 getNextVideo(`${assetURL}/chunk-stream3-000${String(i).padStart(2, 0)}.m4s`); } if (i === 12) { //所有视频片断加载完关闭容器 mediaSource.endOfStream(); URL.revokeObjectURL(video.src); //Blob URL已经使用并加载,不须要再次使用的话能够释放掉。 } i++; }); //加载初始视频 getNextVideo(`${assetURL}/init-stream3.m4s`); } function sourceOpen() { const mediaSource = this; appendVideo(mediaSource) appendAudio(mediaSource) }; function ajax(url, cb) { const xhr = new XMLHttpRequest(); xhr.open("get", url); xhr.responseType = "arraybuffer"; // "text"-字符串 "blob"-Blob对象 "arraybuffer"-ArrayBuffer对象 xhr.onload = function () { cb(xhr.response); }; xhr.send(); } </script>
稍微改动下代码,ok 如今一个基本流媒体播放 搞定
可是,还有一个问题,很严重的问题,就是没有办法拖动进度。
由于它不知道整个视频是什么样的,有多长,是否有声音轨,有几条等等。只知道加载过的片短视频是什么样的。
因此须要一个描述文件 mpd。
MPD是一个XML文件,描述了媒体的分段方式,类型和编解码器(此处为MP4),视频的比特率,长度和基本分段大小。MPD文件还能包含音频信息,您能够将内容拆分为视频和音频播放器的单独流(就像上面我拆成了多个流)。
那么MPD 文件和上面的分段文件 是怎么生成的?须要先了解DASH
DASH(Dynamic Adaptive Streaming over HTTP )是一个规范了自适应内容应当如何被获取的协议。它其实是创建在 MSE 顶部的一个层,用来构建自适应比特率串流客户端。虽然已经有一个相似的协议了(例如 HTTP 串流直播(HLS)),但 DASH 有最好的跨平台兼容性。
是一种服务端、客户端的流媒体解决方案:
服务端:
将视频内容分割为一个个分片,每一个分片能够存在不一样的编码形式(不一样的codec、profile、分辨率、码率等);
播放器端:
就能够根据自由选择须要播放的媒体分片;能够实现adaptive bitrate streaming技术。不一样画质内容无缝切换,提供更好的播放体验。
我的理解:MSE是标准,描述了媒体文件能够流式接收,DASH是协议规范了媒体文件如何分轨,如何分段,如何接收。
概念太多,给你们看点实际的
ffmpeg -i 你的文件.mp4 -c copy -use_template 0 -single_file 0 -f dash index.mpd
执行上面命令能够获得 所需的 mpd 文件 和 分段后媒体文件
ffmpeg能够 面向搜索引擎安装。
很简单了,mpd已经描述了媒体长度提早告诉mediaSource就好了
MediaSource
接口的属性 duration 用来获取或者设置当前媒体展现的时长.
我使用的视频长度是1M8.2s,也就是1分钟+8.2秒=68.2秒
mediaSource.duration = 68.2;
如今再来看看视频就有长度,能够拖动进度了。
到这里还只是仅仅正常播放,还有许多问题没有解决,好比如今拖动进度虽然能够播放,但仍是要加载完以前内容片断才行,须要改进成只加载当前,还有切换多分辨率没有实现,最头痛的是兼容性,若是你坚持使用chrome或其余浏览器没有使用火狐,应该是大部分代码没有办法运行。问题还不少,这时候就要找轮子了。(也没时间搞,由于公司倒闭了,要花点时间找工做,过段时间再深刻研究)
Dash.js是用JavaScript编写的开源MPEG-DASH视频播放器。其目标是提供一个健壮的跨平台播放器,能够在须要视频播放的应用程序中自由重用。
高仿优酷播放器就是应用了dash.js, 可是dash.js也存在一些问题(也不算问题,就是没有直接提供API),好比不能当即切换分辨率,必须等当前已加载片断播放完后才能切换,因此在dash.js 基础上稍加包裹,不敢说封装,人家已经至关完美。提供一些更易上手的播放器API。dash-player