index.ts 9.0 KB

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