123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379 |
- // components/camera-face/index.js
- import { getAuthorize, setAuthorize, throttle, checkVersion } from './utils'
- // 顶部提示信息
- const topTips = {
- ready: '请拿起手机,正对屏幕',
- front: '请正对屏幕',
- left: '请向左转头',
- right: '请向右转头',
- }
- // 底部提示信息
- const bottomTips = {
- recording: '人脸录制中...',
- complete: '已录制完成',
- error: '录制失败'
- }
- const innerAudioContext = wx.createInnerAudioContext()
- let ctx: any = undefined;
- let listener: any = undefined;
- let interval: any = undefined;
- Component({
- // 组件的属性列表
- properties: {
- // 人脸整体可信度 [0-1], 参考wx.faceDetect文档的res.confArray.global
- // 当超过这个可信度且正脸时开始录制人脸, 反之停止录制
- faceCredibility: {
- type: Number,
- value: 0.3
- },
- // 人脸偏移角度正脸数值参考wx.faceDetect文档的res.angleArray
- // 越接近0越正脸,包括p仰俯角(pitch点头), y偏航角(yaw摇头), r翻滚角(roll左右倾)
- faceAngle: {
- type: Object,
- value: { p: 1, y: 1, r: 1 }
- },
- // 录制视频时长,不能超过30s
- duration: {
- type: Number,
- value: 15000
- },
- // 是否压缩视频
- compressed: {
- type: Boolean,
- value: false
- },
- // 前置或者后置 front,back
- devicePosition: {
- type: String,
- value: 'front'
- },
- // 指定期望的相机帧数据尺寸 small,medium,large
- frameSize: {
- type: String,
- value: 'medium'
- },
- // 分辨率 low,medium,high
- resolution: {
- type: String,
- value: 'medium'
- },
- // 闪光灯 auto,on,off,torch
- flash: {
- type: String,
- value: 'off'
- },
- // 检测视频帧的节流时间,默认500毫秒执行一次
- throttleFrequency: {
- type: Number,
- value: 500
- }
- },
- // 组件页面的生命周期
- pageLifetimes: {
- // 页面被隐藏
- hide: function () {
- this.stop()
- },
- },
- detached: function () {
- // 在组件实例被从页面节点树移除时执行
- this.stop()
- },
- // 组件的初始数据
- data: {
- isReading: false, // 是否在准备中
- isRecoding: false, // 是否正在录制中
- isStopRecoding: false, // 是否正在停止录制中
- topTips: topTips.ready, // 顶部提示文字
- bottomTips: '', // 底部提示文字
- seconds: '',
- videoSrc: '',
- isCompleteRecoding: false
- },
- /**
- * 组件的方法列表
- */
- methods: {
- // 开启相机ctx
- async start() {
- const result = await this.initAuthorize()
- if (!result) return false
- if (!ctx) ctx = wx.createCameraContext()
- return true
- },
- // 准备录制
- async readyRecord() {
- if (this.data.isReading) return
- this.setData({ isReading: true })
- wx.showLoading({ title: '加载中..', mask: true })
- // 检测版本号
- const canUse = checkVersion('2.18.0', () => {
- this.triggerEvent('cannotUse')
- })
- if (!canUse) {
- wx.hideLoading()
- this.setData({ isReading: false })
- return
- }
- // 启用相机
- try {
- const result = await this.start()
- if (!result || !ctx) throw new Error()
- } catch (e) {
- wx.hideLoading()
- this.setData({ isReading: false })
- return
- }
- console.log('准备录制')
- this.setData({ topTips: topTips.ready })
- console.log(this.data.topTips)
- // 视频帧回调节流函数
- let fn = throttle((frame: any) => {
- // 人脸识别
- wx.faceDetect({
- frameBuffer: frame.data,
- width: frame.width,
- height: frame.height,
- enableConf: true,
- enableAngle: true,
- success: (res) => this.processFaceData(res),
- fail: () => this.cancel()
- })
- }, this.properties.throttleFrequency)
- // 初始化人脸识别
- wx.initFaceDetect({
- success: () => {
- listener = ctx.onCameraFrame((frame: any) => fn(frame))
- listener.start()
- },
- fail: (err) => {
- console.log('初始人脸识别失败', err)
- // this.setData({ topTips: '' })
- wx.showToast({ title: '未识别到人脸', icon: 'none' })
- },
- complete: () => {
- wx.hideLoading()
- this.setData({ isReading: false })
- }
- })
- },
- // 处理人脸识别数据
- processFaceData(res: any) {
- if (res.confArray && res.angleArray) {
- const { global } = res.confArray
- const g = this.properties.faceCredibility
- const { pitch, yaw, roll } = res.angleArray
- const { p, y, r } = this.properties.faceAngle
- console.log('res.confArray.global:', global)
- console.log('res.angleArray:', pitch, yaw, roll)
- const isGlobal = global >= g
- const isPitch = Math.abs(pitch) <= p
- const isYaw = Math.abs(yaw) <= y
- const isRoll = Math.abs(roll) <= r
- if (isGlobal && isPitch && isYaw && isRoll) {
- console.log('人脸可信,且是正脸')
- if (this.data.isRecoding || this.data.isCompleteRecoding) return
- this.setData({ isRecoding: true })
- this.startRecord() // 开始录制
- } else {
- console.log('人脸不可信,或者不是正脸')
- this.cancel()
- }
- } else {
- console.log('获取人脸识别数据失败', res)
- this.cancel()
- }
- },
- // 开始录制
- startRecord() {
- console.log('开始录制')
- ctx.startRecord({
- success: () => {
- this.setRecordingTips()
- let timer = setTimeout(() => {
- this.completeRecord()
- clearTimeout(timer);
- }, this.properties.duration)
- },
- timeoutCallback: () => {
- // 超过30s或页面 onHide 时会结束录像
- this.stop()
- },
- fail: () => this.stop()
- })
- },
- // 设置录制中的提示文字和倒计时
- setRecordingTips() {
- this.setData({
- bottomTips: bottomTips.recording
- })
- let second = (this.properties.duration / 1000)
- if (interval) interval = clearInterval(interval)
- interval = setInterval(() => {
- console.log('xxxxxx', second)
- // 音频播放
- if (second == 15) {
- innerAudioContext.src = "assets/voice/front.mp3"
- innerAudioContext.play()
- }
- else if (second == 10) {
- innerAudioContext.src = "assets/voice/left.mp3"
- innerAudioContext.play()
- }
- else if (second == 5) {
- innerAudioContext.src = "assets/voice/right.mp3"
- innerAudioContext.play()
- }
- // 修改提示文字
- if (second > 10)
- this.setData({
- topTips: topTips.front,
- })
- else if (second > 5 && second <= 10)
- this.setData({
- topTips: topTips.left,
- })
- else {
- this.setData({
- topTips: topTips.right,
- })
- }
- this.setData({
- seconds: second-- + 's'
- })
- if (second <= 0) {
- interval = clearInterval(interval)
- this.setData({
- seconds: '1s',
- topTips: '',
- bottomTips: bottomTips.complete
- })
- }
- }, 1000)
- },
- // 完成录制
- completeRecord() {
- console.log('完成录制')
- this.setData({ isCompleteRecoding: true })
- ctx.stopRecord({
- compressed: this.properties.compressed,
- success: (res: any) => {
- this.setData({
- videoSrc: res.tempVideoPath
- })
- // 向外触发完成录制的事件
- this.triggerEvent('complete', res.tempVideoPath)
- // this.uploadVideo()
- },
- fail: () => this.stop(),
- complete: () => {
- listener.stop()
- wx.stopFaceDetect()
- interval = clearInterval(interval);
- this.setData({ isCompleteRecoding: false })
- }
- })
- },
- // 人脸移出等取消录制
- cancel() {
- console.log('取消录制')
- // 如果不在录制中或者正在录制完成中就不能取消
- if (!this.data.isRecoding || this.data.isCompleteRecoding) return
- interval = clearInterval(interval);
- ctx.stopRecord({
- complete: () => {
- console.log('取消录制成功')
- this.setData({
- topTips: topTips.ready,
- bottomTips: '',
- seconds: '',
- isRecoding: false
- })
- wx.showToast({ title: '录制失败,请重新录制', icon: 'none' })
- }
- })
- },
- // 用户切入后台等停止使用摄像头
- stop() {
- console.log('停止录制')
- interval = clearInterval(interval);
- if (listener) listener.stop()
- if (ctx && !this.data.isCompleteRecoding) ctx.stopRecord()
- wx.stopFaceDetect()
- setTimeout(() => {
- this.setData({ topTips: '', isRecoding: false })
- }, 500)
- },
- // 用户不允许使用摄像头
- error() {
- // const cameraName = 'scope.camera';
- // this.triggerEvent('noAuth', cameraName)
- },
- // 初始相机和录音权限
- async initAuthorize() {
- const cameraName = 'scope.camera'
- const recordName = 'scope.record'
- const scopeCamera = await getAuthorize(cameraName)
- // 未授权相机
- if (!scopeCamera) {
- // 用户拒绝授权相机
- if (!(await setAuthorize(cameraName))) this.openSetting()
- return false
- }
- const scopeRecord = await getAuthorize(recordName)
- if (!scopeRecord) {
- // 用户拒绝授权录音
- if (!(await setAuthorize(recordName))) {
- this.openSetting()
- return false
- }
- }
- return true
- },
- // 打开设置授权
- openSetting() {
- wx.showModal({
- title: '开启摄像头和录音权限',
- showCancel: true,
- content: '是否打开?',
- success: (res) => {
- this.triggerEvent('noAuth', '打开设置授权')
- if (res.confirm) {
- wx.openSetting()
- }
- }
- })
- },
- }
- })
|