🥑 抱怨黑暗深处,不如提灯前行
其实也就是传一个视频给后端,后端去调用第三方检测
一、啥也不说,先绘制页面
采用flex布局
<template>
<kyc-header :title="'06 活体验证'" />
<div class="content" v-show="show">
<div class="number">
<div :key="index" class="alone" v-for="(item,index) in numList">{{item}}</div>
</div>
<div class="reflash">
<p class="left">请正确念出上方数字</p>
<p @click="claim" class="right">刷新数字</p>
</div>
<div class="face">
<img alt src="@/assets/img/living_auth.png" v-if="!showVideo" />
<!-- 人脸识别 -->
<div class="video" v-show="showVideo">
<video
:src="url"
autoplay
muted
playsinline
ref="videoRef"
webkit-playsinline="true"
x5-video-player-type="h5"
></video>
</div>
</div>
<div class="btn-footer">
<van-button @click.native="getCamera" class="btn" color="#00A4B7" v-if="btnWord==1">开始录制</van-button>
<van-button @click="saveVideo" class="btn" color="#e96040" v-if="btnWord==2">结束录制</van-button>
<van-button @click="reVideo" class="btn" color="#eff0f2" v-if="btnWord==3">重新录制</van-button>
</div>
<div class="explain">
<p>视频录制说明</p>
<ul>
<li>1.请使用前置摄像头</li>
<li>2.保证光线充足、脸部完全入镜、脸部无遮挡物</li>
<li>3.录制视频过程中请使用普通话读一遍上方验证数字</li>
<li>4.如验证失败、数字失效,点击“刷新数字”后重新录制</li>
<li>5.如录制不满意可点击“重新录制”直至录制满意</li>
</ul>
</div>
</div>
<van-sticky class="page_bottom" position="bottom">
<van-button
:class="{disable_bgc:true,click_bgc:btnWord==3}"
:disabled="btnWord!==3"
@click="handleSubmit"
block
class="btn-box"
>提交</van-button>
</van-sticky>
</template>
<style lang='scss' scoped>
.content {
position: absolute;
top: 50px;
bottom: calc(50px + constant(safe-area-inset-bottom));
bottom: calc(50px + env(safe-area-inset-bottom));
overflow: auto;
width: 100%;
-webkit-overflow-scrolling: touch;
display: flex;
flex-direction: column;
justify-content: space-around;
align-items: center;
:deep(.van-button) {
width: 200px;
}
}
.number {
width: 80%;
display: flex;
justify-content: space-between;
flex-wrap: nowrap;
.alone {
width: 55px;
height: 55px;
font-size: 32px;
text-align: center;
background-color: #fff;
}
}
p {
font-size: 14px;
color: #999999;
margin-bottom: 0;
}
.reflash {
height: 20px;
text-align: center;
justify-content: center;
.left {
display: block;
float: left;
}
.right {
display: block;
float: right;
color: #00a4b7;
}
}
.face {
border-radius: 50%;
width: 200px;
height: 200px;
border: 1px solid #ffffff;
img {
width: 100%;
height: 100%;
}
}
.explain {
p {
margin-bottom: 15px;
}
ul {
li {
font-size: 12px;
color: #999999;
margin-bottom: 4px;
}
}
}
//录制
.video {
border-radius: 50%;
width: 100%;
height: 100%;
video {
width: 100%;
height: 100%;
border-radius: 50%;
object-fit: cover;
background-repeat: no-repeat;
background-size: 100% 100%;
z-index: 0;
}
}
</style>
二、接口
三、调用浏览器摄像头和麦克风权限方法
- 其中 constraints 为需要获取的权限列表,这里只需要指定音频 audio 即可
- 其返回是个 Promise,因为用户何时进行授权是不确定的。通过在 Promise 的回调中进行授权成功或失败的处理。
- 在使用前需要判断浏览器是否已经支持相应的 API,此时得到如下的代码:
const getCamera = () => {
// 0️⃣ constraints 为需要获取的权限列表,这里只需要指定音频和视频
let constraints = {
audio: true, //🎤
video: {
facingMode: 'user', // 优先调前置摄像头
},
}
// 1️⃣ 在使用前需要判断浏览器是否已经支持相应的 API
// 老的浏览器可能根本没有实现 mediaDevices,所以我们可以先设置一个空的对象
if (navigator.mediaDevices === undefined) {
navigator.mediaDevices = {}
}
// 一些浏览器部分支持 mediaDevices。我们不能直接给对象设置 getUserMedia,因为这样可能会覆盖已有的属性。这里我们只会在没有getUserMedia属性的时候添加它。
if (navigator.mediaDevices.getUserMedia === undefined) {
navigator.mediaDevices.getUserMedia = function (constraints) {
// 判断浏览器是否支持getUserMedia方法
var getUserMedia =
navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia
if (!getUserMedia) {
Toast('该浏览器不支持getUserMedia,请使用其他浏览器')
return Promise.reject(
new Error('getUserMedia is not implemented in this browser')
)
}
// 否则,为老的navigator.getUserMedia方法包裹一个Promise
return new Promise(function (resolve, reject) {
getUserMedia.call(navigator, constraints, resolve, reject)
})
}
}
// 3️⃣ 使用浏览器提供的 MediaRecorder API
navigator.mediaDevices
.getUserMedia(constraints)
.then((stream) => {
// 4️⃣ 其中成功回调里得到的入参 stream 为 MediaStream 对象。
state.MediaStreamTrack =
typeof stream.stop === 'function' ? stream : stream.getTracks()[0]
state.showVideo = true // 显示录制框
state.isAlreadyRecord = false
let winURL = window.URL || window.webkitURL
if ('srcObject' in proxy.$refs.videoRef) {
proxy.$refs.videoRef.srcObject = stream
} else {
proxy.$refs.videoRef.src = winURL.createObjectURL(stream)
}
proxy.$refs.videoRef.onloadedmetadata = (e) => {
if (stream.active) {
proxy.$refs.videoRef.play()
state.btnWord = 2
}
}
let options = {
videoBitsPerSecond: 2500000,
}
state.mediaRecorder = new MediaRecorder(stream, options)
saveVideo(stream) //5️⃣ 摄像头和🎤权限获取完毕,开始录制视频
})
.catch((err) => {
// console.log(err)
Toast('摄像头开启失败,请检查摄像头是否授权或是否可用!')
})
}
保存录制视频(是要传给后端滴)
const saveVideo = (stream) => {
if (state.isAlreadyRecord) { // 7️⃣ 结束录制
//当录制的数据可用时
state.mediaRecorder.ondataavailable = (e) => {
if (e.data && e.data.size > 0) {
state.recordedBlobs.push(e.data)
}
}
state.mediaRecorder.stop() //8️⃣ 调用浏览器api关闭摄像头和🎤
state.isAlreadyRecord = false
const stream = proxy.$refs.videoRef.srcObject
const tracks = stream.getTracks()
tracks.forEach(function (track) {
track.stop()
})
proxy.$refs.videoRef.srcObject = null
state.showVideo = false
state.btnWord = 3 // 9️⃣ 按钮变成"重新录制"
} else { // 6️⃣ 开始录制
state.isAlreadyRecord = true
state.mediaRecorder.start()
}
}
新定义一个变量isAlreadyRecord用来区分 开始录制 or 结束录制
- 初始化为false
isAlreadyRecord: false, - 柚子点击“开始录制”按钮
state.isAlreadyRecord = true - 点击“结束录制”按钮,
state.isAlreadyRecord = false
重新录制功能[reVideo()]
先清除上一次录制的视频二进制流[closeVideo()],再次调用getCamera()
//重新录制
const reVideo = () => {
state.btnWord = 2
closeVideo()
getCamera()
}
const closeVideo = () => {
state.recordedBlobs = []
// state.isAlreadyRecord = false
// state.MediaStreamTrack && state.MediaStreamTrack.stop()
}
base64转为文件流
//base64转为文件流
const base64toFile = (dataurl, filename = 'file') => {
let arr = dataurl.split(',')
let mime = arr[0].match(/:(.*?);/)[1]
let suffix = mime.split('/')[1]
let bstr = atob(arr[1])
let n = bstr.length
let u8arr = new Uint8Array(n)
while (n--) {
u8arr[n] = bstr.charCodeAt(n)
}
return new File([u8arr], `${filename}.${suffix}`, {
type: mime,
})
}
提交按钮click事件
const handleSubmit = () => {
var blob = new Blob(state.recordedBlobs, { type: 'video/mp4' })
var reader = new FileReader()
reader.readAsDataURL(blob, 'utf-8')
reader.onload = () => {
let str = base64toFile(reader.result) //reader.result为base64格式的视频
let formData = new FormData()
formData.append('file', str) //此时str是文件流
uploadVideo(formData)
}
}
上传视频接口调用
//上传视频接口
const uploadVideo = (formData) => {
_living
.infoSubmit(formData)
.then((res) => {
proxy.$router.push('/living')
})
.catch((err) => {
claim()
})
}
四、附代码
[页面全部代码](www.yuque.com/docs/share/… 《27 人脸识别代码》)
五、vue.config.js添加https:true
devServer: {
https: true,
port: '9091',
},