浏览器屏幕录制-MediaRecorder实战

4,170 阅读3分钟

最近听了组内小伙伴分享的屏幕录制的相关内容-MediaRecorder,然后成功的接入了某个内部项目中。

recorder.gif 今天跟大家分享一下如何利用MediaRecorder,几行代码实现屏幕录制的功能,以及实际场景下可能遇到的问题。

点我体验

MediaRecorder介绍

MediaRecorder 是 MediaStream Recording API 提供的用来进行媒体轻松录制的接口, 他需要通过调用 MediaRecorder() 构造方法进行实例化.

该接口对外暴露的方法和配置项也比较多,大家有兴趣的可以点这里查看

这里主要按照流程,来介绍一些常用的方法。

image.png

1、权限申请

// 获取用户屏幕录制的权限
const stream = await navigator.mediaDevices.getDisplayMedia({
      video: true
    })

2、录制视频类型确认

// 确认当前环境所支持的屏幕录制文件类型
const mime = MediaRecorder.isTypeSupported('video/webm; codecs=vp9') ? 'video/webm; codecs=vp9' : 'video/webm'

3、实例化MediaRecorder

// 需要用到步骤1stream流和和步骤2的mimeType
const mediaRecorder = new MediaRecorder(stream, {
    mimeType: mime
  })

3、事件监听

  • dataavailable 该事件在停止录制后触发(优先于onStop),可用于获取录制的媒体资源 (在事件的 data 属性中会提供一个可用的 Blob 对象)
  • stop 用来处理 stop 事件, 该事件会在媒体录制结束时、媒体流(MediaStream)结束,触发dataavailable后触发。
  // 用于存放录制的blob数据
  const chunks = []
  mediaRecorder.addEventListener('dataavailable', function (e) {
    chunks.push(e.data)
  })
  mediaRecorder.addEventListener('stop', () => {
    const blob = new Blob(chunks, {
      type: chunks[0].type
    })
    // 获取 可用的 url
    const url = URL.createObjectURL(blob)
    // 拿到临时录制文件路径后执行你的操作
    // ...
  }

4、触发录制行为

  mediaRecorder.start()

场景实战

本次应用场景是在一个内部的质量管理平台中,在提问题的步骤,通过屏幕录制来实现问题的描述,期间有遇到了一些常见的问题,跟大家分享下。

常规的富文本中粘贴图片的步骤,都是粘贴即上传,然后拿到一个url来显示,但是对于视频来说,录制完即上传,用户体验着实有点不好,并且存在资源浪费情况。

因此我们在保存的时候再去进行上传和替换。

上文提到,我们在onStop的回调中,可以拿到一个blob流,并生成一个临时路径,来供我们预览使用。

image.png

当我们保存的时候,我们需要做两件事:

1、把富文本内容中video标签src指向临时文件上传至我们自己的服务器,拿到一个真实url

2、富文本描述中用真实的url替换掉对应的临时路径。

代码实现

export function backTraceNode(el) {
  if (!el?.children?.length) {
    return [el]
  }
  return [el, ...Array.from(el.children).map(backTraceNode)].flat()
}
// 1、从html字符串中提取出bolb文件临时路径
const getVideoListByHtml = (html) => {
  const dom = document.createElement('div')
  dom.innerHTML = html
  return backTraceNode(dom)
    .filter((item) => item.nodeName === 'VIDEO')
    .map((v) => v.src)
}

// 2、根据临时路径,生成相应的File
const getFileFromBlobUrl = async (blobUrlList) => {
  const pList = list.map((url) => {
    return fetch(url)
  })
  const data = await Promise.all(pList)
  const blobList = data.map((v) => v.blob())
  const res = await Promise.all(blobList)
  return res.map(blob=>{
    return new File([blob], 'video.webm', { type: 'video/webm' })
  })
}

// 3、文件上传并替换对应的url
const replaceVideoUrl = (html, hashUrlMapList) => {
  const dom = document.createElement('div')
  dom.innerHTML = html
  const videoList = backTraceNode(dom).filter((item) => item.nodeName === 'VIDEO')
  videoList.forEach((el) => {
    el.src = 'https:' + findUrlByHash(el.src, hashUrlMapList)
  })
  return dom.innerHTML
}

注意事项

  • 兼容性不友好,目前仅仅在Chrome中能够满足使用场景。其他浏览器有的不支持该API,有的不支持录制屏幕文件的类型。不过可以提前获取当前浏览器的支持程度,不满足的话,在交互层面设计一下就好,比如不支持该功能的话,隐藏掉或者给个提示语。
const isSupportRecorder = () => {
  const types = ['video/webm; codecs=vp9', 'video/webm']
  const isTypeSupport = types.some((v) => MediaRecorder.isTypeSupported(v))
  const isApiSupport = Boolean(navigator.mediaDevices)
  return isApiSupport && isTypeSupport
 }
  • 出于安全性考虑,浏览器层面申请权限(navigator.mediaDevices),仅支持https或者localhost
  • 富文本内容替换的时候如果存在多个临时文件,需要根据对应的名称hash来替换,否则可能会引起视频前后顺序错乱的问题。

参考文献