背景
对比Web开发和原生开发,通常APP被说到的优势在于两点:1.交互体验更流畅。2.硬件支持更友好。随着HTML5的发展,我们可以通过浏览器做的事情会越来越多,之前只能原生开发做的事情通过浏览器也能实现,比如下面我要说的"视频录制"。
不啰嗦,先看demo:一张canvas画布上有一颗红色的小球左右来回循环滚动,点击"开始录制"按钮,视频就开始录制,再点击"下载录像"按钮,视频结束录制,并且会下载一个webm格式的视频文件在本地。
技术上实现
我这里分享一整个前端录制+服务端存储+下载的完整方案(上面demo是简版,无服务端存储步骤)。整个开发流程可分为3个步骤:1.视频录制,2.上传持久化存储,3.下载视频。整个前端流程可概括成下图
下面详细地介绍各实现步骤
录制视频
首先,调研找到了HTML5 的一个API MediaRecorder 适合来做录制这个事情,摘抄下官网文档:
MediaRecorder 是 MediaStream Recording API 提供的用来进行媒体轻松录制的接口, 他需要通过调用MediaRecorder()
构造方法进行实例化。一个新的MediaRecorder
对象,对指定的MediaStream
对象进行录制,支持的配置项包括设置容器的MIME 类型 (例如"video/webm"
或者"video/mp4"
)和音频及视频的码率或者二者同用一个码率
通过上面介绍可以得知,只要能得到录制对象的 MediaStream (本文展示的是canvas录制,目前能转为MediaStream的对象还有video,audio,电脑前置摄像头输入)就可以轻松的实现录制这个事情。
接着找到canvas如下API,可用于从canvas中获取到我们需要的MediaStream
MediaStream = canvas.captureStream(frameRate);
以上就走通了视频的录制过程:canvas —>MediaStream—>MediaRecord—>视频。整个录制的示例代码如下:
//录制到的数据数据 letallChunks=[]; letcanvas=document.getElementById("canvasId"); letstream=canvas.captureStream(60); // 60 FPS recording letrecorder=newMediaRecorder(stream, { mimeType: 'video/webm;codecs=vp9', }); // canvas 录制回调 recorder.ondataavailable=e=>{ allChunks.push(e.data); } recorder.start(10);
还有一点要注意的是我们这边录制视频的格式是webm, MP4格式的在目前最新chrome版本中暂不支持,可通过下面API查询支持情况
MediaRecorder.isTypeSupported('video/webm'); //true MediaRecorder.isTypeSupported('video/mp4');//false
上传&持久化存储
上传要做的事情相对来说比较明确,只需要将上面提到的allChunks对象上传即可。我这边在业务上考虑到了两个点:1.长时间录制allChunks尺寸会过大,上传会比较耗时。2.录制过程中如有突发情况(刷新或点击链接跳走),视频就无法保存了。
针对于上面两种业务场景,我简单的采用了Blob切片上传,开始录制后,设置一个定时器,每隔10秒发送增量的视频切片给后端,前端示例代码如下:
// 定时向后端发送 增量blob function sendNewChunks(){ let start = 0; let iterationIndex = 0; sendTimer = window.setInterval( () => { let allBlob = new Blob(allChunks); // 把增量视频的片段切下来 let newBlob = allBlob.slice(start, allBlob.size); start = allBlob.size; // 当录像有更新的时候,才向接口发送分片信息 if (newBlob.size > 0) { iterationIndex++; // 在这里Http post 发送newBlob对象 } },10000) }
我这个场景,后端用是node.js,所以在node层直接调用阿里云的OSS API 进行上传即可:
letresult=awaitclient.put('object-name', newBuffer('hello world'));
这边一点需要注意的是,OSS node.js API 这边需要上传的是Buffer格式的数据,在尝试了各种方法后,我最终能走通整个流程做法是:先在前端把Blob格式的视频切片转换成Uint8Array,传递到node.js后再通过new Buffer.from(dataArray)转换成Buffer格式。
下载视频
下载视频方案用的是通过websocket向前端不断的推小的视频切片,再由前端把视频拼接起来,借助浏览器转码下载。
- 为什么要用websocket推送切片,而不直接通过一个请求下载?
- 若直接通过后端把视频拼接起来,再通过一个请求推给前端这种方案在视频尺寸过大时,会有请求callback超时风险。
- 通过websocket可以比较方便的实现流式下载,并在前端做对应可视化展现(做一个像迅雷一样的下载器)。
- 为什么要借助浏览器转码?
- 尝试过后端拼接OSS文件存储的Buffer格式文件,但暂时没找到合适的转码库。而前端可以通过window.URL.createObjectURL,比较简单的把缓存里的Blob对象通过浏览器下载成一个文件,代码如下:
// 通过blob对象下载 function downloadByBlob(blobObj) { const link = document.createElement('a'); link.style.display = 'none'; const downloadUrl = window.URL.createObjectURL(blobObj); link.href = downloadUrl; link.download = `test.webm`; document.body.appendChild(link); link.click(); link.remove(); }
最后
实践过程中,有查阅到的一些相关知识点我在下面列一下,希望对你有所帮助!
相关知识点
- Blob ( Binary Large Object )表示一个二进制文件的数据内容,表示一个不可变、原始数据的类文件对象。(
File
接口基于Blob
,继承了 blob 的功能并将其扩展使其支持用户系统上的文件。) - WebM由Google提出,是一个开放、免费的媒体文件格式。WebM 影片格式其实是以 Matroska(即 MKV)容器格式为基础开发的新容器格式,里面包括了VP8影片轨和 Ogg Vorbis 音轨,其中Google将其拥有的VP8视频编码技术以类似BSD授权开源,Ogg Vorbis 本来就是开放格式。
- 二进制数组相关
- 使用场景:ArrayBuffer对象、TypedArray对象、DataView对象是JavaScript操作二进制数据的一个接口
- 区别:ArrayBuffer对象代表原始的二进制数据,TypedArray对象代表确定类型的二进制数据,DataView对象代表不确定类型的二进制数据
- 目的:这些对象原始的设计目的,与WebGL项目有关。所谓WebGL,就是指浏览器与显卡之间的通信接口,为了满足JavaScript与显卡之间大量的、实时的数据交换,它们之间的数据通信必须是二进制的,而不能是传统的文本格式。文本格式传递一个32位整数,两端的JavaScript脚本与显卡都要进行格式转化,将非常耗时。这时要是存在一种机制,可以像C语言那样,直接操作字节,将4个字节的32位整数,以二进制形式原封不动地送入显卡,脚本的性能就会大幅提升。
- 兼容性相关
- 其中MediaRecorder 和 MediaStream 都是处于Working Draft阶段的API,所以并不是所有浏览器都支持,Chrome (49.0+)和 FireFox(25.0+) 支持的比较好,详情可以点击前面链接进去,拉到页面最底下查看兼容性。