import { getAuthorize, checkVersion } from './utils' const app = getApp(); // 顶部提示信息 // const app = getApp(); const topTips = { ready: '请正对手机,保持光线充足', front: '请正对屏幕', left: '请向左转头', right: '请向右转头', } const bottomTips = { recording: '脸部信息录入中...', complete: '为达到更好的剪辑效果,请您跑步中接近摄像机,避免遮挡。', error: '脸部信息录入失败' } let ctx: WechatMiniprogram.CameraContext; let listener: WechatMiniprogram.CameraFrameListener; let startTime: number = 0; let innerAudioContext: WechatMiniprogram.InnerAudioContext; Component({ ready() { innerAudioContext = wx.createInnerAudioContext(); innerAudioContext.loop = false; }, // 组件的属性列表 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' }, }, // 组件页面的生命周期 pageLifetimes: { // 页面被隐藏 hide: function () { this.stopRecord(); this.stopUI(); }, }, detached: function () { // 在组件实例被从页面节点树移除时执行 this.stopRecord(); this.stopUI(); }, // 组件的初始数据 data: { initFace: false, topTips: topTips.ready, // 顶部提示信息 bottomTips: "", //底部提示信息 gather: 0, // 采集状态:0 -未开始 1 -加载中 2 -录制中 3 -录制结束 seconds: 0, messageAuto: false }, /** * 组件的方法列表 */ methods: { // 准备采集人脸信息 readyRecord() { if (this.data.gather !== 0 && this.data.gather !== 3) return; // 开始采集 this.setData({ bottomTips: bottomTips.recording, gather: 1 }); // 状态转换为加载中 wx.nextTick(() => { wx.showLoading({ title: '加载中..', mask: true }) if (!checkVersion('2.18.0', () => this.triggerEvent('cannotUse'))) { // 基础库不支持 faceDate wx.hideLoading(); this.setData({ gather: 0, bottomTips: "" }); return; } // 获取授权 this.getMssage().then(async (r:any) => { this.setData({ messageAuto: r || false }) // 调用相机 await this.openCamera(); // 准备采集 startTime = Date.now(); this.initFace() // 开始录制 this.startRecord() }) }) }, // 获取消息授权 getMssage() { return new Promise((resolve) => { wx.requestSubscribeMessage({ tmplIds: app.globalData.configPage?.messageID || [], success: (res: WechatMiniprogram.RequestSubscribeMessageSuccessCallbackResult) => { resolve(res.p1mpCydIQ6OtxCSa62NaSFiEkQiTsb8KPFaAs1SuKMw || 'reject'); }, fail: () => { resolve(false); } }) }) }, // init 人脸识别能力 initFace() { // 初始化人脸识别 wx.initFaceDetect({ success: () => { wx.hideLoading(); listener = ctx.onCameraFrame((frame: any) => { const s = 15 - Math.floor((Date.now() - startTime) / 1000); if (this.data.gather !== 2 || s % 500 > 100) return; let tip = ""; switch (true) { case s > 10: tip = topTips.front break; case s > 5 && s <= 10: tip = topTips.left; break default: tip = topTips.right; break; } if (s == 15) innerAudioContext.src = "assets/voice/front.mp3"; if (s == 10) innerAudioContext.src = "assets/voice/left.mp3"; if (s == 5) innerAudioContext.src = "assets/voice/right.mp3"; if (s % 5 === 0 && s !== 0) innerAudioContext.play(); this.setData({ seconds: s, topTips: tip }) if (s <= 0) { // 结束监听 this.stopRecord(true);// 停止录像逻辑 this.stopUI(true); // 重置ui逻辑; return } // 识别人脸是否在画面种 wx.faceDetect({ frameBuffer: frame.data, width: frame.width, height: frame.height, enableConf: true, enableAngle: true, success: (res: WechatMiniprogram.FaceDetectSuccessCallbackResult) => this.processFaceData(res), fail: (err) => { wx.showToast({ title: '未识别到人脸', icon: 'error', duration: 2000 }); this.setData({ seconds: 0, bottomTips: bottomTips.error }) this.stopRecord(); this.stopUI(); } }) }) listener?.start(); this.setData({ initFace: true }) }, fail: () => { wx.hideLoading(); wx.showToast({ title: '初始化人脸识别失败', icon: 'error', duration: 2000 }); this.setData({ seconds: 0, bottomTips: bottomTips.error }) this.stopRecord(); this.stopUI(); } }) }, // 人脸识别数据 processFaceData(res: WechatMiniprogram.FaceDetectSuccessCallbackResult) { if (!res.confArray || !res.angleArray) { // 人脸数据获取失败 wx.showToast({ title: '人脸数据获取失败', icon: 'error', duration: 2000 }); this.stopRecord(); this.stopUI(); return } const { global } = res.confArray; const { pitch, yaw, roll } = res.angleArray const { p, y, r } = this.properties.faceAngle; // 偏移角度 const g = this.properties.faceCredibility;// 自定义可信度; 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) { // 人脸不可信,且非正脸 wx.showToast({ title: '未检测到人脸', icon: 'error', duration: 2000 }); this.setData({ seconds: 0, bottomTips: bottomTips.error }) this.stopRecord(); this.stopUI(); return } }, // 开始录制 startRecord() { ctx.startRecord({ timeout: 15, success: () => { console.log("开始录制") this.setData({ gather: 2, seconds: 15 });// 录制中 }, timeoutCallback: () => { if (this.data.gather !== 2) return; this.stopRecord(); // 停止录像逻辑 this.stopUI(); // 重置ui逻辑; }, // 超出录制时长 fail: () => { this.stopRecord(); // 停止录像逻辑 this.stopUI(); // 重置ui逻辑; }, complete: () => { wx.hideLoading(); } }) }, // 结束录制 stopRecord(isVadeo?: boolean) { this.setData({ gather: isVadeo ? 3 : 0 }); ctx?.stopRecord({ compressed: this.properties.compressed, success: (res) => { wx.hideLoading(); /** * tempThumbPath 视频封面 * tempVideoPath 视频临时文件路径 */ console.log("录制结束", res, isVadeo); if (!isVadeo) return; console.log("是否授权:", this.data.messageAuto) this.triggerEvent('complete', { path: res.tempVideoPath, msg: this.data.messageAuto }) wx.stopFaceDetect({ success: () => { listener?.stop(); }, fail: () => { listener?.stop(); } }); }, fail: (err) => { console.log("录制失败+++>", err); wx.hideLoading(); wx.showToast({ title: "录入失败,请重试", icon: "none" }); this.setData({ seconds: 0, bottomTips: bottomTips.error }) wx.stopFaceDetect({ success: () => { listener?.stop(); }, fail: () => { listener?.stop(); } }); } }) }, // 停止ui变化 stopUI(isEnd?: boolean) { this.setData({ topTips: topTips.ready, bottomTips: isEnd ? bottomTips.complete : bottomTips.error }) }, // 开启相机 async openCamera() { if (!(await this.initAuthorize())) { this.setData({ gather: 0 }); throw new Error("未能正常启动摄像头"); } try { !ctx && (ctx = wx.createCameraContext()); } catch (error) { wx.hideLoading() wx.showToast({ title: "设备不支持视频录入", icon: "error" }) this.setData({ gather: 0 }); } }, // 初始化相机和录音权限 async initAuthorize() { const noauthorize = !(await getAuthorize('scope.camera')) // 相机或录音未授权 if (noauthorize) { this.openSetting(); return false } return true }, // 打开摄像头或录音权限授权框 openSetting() { wx.showModal({ title: '开启摄像头和录音权限', showCancel: true, content: '是否打开?', success: (res) => { this.triggerEvent('noAuth', '打开设置授权') if (res.confirm) wx.openSetting(); } }) } } }) export { }