给Canvas录制一个视频

4,247 阅读3分钟

前言

请使用该Token:1563266971025访问本文Demo,或者点击这里访问。

说起web前端录像📹网页内容,,我们的反应可能是先给页面截屏,再将一帧帧的图像合成视频,截屏的操作则是通过html 2 canvas的方式实现。 如果想要合成流畅的视频,那么就必须截出密集帧图像,这无疑是很消耗性能的。如果有一个原生的API,支持对网页录像就好了!!!

本文介绍的内容包括:

  • MediaRecoder录制媒体流
  • Canvas绘制动画
  • 导出录像视频

MediaRecoder

MediaRecoder提供了一系列用于录制媒体(视频音频等)的接口,通过调用其构造函数可以创建一个MediaRecoder实例对象,在new实例对象时传入媒体对象参数,则可以实现对该媒体对象的录音/像。

其中,媒体对象的来源有两类,一类是当前网页上的媒体,可以是video、audio或者canvas元素;另一类是用户的桌面设备,通过navigator.mediaDevices.getUserMedia()可以取得用户的桌面录像录音权限,这一类技术的应用场景有web RTC。这里讨论的是第一类:针对网页内容的录像.

举个例子: 对video元素录像

const mediaStream = $video.captureStream(10) // 获取媒体元素的媒体流对象
mediaRecord = new MediaRecorder(mediaStream, { // 实例化录制对象
    videoBitsPerSecond: 8500000, // 比特率
    mimeType: 'video/mp4' // 录屏媒体流文件类型
})
mediaRecord.ondataavailable = (e) => { // 接收媒体流数据: Blob类型
    this.chunks.add(e.data)
}

以上仅仅是初始化一个媒体录像实例对象, 我们还需要手动启动录制或停止:

mediaRecord.start()  // 开始录制
mediaRecord.stop() // 停止录制

实例化过程的mimeType参数在使用上有一定的限制,需要预先检测当前浏览器是否支持该类型:

MediaRecorder.isTypeSupported('video/mp4') // true or false

常用属性与方法

  • MediaRecorder.mimeType[只读]:返回实例化过程设置的媒体文件类型。笔者设备测试,如果未设置,默认返回:Chrome -> video/webm;codecs=vp8,Firefox -> video/webm
  • MediaRecorder.state[只读]:返回当前MediaRecorder实例的工作状态,可选值有:inactiverecordingpaused
  • MediaRecorder.stream[只读]:返回当前媒体流,亦即实例化过程传入的媒体流对象
  • MediaRecorder.ignoreMutedMedia:是否静音模式录制;
  • MediaRecorder.start(timeslice):开始录制。timeslice参数可选,表示以该持续时间切片媒体数据;
  • MediaRecorder.pause():暂停录制;
  • MediaRecorder.resume():继续录制;
  • MediaRecorder.stop():停止录制。

事件处理器

  • MediaRecoder.ondataavailable:在该事件中接收媒体录制数据
const chunks = new Set()
mediaRecord.ondataavailable = (e) => {
    chunks.add(e.data)
}

如果在start()中设置了timeslice,那么该事件每隔timeslice时间会触发一次;否则只在录制结束时触发。

  • MediaRecoder.onstart:在调用start()时触发,类似的还有onpauseonresumeonstop
  • MediaRecoder.onerror:捕获错误异常。

captureStream

在前面的举例中我们用到了captureStream这个方法,该方法用于获取媒体流。在H5里面,有两个对象实现了这个API,分别是HTMLMediaElementHTMLCanvasElement。前面示例中我们使用$video.captureStream(),就是应用了HTMLMediaElement元素的接口(audio元素也支持),canvas元素则可以通过HTMLCanvasElement访问该接口。

HTMLMediaElement提供这个接口有什么作用呢?我目前想到的一个应用场景是媒体资源的下载。如果我们需要下载某个视频,通常的做法可能是需要后端提供一个接口返回二进制内容,有了这个API,前端就可以自己实现下载了。

接下来主要介绍HTMLCanvasElementcaptureStream:通过录屏实现对canvas内容的监控。

canvas绘制圆周运动

Step 1:来,左边画个圆

// 参数分别为画布上下文对象、横纵坐标、填充颜色
function drawCircle (ctx, x=100, y=100, color) {
    const radius = 20
    ctx.clearRect(0, 0, 600, 300)
    ctx.beginPath()
    ctx.arc(x, y, radius, 0, 2 * Math.PI, false)
    ctx.lineWidth = 0
    ctx.closePath()
    ctx.fillStyle = color
    ctx.fill()
    ctx.strokeStyle = color
    ctx.stroke()
}

Step 2:右边铺块布

// 初始化画布
function initCanvas () {
  const $circle = $('#circle')
  $circle.width = 600
  $circle.height = 300
  const ctx = $circle.getContext('2d')
  drawCircle(ctx, x, y, color)
}

Step 3:诶,加个动画

canvas动画的核心就是:重复绘制!

function initCanvas () {
  // ...省略初始化
  const cTimer = new Timer(5000) // 动画周期5s
  let color = colors[parseInt(Math.random() * 6)] // 随机颜色
  cTimer.onProgress = (p) => {
      const x = Math.cos(p * 2 * Math.PI) * 50 + 300
      const y = Math.sin(p * 2 * Math.PI) * 50 + 150
      if(x < 300 && y > 150 || x > 300 && y < 150) color = colors[parseInt(Math.random() * 6)] 
      drawCircle(ctx, x, y, color) // 5s内不断画圆
  }
  cTimer.onFinished = () => {
      cTimer.start() // 动画结束之后重新开始
  }
  cTimer.start() // 动起来
}

录频并下载

Step 1:初始化录屏实例

const chunks = new Set()
function createRecord () {
  const mediaStream = $canvas.captureStream(10) // 设置帧频率(FPS)
  mediaRecord = new MediaRecorder(mediaStream, {
      videoBitsPerSecond: 8500000
  })
  mediaRecord.ondataavailable = (e) => { // 接收数据
      chunks.add(e.data)
  }
}

Step 2:控制录屏

mediaRecord.start()  // 开始录屏
this.mediaRecord.stop() // 结束录屏

Step 3:下载录屏

const videoBlob = new Blob(chunks, { 'type' : 'video/mp4' })
videoUrl = window.URL.createObjectURL(videoBlob)

chunks是元素为Blob类型的列表,可能只有一个元素,可能是多个元素,取决于start()中是否设置了timeslice,将这些片段blob重新封装为一个完整的blob数据,并且可以指定数据类型。然后根据blob数据生成一条blob URL。关于blobcreateObjectURL的介绍可以移步这里

最后,借助a标签下载视频:

function download (videoUrl) {
  var a = document.createElement('a')
  a.href = videoUrl
  a.download = 'record-canvas.mp4'
  a.style.display = 'none'
  document.body.appendChild(a)
  a.click()
}

以上,戳Demo

参考