JS MediaRecorder 录制 HTMLVideoElement.captureStream() 无法获取录制的媒体资源

774 阅读1分钟

MediaRecorder 录制 HTMLVideoElement.captureStream()

在开发有关视频的项目时经常会有录制视频的需求,js提供了MediaRecorder对象用于支持录制视频功能。在正常情况下,MediaRecorder对象完全可以满足需求(webm duration问题可自行搜索解决)。注意录制视频需HTMLVideoElement元素在播放状态(谷歌浏览器默认开启硬件加速模式,在老旧显卡上可能导致录制的视频编解码失败,录制的视频文件只有音频没有视频,视频全部黑屏,可以尝试关闭硬件加速模式):

let recorder

function recorderStart(video: HTMLVideoElement) : void {
  const stream = video.captureStream()
  recorder = new MediaRecorder(stream, { mimeType: 'video/webm;codecs=h264' })
  recorder.blobs = []
  recorder.ondataavailable = e => recorder.blobs(e.data)
  recorder.start(5000)
}

function recorderStop() {
  recorder.stop()
  const blob = new Blob(recorder.blobs, { type: 'video/mp4' })
  downloadBlob(blob, 'recorder.mp4')
}

function downloadBlob(blob, name) {
  const url = URL.createObjectURL(blob);
  const a = document.createElement("a");
  a.style.display = "none";
  a.href = url;
  a.download = name;
  document.body.append(a);
  a.click();
  setTimeout(() => {
    document.body.removeChild(a);
    window.URL.revokeObjectURL(url);
  }, 100);
}

无法获取录制的媒体资源

在HTMLVideoElement元素设置了muted = true 或 volume = 0 时,直接使用MediaRecorder录制HTMLVideoElement.captureStream()会出现无法获取录制媒体资源的情况(即ondataavailable事件不会触发),导致这一原因是因为HTMLVideoElement元素设置了禁音或音量设置为0,导致无法捕获音频轨道。解决方法为判断muted = true 或 volume = 0,则只录制视频轨道:

function recorderStart(video: HTMLVideoElement) : void {
  const rowStream = video.captureStream()
  let stream = new MediaStream()
  if (video.muted || !video.volume) {
    rowStream.getVideoTracks().map(track => stream.addTrack(track))
  } else {
    rowStream.getTracks().map(track => stream.addTrack(track))
  }
  recorder = new MediaRecorder(stream, { mimeType: 'video/webm;codecs=h264' })
  recorder.blobs = []
  recorder.ondataavailable = e => recorder.blobs(e.data)
  recorder.start(5000)
}

解决webm duration问题

使用EBML.js解析blob对象:

import { Decoder, tools, Reader } from './EBML.js'

function recorderStop() {
  recorder.stop()
  const blob = new Blob(recorder.blobs, { type: 'video/mp4' })
  dealVideoDuration(blob, 'recorder.mp4', downloadBlob)
}

function dealVideoDuration(inputBlob, name, callback) {
  const reader = new Reader()
  const decoder = new Decoder()
  const tool = tools
  
  const fileReader = new FileReader()
  fileReader.onload = e => {
    const ebmlElms = decoder.decode(e.target.result)
    ebmlElms.forEach(element => {
      reader.read(element)
    })
    reader.stop()
    const refinedMetadataBuf = tool.makeMetadataSeekable(
      reader.metadatas,
      reader.duration,
      reader.cues
    )
    const body = e.target.result.slice(reader.metadataSize)
    const newBlob = new Blob([refinedMetadataBuf, body], {
      type: 'video/webm'
    })
    callback(newBlob, name)
  }
  fileReader.readAsArrayBuffer(inputBlob)
}