最近听了组内小伙伴分享的屏幕录制的相关内容-MediaRecorder
,然后成功的接入了某个内部项目中。
今天跟大家分享一下如何利用
MediaRecorder
,几行代码实现屏幕录制的功能,以及实际场景下可能遇到的问题。
MediaRecorder介绍
MediaRecorder
是 MediaStream Recording API 提供的用来进行媒体轻松录制的接口, 他需要通过调用 MediaRecorder()
构造方法进行实例化.
该接口对外暴露的方法和配置项也比较多,大家有兴趣的可以点这里查看
这里主要按照流程,来介绍一些常用的方法。
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
流,并生成一个临时路径,来供我们预览使用。
当我们保存的时候,我们需要做两件事:
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来替换,否则可能会引起视频前后顺序错乱的问题。