1.前言
之前项目中用Canvas+H5合并流实现录屏功能,但是因为视频流是一直保存到内存中,当你的合成的视频流越来越来,那么导致内存消耗越大(后面浏览器会卡死崩溃),所以必须做些优化,需要将一个大的视频切割成若干个小的视频,然后每个上传到ftp服务器!
2.思路
思路一
MediaRecorder使用start()方法,这个方法可以设置时间,比如start(1000)相当于把每隔个1000毫秒把媒体流存放到一个数组中,比如你需要200M,你可以去计算每隔1000毫秒添加进去的blob大小去计算,当blob的总的大小为200M就把这个视频上传,我之前是按这种方式去做的,但是后面发现有问题,这个分割出来的视频只有第一个视频能播放,后面的视频不能播放(无效视频),当时没发现啥问题,这个问题困扰了我一天,后来才发现原来是元数据的问题,元数据好比描述文件结构,那么可以理解一个文件里面需要元数据和实际的数据,在datavailable事件中data中获得的内容只是生成的整个文件的一部分. 第一个通常包含元数据和一些其他数据,但下一部分不包含元数据,这个就是问题所在,所以为什么只能播放第一个视频,后面的播放不了,因为后面的数据不包含元数据,所以这种方法果断放弃了!
思路二
需要生成多个独立文件,为此,意味着你需要在规定的间隔时间内(5000毫秒)生成一个新的MediaRecorder对象,这样生成出来的所有的文件都是单独独立的文件,包含元数据和实际的数据,项目中我就是用这种方法,大概测试了下1分钟大约需要30M,那么设置10分钟也就差不多300M,这个数据也不大,所以一般的内存300M足够了,下面看下我的代码核心部分
:
// 开始录制
isRecord() {
let record = this.$refs.record;
if (this.stopRecord) {
record.title = "停止录制";
let stream = this.mergeStream(),
//注意要判断浏览器对webm的支持情况,有些时候video格式不对,在ondataavailable监听的时候会拿不到data数据(data的size为空)
mime = MediaRecorder.isTypeSupported("video/webm; codecs=vp9")
? "video/webm; codecs=vp9"
: "video/webm";
//第一次建新的MediaRecorder对象
this.recorder = this.recordAndUpload(stream, mime, this.recordTime);
//后面每隔一分钟创建新的MediaRecorder对象
this.setIntervalTimer = setInterval(() => {
this.recorder = this.recordAndUpload(stream, mime, this.recordTime);
}, this.recordTime * 1000);
this.stopRecord = false;
this.$message({
message: "开始录制",
type: "success",
customClass: "message-alert",
});
} else {
record.title = "开始录制";
this.endCapture();
this.$message({
message: "录制完成",
type: "success",
customClass: "message-alert",
});
//取消动画
window.cancelAnimationFrame(this.animationFrame);
this.stopRecord = true;
this.recorder = null;
}
},
//每隔recordTime时间(60秒)录制一个视频
recordAndUpload(stream, mime, recordTime) {
let chunks = [],
that = this,
videoStartTime,
//创建新的MediaRecorder对象
recorder = new MediaRecorder(stream, {
mimeType: mime,
});
//当触发start或者stop都会执行这个方法
recorder.ondataavailable = function (e) {
chunks.push(e.data);
};
recorder.onstop = () => {
//因为设置了时间,所以文件的时间是确定的
if (that.setTimeoutTimer) {
that.uploadFile(chunks, recordTime * 1000);
} else {
//当手动点了停止,那么这个时间就不是确定的了,需要计算
that.uploadFile(chunks, Date.now() - videoStartTime);
}
};
//每隔recordTime时间(60秒)自动去触发停止stop录像事件
this.setTimeoutTimer = setTimeout(() => {
recorder.stop();
}, recordTime * 1000);
recorder.start();
videoStartTime = Date.now();
return recorder
},
//上传文件到服务,保存录像到数据库
uploadFile(chunks, recordTime) {
let blob = new Blob(chunks, {
type: "video/mp4",
}),
that = this;
//recordTime为文件的时间,fixWebmDuration方法为文件设置时间(可以快进,快退),第三个参数是一个回调函数
fixWebmDuration(blob, recordTime, (fixedBlob) => {
//下载MP4到本地
let fileName = `file_${new Date().getTime()}.mp4`,
file = new window.File([fixedBlob], fileName),
formData = new FormData();
let url = URL.createObjectURL(
new Blob([fixedBlob], { type: "video/mp4" })
),
aLink = document.createElement("a");
aLink.download = fileName;
aLink.href = url;
document.body.appendChild(aLink);
aLink.click();
aLink.remove();
window.URL.revokeObjectURL(url);
//上传到ftp服务器
formData.append("file", file);
that.axios
.post(
//接口
`/ftp/ftpUpload`,
formData,
{
headers: { "Content-Type": "multipart/form-data" },
}
)
.then((data) => {
//保存录像到数据库
let params = new URLSearchParams(); //装载post传值
params.append("cameraID", that.cameraIdChanged);
params.append("videoName", fileName);
params.append("videoUrl", fileName);
that.axios
.post("/video/addVideo", params)
.then((data) => {
console.log(data);
})
.catch((error) => {
that.$message.error("服务器异常");
console.log(error);
});
})
.catch((error) => {
that.$message.error("服务器异常");
console.log(error);
});
});
},
//结束录制
endCapture() {
//清除定时器
clearTimeout(this.setTimeoutTimer);
clearInterval(this.setIntervalTimer);
this.setTimeoutTimer = this.setIntervalTimer = null;
this.recorder.stop();
},
下面是效果图