<template>
|
<div ref="cameraBox" class="camera_box" :style="cameraStyle" v-show="isCameraOpening">
|
<video ref="videoEl" :width="videoWidth" :height="videoHeight" autoplay></video>
|
<canvas ref="canvasEl" :width="videoWidth" :height="videoHeight" style="display:none;"></canvas>
|
|
<el-row justify="center" class="btn_box">
|
<el-button plain text @click="closeCamera()">
|
<Icon icon="material-symbols:cancel" width="28" height="28" style="color: #FA5252" />
|
</el-button>
|
<el-button plain text @click="startRecordImage()">
|
<Icon icon="ant-design:camera-filled" width="28" height="28" style="color: #FA5252" />
|
</el-button>
|
</el-row>
|
</div>
|
</template>
|
|
<script>
|
import { uploadByBase64 } from '@/utils/tool.js'
|
export default {
|
data () {
|
return {
|
recorderOptions: { // recorder 配置项
|
mimeType: 'video/webm;codecs=vp8,opus'
|
},
|
isCameraOpening: false,
|
}
|
},
|
computed: {
|
videoWidth: function () {
|
return 400
|
},
|
videoHeight: function(){
|
return (this.videoWidth * 3) / 4
|
},
|
cameraStyle: function(){
|
return {
|
width: `${this.videoWidth}px`,
|
height: `${this.videoHeight}px`
|
}
|
},
|
},
|
beforeUnmount(){
|
this.closeCamera()
|
},
|
mounted () {
|
this.initNavigatorMedia()
|
this.openCamera()
|
},
|
methods: {
|
initNavigatorMedia: function(){
|
// 获取媒体属性,旧版本浏览器可能不支持mediaDevices,设置一个空对象
|
if (navigator.mediaDevices === undefined) {
|
navigator.mediaDevices = {};
|
}
|
// 使用getUserMedia,因为它会覆盖现有的属性。
|
// 这里,如果缺少getUserMedia属性,就添加它。
|
if (navigator.mediaDevices.getUserMedia === undefined) {
|
this.initGetUserMedia()
|
}
|
},
|
initGetUserMedia: function(){
|
navigator.mediaDevices.getUserMedia = (constraints) => {
|
// 首先获取现存的getUserMedia(如果存在)
|
let getUserMedia =
|
navigator.webkitGetUserMedia ||
|
navigator.mozGetUserMedia ||
|
navigator.getUserMedia;
|
// 有些浏览器不支持,会返回错误信息
|
// 保持接口一致
|
if (!getUserMedia) {//不存在则报错
|
return Promise.reject(
|
new Error("getUserMedia is not implemented in this browser")
|
);
|
}
|
// 否则,使用Promise将调用包装到旧的navigator.getUserMedia
|
return new Promise((resolve, reject) => {
|
getUserMedia.call(navigator, constraints, resolve, reject);
|
});
|
};
|
},
|
openCamera: function(){ // 打开摄像头
|
navigator.mediaDevices.getUserMedia({
|
audio: false,
|
video: {
|
width: this.videoWidth,
|
height: this.videoHeight,
|
}
|
}).then((stream) => {
|
this.isCameraOpening = true
|
this.mediaStream = stream
|
this.initVideoSrcObject()
|
}).catch(err => {
|
console.log(`${err.name}:${err.message}`);
|
this.closeCamera()
|
this.mediaErrorHandler(err)
|
});
|
},
|
closeCamera() { // 关闭摄像头
|
if (!this.isCameraOpening) return false
|
|
this.isCameraOpening = false
|
|
if (this.mediaStream) {
|
let tracks = this.mediaStream.getTracks()
|
tracks.forEach(track => track.stop());
|
}
|
|
if (this.mediaRecorder) this.mediaRecorder.stop();
|
this.$emit('close')
|
},
|
initVideoSrcObject: function(){ // 初始化 视频录制 的 video srcObject
|
// 旧的浏览器可能没有srcObject
|
if ("srcObject" in this.$refs.videoEl) {
|
this.$refs.videoEl.srcObject = this.mediaStream;
|
this.initVideoMoveListener()
|
} else {
|
console.log('浏览器不支持')
|
}
|
},
|
initVideoMoveListener: function(){ // 初始化 视频窗口 的 移动事件
|
let eventState = {}
|
let startMoving = (e) => { // 开始移动的回调
|
e.preventDefault()
|
e.stopPropagation()
|
eventState = {
|
left: this.$refs.cameraBox.offsetLeft,
|
top: this.$refs.cameraBox.offsetTop,
|
x: e.clientX,
|
y: e.clientY
|
}
|
document.addEventListener('mousemove', moving)
|
document.addEventListener('mouseup', endMoving)
|
}
|
let moving = (e) => { // 移动的回调
|
e.preventDefault()
|
e.stopPropagation()
|
|
let margin = 10
|
let left = e.clientX - (eventState.x - eventState.left)
|
let top = e.clientY - (eventState.y - eventState.top)
|
let maxLeft = document.documentElement.clientWidth - this.videoWidth - margin
|
let maxTop = document.documentElement.clientHeight - this.videoHeight - margin
|
|
// 限制移动截图区域不能移到可视区域外
|
if (left < 10) left = margin
|
if (top < 10) top = margin
|
if (left > maxLeft) left = maxLeft
|
if (top > maxTop) top = maxTop
|
|
this.$refs.cameraBox.style.left = `${left}px`
|
this.$refs.cameraBox.style.top = `${top}px`
|
}
|
let endMoving = (e) => { // 结束移动的回调
|
e.preventDefault()
|
document.removeEventListener('mousemove', moving)
|
document.removeEventListener('mouseup', endMoving)
|
}
|
this.$refs.cameraBox.addEventListener('mousedown', startMoving)
|
},
|
initUploadChunk: async function(blobs){ // 初始化上传任务
|
let tmpBlob = new Blob(blobs, { 'type': this.recorderOptions.mimeType });
|
// let file = new File(blobs, fileName, { type: 'video/webm' });
|
let file = null
|
try {
|
// 解决 Webm 视频 duration 问题
|
file = await fixWebmMetaInfo(tmpBlob)
|
} catch (error) {
|
file = tmpBlob
|
console.log(error)
|
}
|
|
let path = await uploadChunk(file, '学习视频', 'webm')
|
if (!path) return false
|
|
if (this.recordingFlag || this.recordCameraFlag) {
|
this.$store.commit('authCamera/recordVideoUrl', path)
|
this.$store.commit('authCamera/faceCaptureAll', { flag: false, imgList: this.faceCaptureAllImgList })
|
} else {
|
this.$store.commit('authCamera/biopsyVideoUrl', path)
|
}
|
},
|
startRecordImage() { // 开始录制图像(拍照)
|
if (!this.$refs.canvasEl) return false
|
|
this.drawImage()
|
// this.closeCamera()
|
},
|
drawImage: function(){ // 绘制图片
|
this.$refs.canvasEl.getContext('2d').drawImage(
|
this.$refs.videoEl,
|
0,
|
0,
|
this.videoWidth,
|
this.videoHeight
|
);
|
this.uploadBase64()
|
},
|
async uploadBase64(){ // 上传图片
|
let base64 = this.$refs.canvasEl.toDataURL("image/png", 1);
|
// const url = await uploadByBase64(base64, '核验照片')
|
if (base64) {
|
this.$emit('handlerSuccess', base64)
|
this.closeCamera()
|
} else {
|
this.$message.error('拍摄失败')
|
}
|
},
|
uploadErrorHandler: function (evt) { // 上传失败回调
|
console.log(JSON.stringify(evt.target))
|
},
|
mediaErrorHandler: function(){ // 开启摄像头或录制屏幕异常处理
|
this.$message.error('当前浏览器不支持,请更换浏览器')
|
},
|
}
|
};
|
</script>
|
|
<style lang="scss" scoped>
|
.camera_box{
|
position: fixed;
|
bottom: 10px;
|
right: 10px;
|
z-index: 300;
|
}
|
.status_box{
|
position: absolute;
|
left: 0;
|
right: 0;
|
padding: 6px;
|
margin-top: -38px;
|
font-size: 0.8rem;
|
color: white;
|
background: rgba(0,0,0,0.4);
|
.normal > span,
|
.abnormal > span{
|
width: 6px;
|
height: 6px;
|
border-radius: 28px;
|
display: inline-block;
|
margin-right: 5px;
|
}
|
.normal > span{
|
background-color: #00E63C;
|
}
|
.abnormal > span{
|
background-color: #FF3232;
|
}
|
}
|
.btn_box{
|
background: rgba(0,0,0,0.4);
|
margin-top: -63px;
|
padding-top: 10px;
|
padding-bottom: 7px;
|
position: absolute;
|
left: 0;
|
right: 0;
|
}
|
.duration{
|
position: absolute;
|
top: 0;
|
right: 0;
|
color: white;
|
text-shadow: 1px 1px 1px black, 1px -1px 1px black, -1px -1px 1px black, -1px 1px 1px black;
|
}
|
</style>
|