facecog-liveness-showcase 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (229) hide show
  1. package/.browserslistrc +15 -0
  2. package/.dockerignore +48 -0
  3. package/.editorconfig +16 -0
  4. package/.eslintrc.json +47 -0
  5. package/.vercelignore +7 -0
  6. package/.vscode/extensions.json +5 -0
  7. package/.vscode/settings.json +3 -0
  8. package/DOCKER.md +221 -0
  9. package/Dockerfile +33 -0
  10. package/README.md +268 -0
  11. package/angular.json +156 -0
  12. package/capacitor.config.ts +9 -0
  13. package/docker-compose.dev.yml +20 -0
  14. package/docker-compose.yml +18 -0
  15. package/ionic.config.json +7 -0
  16. package/jest.config.js +38 -0
  17. package/nginx.conf +50 -0
  18. package/package.json +131 -0
  19. package/patches/ng-packagr+20.3.2.patch +60 -0
  20. package/projects/facecog-liveness-verification/README.md +295 -0
  21. package/projects/facecog-liveness-verification/ng-package.json +7 -0
  22. package/projects/facecog-liveness-verification/package.json +48 -0
  23. package/projects/facecog-liveness-verification/scripts/build-with-wrapper-copy.js +38 -0
  24. package/projects/facecog-liveness-verification/scripts/copy-wrapper-after-ngc.js +35 -0
  25. package/projects/facecog-liveness-verification/sources/FaceLivenessReactWrapper.tsx +320 -0
  26. package/projects/facecog-liveness-verification/src/lib/components/aws-face-liveness/FaceLivenessReactWrapper.generated.d.ts +28 -0
  27. package/projects/facecog-liveness-verification/src/lib/components/aws-face-liveness/FaceLivenessReactWrapper.generated.js +247 -0
  28. package/projects/facecog-liveness-verification/src/lib/components/aws-face-liveness/FaceLivenessReactWrapper.generated.js.map +1 -0
  29. package/projects/facecog-liveness-verification/src/lib/components/aws-face-liveness/FaceLivenessReactWrapper.js.map +1 -0
  30. package/projects/facecog-liveness-verification/src/lib/components/aws-face-liveness/FaceLivenessReactWrapper.ts +5 -0
  31. package/projects/facecog-liveness-verification/src/lib/components/aws-face-liveness/aws-face-liveness.component.ts +500 -0
  32. package/projects/facecog-liveness-verification/src/lib/components/camera-permission/camera-permission.component.html +41 -0
  33. package/projects/facecog-liveness-verification/src/lib/components/camera-permission/camera-permission.component.scss +234 -0
  34. package/projects/facecog-liveness-verification/src/lib/components/camera-permission/camera-permission.component.spec.ts +158 -0
  35. package/projects/facecog-liveness-verification/src/lib/components/camera-permission/camera-permission.component.ts +58 -0
  36. package/projects/facecog-liveness-verification/src/lib/components/camera-verification/camera-verification.component.html +34 -0
  37. package/projects/facecog-liveness-verification/src/lib/components/camera-verification/camera-verification.component.ts +210 -0
  38. package/projects/facecog-liveness-verification/src/lib/components/dialogs/save-custom-pose-dialog.component.ts +174 -0
  39. package/projects/facecog-liveness-verification/src/lib/components/facetec-scan/facetec-scan.component.html +45 -0
  40. package/projects/facecog-liveness-verification/src/lib/components/facetec-scan/facetec-scan.component.scss +87 -0
  41. package/projects/facecog-liveness-verification/src/lib/components/facetec-scan/facetec-scan.component.ts +182 -0
  42. package/projects/facecog-liveness-verification/src/lib/components/intro/intro.component.html +394 -0
  43. package/projects/facecog-liveness-verification/src/lib/components/intro/intro.component.scss +1567 -0
  44. package/projects/facecog-liveness-verification/src/lib/components/intro/intro.component.spec.ts +699 -0
  45. package/projects/facecog-liveness-verification/src/lib/components/intro/intro.component.ts +721 -0
  46. package/projects/facecog-liveness-verification/src/lib/components/live-preview/live-preview.component.html +120 -0
  47. package/projects/facecog-liveness-verification/src/lib/components/live-preview/live-preview.component.scss +611 -0
  48. package/projects/facecog-liveness-verification/src/lib/components/live-preview/live-preview.component.spec.ts +605 -0
  49. package/projects/facecog-liveness-verification/src/lib/components/live-preview/live-preview.component.ts +524 -0
  50. package/projects/facecog-liveness-verification/src/lib/components/liveness-flow/liveness-flow.component.html +73 -0
  51. package/projects/facecog-liveness-verification/src/lib/components/liveness-flow/liveness-flow.component.scss +19 -0
  52. package/projects/facecog-liveness-verification/src/lib/components/liveness-flow/liveness-flow.component.spec.ts +673 -0
  53. package/projects/facecog-liveness-verification/src/lib/components/liveness-flow/liveness-flow.component.ts +963 -0
  54. package/projects/facecog-liveness-verification/src/lib/components/liveness-verification/liveness-verification.component.html +38 -0
  55. package/projects/facecog-liveness-verification/src/lib/components/liveness-verification/liveness-verification.component.scss +10 -0
  56. package/projects/facecog-liveness-verification/src/lib/components/liveness-verification/liveness-verification.component.ts +233 -0
  57. package/projects/facecog-liveness-verification/src/lib/components/pose-selection/pose-selection.component.html +17 -0
  58. package/projects/facecog-liveness-verification/src/lib/components/pose-selection/pose-selection.component.spec.ts +35 -0
  59. package/projects/facecog-liveness-verification/src/lib/components/pose-selection/pose-selection.component.ts +33 -0
  60. package/projects/facecog-liveness-verification/src/lib/components/processing/processing.component.html +17 -0
  61. package/projects/facecog-liveness-verification/src/lib/components/processing/processing.component.scss +156 -0
  62. package/projects/facecog-liveness-verification/src/lib/components/processing/processing.component.spec.ts +46 -0
  63. package/projects/facecog-liveness-verification/src/lib/components/processing/processing.component.ts +18 -0
  64. package/projects/facecog-liveness-verification/src/lib/components/verification-result/verification-result.component.html +190 -0
  65. package/projects/facecog-liveness-verification/src/lib/components/verification-result/verification-result.component.scss +534 -0
  66. package/projects/facecog-liveness-verification/src/lib/components/verification-result/verification-result.component.spec.ts +286 -0
  67. package/projects/facecog-liveness-verification/src/lib/components/verification-result/verification-result.component.ts +155 -0
  68. package/projects/facecog-liveness-verification/src/lib/interfaces/analyze-response.interface.ts +16 -0
  69. package/projects/facecog-liveness-verification/src/lib/interfaces/aws-face-liveness.interface.ts +46 -0
  70. package/projects/facecog-liveness-verification/src/lib/interfaces/backend-adapter.interface.ts +21 -0
  71. package/projects/facecog-liveness-verification/src/lib/interfaces/backend-http-client.interface.ts +93 -0
  72. package/projects/facecog-liveness-verification/src/lib/interfaces/backend-response.interface.ts +9 -0
  73. package/projects/facecog-liveness-verification/src/lib/interfaces/camera-provider.interface.ts +107 -0
  74. package/projects/facecog-liveness-verification/src/lib/interfaces/category-info.interface.ts +9 -0
  75. package/projects/facecog-liveness-verification/src/lib/interfaces/custom-pose-data.interface.ts +14 -0
  76. package/projects/facecog-liveness-verification/src/lib/interfaces/custom-pose-repository.interface.ts +48 -0
  77. package/projects/facecog-liveness-verification/src/lib/interfaces/custom-pose-response.interface.ts +14 -0
  78. package/projects/facecog-liveness-verification/src/lib/interfaces/index.ts +52 -0
  79. package/projects/facecog-liveness-verification/src/lib/interfaces/liveness-action-result.interface.ts +13 -0
  80. package/projects/facecog-liveness-verification/src/lib/interfaces/liveness-config.interface.ts +17 -0
  81. package/projects/facecog-liveness-verification/src/lib/interfaces/liveness-metadata.interface.ts +17 -0
  82. package/projects/facecog-liveness-verification/src/lib/interfaces/liveness-result.interface.ts +24 -0
  83. package/projects/facecog-liveness-verification/src/lib/interfaces/liveness-verification-config.interface.ts +41 -0
  84. package/projects/facecog-liveness-verification/src/lib/interfaces/multi-backend-analyze-response.interface.ts +21 -0
  85. package/projects/facecog-liveness-verification/src/lib/interfaces/multi-backend-liveness-result.interface.ts +14 -0
  86. package/projects/facecog-liveness-verification/src/lib/interfaces/pose-definition.interface.ts +35 -0
  87. package/projects/facecog-liveness-verification/src/lib/interfaces/pose-keypoint.interface.ts +12 -0
  88. package/projects/facecog-liveness-verification/src/lib/interfaces/pose-match-result.interface.ts +9 -0
  89. package/projects/facecog-liveness-verification/src/lib/interfaces/pose-verify-response.interface.ts +8 -0
  90. package/projects/facecog-liveness-verification/src/lib/interfaces/scan-results.interface.ts +29 -0
  91. package/projects/facecog-liveness-verification/src/lib/interfaces/verification-plan.interface.ts +42 -0
  92. package/projects/facecog-liveness-verification/src/lib/interfaces/verification-progress-event.interface.ts +12 -0
  93. package/projects/facecog-liveness-verification/src/lib/interfaces/verification-session.interface.ts +72 -0
  94. package/projects/facecog-liveness-verification/src/lib/interfaces/verification-step-change-event.interface.ts +11 -0
  95. package/projects/facecog-liveness-verification/src/lib/interfaces/video-recording.interface.ts +9 -0
  96. package/projects/facecog-liveness-verification/src/lib/liveness-verification.module.ts +123 -0
  97. package/projects/facecog-liveness-verification/src/lib/models/constants/aws-face-liveness-component.token.ts +23 -0
  98. package/projects/facecog-liveness-verification/src/lib/models/constants/category-info.constant.ts +14 -0
  99. package/projects/facecog-liveness-verification/src/lib/models/constants/default-liveness-config.constant.ts +18 -0
  100. package/projects/facecog-liveness-verification/src/lib/models/constants/index.ts +5 -0
  101. package/projects/facecog-liveness-verification/src/lib/models/constants/liveness-verification-config.token.ts +16 -0
  102. package/projects/facecog-liveness-verification/src/lib/models/constants/pose-definitions.constant.ts +377 -0
  103. package/projects/facecog-liveness-verification/src/lib/models/index.ts +5 -0
  104. package/projects/facecog-liveness-verification/src/lib/models/utils/index.ts +2 -0
  105. package/projects/facecog-liveness-verification/src/lib/models/utils/pose.utils.spec.ts +76 -0
  106. package/projects/facecog-liveness-verification/src/lib/models/utils/pose.utils.ts +59 -0
  107. package/projects/facecog-liveness-verification/src/lib/services/aws-face-liveness.service.ts +49 -0
  108. package/projects/facecog-liveness-verification/src/lib/services/backend-http.service.spec.ts +111 -0
  109. package/projects/facecog-liveness-verification/src/lib/services/backend-http.service.ts +130 -0
  110. package/projects/facecog-liveness-verification/src/lib/services/backends/azure-backend.service.spec.ts +69 -0
  111. package/projects/facecog-liveness-verification/src/lib/services/backends/azure-backend.service.ts +72 -0
  112. package/projects/facecog-liveness-verification/src/lib/services/backends/facetec-backend.service.spec.ts +24 -0
  113. package/projects/facecog-liveness-verification/src/lib/services/backends/facetec-backend.service.ts +35 -0
  114. package/projects/facecog-liveness-verification/src/lib/services/backends/mock-backend.service.spec.ts +36 -0
  115. package/projects/facecog-liveness-verification/src/lib/services/backends/mock-backend.service.ts +39 -0
  116. package/projects/facecog-liveness-verification/src/lib/services/backends/openpose-backend.service.spec.ts +81 -0
  117. package/projects/facecog-liveness-verification/src/lib/services/backends/openpose-backend.service.ts +72 -0
  118. package/projects/facecog-liveness-verification/src/lib/services/backends/rekognition-analysis-backend.service.spec.ts +69 -0
  119. package/projects/facecog-liveness-verification/src/lib/services/backends/rekognition-analysis-backend.service.ts +83 -0
  120. package/projects/facecog-liveness-verification/src/lib/services/camera.service.spec.ts +200 -0
  121. package/projects/facecog-liveness-verification/src/lib/services/camera.service.ts +155 -0
  122. package/projects/facecog-liveness-verification/src/lib/services/custom-poses-api.service.ts +117 -0
  123. package/projects/facecog-liveness-verification/src/lib/services/index.ts +18 -0
  124. package/projects/facecog-liveness-verification/src/lib/services/liveness-backend.service.spec.ts +103 -0
  125. package/projects/facecog-liveness-verification/src/lib/services/liveness-backend.service.ts +61 -0
  126. package/projects/facecog-liveness-verification/src/lib/services/liveness-config.service.spec.ts +109 -0
  127. package/projects/facecog-liveness-verification/src/lib/services/liveness-config.service.ts +70 -0
  128. package/projects/facecog-liveness-verification/src/lib/services/liveness-orchestrator.service.spec.ts +144 -0
  129. package/projects/facecog-liveness-verification/src/lib/services/liveness-orchestrator.service.ts +162 -0
  130. package/projects/facecog-liveness-verification/src/lib/services/pose-detection/hand-gesture-detection.service.ts +315 -0
  131. package/projects/facecog-liveness-verification/src/lib/services/pose-detection/index.ts +5 -0
  132. package/projects/facecog-liveness-verification/src/lib/services/pose-detection/openpose.service.ts +287 -0
  133. package/projects/facecog-liveness-verification/src/lib/services/pose-detection/pose-comparison.service.ts +353 -0
  134. package/projects/facecog-liveness-verification/src/lib/services/pose-detection/pose-matching.service.ts +2370 -0
  135. package/projects/facecog-liveness-verification/src/lib/services/pose-detection/reference-pose.service.ts +271 -0
  136. package/projects/facecog-liveness-verification/src/lib/services/pose-selection.service.spec.ts +183 -0
  137. package/projects/facecog-liveness-verification/src/lib/services/pose-selection.service.ts +179 -0
  138. package/projects/facecog-liveness-verification/src/lib/services/verification-api.service.spec.ts +159 -0
  139. package/projects/facecog-liveness-verification/src/lib/services/verification-api.service.ts +151 -0
  140. package/projects/facecog-liveness-verification/src/lib/services/verification-plan.service.spec.ts +184 -0
  141. package/projects/facecog-liveness-verification/src/lib/services/verification-plan.service.ts +94 -0
  142. package/projects/facecog-liveness-verification/src/lib/services/video-recorder.service.spec.ts +52 -0
  143. package/projects/facecog-liveness-verification/src/lib/services/video-recorder.service.ts +117 -0
  144. package/projects/facecog-liveness-verification/src/lib/types/detection-strategy.type.ts +5 -0
  145. package/projects/facecog-liveness-verification/src/lib/types/index.ts +7 -0
  146. package/projects/facecog-liveness-verification/src/lib/types/liveness-action.type.ts +31 -0
  147. package/projects/facecog-liveness-verification/src/lib/types/liveness-backend.type.ts +5 -0
  148. package/projects/facecog-liveness-verification/src/lib/types/pose-category.type.ts +5 -0
  149. package/projects/facecog-liveness-verification/src/lib/types/pose-difficulty.type.ts +5 -0
  150. package/projects/facecog-liveness-verification/src/lib/types/verification-flow-step.type.ts +5 -0
  151. package/projects/facecog-liveness-verification/src/lib/types/verification-step-kind.type.ts +4 -0
  152. package/projects/facecog-liveness-verification/src/public-api.ts +150 -0
  153. package/projects/facecog-liveness-verification/tsconfig.lib.json +20 -0
  154. package/projects/facecog-liveness-verification/tsconfig.lib.prod.json +11 -0
  155. package/projects/facecog-liveness-verification/tsconfig.spec.json +13 -0
  156. package/projects/facecog-liveness-verification/tsconfig.wrapper.json +15 -0
  157. package/projects/facecog-liveness-verification-test/src/app/app-routing.module.ts +22 -0
  158. package/projects/facecog-liveness-verification-test/src/app/app.component.html +3 -0
  159. package/projects/facecog-liveness-verification-test/src/app/app.component.scss +0 -0
  160. package/projects/facecog-liveness-verification-test/src/app/app.component.ts +11 -0
  161. package/projects/facecog-liveness-verification-test/src/app/app.module.ts +27 -0
  162. package/projects/facecog-liveness-verification-test/src/app/home/home-routing.module.ts +16 -0
  163. package/projects/facecog-liveness-verification-test/src/app/home/home.module.ts +19 -0
  164. package/projects/facecog-liveness-verification-test/src/app/home/home.page.html +39 -0
  165. package/projects/facecog-liveness-verification-test/src/app/home/home.page.scss +97 -0
  166. package/projects/facecog-liveness-verification-test/src/app/home/home.page.spec.ts +24 -0
  167. package/projects/facecog-liveness-verification-test/src/app/home/home.page.ts +92 -0
  168. package/projects/facecog-liveness-verification-test/src/app/home/verification-modal.component.ts +106 -0
  169. package/projects/facecog-liveness-verification-test/src/assets/fonts/gilroy/Gilroy-Bold_0.ttf +0 -0
  170. package/projects/facecog-liveness-verification-test/src/assets/fonts/gilroy/Gilroy-Medium_0.ttf +0 -0
  171. package/projects/facecog-liveness-verification-test/src/assets/fonts/gilroy/Gilroy-Regular_0.ttf +0 -0
  172. package/projects/facecog-liveness-verification-test/src/assets/fonts/gilroy/Gilroy-SemiBold_0.ttf +0 -0
  173. package/projects/facecog-liveness-verification-test/src/assets/fonts/gilroy/Gilroy-Thin_0.ttf +0 -0
  174. package/projects/facecog-liveness-verification-test/src/assets/icon/favicon.png +0 -0
  175. package/projects/facecog-liveness-verification-test/src/assets/images/poses/Five_Fingers_Left.jpg +0 -0
  176. package/projects/facecog-liveness-verification-test/src/assets/images/poses/Left_Palm.jpg +0 -0
  177. package/projects/facecog-liveness-verification-test/src/assets/images/poses/Ok_Sign_Right.jpg +0 -0
  178. package/projects/facecog-liveness-verification-test/src/assets/images/poses/Peace_Sign_Left.jpg +0 -0
  179. package/projects/facecog-liveness-verification-test/src/assets/images/poses/README.md +77 -0
  180. package/projects/facecog-liveness-verification-test/src/assets/images/poses/Right_Palm.jpg +0 -0
  181. package/projects/facecog-liveness-verification-test/src/assets/images/poses/Speak_Phrase.jpg +0 -0
  182. package/projects/facecog-liveness-verification-test/src/assets/images/poses/Three_Fingers_Right.jpg +0 -0
  183. package/projects/facecog-liveness-verification-test/src/assets/images/poses/Thumbs_Up_Left.jpg +0 -0
  184. package/projects/facecog-liveness-verification-test/src/assets/images/poses/Thumbs_Up_Right.jpg +0 -0
  185. package/projects/facecog-liveness-verification-test/src/assets/images/poses/Wave_Right.jpg +0 -0
  186. package/projects/facecog-liveness-verification-test/src/assets/images/poses/blink.jpeg +0 -0
  187. package/projects/facecog-liveness-verification-test/src/assets/images/poses/blink_twice.jpeg +0 -0
  188. package/projects/facecog-liveness-verification-test/src/assets/images/poses/center_face.png +0 -0
  189. package/projects/facecog-liveness-verification-test/src/assets/images/poses/clap.jpeg +0 -0
  190. package/projects/facecog-liveness-verification-test/src/assets/images/poses/cover_mouth.png +0 -0
  191. package/projects/facecog-liveness-verification-test/src/assets/images/poses/cover_right_eye.png +0 -0
  192. package/projects/facecog-liveness-verification-test/src/assets/images/poses/cross_arms.png +0 -0
  193. package/projects/facecog-liveness-verification-test/src/assets/images/poses/face_straight.png +0 -0
  194. package/projects/facecog-liveness-verification-test/src/assets/images/poses/follow_dot.png +0 -0
  195. package/projects/facecog-liveness-verification-test/src/assets/images/poses/look_down.png +0 -0
  196. package/projects/facecog-liveness-verification-test/src/assets/images/poses/look_up.png +0 -0
  197. package/projects/facecog-liveness-verification-test/src/assets/images/poses/move_closer.png +0 -0
  198. package/projects/facecog-liveness-verification-test/src/assets/images/poses/nod.png +0 -0
  199. package/projects/facecog-liveness-verification-test/src/assets/images/poses/open_mouth.png +0 -0
  200. package/projects/facecog-liveness-verification-test/src/assets/images/poses/raise_eyebrow.png +0 -0
  201. package/projects/facecog-liveness-verification-test/src/assets/images/poses/rotate_face.jpeg +0 -0
  202. package/projects/facecog-liveness-verification-test/src/assets/images/poses/shake_head.jpeg +0 -0
  203. package/projects/facecog-liveness-verification-test/src/assets/images/poses/smile.png +0 -0
  204. package/projects/facecog-liveness-verification-test/src/assets/images/poses/tilt_left.png +0 -0
  205. package/projects/facecog-liveness-verification-test/src/assets/images/poses/tilt_right.png +0 -0
  206. package/projects/facecog-liveness-verification-test/src/assets/images/poses/touch_chin_left.jpg +0 -0
  207. package/projects/facecog-liveness-verification-test/src/assets/images/poses/touch_left_cheek.jpeg +0 -0
  208. package/projects/facecog-liveness-verification-test/src/assets/images/poses/touch_nose_right.png +0 -0
  209. package/projects/facecog-liveness-verification-test/src/assets/images/poses/touch_right_cheek.jpeg +0 -0
  210. package/projects/facecog-liveness-verification-test/src/assets/images/poses/turn_left.png +0 -0
  211. package/projects/facecog-liveness-verification-test/src/assets/images/poses/turn_right.png +0 -0
  212. package/projects/facecog-liveness-verification-test/src/assets/images/poses/wink.jpeg +0 -0
  213. package/projects/facecog-liveness-verification-test/src/assets/images/reference-pose.jpg +0 -0
  214. package/projects/facecog-liveness-verification-test/src/assets/shapes.svg +1 -0
  215. package/projects/facecog-liveness-verification-test/src/environments/environment.prod.ts +4 -0
  216. package/projects/facecog-liveness-verification-test/src/environments/environment.ts +17 -0
  217. package/projects/facecog-liveness-verification-test/src/global.scss +288 -0
  218. package/projects/facecog-liveness-verification-test/src/index.html +31 -0
  219. package/projects/facecog-liveness-verification-test/src/main.ts +6 -0
  220. package/projects/facecog-liveness-verification-test/src/polyfills.ts +55 -0
  221. package/projects/facecog-liveness-verification-test/src/theme/nextsapien-theme.scss +174 -0
  222. package/projects/facecog-liveness-verification-test/src/theme/variables.scss +2 -0
  223. package/projects/facecog-liveness-verification-test/src/zone-flags.ts +6 -0
  224. package/projects/facecog-liveness-verification-test/tsconfig.app.json +15 -0
  225. package/projects/facecog-liveness-verification-test/tsconfig.spec.json +14 -0
  226. package/setup-jest.ts +118 -0
  227. package/tsconfig.json +41 -0
  228. package/tsconfig.spec.json +15 -0
  229. package/vercel.json +24 -0
