123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360 |
- import { getAuthorize, checkVersion } from './utils'
- const app = getApp<IAppOption>();
- // 顶部提示信息
- // const app = getApp<IAppOption>();
- 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 { }
|