前言
不久前写了一个视频聊天室的项目,想到一些有趣的功能需求想去实现,视频录制就是其中之一,话不多说,下面就开始介绍及实现。提前看效果
WebAPI
每次实现有趣的功能都要了解新的WebAPI,这次也不例外,接下来将逐步介绍需要哪些核心API来实现视频录制。
MediaRecorder
MediaRecorder
是 MediaStream Recording API 提供的用来进行媒体轻松录制的接口,他需要通过调用MediaRecorder()
构造方法进行实例化。
首先是使用MediaRecorder
来将MediaStream
(媒体流)转为Blob
,然后将Blob
数据保存到磁盘。
但是MediaStream
从哪来?这个要结合实际业务场景确定,在当前这个业务场景下,需要视频录制的是包含所有video
元素的div
容器,有什么手段可以将DOM元素转为媒体流呢?
HTMLCanvasElement.captureStream()
该
HTMLCanvasElement.captureStream()
方法返回的CanvasCaptureMediaStream
是一个实时视频捕获的画布。
HTMLCanvasElement.captureStream()
接口返回一个媒体流,实时捕获canvas
画布的内容,但我们需要的是从div
获取,如何做到?可以将div
容器实时转换为canvas
,这可以借助html2canvas
实现,由于html2canvas
比较全面,它考虑的事情更多,所以耗时相对较长,也就导致每一帧的间隔时间较长,略显卡顿,所以我就了解了下html2canvas
的实现原理,根据业务场景实现了个简易的html2canvas
。
js
function html2canvas(el) {
const rect = el.getBoundingClientRect();
const { width, height, left, top } = rect
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
fillCanvas(ctx, el, {
parentWidth: width,
parentHeight: height,
parentLeft: left,
parentTop: top,
});
return canvas;
}
function fillCanvas(ctx, el, options) {
const { parentWidth, parentHeight, parentLeft, parentTop } = options;
const rect = el.getBoundingClientRect();
const { width, height, left, top } = rect
if (left + width < parentLeft || top + height < parentTop || left > parentLeft + parentWidth || top > parentTop + parentHeight) {
next()
return
}
ctx.save();
const tag = el.tagName;
switch(tag) {
case 'IMG':
case 'SVG':
case 'VIDEO':
case 'CANVAS':
ctx.drawImage(el, left - parentLeft, top - parentTop, width, height);
return
default:
const { backgroundColor } = getComputedStyle(el)
ctx.fillStyle = backgroundColor;
ctx.fillRect(left - parentLeft, top - parentTop, width, height);
break
}
ctx.restore();
next()
function next() {
Array.prototype.forEach.call(el.children, (child) => {
fillCanvas(ctx, child, options)
})
}
}
实现
下面这段代码,接收一个DOM元素作为参数,创建一个canvas
用来创建并捕获媒体流,无限执行html2canvas
生成canvas
并填充给用来捕获媒体流的canvas
,将创建的MediaStream
传递给MediaRecorder
构造函数进行录制,监听MediaRecorder.ondataavailable
事件收集Blob
碎片,监听MediaRecorder.onstop
事件停止录制合并Blob
转为File
并保存。
js
function useVideoRecorder(el) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
let animationFrameId = null
animationFrameId = requestAnimationFrame(function createCanvas() {
animationFrameId = requestAnimationFrame(createCanvas)
const htmlCanvas = html2canvas(el)
const { width, height } = htmlCanvas
canvas.width = width
canvas.height = height
ctx.save()
ctx.drawImage(htmlCanvas, 0, 0, width, height)
ctx.restore()
})
const stream = canvas.captureStream(25);
const recorder = new MediaRecorder(stream);
let chunks = []
recorder.onstop = function() {
cancelAnimationFrame(animationFrameId)
const blob = new Blob(chunks, { type: 'video/mp4' });
saveFile(blobToFile(blob, '视频录制'))
chunks = []
}
recorder.ondataavailable = function(e) {
chunks.push(e.data);
}
recorder.start();
return recorder;
}
js
function blobToFile(blob, fileName) {
return new File([blob], fileName, { type: blob.type});
}
function saveFile(file) {
const a = document.createElement('a');
a.href = URL.createObjectURL(file);
a.download = file.name;
a.click();
URL.revokeObjectURL(a.href);
}
音频&背景
以上代码实现的录制并没有声音,而且当未给DOM元素设置background-color
时,录制的视频是没有背景色的(透明色),改造useVideoRecorder
实现添加录制音频以及视频背景色。
js
function useVideoRecorder(el, options) {
const { background, audioTracks } = options || {}
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
let animationFrameId = null
animationFrameId = requestAnimationFrame(function createCanvas() {
animationFrameId = requestAnimationFrame(createCanvas)
const htmlCanvas = html2canvas(el)
const { width, height } = htmlCanvas
canvas.width = width
canvas.height = height
ctx.save()
if (background) {
ctx.fillStyle = background
ctx.fillRect(0, 0, width, height)
}
ctx.drawImage(htmlCanvas, 0, 0, width, height)
ctx.restore()
})
const stream = canvas.captureStream(25);
if (audioTracks) {
audioTracks.forEach(t => stream.addTrack(t))
}
const recorder = new MediaRecorder(stream);
let chunks = []
recorder.onstop = function() {
cancelAnimationFrame(animationFrameId)
const blob = new Blob(chunks, { type: 'video/mp4' });
saveFile(blobToFile(blob, '视频录制'))
chunks = []
}
recorder.ondataavailable = function(e) {
chunks.push(e.data);
}
recorder.start();
return recorder;
}
useVideoRecorder
新增一个参数options
,包含background
、audioTracks
属性,分别是背景和音频轨道数组;- 再稍加改造,
background
也可以是一个图片,这样就可以添加图片作为背景了,因为业务场景中用不到,这里就不多描述了。
录制效果
最后
以上只介绍了实现视频录制的核心,至于封装组件及UI交互就不多描述了,这取决于你项目的UI设计。
扩展阅读
参考
- MDN
- MediaRecorder:developer.mozilla.org/en-US/docs/...
- HTMLCanvasElement:developer.mozilla.org/zh-CN/docs/...
未经作者授权 禁止转载