@@ -0,0 +1,81 @@
1
+ import { TestBed } from '@angular/core/testing';
2
+ import { of, throwError } from 'rxjs';
3
+ import { OpenPoseBackendService } from './openpose-backend.service';
4
+ import { BACKEND_HTTP_CLIENT } from '../../interfaces/backend-http-client.interface';
5
+
6
+ describe('OpenPoseBackendService', () => {
7
+ describe('without backend client', () => {
8
+ let service: OpenPoseBackendService;
9
+
10
+ beforeEach(() => {
11
+ TestBed.configureTestingModule({ providers: [OpenPoseBackendService] });
12
+ service = TestBed.inject(OpenPoseBackendService);
13
+ });
14
+
15
+ it('should be created', () => {
16
+ expect(service).toBeTruthy();
17
+ });
18
+
19
+ it('isAvailable returns false', () => {
20
+ expect(service.isAvailable()).toBe(false);
21
+ });
22
+
23
+ it('getName returns openpose', () => {
24
+ expect(service.getName()).toBe('openpose');
25
+ });
26
+
27
+ it('verifyLiveness throws error without client', (done) => {
28
+ service.verifyLiveness('img').subscribe({
29
+ error: (err) => {
30
+ expect(err.message).toContain('Backend HTTP client not configured');
31
+ done();
32
+ }
33
+ });
34
+ });
35
+ });
36
+
37
+ describe('with backend client', () => {
38
+ let service: OpenPoseBackendService;
39
+ let mockClient: any;
40
+
41
+ beforeEach(() => {
42
+ mockClient = {
43
+ createSession: jest.fn().mockReturnValue(of({ sessionId: 's1' })),
44
+ analyzeLiveness: jest.fn().mockReturnValue(of({ isLive: true, confidence: 0.9, metadata: { backend: 'openpose' } })),
45
+ };
46
+
47
+ TestBed.configureTestingModule({
48
+ providers: [
49
+ OpenPoseBackendService,
50
+ { provide: BACKEND_HTTP_CLIENT, useValue: mockClient }
51
+ ]
52
+ });
53
+ service = TestBed.inject(OpenPoseBackendService);
54
+ });
55
+
56
+ it('isAvailable returns true', () => {
57
+ expect(service.isAvailable()).toBe(true);
58
+ });
59
+
60
+ it('verifyLiveness creates session and analyzes', (done) => {
61
+ service.verifyLiveness('imgdata').subscribe(res => {
62
+ expect(res.isLive).toBe(true);
63
+ expect(res.confidence).toBe(0.9);
64
+ expect(mockClient.createSession).toHaveBeenCalledWith('openpose');
65
+ expect(mockClient.analyzeLiveness).toHaveBeenCalledWith('s1', 'openpose', 'imgdata', ['imgdata']);
66
+ done();
67
+ });
68
+ });
69
+
70
+ it('verifyLiveness handles error', (done) => {
71
+ mockClient.createSession.mockReturnValue(throwError(() => new Error('Network error')));
72
+
73
+ service.verifyLiveness('imgdata').subscribe({
74
+ error: (err) => {
75
+ expect(err.message).toContain('OpenPose verification failed');
76
+ done();
77
+ }
78
+ });
79
+ });
80
+ });
81
+ });
@@ -0,0 +1,72 @@
1
+ import { Injectable, Inject, Optional } from '@angular/core';
2
+ import { Observable, switchMap, map, catchError, throwError, retry, timeout, timer } from 'rxjs';
3
+ import { BackendResponse } from '../../interfaces/backend-response.interface';
4
+ import { IBackendAdapter } from '../../interfaces/backend-adapter.interface';
5
+ import { IBackendHttpClient, BACKEND_HTTP_CLIENT } from '../../interfaces/backend-http-client.interface';
6
+
7
+ /**
8
+ * OpenPose/MoveNet Liveness Backend
9
+ * Connects to Express backend API which uses TensorFlow.js and MoveNet for pose estimation
10
+ */
11
+ @Injectable({
12
+ providedIn: 'root'
13
+ })
14
+ export class OpenPoseBackendService implements IBackendAdapter {
15
+ private readonly REQUEST_TIMEOUT = 30000; // 30 seconds
16
+ private readonly MAX_RETRIES = 3;
17
+
18
+ constructor(
19
+ @Optional() @Inject(BACKEND_HTTP_CLIENT) private backendClient: IBackendHttpClient | null
20
+ ) {}
21
+
22
+ verifyLiveness(imageData: string, videoBlob?: Blob): Observable<BackendResponse> {
23
+ if (!this.backendClient) {
24
+ console.warn('[OpenPoseBackend] No backend HTTP client configured');
25
+ return throwError(() => new Error('Backend HTTP client not configured. Provide BACKEND_HTTP_CLIENT.'));
26
+ }
27
+
28
+ // Create session with OpenPose backend
29
+ return this.backendClient.createSession('openpose').pipe(
30
+ timeout(this.REQUEST_TIMEOUT),
31
+ switchMap(session => {
32
+ // Analyze liveness with the session
33
+ return this.backendClient!.analyzeLiveness(
34
+ session.sessionId,
35
+ 'openpose',
36
+ imageData,
37
+ [imageData] // Send as frames array
38
+ );
39
+ }),
40
+ timeout(this.REQUEST_TIMEOUT),
41
+ retry({
42
+ count: this.MAX_RETRIES,
43
+ delay: (error, retryCount) => {
44
+ console.warn(`OpenPose backend retry attempt ${retryCount}/${this.MAX_RETRIES}`, error);
45
+ // Exponential backoff: 1s, 2s, 4s
46
+ const delayMs = Math.min(1000 * Math.pow(2, retryCount - 1), 5000);
47
+ return timer(delayMs);
48
+ }
49
+ }),
50
+ map(response => ({
51
+ isLive: response.isLive,
52
+ confidence: response.confidence,
53
+ details: response.metadata
54
+ })),
55
+ catchError(error => {
56
+ console.error('OpenPose backend error after retries:', error);
57
+ if (error.name === 'TimeoutError') {
58
+ return throwError(() => new Error('OpenPose request timed out. Please try again.'));
59
+ }
60
+ return throwError(() => new Error('OpenPose verification failed. Please check your connection.'));
61
+ })
62
+ );
63
+ }
64
+
65
+ isAvailable(): boolean {
66
+ return this.backendClient !== null;
67
+ }
68
+
69
+ getName(): string {
70
+ return 'openpose';
71
+ }
72
+ }
@@ -0,0 +1,69 @@
1
+ import { TestBed } from '@angular/core/testing';
2
+ import { of, throwError } from 'rxjs';
3
+ import { RekognitionAnalysisBackendService } from './rekognition-analysis-backend.service';
4
+ import { BACKEND_HTTP_CLIENT } from '../../interfaces/backend-http-client.interface';
5
+
6
+ describe('RekognitionAnalysisBackendService', () => {
7
+ describe('without backend client', () => {
8
+ let service: RekognitionAnalysisBackendService;
9
+
10
+ beforeEach(() => {
11
+ TestBed.configureTestingModule({ providers: [RekognitionAnalysisBackendService] });
12
+ service = TestBed.inject(RekognitionAnalysisBackendService);
13
+ });
14
+
15
+ it('should be created', () => { expect(service).toBeTruthy(); });
16
+ it('isAvailable returns false', () => { expect(service.isAvailable()).toBe(false); });
17
+ it('getName returns amazon', () => { expect(service.getName()).toBe('amazon'); });
18
+
19
+ it('verifyLiveness throws error without client', (done) => {
20
+ service.verifyLiveness('img').subscribe({
21
+ error: (err) => {
22
+ expect(err.message).toContain('Backend HTTP client not configured');
23
+ done();
24
+ }
25
+ });
26
+ });
27
+ });
28
+
29
+ describe('with backend client', () => {
30
+ let service: RekognitionAnalysisBackendService;
31
+ let mockClient: any;
32
+
33
+ beforeEach(() => {
34
+ mockClient = {
35
+ createSession: jest.fn().mockReturnValue(of({ sessionId: 's1' })),
36
+ analyzeLiveness: jest.fn().mockReturnValue(of({ isLive: true, confidence: 0.92, metadata: { backend: 'amazon' } })),
37
+ };
38
+
39
+ TestBed.configureTestingModule({
40
+ providers: [
41
+ RekognitionAnalysisBackendService,
42
+ { provide: BACKEND_HTTP_CLIENT, useValue: mockClient }
43
+ ]
44
+ });
45
+ service = TestBed.inject(RekognitionAnalysisBackendService);
46
+ });
47
+
48
+ it('isAvailable returns true', () => { expect(service.isAvailable()).toBe(true); });
49
+
50
+ it('verifyLiveness creates session and analyzes', (done) => {
51
+ service.verifyLiveness('imgdata').subscribe(res => {
52
+ expect(res.isLive).toBe(true);
53
+ expect(res.confidence).toBe(0.92);
54
+ expect(mockClient.createSession).toHaveBeenCalledWith('amazon');
55
+ done();
56
+ });
57
+ });
58
+
59
+ it('verifyLiveness handles error', (done) => {
60
+ mockClient.createSession.mockReturnValue(throwError(() => new Error('Network error')));
61
+ service.verifyLiveness('imgdata').subscribe({
62
+ error: (err) => {
63
+ expect(err.message).toContain('Amazon Rekognition verification failed');
64
+ done();
65
+ }
66
+ });
67
+ });
68
+ });
69
+ });
@@ -0,0 +1,83 @@
1
+ import { Injectable, Inject, Optional } from '@angular/core';
2
+ import { Observable, switchMap, map, catchError, throwError, retry, timeout, timer, of } from 'rxjs';
3
+ import { BackendResponse } from '../../interfaces/backend-response.interface';
4
+ import { IBackendAdapter } from '../../interfaces/backend-adapter.interface';
5
+ import { IBackendHttpClient, BACKEND_HTTP_CLIENT } from '../../interfaces/backend-http-client.interface';
6
+
7
+ /**
8
+ * Amazon Rekognition Analysis Backend
9
+ * Connects to Express backend API which calls AWS Rekognition DetectFaces API
10
+ * for face detection and pose/liveness analysis.
11
+ *
12
+ * Note: This is different from AWS Face Liveness service (used in the test app's
13
+ * aws-face-liveness.service.ts which handles FaceLiveness sessions).
14
+ * This service uses Rekognition's DetectFaces for static image analysis.
15
+ */
16
+ @Injectable({
17
+ providedIn: 'root'
18
+ })
19
+ export class RekognitionAnalysisBackendService implements IBackendAdapter {
20
+ private readonly REQUEST_TIMEOUT = 30000; // 30 seconds
21
+ private readonly MAX_RETRIES = 3;
22
+
23
+ constructor(
24
+ @Optional() @Inject(BACKEND_HTTP_CLIENT) private backendClient: IBackendHttpClient | null
25
+ ) {}
26
+
27
+ verifyLiveness(imageData: string, videoBlob?: Blob): Observable<BackendResponse> {
28
+ if (!this.backendClient) {
29
+ console.warn('[RekognitionAnalysisBackend] No backend HTTP client configured');
30
+ return throwError(() => new Error('Backend HTTP client not configured. Provide BACKEND_HTTP_CLIENT.'));
31
+ }
32
+
33
+ // Create session with Amazon backend
34
+ return this.backendClient.createSession('amazon').pipe(
35
+ timeout(this.REQUEST_TIMEOUT),
36
+ switchMap(session => {
37
+ // Analyze liveness with the session
38
+ return this.backendClient!.analyzeLiveness(
39
+ session.sessionId,
40
+ 'amazon',
41
+ imageData,
42
+ [imageData] // Send as frames array
43
+ );
44
+ }),
45
+ timeout(this.REQUEST_TIMEOUT),
46
+ retry({
47
+ count: this.MAX_RETRIES,
48
+ delay: (error, retryCount) => {
49
+ console.warn(`Rekognition backend retry attempt ${retryCount}/${this.MAX_RETRIES}`, error);
50
+ // Exponential backoff: 1s, 2s, 4s
51
+ const delayMs = Math.min(1000 * Math.pow(2, retryCount - 1), 5000);
52
+ return timer(delayMs);
53
+ }
54
+ }),
55
+ map(response => ({
56
+ isLive: response.isLive,
57
+ confidence: response.confidence,
58
+ details: response.metadata
59
+ })),
60
+ catchError(error => {
61
+ console.error('Rekognition backend error after retries:', error);
62
+ if (error.name === 'TimeoutError') {
63
+ return throwError(() => new Error('Amazon Rekognition request timed out. Please try again.'));
64
+ }
65
+ return throwError(() => new Error('Amazon Rekognition verification failed. Please check your connection.'));
66
+ })
67
+ );
68
+ }
69
+
70
+ isAvailable(): boolean {
71
+ return this.backendClient !== null;
72
+ }
73
+
74
+ getName(): string {
75
+ return 'amazon';
76
+ }
77
+ }
78
+
79
+ /**
80
+ * @deprecated Use RekognitionAnalysisBackendService instead.
81
+ * This alias is kept for backwards compatibility.
82
+ */
83
+ export const AmazonBackendService = RekognitionAnalysisBackendService;
@@ -0,0 +1,200 @@
1
+ import { TestBed } from '@angular/core/testing';
2
+ import { CameraService } from './camera.service';
3
+
4
+ describe('CameraService', () => {
5
+ let service: CameraService;
6
+
7
+ beforeEach(() => {
8
+ TestBed.configureTestingModule({
9
+ providers: [CameraService]
10
+ });
11
+ service = TestBed.inject(CameraService);
12
+ });
13
+
14
+ it('should be created', () => {
15
+ expect(service).toBeTruthy();
16
+ });
17
+
18
+ it('getStream returns null initially', () => {
19
+ expect(service.getStream()).toBeNull();
20
+ });
21
+
22
+ it('isRecording returns false initially', () => {
23
+ expect(service.isRecording()).toBe(false);
24
+ });
25
+
26
+ it('isCameraActive returns false initially', () => {
27
+ expect(service.isCameraActive()).toBe(false);
28
+ });
29
+
30
+ it('getCameraState returns correct initial state', () => {
31
+ const state = service.getCameraState();
32
+ expect(state.hasStream).toBe(false);
33
+ expect(state.isActive).toBe(false);
34
+ expect(state.isRecording).toBe(false);
35
+ expect(state.trackCount).toBe(0);
36
+ });
37
+
38
+ it('stopCamera is safe when no stream', () => {
39
+ expect(() => service.stopCamera()).not.toThrow();
40
+ });
41
+
42
+ it('requestPermissions falls back to browser API', async () => {
43
+ const result = await service.requestPermissions();
44
+ expect(result).toBe(true);
45
+ expect(navigator.mediaDevices.getUserMedia).toHaveBeenCalled();
46
+ });
47
+
48
+ it('startCamera sets up stream', async () => {
49
+ const video = document.createElement('video');
50
+ Object.defineProperty(video, 'play', { value: jest.fn().mockResolvedValue(undefined) });
51
+
52
+ const stream = await service.startCamera(video);
53
+ expect(stream).toBeDefined();
54
+ expect(service.getStream()).toBe(stream);
55
+ });
56
+
57
+ it('startCameraLegacy returns true on success', async () => {
58
+ const video = document.createElement('video');
59
+ Object.defineProperty(video, 'play', { value: jest.fn().mockResolvedValue(undefined) });
60
+
61
+ const result = await service.startCameraLegacy(video);
62
+ expect(result).toBe(true);
63
+ });
64
+
65
+ it('startCameraLegacy returns false on failure', async () => {
66
+ (navigator.mediaDevices.getUserMedia as jest.Mock).mockRejectedValueOnce(new Error('No camera'));
67
+ const video = document.createElement('video');
68
+
69
+ const result = await service.startCameraLegacy(video);
70
+ expect(result).toBe(false);
71
+ });
72
+
73
+ it('captureFrame returns data URL', () => {
74
+ const video = document.createElement('video');
75
+ const origCreateElement = document.createElement.bind(document);
76
+ jest.spyOn(document, 'createElement').mockImplementation((tag: string) => {
77
+ const el = origCreateElement(tag);
78
+ if (tag === 'canvas') {
79
+ (el as any).toDataURL = jest.fn().mockReturnValue('data:image/jpeg;base64,mock');
80
+ }
81
+ return el;
82
+ });
83
+ const frame = service.captureFrame(video);
84
+ expect(frame).toContain('data:image');
85
+ (document.createElement as jest.Mock).mockRestore();
86
+ });
87
+
88
+ it('captureFrames returns multiple frames', async () => {
89
+ const video = document.createElement('video');
90
+ const frames = await service.captureFrames(video, 3, 10);
91
+ expect(frames.length).toBe(3);
92
+ });
93
+
94
+ it('analyzeBrightness returns brightness data', () => {
95
+ const video = document.createElement('video');
96
+ const data = service.analyzeBrightness(video);
97
+ expect(data).toBeDefined();
98
+ expect(typeof data.brightness).toBe('number');
99
+ expect(typeof data.isLowLight).toBe('boolean');
100
+ expect(typeof data.isTooLight).toBe('boolean');
101
+ });
102
+
103
+ it('startBrightnessMonitoring returns interval id', () => {
104
+ const video = document.createElement('video');
105
+ const callback = jest.fn();
106
+ const id = service.startBrightnessMonitoring(video, callback, 100);
107
+ expect(typeof id).toBe('number');
108
+ service.stopBrightnessMonitoring(id);
109
+ });
110
+
111
+ it('startRecording returns false when no stream', () => {
112
+ expect(service.startRecording()).toBe(false);
113
+ });
114
+
115
+ it('startRecording returns true when stream exists', async () => {
116
+ const video = document.createElement('video');
117
+ Object.defineProperty(video, 'play', { value: jest.fn().mockResolvedValue(undefined) });
118
+ await service.startCamera(video);
119
+
120
+ const result = service.startRecording();
121
+ expect(result).toBe(true);
122
+ expect(service.isRecording()).toBe(true);
123
+ });
124
+
125
+ it('stopRecording rejects when no recorder', async () => {
126
+ await expect(service.stopRecording()).rejects.toThrow('No media recorder');
127
+ });
128
+
129
+ it('releaseCameraForFaceTec cleans up everything', async () => {
130
+ const video = document.createElement('video');
131
+ Object.defineProperty(video, 'play', { value: jest.fn().mockResolvedValue(undefined) });
132
+ await service.startCamera(video);
133
+
134
+ service.releaseCameraForFaceTec();
135
+ expect(service.getStream()).toBeNull();
136
+ expect(service.isRecording()).toBe(false);
137
+ });
138
+
139
+ it('checkPermissions falls back to browser permissions API', async () => {
140
+ const mockQuery = jest.fn().mockResolvedValue({ state: 'granted' });
141
+ Object.defineProperty(navigator, 'permissions', {
142
+ value: { query: mockQuery },
143
+ writable: true,
144
+ configurable: true
145
+ });
146
+ const result = await service.checkPermissions();
147
+ expect(typeof result).toBe('boolean');
148
+ });
149
+
150
+ it('checkPermissions returns false when all APIs fail', async () => {
151
+ Object.defineProperty(navigator, 'permissions', {
152
+ value: { query: jest.fn().mockRejectedValue(new Error('Not supported')) },
153
+ writable: true,
154
+ configurable: true
155
+ });
156
+ const result = await service.checkPermissions();
157
+ expect(result).toBe(false);
158
+ });
159
+
160
+ it('requestPermissions returns false when getUserMedia fails', async () => {
161
+ (navigator.mediaDevices.getUserMedia as jest.Mock).mockRejectedValueOnce(new Error('Denied'));
162
+ const result = await service.requestPermissions();
163
+ expect(result).toBe(false);
164
+ });
165
+
166
+ it('stopCamera stops tracks and clears stream', async () => {
167
+ const video = document.createElement('video');
168
+ Object.defineProperty(video, 'play', { value: jest.fn().mockResolvedValue(undefined) });
169
+ await service.startCamera(video);
170
+ expect(service.getStream()).not.toBeNull();
171
+
172
+ service.stopCamera();
173
+ expect(service.getStream()).toBeNull();
174
+ });
175
+
176
+ it('reacquireCameraForGestures returns true on success', async () => {
177
+ const video = document.createElement('video');
178
+ Object.defineProperty(video, 'play', { value: jest.fn().mockResolvedValue(undefined) });
179
+ const result = await service.reacquireCameraForGestures(video);
180
+ expect(result).toBe(true);
181
+ });
182
+
183
+ it('reacquireCameraForGestures returns false on failure', async () => {
184
+ (navigator.mediaDevices.getUserMedia as jest.Mock).mockRejectedValueOnce(new Error('No camera'));
185
+ const video = document.createElement('video');
186
+ const result = await service.reacquireCameraForGestures(video);
187
+ expect(result).toBe(false);
188
+ });
189
+
190
+ it('releaseCameraForFaceTec stops recording if active', async () => {
191
+ const video = document.createElement('video');
192
+ Object.defineProperty(video, 'play', { value: jest.fn().mockResolvedValue(undefined) });
193
+ await service.startCamera(video);
194
+ service.startRecording();
195
+ expect(service.isRecording()).toBe(true);
196
+
197
+ service.releaseCameraForFaceTec();
198
+ expect(service.getStream()).toBeNull();
199
+ });
200
+ });
@@ -0,0 +1,155 @@
1
+ import { Injectable } from '@angular/core';
2
+ import { ICameraProvider, BrightnessData } from '../interfaces/camera-provider.interface';
3
+
4
+ @Injectable({
5
+ providedIn: 'root'
6
+ })
7
+ export class CameraService implements ICameraProvider {
8
+ private mediaStream: MediaStream | null = null;
9
+ private mediaRecorder: MediaRecorder | null = null;
10
+ private recordedChunks: Blob[] = [];
11
+
12
+ async checkPermissions(): Promise<boolean> {
13
+ try {
14
+ const { Camera } = await import('@capacitor/camera');
15
+ const p = await Camera.checkPermissions();
16
+ return p.camera === 'granted';
17
+ } catch {
18
+ try {
19
+ const r = await navigator.permissions.query({ name: 'camera' as PermissionName });
20
+ return r.state === 'granted';
21
+ } catch {
22
+ return false;
23
+ }
24
+ }
25
+ }
26
+
27
+ async requestPermissions(): Promise<boolean> {
28
+ try {
29
+ const { Camera } = await import('@capacitor/camera');
30
+ const p = await Camera.requestPermissions();
31
+ if (p.camera === 'granted') return true;
32
+ } catch { /* fallback to browser */ }
33
+ try {
34
+ const stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: 'user' }, audio: false });
35
+ stream.getTracks().forEach(t => t.stop());
36
+ return true;
37
+ } catch {
38
+ return false;
39
+ }
40
+ }
41
+
42
+ async startCamera(videoElement: HTMLVideoElement): Promise<MediaStream> {
43
+ const c = { video: { facingMode: 'user' as const, width: { ideal: 1280 }, height: { ideal: 720 } }, audio: false };
44
+ this.mediaStream = await navigator.mediaDevices.getUserMedia(c);
45
+ videoElement.srcObject = this.mediaStream;
46
+ await videoElement.play();
47
+ return this.mediaStream;
48
+ }
49
+
50
+ async startCameraLegacy(videoElement: HTMLVideoElement): Promise<boolean> {
51
+ try {
52
+ await this.startCamera(videoElement);
53
+ return true;
54
+ } catch { return false; }
55
+ }
56
+
57
+ stopCamera(): void {
58
+ if (this.mediaStream) {
59
+ this.mediaStream.getTracks().forEach(t => t.stop());
60
+ this.mediaStream = null;
61
+ }
62
+ }
63
+
64
+ startRecording(): boolean {
65
+ if (!this.mediaStream) return false;
66
+ try {
67
+ this.recordedChunks = [];
68
+ this.mediaRecorder = new MediaRecorder(this.mediaStream, { mimeType: 'video/webm;codecs=vp9' });
69
+ this.mediaRecorder.ondataavailable = e => { if (e.data.size > 0) this.recordedChunks.push(e.data); };
70
+ this.mediaRecorder.start();
71
+ return true;
72
+ } catch { return false; }
73
+ }
74
+
75
+ stopRecording(): Promise<Blob> {
76
+ return new Promise((resolve, reject) => {
77
+ if (!this.mediaRecorder) { reject(new Error('No media recorder')); return; }
78
+ this.mediaRecorder.onstop = () => resolve(new Blob(this.recordedChunks, { type: 'video/webm' }));
79
+ this.mediaRecorder.stop();
80
+ });
81
+ }
82
+
83
+ captureFrame(videoElement: HTMLVideoElement): string {
84
+ const c = document.createElement('canvas');
85
+ c.width = videoElement.videoWidth;
86
+ c.height = videoElement.videoHeight;
87
+ const ctx = c.getContext('2d');
88
+ if (!ctx) throw new Error('Could not get canvas context');
89
+ ctx.drawImage(videoElement, 0, 0, c.width, c.height);
90
+ return c.toDataURL('image/jpeg', 0.9);
91
+ }
92
+
93
+ async captureFrames(videoElement: HTMLVideoElement, count = 5, interval = 200): Promise<string[]> {
94
+ const out: string[] = [];
95
+ for (let i = 0; i < count; i++) {
96
+ out.push(this.captureFrame(videoElement));
97
+ if (i < count - 1) await new Promise(r => setTimeout(r, interval));
98
+ }
99
+ return out;
100
+ }
101
+
102
+ isRecording(): boolean { return this.mediaRecorder?.state === 'recording'; }
103
+ getStream(): MediaStream | null { return this.mediaStream; }
104
+
105
+ analyzeBrightness(videoElement: HTMLVideoElement): BrightnessData {
106
+ const c = document.createElement('canvas');
107
+ const w = Math.min(videoElement.videoWidth, 320);
108
+ const h = Math.min(videoElement.videoHeight, 240);
109
+ c.width = w; c.height = h;
110
+ const ctx = c.getContext('2d');
111
+ if (!ctx) return { brightness: 128, isLowLight: false, isTooLight: false };
112
+ ctx.drawImage(videoElement, 0, 0, w, h);
113
+ try {
114
+ const d = ctx.getImageData(0, 0, w, h).data;
115
+ let sum = 0;
116
+ for (let i = 0; i < d.length; i += 4) sum += 0.299 * d[i] + 0.587 * d[i + 1] + 0.114 * d[i + 2];
117
+ const b = sum / (d.length / 4);
118
+ return { brightness: b, isLowLight: b < 60, isTooLight: b > 200 };
119
+ } catch {
120
+ return { brightness: 128, isLowLight: false, isTooLight: false };
121
+ }
122
+ }
123
+
124
+ startBrightnessMonitoring(
125
+ videoElement: HTMLVideoElement,
126
+ callback: (data: BrightnessData) => void,
127
+ intervalMs = 1000
128
+ ): number {
129
+ return window.setInterval(() => callback(this.analyzeBrightness(videoElement)), intervalMs);
130
+ }
131
+
132
+ stopBrightnessMonitoring(intervalId: number): void { clearInterval(intervalId); }
133
+
134
+ releaseCameraForFaceTec(): void {
135
+ if (this.isRecording()) this.stopRecording().catch(() => {});
136
+ if (this.mediaStream) { this.mediaStream.getTracks().forEach(t => t.stop()); this.mediaStream = null; }
137
+ this.mediaRecorder = null;
138
+ this.recordedChunks = [];
139
+ }
140
+
141
+ async reacquireCameraForGestures(videoElement: HTMLVideoElement): Promise<boolean> {
142
+ await new Promise(r => setTimeout(r, 500));
143
+ try { await this.startCamera(videoElement); return true; } catch { return false; }
144
+ }
145
+
146
+ isCameraActive(): boolean { return this.mediaStream != null && this.mediaStream.active; }
147
+ getCameraState(): { hasStream: boolean; isActive: boolean; isRecording: boolean; trackCount: number } {
148
+ return {
149
+ hasStream: this.mediaStream != null,
150
+ isActive: this.mediaStream?.active ?? false,
151
+ isRecording: this.isRecording(),
152
+ trackCount: this.mediaStream?.getTracks().length ?? 0
153
+ };
154
+ }
155
+ }