index.ts 8.9 KB

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