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)
}