是这样的,现在有一个 canvas,我需要录制 canvas 上的变化。
有一个立即可以想到的方案,定时获取 canvas 的图片数据(getImageData),然后通过某些库,比如 ffmpeg.wasm 合成视频。这个方案确实是可行的,虽然我没实践,之所以不实践,是因为暂时不想引入 ffmpeg.wasm 这个库,毕竟这个库挺大的。
除了 ffmpeg,还可以通过 canvas.captureStream 和 MediaRecorder 实现视频录制。
startVideoRecord() { if (this.konvaInstance) { const canvas = this.konvaInstance.getStage().content .children[0] as HTMLCanvasElement const stream = canvas.captureStream() const recorder = new MediaRecorder(stream, { mimeType: 'video/webm', }) this.videoData = [] this.videoRecorder = undefined this.videoBlob = undefined this.videoStartTime = Date.now() recorder.ondataavailable = (event) => { if (event.data && event.data.size) { console.log(event.data) this.videoData.push(event.data) } } recorder.onstop = async () => { this.videoBlob = new Blob(this.videoData, { type: 'video/webm' }) } recorder.onerror = (e) => { console.error(e) } this.videoRecorder = recorder recorder.start() return recorder } }
录制的代码差不多就这些,很简单,需要结束的时候只需要调用 recorder.stop() 就可以了。但这样得到的视频文件是有问题的,比如没有 duration,播放器播放的时候无法正确显示时间,也没法拖动进度条。这是因为元数据在录制结束之前就写进数据流的开始位置了,结束之后浏览器也没有修正,所以这实际上是浏览器的 bug,但浏览器开发人员并不准备修复。https://stackoverflow.com/questions/50586612/seeking-is-not-working-in-recorded-video-with-mediarecorder-api/55054346#55054346
关于这个问题的第三方库也是有的,比如 webm-duration-fix 之类的,这种库不止一个,我用了两个库,引入阶段报错,一个修复阶段报错,后来我是用的 ts-ebml 这个库。
/** * 修复 webm 视频没有 duration 的问题 * 但修复后的视频还是有问题,应该是浏览器录制的数据就有问题,暂时无解 * https://stackoverflow.com/questions/50586612/seeking-is-not-working-in-recorded-video-with-mediarecorder-api/55054346#55054346 * @param blob * @returns */ export async function getSeekableBlobAsync(blob: Blob) { const { Buffer } = await import('buffer') // https://github.com/legokichi/ts-ebml/issues/25 // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore window!.Buffer = Buffer const EBML = await import('ts-ebml') const arrayBuffer = await blob.arrayBuffer() const reader = new EBML.Reader() const decoder = new EBML.Decoder() const tools = EBML.tools const ebmlElms = decoder.decode(arrayBuffer) ebmlElms.forEach((element) => { reader.read(element) }) reader.stop() const refinedMetadataBuf = tools.makeMetadataSeekable( reader.metadatas, reader.duration, reader.cues ) const body = arrayBuffer.slice(reader.metadataSize) const newBlob = new Blob([refinedMetadataBuf, body], { type: blob.type, }) return newBlob }
这个库使用过程中有两个问题,一个是 _tools is undefined 这个报错。解决方案是配置 alias。https://github.com/legokichi/ts-ebml/issues/48
另一个问题是 Buffer 不存在,解决方案是引入 Buffer,挂到 window 上。https://github.com/legokichi/ts-ebml/issues/25
这样一来就可以生成 duration 正确的视频了,但是,在播放的时候还是有点问题,比如内容不对。我录制的一个视频后半段内容就没变过,但实际上 canvas 一直在变。
如此一来,我放弃了该方案。这个功能也直接废掉吧,反正内容简单,我也不是一定要用视频,直接将几帧图片合成 GIF 就可以了,虽然说合成速度比较慢(❁´◡`❁)。
等哪天我把那个 GIF 库优化优化。