代码基于vue3,当前chrome版本122.0.6261.131。未验证其他浏览器兼容性。拷贝到工程后进行简单调整即可复用。
<template>
<div>
<el-dialog title="视频录制" :model-value="props.isVideoRecordDialogShow" width="870" :before-close="handleClose">
<div v-loading="isDetecting">
<div class="video-area" v-loading="isLoading || isDetecting" :element-loading-text="isLoading ? '正在启用摄像头' : '正在进行人脸检测'">
<div class="left-content">
<video ref="videoElement" class="left-video"></video>
<div class="video-timer-area" v-if="!recordedVideoURL && recording">
<span class="red-dot"></span>
<div class="timer-text"> {{ `${h}:${m}:${s}` }}</div>
</div>
<div class="center">
<el-button type="primary" style="width: 140px" @click="startRecording" :disabled="isLoading">{{ btntext }}</el-button>
</div>
</div>
<div class="right-content">
<div v-if="recordedVideoURL && !recording">
<video controls class="right-video">
<source :src="recordedVideoURL" type="video/mp4" />
</video>
</div>
<div v-if="!recordedVideoURL || recording">
<el-empty class="right-empty" description="请先在左侧录制视频哦~" :image="emptyImg" />
</div>
<div class="center">
<el-button type="primary" style="width: 140px" :disabled="recording" @click="toUse">使用</el-button>
</div>
</div>
</div>
</div>
</el-dialog>
</div>
</template>
<script setup>
import { computed, ref, onMounted, watch } from 'vue';
import { ElDialog, ElButton, ElMessage, vLoading } from 'element-plus';
import { DetectFace } from '@/api/index.js';
import emptyImg from '@/assets/icons/svg/video.svg';
const props = defineProps({
isVideoRecordDialogShow: {
type: Boolean,
default: false,
},
});
let btntext = computed(() => (recording.value ? '停止录制' : '开始录制'));
let isLoading = ref(false);
const emit = defineEmits(['closeVideoRecordDialog', 'setRecordVideo']);
const handleClose = () => {
// 释放摄像头
videoStream.value && videoStream.value.getTracks().forEach((track) => track.stop());
emit('closeVideoRecordDialog');
};
const videoStream = ref(null);
const mediaRecorder = ref(null);
const recordedChunks = ref([]);
const recordedVideoURL = ref(null);
const recording = ref(false);
const snapshotURL = ref(null);
const videoElement = ref(null);
const canvasElement = ref(null);
const recorderFile = ref(null);
onMounted(async () => {
isLoading.value = true;
videoStream.value = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
videoElement.value.srcObject = videoStream.value;
// 避免回音
videoElement.value.volume = 0;
videoElement.value.play();
isLoading.value = false;
});
const startRecording = async () => {
recordedVideoURL.value = null;
try {
// 如果已经在录制中,就停止录制
if (mediaRecorder.value && recording.value) {
stopRecording();
return;
}
// 开始计时
startTimer();
mediaRecorder.value = new MediaRecorder(videoStream.value);
recordedChunks.value = [];
mediaRecorder.value.ondataavailable = (event) => {
if (event.data.size > 0) {
recordedChunks.value.push(event.data);
}
};
mediaRecorder.value.onstop = () => {
recorderFile.value = new Blob(recordedChunks.value, { type: 'video/mp4' });
recordedVideoURL.value = URL.createObjectURL(recorderFile.value);
// 结束计时
stopTimer();
};
mediaRecorder.value.start();
recording.value = true;
} catch (error) {
// console.error('Error accessing media devices: ', error);
}
};
const stopRecording = () => {
if (mediaRecorder.value && recording.value) {
mediaRecorder.value.stop();
// videoStream.value.getTracks().forEach(track => track.stop());
recording.value = false;
}
};
let isDetecting = ref(false);
const toUse = () => {
if (!recordedVideoURL.value) {
ElMessage.warning('请先录制视频');
return;
}
let files = new File([recorderFile.value], 'recordedVideo.mp4', { type: 'video/mp4' });
// 对视频进行人脸检测
const formData = new FormData();
formData.append('video', files);
isDetecting.value = true;
DetectFace(formData)
.then((res) => {
// 人脸检测通过,可以进行使用
emit('setRecordVideo', files, recordedVideoURL.value);
handleClose();
})
.catch((e) => {
// ElMessage.error('视频中未检测到人脸,请重新录制');
})
.finally(() => {
isDetecting.value = false;
});
};
// 定义一个计时器, 以hh:mm:ss格式显示时间
let timer = ref(null);
let h = ref('00');
let m = ref('00');
let s = ref('00');
const startTimer = () => {
let time = 0;
timer.value = setInterval(() => {
time++;
h.value = Math.floor(time / 3600)
.toString()
.padStart(2, '0');
m.value = Math.floor((time % 3600) / 60)
.toString()
.padStart(2, '0');
s.value = (time % 60).toString().padStart(2, '0');
}, 1000);
};
// 停止计时
const stopTimer = () => {
clearInterval(timer.value);
h.value = '00';
m.value = '00';
s.value = '00';
};
// 当录制时长超过5s时,自动停止录制
watch(
() => s.value,
(newV) => {
if (newV === '20') {
ElMessage.warning('录制时长到达20s,自动停止录制');
stopRecording();
}
},
{ immediate: true },
);
</script>
<style lang="less" scoped>
.show-record {
visibility: visible;
}
.hidden-record {
visibility: hidden;
}
.video-area {
display: flex;
justify-content: center;
align-items: center;
.left-content {
position: relative;
}
.right-content {
margin-left: 20px;
}
}
.left-video {
width: 400px;
height: 300px;
border-radius: @borderRadius;
margin-bottom: 10px;
}
.right-video {
width: 400px;
height: 300px;
border-radius: @borderRadius;
margin-bottom: 10px;
}
.right-empty {
width: 400px;
height: 300px;
margin-bottom: 15px;
background-color: #e2eefa;
border-radius: @borderRadius;
}
.video-timer-area {
position: absolute;
top: 10px;
right: 10px;
.timer-text {
width: 70px;
display: inline-block;
}
}
</style>