前端实现音视频录制功能

前言

  不久前写了一个视频聊天室的项目,不断地冒出有趣的功能需求想法想去实现,视频录制就是其中之一,话不多说,下面就开始介绍及实现。提前看效果

WebAPI

  每次实现有趣的功能都要了解新的WebAPI,这次也不例外,接下来将逐步介绍需要哪些核心API来实现视频录制。

MediaRecorder

MediaRecorderMediaStream 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

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并保存。

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;
}
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实现添加录制音频以及视频背景色。

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,包含backgroundaudioTracks属性,分别是背景和音频轨道数组;
  • 再稍加改造,background也可以是一个图片,这样就可以添加图片作为背景了,因为业务场景中用不到,这里就不多描述了。

录制效果

20231104_145350.gif

视频录制.gif

点击看完整效果 在线体验

最后

  以上只介绍了实现视频录制的核心,至于封装组件及UI交互就不多描述了,这取决于你项目的UI设计。

扩展阅读

参考

未经作者授权 禁止转载

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小米爱老鼠

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值