目录
1、需求背景
当前vue图片组件有上传图片组件,截图组件,预览图片组件,但是没有从电脑摄像头或手机摄像头获取现场照片的能力,为了实现人脸注册和人脸登陆,这篇博文就来实现这个功能。
实现网页调用计算机摄像头,需要使用https协议。
2、实现步骤
1)开发环境开启https
在vue-config.js文件中找到devServer配置,新增 https:true选项即可以开启。
devServer: {
host: '0.0.0.0',
port: port,
open: true,
https: true,
proxy: {
'/admin-api/': { // 匹配以//admin-api开头的才生效
target: "http://192.168.1.222:48080", // 实际请求主机,端口,协议
changeOrigin: true, // 如果接口跨域这里就要这个参数配置
secure:false, // 如果实际请求地址是https接口,则设置为true
pathRewrite: {
'^/admin-api/': '/admin-api/' // 将本地 ~/admin-api/ping 请求转发为请求 http://192.168.1.222:48080/admin-api/ping
}
},
},
disableHostCheck: true
},
2)摄像头初始化(默认优先前置摄像头)
要调用网页摄像头,除了开启https服务,还需要对浏览器进行适配,不同的浏览器获取UserMedia方法略有不同,initUserMedia方法实现初始化工作,代码如下:
initUserMedia(){
// 旧版本浏览器可能根本不支持mediaDevices,我们首先设置一个空对象
if (navigator.mediaDevices === undefined) {
navigator.mediaDevices = {}
}
// 一些浏览器实现了部分mediaDevices,我们不能只分配一个对象
// 使用getUserMedia,因为它会覆盖现有的属性。
// 这里,如果缺少getUserMedia属性,就添加它。
if (navigator.mediaDevices.getUserMedia === undefined) {
navigator.mediaDevices.getUserMedia = function (constraints) {
// 首先获取现存的getUserMedia(如果存在)
var 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(function (resolve, reject) {
getUserMedia.call(navigator, constraints, resolve, reject)
})
}
}
},
摄像头初始化参数参考,前置摄像头facingMode: "user",
constraints: {
audio: false, // 是否打开麦克风
video: {
width: 400,
height: 400,
frameRate: { ideal: 24, max: 24 }, // 视频帧频,不同设备的性能不一样,不宜太高
transform: 'scaleX(-1)',
facingMode: "user", // 优先打开前置摄像头
}
},
3)打开,切换,关闭摄像头
// 调用权限(打开摄像头功能)
async openCamera() {
this.closeCamera();
this.openVideo = true;
this.loadingVideo = true;
this.video = this.$refs.video
this.initUserMedia()
if(!this.constraints.video.deviceId){
var devices = await navigator.mediaDevices.enumerateDevices()
this.videoList = devices.filter(d=>d.kind=='videoinput');
if(this.videoList.length<1) {
this.$modal.msgError("没有检测到视频设备")
return;
}
// console.log(this.videoList);
var deviceId = this.videoList[0].deviceId;
if(deviceId) {
this.deviceId = deviceId;
this.constraints.video.deviceId={ exact: deviceId}
}
}
navigator.mediaDevices.getUserMedia(this.constraints).then((stream) =>{
this.stream = stream
// 旧的浏览器可能没有srcObject
if ('srcObject' in this.video) {
// console.log("srcObject")
this.video.srcObject = this.stream
this.$emit("ready", this.stream)
} else {
// 避免在新的浏览器中使用它,因为它正在被弃用。
this.video.src = window.URL.createObjectURL(this.stream )
}
this.video.onloadedmetadata = (e)=> {
this.video.play();
this.loadingVideo = false;
}
this.$emit("ready", this.stream)
}).catch(err => {
console.log(err)
this.openVideo = false;
this.loadingVideo = false;
})
},
其中,这一段实现多摄像头探测
if(!this.constraints.video.deviceId){
var devices = await navigator.mediaDevices.enumerateDevices()
this.videoList = devices.filter(d=>d.kind=='videoinput');
if(this.videoList.length<1) {
this.$modal.msgError("没有检测到视频设备")
return;
}
// console.log(this.videoList);
var deviceId = this.videoList[0].deviceId;
if(deviceId) {
this.deviceId = deviceId;
this.constraints.video.deviceId={ exact: deviceId}
}
}
摄像头切换
changeVideo(index){
var deviceId = this.videoList[index].deviceId;
if(this.deviceId == deviceId) return;
this.deviceId = deviceId;
this.closeCamera();
this.constraints.video.deviceId={ exact: deviceId}
this.openCamera();
},
关闭摄像头
// 关闭摄像头
closeCamera() {
if(this.video && this.video.srcObject){
this.video.srcObject.getTracks().forEach(track => track.stop());
this.video.srcObject = null
}
this.stream = undefined;
},
4)拍照
// 绘制图片(拍照功能)
takePhoto() {
// console.log("takePhoto")
var canvas = this.$refs.canvas
var canvasContext = canvas.getContext('2d')
// 点击,canvas画图
if(this.video){
canvasContext.drawImage(this.video, 0, 0, this.width, this.height)
// 获取图片base64链接
this.image = canvas.toDataURL('image/png')
this.$emit("capture", this.image)
}
},
5)图片上传及完整源码
新增src\components\VideoRecorder\VideoBox.vue文件
<template>
<div class="border1 border-round-5" :style="{width:width+'px', height:height+'px'}" style="position:relative;">
<canvas ref="canvas" style="display:none;" :width="width" :height="height"></canvas>
<div v-show="openVideo" v-loading="loadingVideo" class="flex-center">
<video ref="video" :width="width" :height="height" autoplay style=""></video>
</div>
<div v-if="videoList.length>1" style="position:absolute; top:10px; left:10px;">
<span class="margin-r-10 border-round-5 bg-white inline-block color-grey" style="line-height: 30px; width:30px; height:30px; text-align:center;vertical-align:middle;"
:style="{background: d.deviceId==deviceId?'#66aa6630':'#EEEEEE30'}"
v-for="(d, index) in videoList" :key="d.deviceId" @click="changeVideo(index)">{{index+1}}</span>
</div>
</div>
</template>
<script>
import store from '@/store'
export default {
name:"VideoBox",
props: {
value: String,
width: {
type:Number,
default: 240
},
height:{
type:Number,
default: 240
},
},
watch:{
value: {
handler(val) {
this.image = val
}
},
},
data() {
return {
constraints: {
audio: false,
video: {
width: 400,
height: 400,
frameRate: { ideal: 24, max: 24 },
transform: 'scaleX(-1)',
// facingMode: "user",
}
},
deviceId: undefined,
videoList: [],
stream: null,
// 拍照
video:null,
image: this.value,
openVideo: false,
loadingVideo: false,
//
mimeType:'video/webm',
chunks:[],
}
},
mounted() {
this.openCamera();
},
destroyed() {
this.closeCamera()
},
methods: {
changeVideo(index){
var deviceId = this.videoList[index].deviceId;
if(this.deviceId == deviceId) return;
this.deviceId = deviceId;
this.closeCamera();
this.constraints.video.deviceId={ exact: deviceId}
this.openCamera();
},
removeImage(e){
if(e && e.stopPropagation){
e.stopPropagation();
}
this.image = null;
this.$emit("input", this.image);
},
initUserMedia(){
// 旧版本浏览器可能根本不支持mediaDevices,我们首先设置一个空对象
if (navigator.mediaDevices === undefined) {
navigator.mediaDevices = {}
}
// 一些浏览器实现了部分mediaDevices,我们不能只分配一个对象
// 使用getUserMedia,因为它会覆盖现有的属性。
// 这里,如果缺少getUserMedia属性,就添加它。
if (navigator.mediaDevices.getUserMedia === undefined) {
navigator.mediaDevices.getUserMedia = function (constraints) {
// 首先获取现存的getUserMedia(如果存在)
var 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(function (resolve, reject) {
getUserMedia.call(navigator, constraints, resolve, reject)
})
}
}
},
// 调用权限(打开摄像头功能)
async openCamera() {
this.closeCamera();
this.openVideo = true;
this.loadingVideo = true;
this.video = this.$refs.video
this.initUserMedia()
if(!this.constraints.video.deviceId){
var devices = await navigator.mediaDevices.enumerateDevices()
this.videoList = devices.filter(d=>d.kind=='videoinput');
if(this.videoList.length<1) {
this.$modal.msgError("没有检测到视频设备")
return;
}
// console.log(this.videoList);
var deviceId = this.videoList[0].deviceId;
if(deviceId) {
this.deviceId = deviceId;
this.constraints.video.deviceId={ exact: deviceId}
}
}
navigator.mediaDevices.getUserMedia(this.constraints).then((stream) =>{
this.stream = stream
// 旧的浏览器可能没有srcObject
if ('srcObject' in this.video) {
// console.log("srcObject")
this.video.srcObject = this.stream
this.$emit("ready", this.stream)
} else {
// 避免在新的浏览器中使用它,因为它正在被弃用。
this.video.src = window.URL.createObjectURL(this.stream )
}
this.video.onloadedmetadata = (e)=> {
this.video.play();
this.loadingVideo = false;
}
this.$emit("ready", this.stream)
}).catch(err => {
console.log(err)
this.openVideo = false;
this.loadingVideo = false;
})
},
// 关闭摄像头
closeCamera() {
// console.log("closeCamera")
if(this.video && this.video.srcObject){
this.video.srcObject.getTracks().forEach(track => track.stop());
this.video.srcObject = null
}
this.stream = undefined;
},
// 绘制图片(拍照功能)
takePhoto() {
// console.log("takePhoto")
var canvas = this.$refs.canvas
var canvasContext = canvas.getContext('2d')
// 点击,canvas画图
if(this.video){
canvasContext.drawImage(this.video, 0, 0, this.width, this.height)
// 获取图片base64链接
this.image = canvas.toDataURL('image/png')
this.$emit("capture", this.image)
}
},
}
}
</script>
3、应用效果
新建src\views\system\user\profile\UserFaceList.vue 内容如下:
<template>
<div>
<div class="">
<div>
<el-image v-if="image" :src="image" style="width:300px;height:300px"/>
<VideoBox v-show="!image" class="border1 border-round-5" ref="videoBox" :width="300" :height="300" @click="takePhoto" @capture="handleCapture" ></VideoBox>
<div class="flex-row-between padding-20" >
<el-button type="primary" @click="takePhoto">{{image?'重拍':'拍照'}}</el-button>
<el-button class="margin-l-20" type="primary">注册</el-button>
<div class="fill-rest"></div>
</div>
</div>
</div>
</div>
</template>
<script>
import { getDictDatas, DICT_TYPE } from '@/utils/dict'
import * as userFace from "@/api/system/userFace";
import VideoBox from '@/components/VideoRecorder/VideoBox';
export default {
name: "UserFaceList",
components:{
VideoBox
},
data() {
return {
image: undefined,
};
},
mounted(){
},
destroyed(){
},
methods: {
startCamera(){
if(this.$refs.videoBox){
this.$refs.videoBox.openCamera();
}
},
takePhoto(){
if(this.image) {
this.image = undefined;
if(this.$refs.videoBox){
this.$refs.videoBox.openCamera();
}
} else {
if(this.$refs.videoBox){
this.$refs.videoBox.takePhoto();
this.$refs.videoBox.closeCamera();
}
}
},
handleCapture(image){
console.log("handleCapture")
this.image = image;
},
}
};
</script>
在src\views\system\user\profile\index.vue中调用UserFaceList组件
<el-tab-pane label="人脸登陆" name="userFace">
<UserFaceList v-if="activeTab=='userFace'" />
</el-tab-pane>
效果如下

后续设计system_user_face表结合system_users一起实现多对一人脸登陆。
============= 相关博文 ============
使用码云gitee登录ruoyi-vue-pro——坑比较多
Vue2 打包部署后通过修改配置文件修改全局变量——实时生效
只需3句让Vue3 打包部署后通过修改配置文件修改全局变量——实时生效
============= 相关博文 ============
1万+

被折叠的 条评论
为什么被折叠?



