index.ts 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. // pages/faceRecognition/index.ts
  2. import { base } from "../../config/index";
  3. let ctx: WechatMiniprogram.CameraContext | undefined = undefined;
  4. let listener: WechatMiniprogram.CameraFrameListener | undefined;
  5. let createVKSession: WechatMiniprogram.VKSession | undefined = undefined;
  6. let innerAudioContext: WechatMiniprogram.InnerAudioContext;
  7. const app = getApp<IAppOption>();
  8. const topTips = {
  9. ready: '请正对手机,保持光线充足',
  10. front: '请正对屏幕',
  11. left: '请向左转头',
  12. right: '请向右转头',
  13. }
  14. const bottomTips = {
  15. recording: '脸部信息录入中...',
  16. complete: '为达到更好的剪辑效果,请您跑步中接近摄像机,避免遮挡。',
  17. error: '脸部信息录入失败'
  18. }
  19. let T: number = 0;
  20. let notify = ""
  21. const daoJiShi: number = 15;
  22. let isOver = false;
  23. Page({
  24. /**
  25. * 页面的初始数据
  26. */
  27. data: {
  28. isStartVideo: false, // 是否点击开始按钮
  29. seconds: 0, // 倒计
  30. topTips: topTips.ready, // 提示语句
  31. bottomTips: "" // 底部提示语句
  32. },
  33. // 开始初始化录制
  34. async initCamera() {
  35. if (this.data.isStartVideo) return;
  36. wx.showLoading({
  37. title: ""
  38. })
  39. this.setData({
  40. isStartVideo: true
  41. })
  42. wx.nextTick(() => {
  43. app.getPageInfo({
  44. success: (res: any) => {
  45. const configPage = res || {};
  46. this.getMssage(configPage).then(async (r: any) => {
  47. console.log('是否授权发送消息:', r);
  48. notify = r;
  49. ctx = wx.createCameraContext();
  50. this.createVK();
  51. this.addListen();
  52. ctx.startRecord({
  53. timeout: daoJiShi, // 录制时长
  54. selfieMirror: false,
  55. timeoutCallback: (res: any) => {
  56. // 超出录制时长 关闭录屏
  57. createVKSession?.stop();
  58. listener?.stop();
  59. this.setData({
  60. topTips: topTips.ready, // 提示语句
  61. bottomTips: bottomTips.complete
  62. })
  63. let t = setTimeout(() => {
  64. this.getUrl(res);
  65. clearTimeout(t)
  66. }, 2000);
  67. },
  68. success: () => {
  69. // 调用成功,开始录制
  70. wx.hideLoading();
  71. this.setData({
  72. seconds: daoJiShi,
  73. bottomTips: bottomTips.recording,
  74. topTips: topTips.front
  75. });
  76. innerAudioContext.src = "assets/voice/front.mp3";
  77. innerAudioContext?.play();
  78. },
  79. fail: () => {
  80. // 调用失败
  81. wx.hideLoading();
  82. this.overVideo();
  83. wx.showToast({
  84. icon: "none",
  85. title: "相机调用失败",
  86. duration: 2000
  87. })
  88. }
  89. })
  90. });
  91. }
  92. })
  93. })
  94. },
  95. // 添加监听
  96. addListen() {
  97. T = Date.now();
  98. listener = ctx?.onCameraFrame((frame: any) => {
  99. const Time = daoJiShi * 1000 - (Date.now() - T);
  100. if (Time % 1000 >= 50) return
  101. const upData: {
  102. seconds: number,
  103. topTips?: string
  104. } = {
  105. seconds: Math.floor(Time / 1000)
  106. }
  107. if (upData.seconds == 10) {
  108. innerAudioContext.src = "assets/voice/left.mp3";
  109. upData.topTips = topTips.left;
  110. }
  111. if (upData.seconds == 5) {
  112. innerAudioContext.src = "assets/voice/right.mp3";
  113. upData.topTips = topTips.right;
  114. }
  115. if (upData.seconds % 5 === 0 && upData.seconds !== 0) innerAudioContext?.play();
  116. this.setData(upData)
  117. // 识别人脸是否在画面种
  118. createVKSession?.detectFace({
  119. frameBuffer: frame.data, // 图片 ArrayBuffer 数据。人脸图像像素点数据,每四项表示一个像素点的 RGBA
  120. width: frame.width, // 图像宽度
  121. height: frame.height, // 图像高度
  122. scoreThreshold: 0.5, // 评分阈值
  123. sourceType: 1
  124. })
  125. });
  126. listener?.start();
  127. },
  128. // 创建人脸追踪
  129. createVK() {
  130. // 初始化人脸识别
  131. createVKSession = wx.createVKSession({
  132. version: "v1",
  133. track: {
  134. plane: {
  135. mode: 1
  136. },
  137. face: { mode: 2 } // mode: 1 - 使用摄像头;2 - 手动传入图像
  138. },
  139. })
  140. // 静态图片检测模式下,每调一次 detectFace 接口就会触发一次 updateAnchors 事件
  141. createVKSession?.on('updateAnchors', (anchors) => {
  142. for (let I = 0; I < anchors.length; I++) {
  143. const anchor = anchors[I];
  144. const W = anchor.size.width * 100;
  145. let titlet = "";
  146. if (W < 20 || W > 80) titlet = "请远离屏幕"
  147. if (W < 20) titlet = "请靠近屏幕"
  148. titlet && wx.showToast({
  149. icon: "none",
  150. title: titlet
  151. })
  152. }
  153. })
  154. createVKSession?.on('removeAnchors', () => {
  155. wx.showToast({
  156. title: "未检测到人脸, 请重新采集",
  157. icon: "error"
  158. });
  159. this.overVideo();
  160. })
  161. createVKSession?.start(() => { });
  162. },
  163. // 结束录像
  164. overVideo() {
  165. if (isOver) return;
  166. isOver = true;
  167. ctx?.stopRecord({
  168. compressed: true,
  169. success: () => {
  170. ctx = undefined;
  171. isOver = false;
  172. listener?.stop();
  173. this.setData({
  174. isStartVideo: false,
  175. bottomTips: "",
  176. topTips: topTips.ready,
  177. seconds: 0
  178. })
  179. },
  180. fail: () => {
  181. ctx = undefined;
  182. isOver = false;
  183. listener?.stop();
  184. this.setData({
  185. isStartVideo: false,
  186. bottomTips: "",
  187. topTips: topTips.ready,
  188. seconds: 0
  189. })
  190. }
  191. })
  192. createVKSession?.stop();
  193. innerAudioContext?.pause();
  194. innerAudioContext?.destroy();
  195. },
  196. // 获取本地临时地址
  197. getUrl(res: WechatMiniprogram.StartRecordTimeoutCallbackResult) {
  198. wx.showLoading({
  199. title: '上传进度:0%',
  200. mask: true //是否显示透明蒙层,防止触摸穿透
  201. })
  202. const uploadTask = wx.uploadFile({
  203. url: base.url + '/v3/upload',
  204. filePath: res.tempVideoPath,
  205. name: 'file', //服务器定义的Key值
  206. formData: {
  207. notify
  208. },
  209. header: {
  210. Authorization: wx.getStorageSync("token")
  211. },
  212. success: () => {
  213. wx.hideLoading();
  214. wx.showToast({
  215. title: "上传成功",
  216. duration: 2000,
  217. icon: "none"
  218. });
  219. this.setData({
  220. isStartVideo: false,
  221. bottomTips: "",
  222. })
  223. let time = setTimeout(() => {
  224. clearTimeout(time);
  225. wx.reLaunch({
  226. url: "/pages/downloadPage/index"
  227. })
  228. }, 2000)
  229. },
  230. fail: () => {
  231. wx.hideLoading();
  232. wx.showToast({
  233. title: "上传失败",
  234. icon: "none",
  235. duration: 2000
  236. })
  237. this.setData({
  238. topTips: topTips.ready, // 提示语句
  239. bottomTips: bottomTips.error
  240. })
  241. },
  242. complete: () => {
  243. }
  244. })
  245. //监听上传进度变化事件
  246. uploadTask.onProgressUpdate((res) => {
  247. wx.showLoading({
  248. title: '上传进度:' + res.progress + '%',
  249. mask: true //是否显示透明蒙层,防止触摸穿透
  250. })
  251. })
  252. },
  253. // 非正常中止
  254. improperStop() {
  255. this.overVideo();
  256. wx.navigateBack();
  257. },
  258. // 未授权
  259. unauthorized() {
  260. wx.showToast({
  261. title: "拒绝授权将无法录制视频信息"
  262. });
  263. this.overVideo();
  264. wx.navigateBack();
  265. },
  266. // 初始化完成
  267. initialization() { },
  268. // 获取消息授权
  269. getMssage(configPage: any) {
  270. return new Promise((resolve) => {
  271. if (!configPage.messageID || !wx.canIUse("requestSubscribeMessage")) {
  272. resolve("reject");
  273. return
  274. }
  275. wx.requestSubscribeMessage({
  276. tmplIds: configPage?.messageID || [],
  277. success: (res: WechatMiniprogram.RequestSubscribeMessageSuccessCallbackResult) => {
  278. resolve(res.p1mpCydIQ6OtxCSa62NaSFiEkQiTsb8KPFaAs1SuKMw || 'reject');
  279. },
  280. fail: () => {
  281. resolve("reject");
  282. }
  283. })
  284. })
  285. },
  286. /**
  287. * 生命周期函数--监听页面加载
  288. */
  289. onLoad() {
  290. innerAudioContext = wx.createInnerAudioContext();
  291. innerAudioContext.loop = false;
  292. },
  293. /**
  294. * 生命周期函数--监听页面初次渲染完成
  295. */
  296. onReady() { },
  297. /**
  298. * 生命周期函数--监听页面显示
  299. */
  300. onShow() {
  301. },
  302. /**
  303. * 生命周期函数--监听页面隐藏
  304. */
  305. onHide() {
  306. // 在录制中退出后台页面隐藏,返回上一页,确保重新进入当前页
  307. this.overVideo();
  308. },
  309. /**
  310. * 生命周期函数--监听页面卸载
  311. */
  312. onUnload() {
  313. this.overVideo();
  314. },
  315. /**
  316. * 页面相关事件处理函数--监听用户下拉动作
  317. */
  318. onPullDownRefresh() {
  319. },
  320. /**
  321. * 页面上拉触底事件的处理函数
  322. */
  323. onReachBottom() {
  324. },
  325. /**
  326. * 用户点击右上角分享
  327. */
  328. // onShareAppMessage() {
  329. // },
  330. })
  331. export { }