lostvita / blog

60 stars 47 forks source link

给Canvas录制一个视频 #22

Open lostvita opened 4 years ago

lostvita commented 4 years ago

前言

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

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

本文介绍的内容包括:

其中, 媒体对象的来源有两类, 一类是当前网页上的媒体, 可以是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

常用属性与方法

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

接下来主要介绍HTMLCanvasElement的captureStream:通过录屏实现对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 () {
  var a = document.createElement('a')
  a.href = this.videoUrl
  a.download = 'record-canvas.mp4'
  a.style.display = 'none'
  document.body.appendChild(a)
  a.click()
}

以上,戳Demo