@incodetech/core 2.1.1 → 2.2.0-rc.0
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.
- package/dist/{3dAvatarProvider-BwOmTMOy.esm.js → 3dAvatarProvider-g02DTFqT.esm.js} +811 -880
- package/dist/Actor-iTq7YY48.d.ts +1 -0
- package/dist/BrowserDiagnosticsCryptoProvider-CriIh1Gt.esm.js +95 -0
- package/dist/BrowserLazyImportProvider-BhoA957O.esm.js +114 -0
- package/dist/{BrowserStorageProvider-CBHBhKQN.esm.js → BrowserStorageProvider-k-FDkC4W.esm.js} +2 -4
- package/dist/{BrowserTimerProvider-CHloo0eQ.esm.js → BrowserTimerProvider-BXhgNxLt.esm.js} +1 -2
- package/dist/{BrowserVisibilityObserverProvider-GBS6XSii.esm.js → BrowserVisibilityObserverProvider-BX1BiLDK.esm.js} +1 -2
- package/dist/Face2DProvider-UUQt_vRC.esm.js +437 -0
- package/dist/{createFaceAvatar-CpdYp9Iy.d.ts → FaceDetectionProvider-Mh6q3EbA.d.ts} +107 -159
- package/dist/IBrowserEnvironmentCapability-DEaRRk-R.esm.js +9 -0
- package/dist/IFaceAvatarCapability-BrL19G0s.d.ts +23 -0
- package/dist/IFeatureManagementCapability-DdEUp6tB.d.ts +1125 -0
- package/dist/IIdCaptureCapability-DHTT3FC2.d.ts +237 -0
- package/dist/IStorageCapability-ORkQfK3F.d.ts +242 -0
- package/dist/{ITimerCapability-DJ73P4RK.esm.js → ITimerCapability-DgdDzp8G.esm.js} +1 -2
- package/dist/{Manager-goNPc_h_.d.ts → Manager-CQQ99QOI.d.ts} +1 -0
- package/dist/{MotionSensorProvider-BHuHoYvJ.esm.js → MotionSensorProvider-DGiV77J_.esm.js} +2 -37
- package/dist/{OpenViduRecordingProvider-CK6A5kiU.esm.js → OpenViduRecordingProvider-Du98qevC.esm.js} +2 -4
- package/dist/PrivacyLensProvider-D8PS7AJa.esm.js +405 -0
- package/dist/StateMachine-EDpYWy0i.d.ts +6 -0
- package/dist/{StreamCanvasCapture-CAqUdVkM.esm.js → StreamCanvasCapture-D2XcXCZ4.esm.js} +2 -4
- package/dist/{addressSearch-D7msRieq.esm.js → addressSearch-DQyrvDQj.esm.js} +10 -21
- package/dist/ae-signature.d.ts +4 -6
- package/dist/ae-signature.esm.js +10 -11
- package/dist/antifraud.d.ts +1 -6
- package/dist/antifraud.esm.js +3 -8
- package/dist/{antifraudManager-5KA84wtF.esm.js → antifraudManager-B8Plzf7a.esm.js} +11 -8
- package/dist/{antifraudManager-DCiNI6np.d.ts → antifraudManager-BY36bS12.d.ts} +3 -3
- package/dist/{antifraudStateMachine-BQVw46N5.esm.js → antifraudStateMachine-r4VxSBS4.esm.js} +5 -7
- package/dist/api-Bx3gKW_y.esm.js +129 -0
- package/dist/{apiError-DkOXfFBu.esm.js → apiError-DZfAyExM.esm.js} +25 -3
- package/dist/authentication.d.ts +3 -19
- package/dist/authentication.esm.js +3 -35
- package/dist/{authenticationManager-BeT6tNZm.d.ts → authenticationManager-BBMGTcwW.d.ts} +3 -4
- package/dist/{authenticationManager-BryLnSaF.esm.js → authenticationManager-DscbQwIl.esm.js} +7 -10
- package/dist/{authenticationStateMachine-DbdxfGHx.esm.js → authenticationStateMachine-SyuM00Fa.esm.js} +11 -15
- package/dist/avatar.d.ts +2 -2
- package/dist/avatar.esm.js +2 -3
- package/dist/avatar2dWarm-DqucNFX4.esm.js +1330 -0
- package/dist/avatarStats-CdUOb3VD.esm.js +136 -0
- package/dist/{backCameraStream-ChrMTJv-.esm.js → backCameraStream-CUeYm5eu.esm.js} +20 -8
- package/dist/browser-DlFgHpoC-DnyrCR8r.esm.js +1513 -0
- package/dist/callback-645_omgz.d.ts +133 -0
- package/dist/camera-_2sXMXiF.esm.js +140 -0
- package/dist/camera.d.ts +19 -2
- package/dist/camera.esm.js +2 -6
- package/dist/{canvas-Pi10hG_l.esm.js → canvas-E1zGkel4.esm.js} +1 -2
- package/dist/certificate-issuance.d.ts +34 -39
- package/dist/certificate-issuance.esm.js +4 -11
- package/dist/{certificateIssuanceStateMachine-DlZQT1_u.esm.js → certificateIssuanceStateMachine-COd7_l2T.esm.js} +7 -11
- package/dist/consent.d.ts +1 -4
- package/dist/consent.esm.js +3 -8
- package/dist/{consentManager-BwNu-uC-.esm.js → consentManager-BApTlfA0.esm.js} +5 -7
- package/dist/{consentManager-B4PMGXW-.d.ts → consentManager-uhDPdU6i.d.ts} +55 -61
- package/dist/{consentStateMachine-BOkVFiU7.esm.js → consentStateMachine-WD-S-Yhs.esm.js} +5 -7
- package/dist/{cpf-16m_cGAM.esm.js → cpf-BIADjddF.esm.js} +1 -2
- package/dist/createFaceAvatar-BEj18KXi.d.ts +50 -0
- package/dist/createFaceAvatar-DQKaRise.esm.js +103 -0
- package/dist/cross-document-data-match.d.ts +2 -26
- package/dist/cross-document-data-match.esm.js +2 -71
- package/dist/crossDocumentDataMatchManager-Dq8Z3SOg.d.ts +41 -0
- package/dist/crossDocumentDataMatchManager-OmYvyung.esm.js +84 -0
- package/dist/curp-validation.d.ts +1 -11
- package/dist/curp-validation.esm.js +3 -8
- package/dist/{curpValidationManager-CoqODZx5.d.ts → curpValidationManager-DVGr7Fq7.d.ts} +4 -6
- package/dist/{curpValidationManager-Cbji2inV.esm.js → curpValidationManager-UZxcrsrz.esm.js} +11 -7
- package/dist/{curpValidationStateMachine-Dlw4qfFb.esm.js → curpValidationStateMachine-9KwnqeoB.esm.js} +22 -14
- package/dist/custom-fields.d.ts +2 -5
- package/dist/custom-fields.esm.js +24 -11
- package/dist/custom-watchlist.d.ts +1 -3
- package/dist/custom-watchlist.esm.js +11 -10
- package/dist/{customWatchlistStateMachine-D8w0mNxm.esm.js → customWatchlistStateMachine-B_ey3TFj.esm.js} +5 -7
- package/dist/{dateUtils-BW6mBV6p.esm.js → dateUtils-CvG0FvK8.esm.js} +1 -2
- package/dist/{deepsightService-CViys6T4.esm.js → deepsightLoader-DcWjMC6A.esm.js} +68 -14
- package/dist/{deepsightService-BuTOFTr8.d.ts → deepsightService-DLVQS_RH.d.ts} +41 -2
- package/dist/device.d.ts +10 -2
- package/dist/device.esm.js +21 -21
- package/dist/document-capture.d.ts +3 -1816
- package/dist/document-capture.esm.js +3 -245
- package/dist/document-upload.d.ts +48 -55
- package/dist/document-upload.esm.js +8 -14
- package/dist/documentCaptureManager-B4Kivr8_.d.ts +2062 -0
- package/dist/documentCaptureManager-DszmcU-l.esm.js +296 -0
- package/dist/{documentCaptureStateMachine-BkTvU3yq.esm.js → documentCaptureStateMachine-D2jBwwVb.esm.js} +66 -40
- package/dist/dynamic-forms.d.ts +2 -4
- package/dist/dynamic-forms.esm.js +3 -9
- package/dist/{dynamicFormsManager-CHXUBNs4.esm.js → dynamicFormsManager-BoZ2-OUI.esm.js} +48 -9
- package/dist/{dynamicFormsManager-CKZkE7Wo.d.ts → dynamicFormsManager-fhK0yINk.d.ts} +22 -46
- package/dist/{dynamicFormsStateMachine-DJ1HGZff.esm.js → dynamicFormsStateMachine-D0RzkoEV.esm.js} +161 -34
- package/dist/ekyb.d.ts +5 -137
- package/dist/ekyb.esm.js +4 -179
- package/dist/ekybManager-DTvfxAOZ.esm.js +178 -0
- package/dist/ekybManager-Dzswb_b8.d.ts +114 -0
- package/dist/{ekybStateMachine-D9GzA4MW.esm.js → ekybStateMachine-BlTIozb6.esm.js} +16 -193
- package/dist/ekyc.d.ts +2 -79
- package/dist/ekyc.esm.js +4 -154
- package/dist/ekycManager-BFpHbBoT.d.ts +97 -0
- package/dist/ekycManager-oTstTJuL.esm.js +156 -0
- package/dist/{ekycStateMachine-DnVRiu3s.esm.js → ekycStateMachine-DHvksnrr.esm.js} +141 -423
- package/dist/ekycUtils-Dew1rBG0.esm.js +223 -0
- package/dist/electronic-signature.d.ts +3 -5
- package/dist/electronic-signature.esm.js +2 -10
- package/dist/{index-36QX_21w.d.ts → electronicSignatureManager-CcUXQZS7.d.ts} +220 -251
- package/dist/{electronicSignatureManager-DESRgObX.esm.js → electronicSignatureManager-DRYlHPjT.esm.js} +47 -33
- package/dist/email.d.ts +1 -5
- package/dist/email.esm.js +3 -10
- package/dist/{emailManager-CMS4q_r6.d.ts → emailManager-CADXW_9X.d.ts} +4 -7
- package/dist/{emailManager-B9Vrv3Ma.esm.js → emailManager-tE-YvNtS.esm.js} +76 -12
- package/dist/{emailStateMachine-CbmKFVSi.esm.js → emailStateMachine-Bm894MHB.esm.js} +7 -9
- package/dist/{endpoints-CwrFQfW7.esm.js → endpoints-CCzL3XD_.esm.js} +55 -6
- package/dist/{events-1wxZ97yf.esm.js → events-CxfNJVZW.esm.js} +43 -176
- package/dist/events.d.ts +28 -10
- package/dist/events.esm.js +2 -4
- package/dist/extensibility.d.ts +37 -37
- package/dist/extensibility.esm.js +31 -80
- package/dist/{externalFetch-hvmz_npP.esm.js → externalFetch-BKBThAUC.esm.js} +2 -4
- package/dist/face-match.d.ts +2 -225
- package/dist/face-match.esm.js +3 -173
- package/dist/{faceCaptureManagerFactory-C9TiGbkA.esm.js → faceCaptureManagerFactory-BDAsBCkh.esm.js} +52 -31
- package/dist/{faceCaptureManagerFactory-DiJ64oem.d.ts → faceCaptureManagerFactory-BxWPpxyd.d.ts} +63 -24
- package/dist/{faceCaptureSetup-BqhqfFHL.esm.js → faceCaptureSetup-C7Qv79fA.esm.js} +57 -36
- package/dist/faceMatchManager-BXp2WUT-.esm.js +201 -0
- package/dist/faceMatchManager-BnvrZM2F.d.ts +268 -0
- package/dist/{faceMatchStateMachine-MDb30gCm.esm.js → faceMatchStateMachine-CJiO2yLO.esm.js} +14 -10
- package/dist/feature-management-DS_Nj5Gg.esm.js +615 -0
- package/dist/feature-management.d.ts +173 -0
- package/dist/feature-management.esm.js +2 -0
- package/dist/field-comparison.d.ts +1 -4
- package/dist/field-comparison.esm.js +2 -7
- package/dist/{fieldComparisonManager-OLKVsG2Z.d.ts → fieldComparisonManager-CBvEGxFx.d.ts} +3 -5
- package/dist/{fieldComparisonManager-Cw4uzD7S.esm.js → fieldComparisonManager-DhnYdYZ2.esm.js} +6 -11
- package/dist/fiscal-qr.d.ts +1 -6
- package/dist/fiscal-qr.esm.js +14 -29
- package/dist/flow-events.d.ts +2 -9
- package/dist/flow.d.ts +59 -21
- package/dist/flow.esm.js +132 -49
- package/dist/{flowCompletionService-B6P6_EF3.esm.js → flowCompletionService-BSe3p7pg.esm.js} +4 -6
- package/dist/flowServices-unhF9V9F.esm.js +620 -0
- package/dist/geolocation.d.ts +1 -4
- package/dist/geolocation.esm.js +3 -9
- package/dist/{geolocationManager-CF6-NizA.esm.js → geolocationManager-Bv9MSVNr.esm.js} +44 -16
- package/dist/geolocationManager-Cz744E5S.d.ts +506 -0
- package/dist/{geolocationStateMachine-CD2XJs1b.esm.js → geolocationStateMachine-BxD6JK1M.esm.js} +53 -28
- package/dist/{getBrowser-1yNcfFZR.esm.js → getBrowser-Cb8Zgrgp.esm.js} +1 -2
- package/dist/getBrowser-DNrQeL4T.esm.js +16 -0
- package/dist/getDeviceClass-9y-kbVDw.esm.js +19 -0
- package/dist/{getDeviceClass-C-Dg6IsJ.esm.js → getDeviceClass-CeBiEayS.esm.js} +1 -2
- package/dist/getParsedUserAgent-pJjATANs.esm.js +1622 -0
- package/dist/government-validation.d.ts +3 -6
- package/dist/government-validation.esm.js +7 -13
- package/dist/{governmentValidationStateMachine-BE5wiQ7Y.esm.js → governmentValidationStateMachine-BYKYUdza.esm.js} +8 -11
- package/dist/home.d.ts +14 -22
- package/dist/home.esm.js +3 -8
- package/dist/http.d.ts +0 -1
- package/dist/http.esm.js +2 -4
- package/dist/id-ocr.d.ts +8 -7
- package/dist/id-ocr.esm.js +20 -9
- package/dist/id-selfie-flow.d.ts +179 -0
- package/dist/id-selfie-flow.esm.js +917 -0
- package/dist/id-verification.d.ts +2 -191
- package/dist/id-verification.esm.js +3 -43
- package/dist/id.d.ts +7 -18
- package/dist/id.esm.js +47 -53
- package/dist/{idCaptureDefaults-CZajItdO.esm.js → idCaptureDefaults-WeHbQM7W.esm.js} +54 -17
- package/dist/idCaptureManager-BjU-Wyow.d.ts +429 -0
- package/dist/{idCaptureManager-DASPUMtC.esm.js → idCaptureManager-BjiBSrHm.esm.js} +173 -34
- package/dist/idCaptureServices-CFDwqTtQ.esm.js +396 -0
- package/dist/{idCaptureStateMachine-DZ2ZTSxu.esm.js → idCaptureStateMachine-CRwQh8jc.esm.js} +438 -585
- package/dist/{idOcrManager-Tm4LzKR0.esm.js → idOcrManager-CvJZhmBR.esm.js} +4 -7
- package/dist/{idOcrManager-DLfAjDZn.d.ts → idOcrManager-DXc_xRN7.d.ts} +62 -229
- package/dist/{idOcrStateMachine-BJxZCxwS.esm.js → idOcrStateMachine-Bq_e3l4E.esm.js} +15 -20
- package/dist/idVerificationManager-BB4Pj_N6.d.ts +197 -0
- package/dist/idVerificationManager-CduNXhWd.esm.js +51 -0
- package/dist/{idVerificationStateMachine-DXN9WcdM.esm.js → idVerificationStateMachine-GEQWUPU2.esm.js} +5 -7
- package/dist/identity-reuse.d.ts +1 -4
- package/dist/identity-reuse.esm.js +3 -8
- package/dist/{identityReuseManager-LjkPvSQN.esm.js → identityReuseManager-D5vXknnJ.esm.js} +24 -7
- package/dist/{identityReuseManager-CLVOYxcp.d.ts → identityReuseManager-v2TH5g7e.d.ts} +38 -43
- package/dist/{identityReuseStateMachine-Bp-zONoV.esm.js → identityReuseStateMachine-AubRPhIi.esm.js} +6 -8
- package/dist/index-B8weJxmL.d.ts +1 -0
- package/dist/index-BKOWUhxG.d.ts +258 -0
- package/dist/{index-DrpWp15Z.d.ts → index-CLMF0QLQ.d.ts} +3 -3
- package/dist/index.d.ts +1 -3
- package/dist/index.esm.js +2 -15
- package/dist/{invokeOnCaptureCallback-CCS2Ruzi.esm.js → invokeOnCaptureCallback-1m1VJ3Q4.esm.js} +2 -4
- package/dist/{lib-Bjd6nOLG.esm.js → lib-DAyUtOTj.esm.js} +1510 -1589
- package/dist/mandatory-consent.d.ts +2 -10
- package/dist/mandatory-consent.esm.js +3 -8
- package/dist/{mandatoryConsentManager-DeBmOHcQ.d.ts → mandatoryConsentManager-B29kR0XA.d.ts} +60 -85
- package/dist/{mandatoryConsentManager-DZ26sg4B.esm.js → mandatoryConsentManager-BeELm1zS.esm.js} +4 -7
- package/dist/{mandatoryConsentStateMachine-_44ZoDSh.esm.js → mandatoryConsentStateMachine-zroQGzwb.esm.js} +5 -7
- package/dist/manualReviewService-D4IkipnN.esm.js +15 -0
- package/dist/{mediaRecorderOptions-CClCKAiF.esm.js → mediaRecorderOptions-dALSV8af.esm.js} +2 -4
- package/dist/{mediapipeShared-Bcmkzs3x.esm.js → mediapipeShared-B6oNoXEQ.esm.js} +21 -9
- package/dist/{onDeviceFaceResults-DILtthks.esm.js → onDeviceFaceResults-f81FVdQs.esm.js} +30 -87
- package/dist/openviduLazy-CCDpFWg1.esm.js +10 -0
- package/dist/{otp-B1YeyRXu.d.ts → otp-CEWfTj9v.d.ts} +0 -1
- package/dist/{otp-uaXiKt_R.esm.js → otp-D2k3PeSL.esm.js} +3 -14
- package/dist/{permissionGuards-BNOe4Tnu.esm.js → permissionGuards-KINpmKtI.esm.js} +2 -4
- package/dist/{permissionServices-C_kxhNeo.esm.js → permissionServices-UvUcOSRb.esm.js} +35 -28
- package/dist/personhood.d.ts +13 -20
- package/dist/personhood.esm.js +153 -85
- package/dist/phone.d.ts +1 -5
- package/dist/phone.esm.js +3 -10
- package/dist/{phoneManager-_KtitbUA.d.ts → phoneManager-BBU8Lq0O.d.ts} +4 -7
- package/dist/{phoneManager-CRblfdcI.esm.js → phoneManager-Cpiai2IM.esm.js} +82 -12
- package/dist/{phoneStateMachine-DjFGWMjQ.esm.js → phoneStateMachine-E1fUUcNM.esm.js} +7 -9
- package/dist/{platform-0Ot-bF_a.esm.js → platform-BJrm5nzZ.esm.js} +31 -11
- package/dist/privacyLensWorker-DI-Rg83h.esm.js +355 -0
- package/dist/qe-signature.d.ts +4 -6
- package/dist/qe-signature.esm.js +10 -11
- package/dist/{recordingService-CxaYkITO.esm.js → recordingService-CFpfdXSS.esm.js} +13 -31
- package/dist/redirect-to-mobile.d.ts +2 -6
- package/dist/redirect-to-mobile.esm.js +3 -10
- package/dist/{redirectToMobileManager-dv29o_LS.d.ts → redirectToMobileManager-BD2PvT5B.d.ts} +8 -44
- package/dist/{redirectToMobileManager-DbuNUlru.esm.js → redirectToMobileManager-C0dfc7f6.esm.js} +28 -8
- package/dist/{redirectToMobileStateMachine-BRaIJWOM.esm.js → redirectToMobileStateMachine-C9SYCJcH.esm.js} +74 -18
- package/dist/rolldown-runtime-FZ4Itg2g.esm.js +42 -0
- package/dist/{runChildModule-kojSv3MH.esm.js → runChildModule-DpvgAGFA.esm.js} +10 -9
- package/dist/{WasmUtilProvider-BgzcXBdl.esm.js → safeStorage-Ep56I-f_.esm.js} +98 -473
- package/dist/selfie.d.ts +8 -21
- package/dist/selfie.esm.js +40 -47
- package/dist/selfieErrorUtils-C2PYBVsp.esm.js +90 -0
- package/dist/{selfieManager-B1Px3GGa.esm.js → selfieManager-Br4onIMy.esm.js} +7 -10
- package/dist/{selfieManager-4juKfrHG.d.ts → selfieManager-DwlCppg0.d.ts} +3 -4
- package/dist/{selfieStateMachine-D0TS93Tu.esm.js → selfieStateMachine-CNGs8vFK.esm.js} +14 -29
- package/dist/{session-CLsU2Jnb.esm.js → session-CX2H3tmI.esm.js} +16 -7
- package/dist/session.d.ts +12 -10
- package/dist/session.esm.js +5 -15
- package/dist/sessionInitializer-CV6Rj_dn.esm.js +1865 -0
- package/dist/{setup-CfNsYAeB.esm.js → setup-CYdVacqv.esm.js} +319 -137
- package/dist/{setup-CmA2apmN.d.ts → setup-DywQeePQ.d.ts} +186 -4
- package/dist/signature.d.ts +2 -96
- package/dist/signature.esm.js +3 -108
- package/dist/signatureManager-DGBSWz2_.d.ts +106 -0
- package/dist/signatureManager-DdPgcmou.esm.js +113 -0
- package/dist/{signatureStateMachine-CdhlMih-.esm.js → signatureStateMachine-Cf73DGWO.esm.js} +5 -7
- package/dist/spawn-D9jgw9pW.d.ts +1441 -0
- package/dist/src-0Jurt29N.esm.js +4482 -0
- package/dist/{stats-BVy52wLy.esm.js → stats-B0nFKawG.esm.js} +3 -5
- package/dist/stats.esm.js +2 -5
- package/dist/topLevelStateName-B5i2avbh.esm.js +41 -0
- package/dist/{tri-CFAPtAUM.esm.js → tri-4RsGrIt6.esm.js} +3 -119
- package/dist/tri.d.ts +2 -2
- package/dist/tri.esm-BIjHhBJ2.esm.js +2620 -0
- package/dist/tri.esm.js +2 -4
- package/dist/trueSight-B8G85W4z.esm.js +1 -0
- package/dist/trueSightService-6dTpiaTh.esm.js +1894 -0
- package/dist/trust-graph.d.ts +4 -5
- package/dist/trust-graph.esm.js +13 -9
- package/dist/types-BEwVKkLJ.d.ts +15 -0
- package/dist/{types-BKLb3C42.esm.js → types-BN5RPPIZ.esm.js} +1 -2
- package/dist/types-BSBzhHRH.d.ts +57 -0
- package/dist/types-BX_XkMsQ2.d.ts +21 -0
- package/dist/types-BfHiko5D.d.ts +48 -0
- package/dist/{types-D8Cl1N2Z.d.ts → types-BfgmV7s72.d.ts} +2 -2
- package/dist/types-CJ7Yl9K-2.d.ts +164 -0
- package/dist/types-DPIDuhut.d.ts +40 -0
- package/dist/types-DVmXIdzW.d.ts +82 -0
- package/dist/types-DelMqZLe.d.ts +105 -0
- package/dist/types-EELWyWpd.d.ts +6 -0
- package/dist/{types-fO6KIdol.d.ts → types-Hmd7U0JA.d.ts} +80 -6
- package/dist/{types-BUgeB2X6.d.ts → types-KPvmag_B.d.ts} +7 -7
- package/dist/types-M6eb82ra2.d.ts +589 -0
- package/dist/{types-DV3wkhQ4.d.ts → types-Vb4P9ovf.d.ts} +1 -3
- package/dist/{urlUtils-CpiYwn7w.esm.js → urlUtils-D6phjq1-.esm.js} +1 -2
- package/dist/{video-ZY92rkel.esm.js → video-BtSWtHy1.esm.js} +1 -2
- package/dist/video-selfie.d.ts +71 -117
- package/dist/video-selfie.esm.js +575 -643
- package/dist/videoSelfieCameraWarmer-h_VUk5Vu.esm.js +651 -0
- package/dist/videoSelfieStepDetector-D3oo2ofo.d.ts +123 -0
- package/dist/wasm.d.ts +2 -4
- package/dist/wasm.esm.js +4 -15
- package/dist/wasmDefaults-DTUrftxR.esm.js +125 -0
- package/dist/{wasmDsFlagSync-Ch0mFJNT.esm.js → wasmDsFlagSync-BqUCwLY8.esm.js} +2 -4
- package/dist/wasmWebClient-DM8lkb0z.esm.js +407 -0
- package/dist/watchlist-for-business.d.ts +1 -3
- package/dist/watchlist-for-business.esm.js +9 -9
- package/dist/watchlist.d.ts +1 -52
- package/dist/watchlist.esm.js +3 -70
- package/dist/{watchlistForBusinessStateMachine-R1Yp1g5A.esm.js → watchlistForBusinessStateMachine-BcBMkMvV.esm.js} +5 -7
- package/dist/watchlistManager-CjSMmbyq.esm.js +83 -0
- package/dist/watchlistManager-DLXef90v.d.ts +68 -0
- package/dist/{watchlistStateMachine-D07UVq1m.esm.js → watchlistStateMachine-Bl7Wy9Cm.esm.js} +5 -7
- package/dist/workflow.d.ts +239 -203
- package/dist/workflow.esm.js +156 -101
- package/dist/xstate-actors.esm-CtI5S0f4.esm.js +310 -0
- package/dist/{xstate.esm-nY3M5UYm.esm.js → xstate.esm-Cf4e0Cfd.esm.js} +136 -610
- package/package.json +14 -9
- package/dist/Actor-BwgmOPkb.d.ts +0 -2
- package/dist/Face2DProvider-CZU11g6r.esm.js +0 -824
- package/dist/FaceDetectionProvider-CPWg-PAH.d.ts +0 -119
- package/dist/IWasmUtilCapability-DaSNxgA8.d.ts +0 -150
- package/dist/PrivacyLensProvider-B_zrV25p.esm.js +0 -339
- package/dist/StateMachine-D5WO6lHS.d.ts +0 -2
- package/dist/StreamCanvasCapture-DHaWferF.d.ts +0 -42
- package/dist/ae-signature-Pp4oCAHD.esm.js +0 -12
- package/dist/api-DsbUi7TO.esm.js +0 -72
- package/dist/browser-DlFgHpoC-Br3G10gw.esm.js +0 -1425
- package/dist/browserSimulation-DZwEy9bu.esm.js +0 -2804
- package/dist/camera-CxxXV0VN.esm.js +0 -79
- package/dist/canvas-90BqS0NF.d.ts +0 -112
- package/dist/chunk-CIKcdghV.esm.js +0 -49
- package/dist/clientLifecycle-VyzRfALg.esm.js +0 -534
- package/dist/cpf-ocr.d.ts +0 -10
- package/dist/cpf-ocr.esm.js +0 -8
- package/dist/cpfOcrManager-9eM4z4o7.esm.js +0 -190
- package/dist/cpfOcrManager-C5U9E0hf.d.ts +0 -216
- package/dist/createFaceAvatar-BZSg2MNo.esm.js +0 -46
- package/dist/deepsightLoader-CU_tiLMx.esm.js +0 -62
- package/dist/flowServices-DkQmOkLV.esm.js +0 -208
- package/dist/geolocationManager-DaHYFkVO.d.ts +0 -156
- package/dist/getDeviceClass-DIjnTPjR.esm.js +0 -14
- package/dist/http-CnsKEaJY.esm.js +0 -0
- package/dist/idCaptureManager-CwuXtVu6.d.ts +0 -1013
- package/dist/index-BAyZ8_iI.d.ts +0 -93
- package/dist/openviduLazy-jPgABwrt.esm.js +0 -3
- package/dist/openviduLazy-wEojM41w.esm.js +0 -12
- package/dist/qe-signature-BnWIpR-O.esm.js +0 -12
- package/dist/sessionInitializer-Dj7-aJaP.esm.js +0 -547
- package/dist/tri.esm-DDJ1zOqG.esm.js +0 -2593
- package/dist/types-BcF-fii5.d.ts +0 -5
- package/dist/videoSelfieSteps-Djpv7cjb.esm.js +0 -58
- /package/dist/{IVisibilityObserverCapability-BZ1rE7ly.d.ts → IVisibilityObserverCapability-D4tvIB5v.d.ts} +0 -0
- /package/dist/{camera-CKU96nwJ.d.ts → camera-9wWD-pwg.d.ts} +0 -0
- /package/dist/{flowCompletionService-BOSTlAWo.d.ts → flowCompletionService-BlAHsKyv.d.ts} +0 -0
- /package/dist/{types-BgjMAEXl.d.ts → types-Bk_6dmDa.d.ts} +0 -0
- /package/dist/{warmup-Dd0iq6KF.d.ts → warmup-Dv2alr2-.d.ts} +0 -0
|
@@ -0,0 +1,1330 @@
|
|
|
1
|
+
import { i as detectWorkerSourceInstrumentation, o as createFramePacer } from "./privacyLensWorker-DI-Rg83h.esm.js";
|
|
2
|
+
//#region ../infra/src/avatar/2d-default-avatar/assets.ts
|
|
3
|
+
/**
|
|
4
|
+
* 2D avatar MediaPipe asset resolution — **self-contained** (no shared
|
|
5
|
+
* `avatarDefaults`; owns its asset config). No VRM here — 2D satin face.
|
|
6
|
+
* See docs/features/FACE_AVATAR.md → Isolation and ownership.
|
|
7
|
+
*
|
|
8
|
+
* Prebuilt tasks-vision ESM + wasm + the face-landmarker model. Public CDNs for
|
|
9
|
+
* development; production should self-host.
|
|
10
|
+
*/
|
|
11
|
+
const DEFAULT_MEDIAPIPE_MODULE_URL = "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.35";
|
|
12
|
+
const DEFAULT_FACE_LANDMARKER_MODEL_URL = "https://storage.googleapis.com/mediapipe-models/face_landmarker/face_landmarker/float16/1/face_landmarker.task";
|
|
13
|
+
function valid(value) {
|
|
14
|
+
return typeof value === "string" && value.trim() !== "";
|
|
15
|
+
}
|
|
16
|
+
function resolveAvatar2DAssets(overrides) {
|
|
17
|
+
const moduleUrl = valid(overrides?.mediapipeModulePath) ? overrides.mediapipeModulePath : DEFAULT_MEDIAPIPE_MODULE_URL;
|
|
18
|
+
return {
|
|
19
|
+
mediapipeModulePath: moduleUrl,
|
|
20
|
+
mediapipeWasmPath: valid(overrides?.mediapipeWasmPath) ? overrides.mediapipeWasmPath : `${moduleUrl}/wasm`,
|
|
21
|
+
faceLandmarkerModelPath: valid(overrides?.faceLandmarkerModelPath) ? overrides.faceLandmarkerModelPath : DEFAULT_FACE_LANDMARKER_MODEL_URL
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
//#endregion
|
|
25
|
+
//#region ../infra/src/avatar/renderers/SatinSmileyRenderer.ts
|
|
26
|
+
/**
|
|
27
|
+
* The satin-smiley drawing core. SELF-CONTAINED (serialized via toString into
|
|
28
|
+
* the avatar-2d worker blob): no module-level references — all constants,
|
|
29
|
+
* palettes and helpers are inside; keep it free of spread/async helpers.
|
|
30
|
+
*/
|
|
31
|
+
function createSatinSmileyCore(canvas) {
|
|
32
|
+
const SPRITE = 512;
|
|
33
|
+
const HEAD_CX = 256;
|
|
34
|
+
const HEAD_CY = 256;
|
|
35
|
+
const HEAD_R = 150;
|
|
36
|
+
const HALO_R = 178;
|
|
37
|
+
const EYE_Y = 218;
|
|
38
|
+
const EYE_DX = 56;
|
|
39
|
+
const EYE_RX = 12;
|
|
40
|
+
const NOSE_Y0 = 258;
|
|
41
|
+
const NOSE_Y1 = 292;
|
|
42
|
+
const MOUTH_Y = 330;
|
|
43
|
+
const MOUTH_HALF = 44;
|
|
44
|
+
const YAW_PARALLAX = 90;
|
|
45
|
+
const PITCH_PARALLAX = 72;
|
|
46
|
+
const REST_SMILE = .35;
|
|
47
|
+
const PALETTE_ALPHA_30 = .08;
|
|
48
|
+
const PRESENCE_ALPHA_30 = .12;
|
|
49
|
+
const REF_FRAME_MS = 1e3 / 30;
|
|
50
|
+
const MAX_RENDER_DT_MS = 500;
|
|
51
|
+
const RENDER_DT_SEED_MS = REF_FRAME_MS;
|
|
52
|
+
const clamp = (v, lo, hi) => Math.min(hi, Math.max(lo, v));
|
|
53
|
+
const hexToRgb = (hex) => {
|
|
54
|
+
const h = hex.replace("#", "");
|
|
55
|
+
return [
|
|
56
|
+
parseInt(h.slice(0, 2), 16),
|
|
57
|
+
parseInt(h.slice(2, 4), 16),
|
|
58
|
+
parseInt(h.slice(4, 6), 16)
|
|
59
|
+
];
|
|
60
|
+
};
|
|
61
|
+
const rgb = (c) => `rgb(${c[0] | 0}, ${c[1] | 0}, ${c[2] | 0})`;
|
|
62
|
+
const rgba = (c, a) => `rgba(${c[0] | 0}, ${c[1] | 0}, ${c[2] | 0}, ${a})`;
|
|
63
|
+
const buildPalette = (stops, occlusion, contact, feature, halo) => ({
|
|
64
|
+
stops: [
|
|
65
|
+
hexToRgb(stops[0]),
|
|
66
|
+
hexToRgb(stops[1]),
|
|
67
|
+
hexToRgb(stops[2]),
|
|
68
|
+
hexToRgb(stops[3])
|
|
69
|
+
],
|
|
70
|
+
occlusion: hexToRgb(occlusion),
|
|
71
|
+
contact: hexToRgb(contact),
|
|
72
|
+
feature: hexToRgb(feature),
|
|
73
|
+
halo: hexToRgb(halo)
|
|
74
|
+
});
|
|
75
|
+
const PALETTES = {
|
|
76
|
+
detecting: buildPalette([
|
|
77
|
+
"#f4f9ff",
|
|
78
|
+
"#dbeafe",
|
|
79
|
+
"#cbe0fb",
|
|
80
|
+
"#aacdf3"
|
|
81
|
+
], "#6f9fd8", "#3a5f8f", "#007aff", "#2f86f0"),
|
|
82
|
+
success: buildPalette([
|
|
83
|
+
"#f0fdf4",
|
|
84
|
+
"#dcfce7",
|
|
85
|
+
"#bbf7d0",
|
|
86
|
+
"#86c79a"
|
|
87
|
+
], "#3f7a52", "#1f5132", "#15803d", "#22c55e"),
|
|
88
|
+
error: buildPalette([
|
|
89
|
+
"#fff1f1",
|
|
90
|
+
"#fee2e2",
|
|
91
|
+
"#fecaca",
|
|
92
|
+
"#e89a9a"
|
|
93
|
+
], "#8c4a4a", "#5f1e1e", "#b91c1c", "#dc2626")
|
|
94
|
+
};
|
|
95
|
+
const clonePalette = (p) => ({
|
|
96
|
+
stops: [
|
|
97
|
+
p.stops[0].slice(),
|
|
98
|
+
p.stops[1].slice(),
|
|
99
|
+
p.stops[2].slice(),
|
|
100
|
+
p.stops[3].slice()
|
|
101
|
+
],
|
|
102
|
+
occlusion: p.occlusion.slice(),
|
|
103
|
+
contact: p.contact.slice(),
|
|
104
|
+
feature: p.feature.slice(),
|
|
105
|
+
halo: p.halo.slice()
|
|
106
|
+
});
|
|
107
|
+
const easeRgb = (cur, target, t) => {
|
|
108
|
+
cur[0] += (target[0] - cur[0]) * t;
|
|
109
|
+
cur[1] += (target[1] - cur[1]) * t;
|
|
110
|
+
cur[2] += (target[2] - cur[2]) * t;
|
|
111
|
+
};
|
|
112
|
+
/** Ease every channel of `cur` toward `target` in place (state crossfade). */
|
|
113
|
+
const easePaletteToward = (cur, target, t) => {
|
|
114
|
+
for (let i = 0; i < 4; i += 1) easeRgb(cur.stops[i], target.stops[i], t);
|
|
115
|
+
easeRgb(cur.occlusion, target.occlusion, t);
|
|
116
|
+
easeRgb(cur.contact, target.contact, t);
|
|
117
|
+
easeRgb(cur.feature, target.feature, t);
|
|
118
|
+
easeRgb(cur.halo, target.halo, t);
|
|
119
|
+
};
|
|
120
|
+
/** Frame-rate-independent EMA factor for a 30fps-authored alpha. */
|
|
121
|
+
const dtAlpha = (refAlpha, dtMs) => {
|
|
122
|
+
if (refAlpha <= 0) return 0;
|
|
123
|
+
if (refAlpha >= 1) return 1;
|
|
124
|
+
const tau = -33.333333333333336 / Math.log(1 - refAlpha);
|
|
125
|
+
return 1 - Math.exp(-dtMs / tau);
|
|
126
|
+
};
|
|
127
|
+
/** Dev override: read directly on main; bridged via setOverrideState in workers. */
|
|
128
|
+
const resolveGlobalOverride = () => {
|
|
129
|
+
const v = globalThis.__incodeAvatarState;
|
|
130
|
+
return v === "detecting" || v === "success" || v === "error" ? v : null;
|
|
131
|
+
};
|
|
132
|
+
let ctx = canvas.getContext("2d");
|
|
133
|
+
const size = canvas.width;
|
|
134
|
+
let presence = 0;
|
|
135
|
+
let faceState = "detecting";
|
|
136
|
+
let overrideState = null;
|
|
137
|
+
const eased = clonePalette(PALETTES.detecting);
|
|
138
|
+
let lastRenderAt = 0;
|
|
139
|
+
let ballGradients = null;
|
|
140
|
+
let specGradient = null;
|
|
141
|
+
const resolveBallGradients = (c2d) => {
|
|
142
|
+
const p = eased;
|
|
143
|
+
const parts = [];
|
|
144
|
+
parts.push(p.contact[0], p.contact[1], p.contact[2]);
|
|
145
|
+
for (let i = 0; i < 4; i += 1) parts.push(p.stops[i][0], p.stops[i][1], p.stops[i][2]);
|
|
146
|
+
parts.push(p.occlusion[0], p.occlusion[1], p.occlusion[2]);
|
|
147
|
+
const key = parts.map((v) => v | 0).join(",");
|
|
148
|
+
if (ballGradients && ballGradients.key === key) return ballGradients;
|
|
149
|
+
const contact = c2d.createRadialGradient(256, 424, 0, 256, 424, 122);
|
|
150
|
+
contact.addColorStop(0, rgba(p.contact, .28));
|
|
151
|
+
contact.addColorStop(1, rgba(p.contact, 0));
|
|
152
|
+
const body = c2d.createRadialGradient(HEAD_CX - HEAD_R * .28, HEAD_CY - HEAD_R * .34, HEAD_R * .08, HEAD_CX, HEAD_CY, HEAD_R * 1.05);
|
|
153
|
+
body.addColorStop(0, rgb(p.stops[0]));
|
|
154
|
+
body.addColorStop(.45, rgb(p.stops[1]));
|
|
155
|
+
body.addColorStop(.8, rgb(p.stops[2]));
|
|
156
|
+
body.addColorStop(1, rgb(p.stops[3]));
|
|
157
|
+
const occlusion = c2d.createRadialGradient(HEAD_CX, HEAD_CY, HEAD_R * .68, HEAD_CX, HEAD_CY, HEAD_R);
|
|
158
|
+
occlusion.addColorStop(0, rgba(p.occlusion, 0));
|
|
159
|
+
occlusion.addColorStop(1, rgba(p.occlusion, .22));
|
|
160
|
+
ballGradients = {
|
|
161
|
+
key,
|
|
162
|
+
contact,
|
|
163
|
+
body,
|
|
164
|
+
occlusion
|
|
165
|
+
};
|
|
166
|
+
return ballGradients;
|
|
167
|
+
};
|
|
168
|
+
/** Soft 3D-shaded ball via cached runtime gradients (state colours ease). */
|
|
169
|
+
const drawBall = (c2d) => {
|
|
170
|
+
const g = resolveBallGradients(c2d);
|
|
171
|
+
c2d.fillStyle = g.contact;
|
|
172
|
+
c2d.beginPath();
|
|
173
|
+
c2d.ellipse(256, 424, 122, 26, 0, 0, Math.PI * 2);
|
|
174
|
+
c2d.fill();
|
|
175
|
+
c2d.fillStyle = g.body;
|
|
176
|
+
c2d.beginPath();
|
|
177
|
+
c2d.arc(HEAD_CX, HEAD_CY, HEAD_R, 0, Math.PI * 2);
|
|
178
|
+
c2d.fill();
|
|
179
|
+
c2d.fillStyle = g.occlusion;
|
|
180
|
+
c2d.beginPath();
|
|
181
|
+
c2d.arc(HEAD_CX, HEAD_CY, HEAD_R, 0, Math.PI * 2);
|
|
182
|
+
c2d.fill();
|
|
183
|
+
if (!specGradient) {
|
|
184
|
+
const spec = c2d.createRadialGradient(200, 190, 0, 200, 190, 64);
|
|
185
|
+
spec.addColorStop(0, "rgba(255, 255, 255, 0.85)");
|
|
186
|
+
spec.addColorStop(1, "rgba(255, 255, 255, 0)");
|
|
187
|
+
specGradient = spec;
|
|
188
|
+
}
|
|
189
|
+
c2d.fillStyle = specGradient;
|
|
190
|
+
c2d.beginPath();
|
|
191
|
+
c2d.ellipse(200, 190, 64, 48, 0, 0, Math.PI * 2);
|
|
192
|
+
c2d.fill();
|
|
193
|
+
};
|
|
194
|
+
/**
|
|
195
|
+
* Dashed halo ring — a STATIC face-placement guide, the avatar-mode
|
|
196
|
+
* counterpart of the white silhouette outline in the normal video view:
|
|
197
|
+
* it marks where the user should put their face, so it must NOT move or
|
|
198
|
+
* scale with the tracked pose (the smiley moves within it; a too-close
|
|
199
|
+
* ball outgrowing the static ring reads as the same "back up" cue the
|
|
200
|
+
* video outline gives). Fixed centre, fixed radius, always on.
|
|
201
|
+
*/
|
|
202
|
+
const drawHalo = (c2d, s, k) => {
|
|
203
|
+
c2d.save();
|
|
204
|
+
c2d.translate(s / 2, s / 2);
|
|
205
|
+
c2d.globalAlpha = .9;
|
|
206
|
+
c2d.strokeStyle = rgb(eased.halo);
|
|
207
|
+
c2d.lineWidth = 4 * k;
|
|
208
|
+
c2d.lineCap = "round";
|
|
209
|
+
c2d.setLineDash([15 * k, 12 * k]);
|
|
210
|
+
c2d.beginPath();
|
|
211
|
+
c2d.arc(0, 0, HALO_R * k, 0, Math.PI * 2);
|
|
212
|
+
c2d.stroke();
|
|
213
|
+
c2d.setLineDash([]);
|
|
214
|
+
c2d.restore();
|
|
215
|
+
};
|
|
216
|
+
/** One eye: a near-round dot (open) that flattens to a line as blink → 1. */
|
|
217
|
+
const drawEye = (c2d, cx, cy, blink, color) => {
|
|
218
|
+
const ry = 2.5 + (1 - blink) * 14;
|
|
219
|
+
c2d.fillStyle = color;
|
|
220
|
+
c2d.beginPath();
|
|
221
|
+
c2d.ellipse(cx, cy, EYE_RX, ry, 0, 0, Math.PI * 2);
|
|
222
|
+
c2d.fill();
|
|
223
|
+
};
|
|
224
|
+
/** Nose: a short, rounded vertical line under the eyes (personhood cue). */
|
|
225
|
+
const drawNose = (c2d, color) => {
|
|
226
|
+
c2d.strokeStyle = color;
|
|
227
|
+
c2d.lineWidth = 8;
|
|
228
|
+
c2d.lineCap = "round";
|
|
229
|
+
c2d.beginPath();
|
|
230
|
+
c2d.moveTo(HEAD_CX, NOSE_Y0);
|
|
231
|
+
c2d.lineTo(HEAD_CX, NOSE_Y1);
|
|
232
|
+
c2d.stroke();
|
|
233
|
+
};
|
|
234
|
+
/** Mouth: a rounded line — flat at rest, bowing up with smile, down on frown. */
|
|
235
|
+
const drawMouth = (c2d, smile, color) => {
|
|
236
|
+
const cy = MOUTH_Y;
|
|
237
|
+
const curve = 2 + smile * 32;
|
|
238
|
+
const lift = smile * 10;
|
|
239
|
+
c2d.strokeStyle = color;
|
|
240
|
+
c2d.lineWidth = 11;
|
|
241
|
+
c2d.lineCap = "round";
|
|
242
|
+
c2d.beginPath();
|
|
243
|
+
c2d.moveTo(HEAD_CX - MOUTH_HALF, cy - lift);
|
|
244
|
+
c2d.quadraticCurveTo(HEAD_CX, cy + curve, 300, cy - lift);
|
|
245
|
+
c2d.stroke();
|
|
246
|
+
};
|
|
247
|
+
return {
|
|
248
|
+
setState(state) {
|
|
249
|
+
faceState = state;
|
|
250
|
+
},
|
|
251
|
+
setOverrideState(state) {
|
|
252
|
+
overrideState = state;
|
|
253
|
+
},
|
|
254
|
+
dispose() {
|
|
255
|
+
ctx = null;
|
|
256
|
+
ballGradients = null;
|
|
257
|
+
specGradient = null;
|
|
258
|
+
},
|
|
259
|
+
render(frame) {
|
|
260
|
+
const c2d = ctx;
|
|
261
|
+
if (!c2d) return;
|
|
262
|
+
const s = size;
|
|
263
|
+
c2d.clearRect(0, 0, s, s);
|
|
264
|
+
const nowMs = performance.now();
|
|
265
|
+
const dtMs = clamp(lastRenderAt > 0 ? nowMs - lastRenderAt : RENDER_DT_SEED_MS, 1, MAX_RENDER_DT_MS);
|
|
266
|
+
lastRenderAt = nowMs;
|
|
267
|
+
const state = overrideState ?? resolveGlobalOverride() ?? faceState;
|
|
268
|
+
easePaletteToward(eased, PALETTES[state], dtAlpha(PALETTE_ALPHA_30, dtMs));
|
|
269
|
+
const target = frame.tracked ? 1 : 0;
|
|
270
|
+
presence += (target - presence) * dtAlpha(PRESENCE_ALPHA_30, dtMs);
|
|
271
|
+
const faceAlpha = presence;
|
|
272
|
+
const k = s / SPRITE;
|
|
273
|
+
const slideX = frame.rootOffset.x * s * .03;
|
|
274
|
+
const slideY = frame.rootOffset.y * s * .03;
|
|
275
|
+
const scale = 1 + clamp(frame.depth, -.6, .8) * .35;
|
|
276
|
+
const yaw = clamp(frame.headEuler.y, -.9, .9);
|
|
277
|
+
const pitch = clamp(frame.headEuler.x, -.9, .9);
|
|
278
|
+
drawHalo(c2d, s, k);
|
|
279
|
+
c2d.save();
|
|
280
|
+
c2d.translate(s / 2 + slideX, s / 2 + slideY);
|
|
281
|
+
c2d.rotate(frame.headEuler.z);
|
|
282
|
+
c2d.scale(scale * k, scale * k);
|
|
283
|
+
c2d.translate(-512 / 2, -512 / 2);
|
|
284
|
+
if (faceAlpha > .02) {
|
|
285
|
+
c2d.globalAlpha = faceAlpha;
|
|
286
|
+
drawBall(c2d);
|
|
287
|
+
const featX = yaw * YAW_PARALLAX;
|
|
288
|
+
const featY = pitch * PITCH_PARALLAX;
|
|
289
|
+
c2d.save();
|
|
290
|
+
c2d.translate(HEAD_CX, HEAD_CY);
|
|
291
|
+
c2d.scale(1 - Math.abs(yaw) * .24, 1 - Math.abs(pitch) * .12);
|
|
292
|
+
c2d.translate(-256, -256);
|
|
293
|
+
c2d.translate(featX, featY);
|
|
294
|
+
const feat = rgb(eased.feature);
|
|
295
|
+
let blinkL = clamp(frame.blink.left, 0, 1);
|
|
296
|
+
let blinkR = clamp(frame.blink.right, 0, 1);
|
|
297
|
+
if (state === "success") {
|
|
298
|
+
blinkL = 0;
|
|
299
|
+
blinkR = .85;
|
|
300
|
+
}
|
|
301
|
+
drawEye(c2d, HEAD_CX - EYE_DX, EYE_Y, blinkL, feat);
|
|
302
|
+
drawEye(c2d, 312, EYE_Y, blinkR, feat);
|
|
303
|
+
drawNose(c2d, feat);
|
|
304
|
+
let mouth = Math.max(REST_SMILE, clamp(frame.smile ?? 0, 0, 1));
|
|
305
|
+
if (state === "success") mouth = 1;
|
|
306
|
+
else if (state === "error") mouth = -.55;
|
|
307
|
+
drawMouth(c2d, mouth, feat);
|
|
308
|
+
c2d.restore();
|
|
309
|
+
c2d.globalAlpha = 1;
|
|
310
|
+
}
|
|
311
|
+
c2d.restore();
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Main-thread wrapper: owns a DOM `<canvas>` and delegates to the shared
|
|
317
|
+
* drawing core. Public shape unchanged for existing consumers (`incode-2d`,
|
|
318
|
+
* `avatar-2d` main path).
|
|
319
|
+
*/
|
|
320
|
+
var SatinSmileyRenderer = class {
|
|
321
|
+
constructor(size = 512) {
|
|
322
|
+
this.core = null;
|
|
323
|
+
this.pendingState = "detecting";
|
|
324
|
+
this.canvas = document.createElement("canvas");
|
|
325
|
+
this.canvas.width = size;
|
|
326
|
+
this.canvas.height = size;
|
|
327
|
+
}
|
|
328
|
+
getCanvas() {
|
|
329
|
+
return this.canvas;
|
|
330
|
+
}
|
|
331
|
+
/** Set the verdict state (detecting/success/error); colours ease toward it. */
|
|
332
|
+
setState(state) {
|
|
333
|
+
this.pendingState = state;
|
|
334
|
+
this.core?.setState(state);
|
|
335
|
+
}
|
|
336
|
+
init() {
|
|
337
|
+
this.core = createSatinSmileyCore(this.canvas);
|
|
338
|
+
this.core.setState(this.pendingState);
|
|
339
|
+
}
|
|
340
|
+
dispose() {
|
|
341
|
+
this.core?.dispose();
|
|
342
|
+
this.core = null;
|
|
343
|
+
}
|
|
344
|
+
render(frame) {
|
|
345
|
+
this.core?.render(frame);
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
//#endregion
|
|
349
|
+
//#region ../infra/src/avatar/2d-default-avatar/pose.ts
|
|
350
|
+
const AVATAR2D_POSE_CONFIG = {
|
|
351
|
+
yawGain: 2.2,
|
|
352
|
+
pitchUpGain: 3.4,
|
|
353
|
+
pitchDownGain: 2.6,
|
|
354
|
+
rollGain: 1,
|
|
355
|
+
rotMax: .9,
|
|
356
|
+
lateralGain: 1.6,
|
|
357
|
+
lateralMax: 1,
|
|
358
|
+
depthGain: 2,
|
|
359
|
+
depthMax: .8,
|
|
360
|
+
signYaw: 1,
|
|
361
|
+
signPitch: 1,
|
|
362
|
+
signRoll: 1,
|
|
363
|
+
signX: 1,
|
|
364
|
+
signY: 1
|
|
365
|
+
};
|
|
366
|
+
const AVATAR2D_DEADZONE = {
|
|
367
|
+
rot: .005,
|
|
368
|
+
roll: .006,
|
|
369
|
+
pos: .03,
|
|
370
|
+
depth: .1
|
|
371
|
+
};
|
|
372
|
+
/**
|
|
373
|
+
* Below this face-size ratio the detector emits sporadic single-frame outlier
|
|
374
|
+
* spikes ("the avatar jumps") that the deadzone (idle-only) and the EMA (delays,
|
|
375
|
+
* doesn't reject) both miss; run the {@link Median} first in that regime. At/above
|
|
376
|
+
* it, the face is near and exp smoothing alone is enough. (FACE_AVATAR.md → Pose pipeline.)
|
|
377
|
+
*/
|
|
378
|
+
const MEDIAN_FACE_RATIO = .35;
|
|
379
|
+
/**
|
|
380
|
+
* Single-axis rolling-window median for outlier rejection. Returns the latest
|
|
381
|
+
* sample until the window fills (no start-up lag spike), then the middle of the
|
|
382
|
+
* sorted window. The buffer stays in insertion order so `shift()` evicts the
|
|
383
|
+
* OLDEST sample, not the smallest. Ported from the incode-personhood-widget
|
|
384
|
+
* `MedianFilter` — **copied, not shared** (POC isolation; renderer is the only
|
|
385
|
+
* shared surface). Use three independent instances (one per axis).
|
|
386
|
+
*
|
|
387
|
+
* SELF-CONTAINED (window size is a ctor arg, `slice()` not spread) — it is
|
|
388
|
+
* `toString()`-serialized into the avatar-2d worker.
|
|
389
|
+
*/
|
|
390
|
+
var Median = class {
|
|
391
|
+
constructor(windowSize = 7) {
|
|
392
|
+
this.buf = [];
|
|
393
|
+
this.windowSize = windowSize;
|
|
394
|
+
}
|
|
395
|
+
reset() {
|
|
396
|
+
this.buf = [];
|
|
397
|
+
}
|
|
398
|
+
filter(x) {
|
|
399
|
+
this.buf.push(x);
|
|
400
|
+
if (this.buf.length > this.windowSize) this.buf.shift();
|
|
401
|
+
if (this.buf.length < this.windowSize) return x;
|
|
402
|
+
const sorted = this.buf.slice().sort((a, b) => a - b);
|
|
403
|
+
return sorted[Math.floor(sorted.length / 2)];
|
|
404
|
+
}
|
|
405
|
+
};
|
|
406
|
+
/**
|
|
407
|
+
* Caps on the auto-calibrated neutral so calibration cancels small device/framing
|
|
408
|
+
* bias but NOT the user's real starting orientation. Yaw/roll only (straight ≈ 0
|
|
409
|
+
* for both); pitch + position are left unbounded (pitch has a person-specific
|
|
410
|
+
* baseline; position is genuine framing) — bounding those risks an "always looks
|
|
411
|
+
* down/off-centre" artifact. Tune on device.
|
|
412
|
+
*/
|
|
413
|
+
const AVATAR2D_NEUTRAL_BIAS = {
|
|
414
|
+
yaw: .06,
|
|
415
|
+
roll: .05
|
|
416
|
+
};
|
|
417
|
+
/**
|
|
418
|
+
* Extract the compact payload from a FaceLandmarker result. Returns null when
|
|
419
|
+
* no usable face. **SELF-CONTAINED** — no module refs (the mesh indices are
|
|
420
|
+
* inlined: nose tip 4, left eye outer 33 / inner 133, right eye outer 263 /
|
|
421
|
+
* inner 362, forehead 10, chin 152 — see the LM_* docs above) — because it is
|
|
422
|
+
* `toString()`-serialized into the avatar-2d worker; it is also the first half
|
|
423
|
+
* of the main-thread {@link computeRawPose}. Keep it free of spread/helpers.
|
|
424
|
+
*/
|
|
425
|
+
function extractCompactFace(result) {
|
|
426
|
+
const lm = result.faceLandmarks && result.faceLandmarks[0];
|
|
427
|
+
if (!lm) return null;
|
|
428
|
+
const nose = lm[4];
|
|
429
|
+
const eyeLo = lm[33];
|
|
430
|
+
const eyeLi = lm[133];
|
|
431
|
+
const eyeRo = lm[263];
|
|
432
|
+
const eyeRi = lm[362];
|
|
433
|
+
const forehead = lm[10];
|
|
434
|
+
const chin = lm[152];
|
|
435
|
+
if (!nose || !eyeLo || !eyeLi || !eyeRo || !eyeRi || !forehead || !chin) return null;
|
|
436
|
+
let minX = Infinity;
|
|
437
|
+
let minY = Infinity;
|
|
438
|
+
let maxX = -Infinity;
|
|
439
|
+
let maxY = -Infinity;
|
|
440
|
+
for (let i = 0; i < lm.length; i += 1) {
|
|
441
|
+
const p = lm[i];
|
|
442
|
+
if (p.x < minX) minX = p.x;
|
|
443
|
+
if (p.x > maxX) maxX = p.x;
|
|
444
|
+
if (p.y < minY) minY = p.y;
|
|
445
|
+
if (p.y > maxY) maxY = p.y;
|
|
446
|
+
}
|
|
447
|
+
let blinkL = 0;
|
|
448
|
+
let blinkR = 0;
|
|
449
|
+
let jawOpen = 0;
|
|
450
|
+
let smileL = 0;
|
|
451
|
+
let smileR = 0;
|
|
452
|
+
const shapes = result.faceBlendshapes && result.faceBlendshapes[0];
|
|
453
|
+
const categories = shapes && shapes.categories || [];
|
|
454
|
+
for (const c of categories) if (c.categoryName === "eyeBlinkLeft") blinkL = c.score;
|
|
455
|
+
else if (c.categoryName === "eyeBlinkRight") blinkR = c.score;
|
|
456
|
+
else if (c.categoryName === "jawOpen") jawOpen = c.score;
|
|
457
|
+
else if (c.categoryName === "mouthSmileLeft") smileL = c.score;
|
|
458
|
+
else if (c.categoryName === "mouthSmileRight") smileR = c.score;
|
|
459
|
+
return {
|
|
460
|
+
nx: nose.x,
|
|
461
|
+
ny: nose.y,
|
|
462
|
+
lox: eyeLo.x,
|
|
463
|
+
loy: eyeLo.y,
|
|
464
|
+
lix: eyeLi.x,
|
|
465
|
+
liy: eyeLi.y,
|
|
466
|
+
rox: eyeRo.x,
|
|
467
|
+
roy: eyeRo.y,
|
|
468
|
+
rix: eyeRi.x,
|
|
469
|
+
riy: eyeRi.y,
|
|
470
|
+
foreheadY: forehead.y,
|
|
471
|
+
chinY: chin.y,
|
|
472
|
+
bboxW: maxX - minX,
|
|
473
|
+
bboxH: maxY - minY,
|
|
474
|
+
blinkL,
|
|
475
|
+
blinkR,
|
|
476
|
+
jawOpen,
|
|
477
|
+
smile: (smileL + smileR) / 2
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* Raw pose from a compact face, in **pixel space** (multiply normalized coords
|
|
482
|
+
* by the DETECT input width/height — worker and main must use the same dims as
|
|
483
|
+
* calibration) so distances/angles aren't skewed on non-square frames.
|
|
484
|
+
* Returns null on a non-finite frame (NaN guard).
|
|
485
|
+
*/
|
|
486
|
+
function rawPoseFromCompact(c, width, height) {
|
|
487
|
+
const nx = c.nx * width;
|
|
488
|
+
const ny = c.ny * height;
|
|
489
|
+
const lx = (c.lox + c.lix) / 2 * width;
|
|
490
|
+
const ly = (c.loy + c.liy) / 2 * height;
|
|
491
|
+
const rx = (c.rox + c.rix) / 2 * width;
|
|
492
|
+
const ry = (c.roy + c.riy) / 2 * height;
|
|
493
|
+
const fy = c.foreheadY * height;
|
|
494
|
+
const cy = c.chinY * height;
|
|
495
|
+
const eyeMidX = (lx + rx) / 2;
|
|
496
|
+
const eyeMidY = (ly + ry) / 2;
|
|
497
|
+
const faceMidY = (fy + cy) / 2;
|
|
498
|
+
const eyeDist = Math.hypot(rx - lx, ry - ly) || 1e-4;
|
|
499
|
+
const faceH = Math.abs(cy - fy) || 1e-4;
|
|
500
|
+
const yaw = (nx - eyeMidX) / eyeDist;
|
|
501
|
+
const pitch = (ny - faceMidY) / faceH;
|
|
502
|
+
const roll = Math.atan2(ry - ly, rx - lx);
|
|
503
|
+
if (!Number.isFinite(yaw) || !Number.isFinite(pitch) || !Number.isFinite(roll) || !Number.isFinite(eyeMidX) || !Number.isFinite(eyeDist)) return null;
|
|
504
|
+
let faceSizeRatio;
|
|
505
|
+
if (width > 0 && height > 0) {
|
|
506
|
+
const bw = c.bboxW * width;
|
|
507
|
+
const bh = c.bboxH * height;
|
|
508
|
+
if (bw > 0 && bh > 0) faceSizeRatio = Math.sqrt(bw * bh) / Math.min(width, height);
|
|
509
|
+
}
|
|
510
|
+
return {
|
|
511
|
+
yaw,
|
|
512
|
+
pitch,
|
|
513
|
+
roll,
|
|
514
|
+
eyeMidX,
|
|
515
|
+
eyeMidY,
|
|
516
|
+
eyeDist,
|
|
517
|
+
faceH,
|
|
518
|
+
faceSizeRatio
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* Map a raw pose (relative to neutral) into head euler + offset + depth.
|
|
523
|
+
* SELF-CONTAINED (deadzones arrive as a param; clamp/deadzone inlined) — it is
|
|
524
|
+
* `toString()`-serialized into the avatar-2d worker.
|
|
525
|
+
*/
|
|
526
|
+
function mapToPose(raw, neutral, cfg, dz) {
|
|
527
|
+
const deadzone = (v, d) => Math.abs(v) <= d ? 0 : v - Math.sign(v) * d;
|
|
528
|
+
const clamp = (v, lo, hi) => Math.min(hi, Math.max(lo, v));
|
|
529
|
+
let dRoll = raw.roll - neutral.roll;
|
|
530
|
+
dRoll = Math.atan2(Math.sin(dRoll), Math.cos(dRoll));
|
|
531
|
+
const dist = neutral.dist || 1e-4;
|
|
532
|
+
const dPitch = deadzone(raw.pitch - neutral.pitch, dz.rot);
|
|
533
|
+
const pitchGain = dPitch < 0 ? cfg.pitchUpGain : cfg.pitchDownGain;
|
|
534
|
+
const dYaw = deadzone(raw.yaw - neutral.yaw, dz.rot);
|
|
535
|
+
const dRollDz = deadzone(dRoll, dz.roll);
|
|
536
|
+
const relX = deadzone((raw.eyeMidX - neutral.cx) / dist, dz.pos);
|
|
537
|
+
const relY = deadzone((raw.eyeMidY - neutral.cy) / dist, dz.pos);
|
|
538
|
+
const faceHN = neutral.faceH || 1e-4;
|
|
539
|
+
const relZ = deadzone((raw.faceH - neutral.faceH) / faceHN, dz.depth);
|
|
540
|
+
return {
|
|
541
|
+
headEuler: {
|
|
542
|
+
x: clamp(dPitch * pitchGain * cfg.signPitch, -cfg.rotMax, cfg.rotMax),
|
|
543
|
+
y: clamp(dYaw * cfg.yawGain * cfg.signYaw, -cfg.rotMax, cfg.rotMax),
|
|
544
|
+
z: clamp(dRollDz * cfg.rollGain * cfg.signRoll, -cfg.rotMax, cfg.rotMax)
|
|
545
|
+
},
|
|
546
|
+
rootOffset: {
|
|
547
|
+
x: clamp(relX * cfg.lateralGain * cfg.signX, -cfg.lateralMax, cfg.lateralMax),
|
|
548
|
+
y: clamp(relY * cfg.lateralGain * cfg.signY, -cfg.lateralMax, cfg.lateralMax)
|
|
549
|
+
},
|
|
550
|
+
depth: clamp(relZ * cfg.depthGain, -cfg.depthMax, cfg.depthMax)
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
//#endregion
|
|
554
|
+
//#region ../infra/src/avatar/2d-default-avatar/avatar2dCore.ts
|
|
555
|
+
/**
|
|
556
|
+
* Avatar-2d shared cores (ENG-49806 render-in-worker refactor).
|
|
557
|
+
*
|
|
558
|
+
* The lens's `privacyLensGlCore.ts` pattern applied to the 2D avatar: the
|
|
559
|
+
* pieces of the pipeline that must run in BOTH transports (main thread and the
|
|
560
|
+
* worker) live here as **self-contained, `toString()`-serializable factories**
|
|
561
|
+
* with a single source of truth:
|
|
562
|
+
*
|
|
563
|
+
* - {@link createAvatar2DPoseCore} — compact-face ingestion → median outlier
|
|
564
|
+
* rejection → averaged-neutral calibration (velocity-gated, time-boxed) →
|
|
565
|
+
* face-lost gate → dt-corrected EMA smoothing → the pose to render.
|
|
566
|
+
*
|
|
567
|
+
* Serialization rules (same as the lens): factory bodies must not reference
|
|
568
|
+
* ANY module-level binding — tuning constants arrive via the JSON-injectable
|
|
569
|
+
* {@link AVATAR2D_POSE_CORE_DEFAULTS} config, and cross-function calls go
|
|
570
|
+
* through the `deps` object (minifier-safe). Types are fine (they vanish).
|
|
571
|
+
*/
|
|
572
|
+
/**
|
|
573
|
+
* The pose-pipeline tuning, single home (both transports; the worker builder
|
|
574
|
+
* JSON-injects it). `dtSeedMs` is derived from the effective maxFps at start.
|
|
575
|
+
*/
|
|
576
|
+
const AVATAR2D_POSE_CORE_DEFAULTS = {
|
|
577
|
+
pose: AVATAR2D_POSE_CONFIG,
|
|
578
|
+
deadzone: AVATAR2D_DEADZONE,
|
|
579
|
+
neutralBias: AVATAR2D_NEUTRAL_BIAS,
|
|
580
|
+
medianWindow: 7,
|
|
581
|
+
medianFaceRatio: MEDIAN_FACE_RATIO,
|
|
582
|
+
medianMaxGapMs: 120,
|
|
583
|
+
neutralFrames: 20,
|
|
584
|
+
calMotionYawPitch: .012,
|
|
585
|
+
calMotionRoll: .02,
|
|
586
|
+
calFallbackMult: 3,
|
|
587
|
+
calMinFrames: 1,
|
|
588
|
+
calMaxMs: 300,
|
|
589
|
+
faceLostMs: 600,
|
|
590
|
+
faceLostMinMisses: 2,
|
|
591
|
+
faceLostHardMs: 3e3,
|
|
592
|
+
headAlpha: .4,
|
|
593
|
+
posAlpha: .35,
|
|
594
|
+
depthAlpha: .2,
|
|
595
|
+
exprAlpha: .5,
|
|
596
|
+
refFrameMs: 1e3 / 30,
|
|
597
|
+
maxEmaDtMs: 100,
|
|
598
|
+
dtSeedMs: 1e3 / 30
|
|
599
|
+
};
|
|
600
|
+
/** Main-thread deps instance (the worker builder assembles its own). */
|
|
601
|
+
function createAvatar2DPoseCoreDeps() {
|
|
602
|
+
return {
|
|
603
|
+
rawPoseFromCompact,
|
|
604
|
+
mapToPose,
|
|
605
|
+
createMedian: (windowSize) => new Median(windowSize)
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* The pose pipeline, transport-agnostic. SELF-CONTAINED (serialized via
|
|
610
|
+
* toString into the worker blob): everything arrives via `cfg` / `deps`.
|
|
611
|
+
*/
|
|
612
|
+
function createAvatar2DPoseCore(cfg, deps) {
|
|
613
|
+
const neutralPose = () => ({
|
|
614
|
+
tracked: false,
|
|
615
|
+
headEuler: {
|
|
616
|
+
x: 0,
|
|
617
|
+
y: 0,
|
|
618
|
+
z: 0
|
|
619
|
+
},
|
|
620
|
+
rootOffset: {
|
|
621
|
+
x: 0,
|
|
622
|
+
y: 0
|
|
623
|
+
},
|
|
624
|
+
depth: 0,
|
|
625
|
+
blink: {
|
|
626
|
+
left: 0,
|
|
627
|
+
right: 0
|
|
628
|
+
},
|
|
629
|
+
mouthOpen: 0,
|
|
630
|
+
smile: 0
|
|
631
|
+
});
|
|
632
|
+
const copyPoseInto = (dst, src) => {
|
|
633
|
+
dst.tracked = src.tracked;
|
|
634
|
+
dst.headEuler.x = src.headEuler.x;
|
|
635
|
+
dst.headEuler.y = src.headEuler.y;
|
|
636
|
+
dst.headEuler.z = src.headEuler.z;
|
|
637
|
+
dst.rootOffset.x = src.rootOffset.x;
|
|
638
|
+
dst.rootOffset.y = src.rootOffset.y;
|
|
639
|
+
dst.depth = src.depth;
|
|
640
|
+
dst.blink.left = src.blink.left;
|
|
641
|
+
dst.blink.right = src.blink.right;
|
|
642
|
+
dst.mouthOpen = src.mouthOpen;
|
|
643
|
+
dst.smile = src.smile;
|
|
644
|
+
};
|
|
645
|
+
const clampValue = (v, lo, hi) => Math.min(hi, Math.max(lo, v));
|
|
646
|
+
const dtAlpha = (refAlpha, dtMs) => {
|
|
647
|
+
if (refAlpha <= 0) return 0;
|
|
648
|
+
if (refAlpha >= 1) return 1;
|
|
649
|
+
const tau = -cfg.refFrameMs / Math.log(1 - refAlpha);
|
|
650
|
+
return 1 - Math.exp(-dtMs / tau);
|
|
651
|
+
};
|
|
652
|
+
const current = neutralPose();
|
|
653
|
+
let target = neutralPose();
|
|
654
|
+
let lastFaceAt = 0;
|
|
655
|
+
let missedDetects = 0;
|
|
656
|
+
let lastTickAt = 0;
|
|
657
|
+
let calibrated = false;
|
|
658
|
+
let nFrames = 0;
|
|
659
|
+
let calAttempts = 0;
|
|
660
|
+
let calStartAt = 0;
|
|
661
|
+
let prevCalYaw = NaN;
|
|
662
|
+
let prevCalPitch = NaN;
|
|
663
|
+
let prevCalRoll = NaN;
|
|
664
|
+
let seeded = false;
|
|
665
|
+
const sum = {
|
|
666
|
+
yaw: 0,
|
|
667
|
+
pitch: 0,
|
|
668
|
+
roll: 0,
|
|
669
|
+
cx: 0,
|
|
670
|
+
cy: 0,
|
|
671
|
+
dist: 0,
|
|
672
|
+
faceH: 0
|
|
673
|
+
};
|
|
674
|
+
let neutral = {
|
|
675
|
+
yaw: 0,
|
|
676
|
+
pitch: 0,
|
|
677
|
+
roll: 0,
|
|
678
|
+
cx: 0,
|
|
679
|
+
cy: 0,
|
|
680
|
+
dist: 1,
|
|
681
|
+
faceH: 1
|
|
682
|
+
};
|
|
683
|
+
const medYaw = deps.createMedian(cfg.medianWindow);
|
|
684
|
+
const medPitch = deps.createMedian(cfg.medianWindow);
|
|
685
|
+
const medRoll = deps.createMedian(cfg.medianWindow);
|
|
686
|
+
return {
|
|
687
|
+
ingest(face, width, height, now) {
|
|
688
|
+
let landed = false;
|
|
689
|
+
const raw0 = face ? deps.rawPoseFromCompact(face, width, height) : null;
|
|
690
|
+
if (face && raw0) {
|
|
691
|
+
landed = true;
|
|
692
|
+
const small = !(now - lastFaceAt > cfg.medianMaxGapMs) && raw0.faceSizeRatio !== void 0 && raw0.faceSizeRatio < cfg.medianFaceRatio;
|
|
693
|
+
let raw = raw0;
|
|
694
|
+
if (small) raw = {
|
|
695
|
+
yaw: medYaw.filter(raw0.yaw),
|
|
696
|
+
pitch: medPitch.filter(raw0.pitch),
|
|
697
|
+
roll: medRoll.filter(raw0.roll),
|
|
698
|
+
eyeMidX: raw0.eyeMidX,
|
|
699
|
+
eyeMidY: raw0.eyeMidY,
|
|
700
|
+
eyeDist: raw0.eyeDist,
|
|
701
|
+
faceH: raw0.faceH,
|
|
702
|
+
faceSizeRatio: raw0.faceSizeRatio
|
|
703
|
+
};
|
|
704
|
+
else {
|
|
705
|
+
medYaw.reset();
|
|
706
|
+
medPitch.reset();
|
|
707
|
+
medRoll.reset();
|
|
708
|
+
}
|
|
709
|
+
lastFaceAt = now;
|
|
710
|
+
if (!calibrated) {
|
|
711
|
+
calAttempts += 1;
|
|
712
|
+
if (calStartAt === 0) calStartAt = now;
|
|
713
|
+
const dRollMotion = Math.atan2(Math.sin(raw.roll - prevCalRoll), Math.cos(raw.roll - prevCalRoll));
|
|
714
|
+
const moved = Number.isFinite(prevCalYaw) && (Math.abs(raw.yaw - prevCalYaw) > cfg.calMotionYawPitch || Math.abs(raw.pitch - prevCalPitch) > cfg.calMotionYawPitch || Math.abs(dRollMotion) > cfg.calMotionRoll);
|
|
715
|
+
prevCalYaw = raw.yaw;
|
|
716
|
+
prevCalPitch = raw.pitch;
|
|
717
|
+
prevCalRoll = raw.roll;
|
|
718
|
+
const fallback = calAttempts >= cfg.neutralFrames * cfg.calFallbackMult || now - calStartAt > cfg.calMaxMs;
|
|
719
|
+
if (!moved || fallback) {
|
|
720
|
+
nFrames += 1;
|
|
721
|
+
sum.yaw += raw.yaw;
|
|
722
|
+
sum.pitch += raw.pitch;
|
|
723
|
+
sum.roll += raw.roll;
|
|
724
|
+
sum.cx += raw.eyeMidX;
|
|
725
|
+
sum.cy += raw.eyeMidY;
|
|
726
|
+
sum.dist += raw.eyeDist;
|
|
727
|
+
sum.faceH += raw.faceH;
|
|
728
|
+
}
|
|
729
|
+
const timeBoxed = nFrames >= cfg.calMinFrames && now - calStartAt > cfg.calMaxMs;
|
|
730
|
+
if (nFrames >= cfg.neutralFrames || timeBoxed) {
|
|
731
|
+
const n = nFrames;
|
|
732
|
+
neutral = {
|
|
733
|
+
yaw: clampValue(sum.yaw / n, -cfg.neutralBias.yaw, cfg.neutralBias.yaw),
|
|
734
|
+
pitch: sum.pitch / n,
|
|
735
|
+
roll: clampValue(sum.roll / n, -cfg.neutralBias.roll, cfg.neutralBias.roll),
|
|
736
|
+
cx: sum.cx / n,
|
|
737
|
+
cy: sum.cy / n,
|
|
738
|
+
dist: sum.dist / n || 1,
|
|
739
|
+
faceH: sum.faceH / n || 1
|
|
740
|
+
};
|
|
741
|
+
calibrated = true;
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
if (calibrated) {
|
|
745
|
+
const mapped = deps.mapToPose(raw, neutral, cfg.pose, cfg.deadzone);
|
|
746
|
+
target = {
|
|
747
|
+
tracked: true,
|
|
748
|
+
headEuler: {
|
|
749
|
+
x: mapped.headEuler.x,
|
|
750
|
+
y: mapped.headEuler.y,
|
|
751
|
+
z: mapped.headEuler.z
|
|
752
|
+
},
|
|
753
|
+
rootOffset: {
|
|
754
|
+
x: mapped.rootOffset.x,
|
|
755
|
+
y: mapped.rootOffset.y
|
|
756
|
+
},
|
|
757
|
+
depth: mapped.depth,
|
|
758
|
+
blink: {
|
|
759
|
+
left: face.blinkL,
|
|
760
|
+
right: face.blinkR
|
|
761
|
+
},
|
|
762
|
+
mouthOpen: face.jawOpen,
|
|
763
|
+
smile: face.smile
|
|
764
|
+
};
|
|
765
|
+
if (!seeded) {
|
|
766
|
+
copyPoseInto(current, target);
|
|
767
|
+
seeded = true;
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
missedDetects = landed ? 0 : missedDetects + 1;
|
|
772
|
+
},
|
|
773
|
+
tick(now) {
|
|
774
|
+
const dtMs = clampValue(lastTickAt > 0 ? now - lastTickAt : cfg.dtSeedMs, 1, cfg.maxEmaDtMs);
|
|
775
|
+
lastTickAt = now;
|
|
776
|
+
if (now - lastFaceAt > cfg.faceLostMs && missedDetects >= cfg.faceLostMinMisses || now - lastFaceAt > cfg.faceLostHardMs) target = neutralPose();
|
|
777
|
+
const ha = dtAlpha(cfg.headAlpha, dtMs);
|
|
778
|
+
const pa = dtAlpha(cfg.posAlpha, dtMs);
|
|
779
|
+
const da = dtAlpha(cfg.depthAlpha, dtMs);
|
|
780
|
+
const ea = dtAlpha(cfg.exprAlpha, dtMs);
|
|
781
|
+
const c = current;
|
|
782
|
+
const t = target;
|
|
783
|
+
c.tracked = t.tracked;
|
|
784
|
+
c.headEuler.x += (t.headEuler.x - c.headEuler.x) * ha;
|
|
785
|
+
c.headEuler.y += (t.headEuler.y - c.headEuler.y) * ha;
|
|
786
|
+
c.headEuler.z += (t.headEuler.z - c.headEuler.z) * ha;
|
|
787
|
+
c.rootOffset.x += (t.rootOffset.x - c.rootOffset.x) * pa;
|
|
788
|
+
c.rootOffset.y += (t.rootOffset.y - c.rootOffset.y) * pa;
|
|
789
|
+
c.depth += (t.depth - c.depth) * da;
|
|
790
|
+
c.blink.left += (t.blink.left - c.blink.left) * ea;
|
|
791
|
+
c.blink.right += (t.blink.right - c.blink.right) * ea;
|
|
792
|
+
c.mouthOpen += (t.mouthOpen - c.mouthOpen) * ea;
|
|
793
|
+
c.smile += (t.smile - c.smile) * ea;
|
|
794
|
+
return current;
|
|
795
|
+
}
|
|
796
|
+
};
|
|
797
|
+
}
|
|
798
|
+
//#endregion
|
|
799
|
+
//#region ../infra/src/avatar/2d-default-avatar/avatar2dWorker.ts
|
|
800
|
+
/**
|
|
801
|
+
* Worker mode for the 2D avatar (ENG-49806) — TWO workers, full pipeline
|
|
802
|
+
* off-main.
|
|
803
|
+
*
|
|
804
|
+
* Under a saturated main thread (the synchronous WASM capture pipeline runs
|
|
805
|
+
* back-to-back on throttled/low-tier devices) ANY main-thread loop is starved
|
|
806
|
+
* to single-digit fps no matter how cheap its frame is. And on low-tier
|
|
807
|
+
* silicon a SINGLE worker is not enough either: one MediaPipe inference costs
|
|
808
|
+
* ~200-250ms on a Cortex-A53 under capture contention, blocking that worker's
|
|
809
|
+
* own render loop to ~8fps (measured on a Galaxy A04e; adaptive cadence alone
|
|
810
|
+
* capped inference duty at 50% but every inference still froze the ball
|
|
811
|
+
* mid-drift). So the pipeline is SPLIT:
|
|
812
|
+
*
|
|
813
|
+
* - RENDER worker — pose core + SatinSmiley on the transferred
|
|
814
|
+
* `OffscreenCanvas`, worker-rAF loop + tolerance pacer. Its frame costs
|
|
815
|
+
* ~0.3ms and NOTHING in it ever blocks → ~25-30fps even on the A04e.
|
|
816
|
+
* - TRACK worker — camera track (cloned) → `MediaStreamTrackProcessor` →
|
|
817
|
+
* `VideoFrame` downscale → FaceLandmarker, at an adaptive cadence (EMA'd
|
|
818
|
+
* cost × 2 — never more than ~50% of its own thread). Each detect posts a
|
|
819
|
+
* tiny compact-face message over a `MessageChannel` DIRECTLY to the render
|
|
820
|
+
* worker (never through the contended main thread).
|
|
821
|
+
*
|
|
822
|
+
* The main thread only bridges dev flags and verdict-state cues, and
|
|
823
|
+
* re-publishes the render worker's stats snapshots.
|
|
824
|
+
*
|
|
825
|
+
* Boot keeps the instant-mount contract (sub-second halo): the render
|
|
826
|
+
* worker's `'started'` handshake covers only its core init (ms). The track
|
|
827
|
+
* worker's MediaPipe bring-up (module + ~3.7MB model + WASM compile) runs in
|
|
828
|
+
* the background — or, better, ALREADY RAN during the tutorial via the warm
|
|
829
|
+
* registry (`avatar2dWarm.ts` warms a track worker; the provider adopts it).
|
|
830
|
+
* Tracking failure leaves the avatar in the searching state ('trackingFatal'
|
|
831
|
+
* logged) — the face stays concealed either way.
|
|
832
|
+
*
|
|
833
|
+
* Support: Chromium 94+ (Chrome/Edge/Android WebView/Samsung Internet) — the
|
|
834
|
+
* low-tier Android target segment. Safari/Firefox and strict-CSP hosts fall
|
|
835
|
+
* back to the main-thread path automatically (`Face2DProvider`).
|
|
836
|
+
*
|
|
837
|
+
* Packaging: like the lens, both worker scripts are assembled AT RUNTIME from
|
|
838
|
+
* `Function.prototype.toString()` of the self-contained factories and loaded
|
|
839
|
+
* via Blob URLs — no separate worker chunks, bundler-agnostic. They are
|
|
840
|
+
* CLASSIC workers (NOT `{ type: 'module' }`): MediaPipe's wasm glue loads
|
|
841
|
+
* itself via `importScripts()`, which module workers lack — booting
|
|
842
|
+
* tasks-vision in a module worker dies with "ModuleFactory not set". The
|
|
843
|
+
* tasks-vision ESM is instead dynamic-imported via a Function-constructed
|
|
844
|
+
* `import()` (see `avatar2dTrackWorkerMain`) — supported in Chromium classic
|
|
845
|
+
* dedicated workers, the only environment these workers run in (probe-gated).
|
|
846
|
+
* No `window.Module` shield is needed in-worker — ml-wasm-kit never runs
|
|
847
|
+
* there.
|
|
848
|
+
*/
|
|
849
|
+
/**
|
|
850
|
+
* Whether the worker path is available in this environment. Anything missing
|
|
851
|
+
* (Safari's worker-only MSTP shape, Firefox, old WebView) routes to the
|
|
852
|
+
* main-thread fallback.
|
|
853
|
+
*/
|
|
854
|
+
function canUseAvatar2DWorker() {
|
|
855
|
+
const g = globalThis;
|
|
856
|
+
if (typeof g.MediaStreamTrackProcessor !== "function" || typeof g.Worker !== "function" || typeof g.MessageChannel !== "function" || typeof g.HTMLCanvasElement !== "function") return false;
|
|
857
|
+
return "transferControlToOffscreen" in g.HTMLCanvasElement.prototype;
|
|
858
|
+
}
|
|
859
|
+
/**
|
|
860
|
+
* RENDER worker logic. SELF-CONTAINED (serialized via toString into the
|
|
861
|
+
* blob): everything arrives via `deps` / messages; no module refs. The loop
|
|
862
|
+
* is worker-rAF when available (Chromium exposes it in dedicated workers),
|
|
863
|
+
* else setTimeout ~60Hz; the tolerance-based pacer enforces maxFps. Pose
|
|
864
|
+
* updates arrive on the channel port and are ingested on receipt — nothing
|
|
865
|
+
* in this worker ever blocks longer than a canvas-2D draw (~0.3ms).
|
|
866
|
+
*/
|
|
867
|
+
function avatar2dRenderWorkerMain(scope, deps) {
|
|
868
|
+
let stopped = false;
|
|
869
|
+
let poseCore = null;
|
|
870
|
+
let smiley = null;
|
|
871
|
+
let pacer = null;
|
|
872
|
+
let port = null;
|
|
873
|
+
let statsEnabled = false;
|
|
874
|
+
let rafId = null;
|
|
875
|
+
let timerId = null;
|
|
876
|
+
const PUBLISH_MS = 500;
|
|
877
|
+
const GAP_WINDOW = 128;
|
|
878
|
+
let renderedTotal = 0;
|
|
879
|
+
let dropped = 0;
|
|
880
|
+
let winStart = NaN;
|
|
881
|
+
let winFrames = 0;
|
|
882
|
+
let drawSum = 0;
|
|
883
|
+
let detectSum = 0;
|
|
884
|
+
let detectCount = 0;
|
|
885
|
+
const gaps = [];
|
|
886
|
+
let lastFrameAt = NaN;
|
|
887
|
+
let trackingStatus = "loading";
|
|
888
|
+
let detectsRun = 0;
|
|
889
|
+
let facesFound = 0;
|
|
890
|
+
let lastDims = "";
|
|
891
|
+
let debugIntervalMs = 0;
|
|
892
|
+
const publishStats = (nowMs) => {
|
|
893
|
+
const elapsed = nowMs - winStart;
|
|
894
|
+
if (!(elapsed > 0)) return;
|
|
895
|
+
const fps = Math.round(winFrames / elapsed * 1e3 * 10) / 10;
|
|
896
|
+
const snapshot = {
|
|
897
|
+
variant: "avatar-2d",
|
|
898
|
+
mode: "worker",
|
|
899
|
+
avatarFps: fps,
|
|
900
|
+
renderedFps: fps,
|
|
901
|
+
renderedTotal,
|
|
902
|
+
droppedByThrottle: dropped,
|
|
903
|
+
tsMs: nowMs
|
|
904
|
+
};
|
|
905
|
+
if (winFrames > 0) snapshot.drawMsAvg = Math.round(drawSum / winFrames * 100) / 100;
|
|
906
|
+
if (detectCount > 0) snapshot.uploadMsAvg = Math.round(detectSum / detectCount * 100) / 100;
|
|
907
|
+
if (gaps.length > 0) {
|
|
908
|
+
const sorted = gaps.slice().sort((a, b) => a - b);
|
|
909
|
+
const idx = Math.min(sorted.length - 1, Math.floor(sorted.length * .95));
|
|
910
|
+
snapshot.frameGapP95Ms = Math.round(sorted[idx] * 10) / 10;
|
|
911
|
+
}
|
|
912
|
+
snapshot.debug = "landmarker=" + trackingStatus + " dims=" + (lastDims || "?") + " faces=" + facesFound + "/" + detectsRun + " detInt=" + Math.round(debugIntervalMs) + "ms";
|
|
913
|
+
scope.postMessage({
|
|
914
|
+
type: "stats",
|
|
915
|
+
snapshot
|
|
916
|
+
});
|
|
917
|
+
winStart = nowMs;
|
|
918
|
+
winFrames = 0;
|
|
919
|
+
drawSum = 0;
|
|
920
|
+
detectSum = 0;
|
|
921
|
+
detectCount = 0;
|
|
922
|
+
};
|
|
923
|
+
const renderTick = () => {
|
|
924
|
+
if (stopped || !poseCore || !smiley || !pacer) return;
|
|
925
|
+
if (typeof requestAnimationFrame === "function") rafId = requestAnimationFrame(renderTick);
|
|
926
|
+
else timerId = setTimeout(renderTick, 16);
|
|
927
|
+
const now = performance.now();
|
|
928
|
+
if (!pacer.shouldRender(now)) {
|
|
929
|
+
if (statsEnabled) dropped += 1;
|
|
930
|
+
return;
|
|
931
|
+
}
|
|
932
|
+
const frameStart = statsEnabled ? performance.now() : 0;
|
|
933
|
+
smiley.render(poseCore.tick(now));
|
|
934
|
+
if (statsEnabled) {
|
|
935
|
+
renderedTotal += 1;
|
|
936
|
+
if (Number.isNaN(winStart)) winStart = now;
|
|
937
|
+
else {
|
|
938
|
+
winFrames += 1;
|
|
939
|
+
drawSum += performance.now() - frameStart;
|
|
940
|
+
}
|
|
941
|
+
if (!Number.isNaN(lastFrameAt)) {
|
|
942
|
+
gaps.push(now - lastFrameAt);
|
|
943
|
+
if (gaps.length > GAP_WINDOW) gaps.shift();
|
|
944
|
+
}
|
|
945
|
+
lastFrameAt = now;
|
|
946
|
+
if (now - winStart >= PUBLISH_MS) publishStats(now);
|
|
947
|
+
}
|
|
948
|
+
};
|
|
949
|
+
const onPortMessage = (event) => {
|
|
950
|
+
const msg = event.data;
|
|
951
|
+
if (msg.kind === "face") {
|
|
952
|
+
if (poseCore) poseCore.ingest(msg.face, msg.width, msg.height, performance.now());
|
|
953
|
+
detectsRun += 1;
|
|
954
|
+
if (msg.face) facesFound += 1;
|
|
955
|
+
lastDims = msg.width + "x" + msg.height;
|
|
956
|
+
debugIntervalMs = msg.detIntervalMs;
|
|
957
|
+
if (statsEnabled) {
|
|
958
|
+
detectSum += msg.detectMs;
|
|
959
|
+
detectCount += 1;
|
|
960
|
+
}
|
|
961
|
+
} else if (msg.kind === "tracking") trackingStatus = msg.status === "up" ? "up" : "FAILED";
|
|
962
|
+
};
|
|
963
|
+
scope.onmessage = (event) => {
|
|
964
|
+
const msg = event.data;
|
|
965
|
+
if (msg.type === "start") {
|
|
966
|
+
try {
|
|
967
|
+
smiley = deps.createSmileyCore(msg.canvas);
|
|
968
|
+
poseCore = deps.createPoseCore(msg.poseConfig);
|
|
969
|
+
pacer = deps.createPacer(msg.maxFps, 8);
|
|
970
|
+
port = msg.port;
|
|
971
|
+
port.onmessage = onPortMessage;
|
|
972
|
+
} catch (error) {
|
|
973
|
+
scope.postMessage({
|
|
974
|
+
type: "fatal",
|
|
975
|
+
message: String(error)
|
|
976
|
+
});
|
|
977
|
+
return;
|
|
978
|
+
}
|
|
979
|
+
statsEnabled = msg.statsEnabled;
|
|
980
|
+
scope.postMessage({ type: "started" });
|
|
981
|
+
renderTick();
|
|
982
|
+
} else if (msg.type === "state") smiley?.setState(msg.state);
|
|
983
|
+
else if (msg.type === "override") smiley?.setOverrideState(msg.state);
|
|
984
|
+
else if (msg.type === "statsEnabled") {
|
|
985
|
+
if (msg.value && !statsEnabled) {
|
|
986
|
+
winStart = NaN;
|
|
987
|
+
lastFrameAt = NaN;
|
|
988
|
+
}
|
|
989
|
+
statsEnabled = msg.value;
|
|
990
|
+
} else if (msg.type === "stop") {
|
|
991
|
+
stopped = true;
|
|
992
|
+
if (rafId != null && typeof cancelAnimationFrame === "function") cancelAnimationFrame(rafId);
|
|
993
|
+
if (timerId != null) clearTimeout(timerId);
|
|
994
|
+
try {
|
|
995
|
+
port?.close();
|
|
996
|
+
} catch {}
|
|
997
|
+
port = null;
|
|
998
|
+
smiley?.dispose();
|
|
999
|
+
smiley = null;
|
|
1000
|
+
scope.postMessage({ type: "stopped" });
|
|
1001
|
+
}
|
|
1002
|
+
};
|
|
1003
|
+
}
|
|
1004
|
+
/**
|
|
1005
|
+
* TRACK worker logic. SELF-CONTAINED (serialized via toString into the
|
|
1006
|
+
* blob). Detect pump: sequential read → adaptive cadence gate → downscale →
|
|
1007
|
+
* detect → post compact face to the render worker's port → close. MSTP
|
|
1008
|
+
* buffers at most one frame internally, so reads always yield the freshest
|
|
1009
|
+
* frame, and closing every frame promptly keeps the camera's frame pool
|
|
1010
|
+
* healthy — an unclosed VideoFrame can stall the CAPTURE pipeline, which the
|
|
1011
|
+
* avatar must never do (cosmetic-only invariant).
|
|
1012
|
+
*/
|
|
1013
|
+
function avatar2dTrackWorkerMain(scope, deps) {
|
|
1014
|
+
let stopped = false;
|
|
1015
|
+
let reader = null;
|
|
1016
|
+
let landmarker = null;
|
|
1017
|
+
let port = null;
|
|
1018
|
+
let inputCanvas = null;
|
|
1019
|
+
let inputCtx = null;
|
|
1020
|
+
let landmarkerPromise = null;
|
|
1021
|
+
let lastPaths = null;
|
|
1022
|
+
const initLandmarker = (paths) => (async () => {
|
|
1023
|
+
lastPaths = {
|
|
1024
|
+
mediapipeModulePath: paths.mediapipeModulePath,
|
|
1025
|
+
mediapipeWasmPath: paths.mediapipeWasmPath,
|
|
1026
|
+
faceLandmarkerModelPath: paths.faceLandmarkerModelPath
|
|
1027
|
+
};
|
|
1028
|
+
try {
|
|
1029
|
+
const modelBytesPromise = fetch(paths.faceLandmarkerModelPath).then((res) => {
|
|
1030
|
+
if (!res.ok) throw new Error(`model fetch HTTP ${res.status}`);
|
|
1031
|
+
return res.arrayBuffer();
|
|
1032
|
+
}).then((buf) => new Uint8Array(buf)).catch(() => null);
|
|
1033
|
+
const tasksVision = await new Function("u", "return import(u)")(paths.mediapipeModulePath);
|
|
1034
|
+
const fileset = await tasksVision.FilesetResolver.forVisionTasks(paths.mediapipeWasmPath);
|
|
1035
|
+
const modelBytes = await modelBytesPromise;
|
|
1036
|
+
landmarker = await tasksVision.FaceLandmarker.createFromOptions(fileset, {
|
|
1037
|
+
baseOptions: {
|
|
1038
|
+
...modelBytes ? { modelAssetBuffer: modelBytes } : { modelAssetPath: paths.faceLandmarkerModelPath },
|
|
1039
|
+
delegate: "CPU"
|
|
1040
|
+
},
|
|
1041
|
+
runningMode: "VIDEO",
|
|
1042
|
+
numFaces: 1,
|
|
1043
|
+
outputFaceBlendshapes: true,
|
|
1044
|
+
outputFacialTransformationMatrixes: false
|
|
1045
|
+
});
|
|
1046
|
+
} catch (error) {
|
|
1047
|
+
scope.postMessage({
|
|
1048
|
+
type: "trackingFatal",
|
|
1049
|
+
message: String(error)
|
|
1050
|
+
});
|
|
1051
|
+
return false;
|
|
1052
|
+
}
|
|
1053
|
+
if (stopped) {
|
|
1054
|
+
try {
|
|
1055
|
+
landmarker.close();
|
|
1056
|
+
} catch {}
|
|
1057
|
+
landmarker = null;
|
|
1058
|
+
return false;
|
|
1059
|
+
}
|
|
1060
|
+
try {
|
|
1061
|
+
const warmupCanvas = new OffscreenCanvas(320, 240);
|
|
1062
|
+
const warmupCtx = warmupCanvas.getContext("2d");
|
|
1063
|
+
if (warmupCtx) {
|
|
1064
|
+
warmupCtx.fillRect(0, 0, 320, 240);
|
|
1065
|
+
landmarker.detectForVideo(warmupCanvas, performance.now());
|
|
1066
|
+
}
|
|
1067
|
+
} catch {}
|
|
1068
|
+
return true;
|
|
1069
|
+
})();
|
|
1070
|
+
const detectPump = async (cfg) => {
|
|
1071
|
+
let lastDetectAt = -Infinity;
|
|
1072
|
+
let detectCostEma = 0;
|
|
1073
|
+
let minIntervalMs = cfg.detectMinIntervalMs;
|
|
1074
|
+
const SEARCH_MISSES = 3;
|
|
1075
|
+
const SEARCH_MIN_INTERVAL_MS = 330;
|
|
1076
|
+
const SEARCH_DUTY_FACTOR = 4;
|
|
1077
|
+
let noFaceStreak = 0;
|
|
1078
|
+
const MAX_DETECT_THROWS = 30;
|
|
1079
|
+
let detectThrows = 0;
|
|
1080
|
+
while (!stopped && reader && landmarker && port) {
|
|
1081
|
+
let result;
|
|
1082
|
+
try {
|
|
1083
|
+
result = await reader.read();
|
|
1084
|
+
} catch {
|
|
1085
|
+
break;
|
|
1086
|
+
}
|
|
1087
|
+
if (result.done) break;
|
|
1088
|
+
const frame = result.value;
|
|
1089
|
+
try {
|
|
1090
|
+
const now = performance.now();
|
|
1091
|
+
if (now - lastDetectAt < minIntervalMs - cfg.detectIntervalToleranceMs) continue;
|
|
1092
|
+
const fw = frame.displayWidth;
|
|
1093
|
+
const fh = frame.displayHeight;
|
|
1094
|
+
if (!(fw > 0 && fh > 0)) continue;
|
|
1095
|
+
lastDetectAt = now;
|
|
1096
|
+
const scale = Math.min(1, cfg.detectMaxEdge / Math.max(fw, fh));
|
|
1097
|
+
const dw = Math.max(1, Math.round(fw * scale));
|
|
1098
|
+
const dh = Math.max(1, Math.round(fh * scale));
|
|
1099
|
+
if (!inputCanvas || inputCanvas.width !== dw || inputCanvas.height !== dh) {
|
|
1100
|
+
inputCanvas = new OffscreenCanvas(dw, dh);
|
|
1101
|
+
inputCtx = inputCanvas.getContext("2d");
|
|
1102
|
+
}
|
|
1103
|
+
if (!inputCtx) continue;
|
|
1104
|
+
inputCtx.drawImage(frame, 0, 0, dw, dh);
|
|
1105
|
+
let detected = null;
|
|
1106
|
+
try {
|
|
1107
|
+
detected = landmarker.detectForVideo(inputCanvas, now);
|
|
1108
|
+
detectThrows = 0;
|
|
1109
|
+
} catch {
|
|
1110
|
+
detected = null;
|
|
1111
|
+
detectThrows += 1;
|
|
1112
|
+
}
|
|
1113
|
+
const face = detected ? deps.extractCompact(detected) : null;
|
|
1114
|
+
const detectCost = performance.now() - now;
|
|
1115
|
+
const reacquired = face !== null && noFaceStreak >= SEARCH_MISSES;
|
|
1116
|
+
noFaceStreak = face ? 0 : noFaceStreak + 1;
|
|
1117
|
+
detectCostEma = detectCostEma === 0 || reacquired ? detectCost : detectCostEma + (detectCost - detectCostEma) * .3;
|
|
1118
|
+
minIntervalMs = noFaceStreak >= SEARCH_MISSES ? Math.max(SEARCH_MIN_INTERVAL_MS, detectCostEma * SEARCH_DUTY_FACTOR) : Math.max(cfg.detectMinIntervalMs, detectCostEma * 1.5);
|
|
1119
|
+
port.postMessage({
|
|
1120
|
+
kind: "face",
|
|
1121
|
+
face,
|
|
1122
|
+
width: dw,
|
|
1123
|
+
height: dh,
|
|
1124
|
+
detectMs: detectCost,
|
|
1125
|
+
detIntervalMs: minIntervalMs
|
|
1126
|
+
});
|
|
1127
|
+
} catch {} finally {
|
|
1128
|
+
frame.close();
|
|
1129
|
+
}
|
|
1130
|
+
if (detectThrows >= MAX_DETECT_THROWS && !stopped && lastPaths) {
|
|
1131
|
+
detectThrows = 0;
|
|
1132
|
+
try {
|
|
1133
|
+
landmarker?.close();
|
|
1134
|
+
} catch {}
|
|
1135
|
+
landmarker = null;
|
|
1136
|
+
landmarkerPromise = initLandmarker(lastPaths);
|
|
1137
|
+
if (!await landmarkerPromise) break;
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
};
|
|
1141
|
+
scope.onmessage = (event) => {
|
|
1142
|
+
const msg = event.data;
|
|
1143
|
+
if (msg.type === "warm") {
|
|
1144
|
+
if (!landmarkerPromise) {
|
|
1145
|
+
landmarkerPromise = initLandmarker(msg);
|
|
1146
|
+
landmarkerPromise.then((ok) => {
|
|
1147
|
+
if (ok) scope.postMessage({ type: "warmed" });
|
|
1148
|
+
});
|
|
1149
|
+
}
|
|
1150
|
+
} else if (msg.type === "start") {
|
|
1151
|
+
port = msg.port;
|
|
1152
|
+
(async () => {
|
|
1153
|
+
let ok = landmarkerPromise ? await landmarkerPromise : false;
|
|
1154
|
+
if (!ok && !stopped) {
|
|
1155
|
+
landmarkerPromise = initLandmarker(msg);
|
|
1156
|
+
ok = await landmarkerPromise;
|
|
1157
|
+
}
|
|
1158
|
+
if (!ok || stopped || !landmarker || !port) {
|
|
1159
|
+
try {
|
|
1160
|
+
port?.postMessage({
|
|
1161
|
+
kind: "tracking",
|
|
1162
|
+
status: "failed"
|
|
1163
|
+
});
|
|
1164
|
+
} catch {}
|
|
1165
|
+
return;
|
|
1166
|
+
}
|
|
1167
|
+
port.postMessage({
|
|
1168
|
+
kind: "tracking",
|
|
1169
|
+
status: "up"
|
|
1170
|
+
});
|
|
1171
|
+
reader = msg.readable.getReader();
|
|
1172
|
+
detectPump({
|
|
1173
|
+
detectMaxEdge: msg.detectMaxEdge,
|
|
1174
|
+
detectMinIntervalMs: msg.detectMinIntervalMs,
|
|
1175
|
+
detectIntervalToleranceMs: msg.detectIntervalToleranceMs
|
|
1176
|
+
});
|
|
1177
|
+
})();
|
|
1178
|
+
} else if (msg.type === "stop") {
|
|
1179
|
+
stopped = true;
|
|
1180
|
+
try {
|
|
1181
|
+
reader?.cancel();
|
|
1182
|
+
} catch {}
|
|
1183
|
+
try {
|
|
1184
|
+
landmarker?.close();
|
|
1185
|
+
} catch {}
|
|
1186
|
+
landmarker = null;
|
|
1187
|
+
try {
|
|
1188
|
+
port?.close();
|
|
1189
|
+
} catch {}
|
|
1190
|
+
port = null;
|
|
1191
|
+
scope.postMessage({ type: "stopped" });
|
|
1192
|
+
}
|
|
1193
|
+
};
|
|
1194
|
+
}
|
|
1195
|
+
/**
|
|
1196
|
+
* Assemble the RENDER worker script (pose core + smiley core + pacer + main).
|
|
1197
|
+
* Everything referenced must be self-contained (see module doc); the deps
|
|
1198
|
+
* wiring re-creates the same composition `createAvatar2DPoseCoreDeps`
|
|
1199
|
+
* provides on the main thread. Runtime config travels in the 'start' message.
|
|
1200
|
+
*/
|
|
1201
|
+
function buildAvatar2DRenderWorkerSource() {
|
|
1202
|
+
return [
|
|
1203
|
+
"\"use strict\";",
|
|
1204
|
+
`const rawPoseFromCompact = ${rawPoseFromCompact.toString()};`,
|
|
1205
|
+
`const mapToPose = ${mapToPose.toString()};`,
|
|
1206
|
+
`const Median = ${Median.toString()};`,
|
|
1207
|
+
`const createPoseCoreImpl = ${createAvatar2DPoseCore.toString()};`,
|
|
1208
|
+
`const createSmileyCore = ${createSatinSmileyCore.toString()};`,
|
|
1209
|
+
`const createPacer = ${createFramePacer.toString()};`,
|
|
1210
|
+
`const workerMain = ${avatar2dRenderWorkerMain.toString()};`,
|
|
1211
|
+
`workerMain(self, {
|
|
1212
|
+
createPoseCore: (cfg) =>
|
|
1213
|
+
createPoseCoreImpl(cfg, {
|
|
1214
|
+
rawPoseFromCompact,
|
|
1215
|
+
mapToPose,
|
|
1216
|
+
createMedian: (windowSize) => new Median(windowSize),
|
|
1217
|
+
}),
|
|
1218
|
+
createSmileyCore,
|
|
1219
|
+
createPacer,
|
|
1220
|
+
});`
|
|
1221
|
+
].join("\n");
|
|
1222
|
+
}
|
|
1223
|
+
/** Assemble the TRACK worker script (compact-face extraction + main). */
|
|
1224
|
+
function buildAvatar2DTrackWorkerSource() {
|
|
1225
|
+
return [
|
|
1226
|
+
"\"use strict\";",
|
|
1227
|
+
`const extractCompact = ${extractCompactFace.toString()};`,
|
|
1228
|
+
`const workerMain = ${avatar2dTrackWorkerMain.toString()};`,
|
|
1229
|
+
"workerMain(self, { extractCompact });"
|
|
1230
|
+
].join("\n");
|
|
1231
|
+
}
|
|
1232
|
+
//#endregion
|
|
1233
|
+
//#region ../infra/src/avatar/2d-default-avatar/avatar2dWarm.ts
|
|
1234
|
+
/**
|
|
1235
|
+
* Pre-capture warm-up registry for the avatar-2d worker (ENG-49806 AC#1).
|
|
1236
|
+
*
|
|
1237
|
+
* The heavy part of avatar start is the MediaPipe bring-up (module + ~3.7MB
|
|
1238
|
+
* model download + WASM compile) — seconds on a low-tier device, which made
|
|
1239
|
+
* the smiley LOSE THE RACE against a fast capture on the Galaxy-A04e-class
|
|
1240
|
+
* target. `warmAvatar2D()` boots the real avatar worker during the tutorial
|
|
1241
|
+
* phase and runs that bring-up WITHOUT camera or canvas (cosmetic-only by
|
|
1242
|
+
* construction — no media is touched); the provider then ADOPTS the warm
|
|
1243
|
+
* worker at capture start via `takeWarmAvatar2DWorker()` and tracking engages
|
|
1244
|
+
* near-instantly.
|
|
1245
|
+
*
|
|
1246
|
+
* Lifecycle: one warm entry at a time (keyed by the resolved asset URLs —
|
|
1247
|
+
* idempotent re-warms are no-ops; different assets replace the entry). The
|
|
1248
|
+
* entry is consumed on adoption, released explicitly by the capture machine's
|
|
1249
|
+
* terminal states, and self-disposes after WARM_TTL_MS as the backstop for
|
|
1250
|
+
* teardown paths that never reach either (e.g. the host unmounting the module
|
|
1251
|
+
* mid-tutorial).
|
|
1252
|
+
*/
|
|
1253
|
+
/** Idle warm workers self-dispose after this long without adoption. */
|
|
1254
|
+
const WARM_TTL_MS = 5 * 6e4;
|
|
1255
|
+
let entry = null;
|
|
1256
|
+
function assetsKey(assets) {
|
|
1257
|
+
return assets.mediapipeModulePath + "\n" + assets.mediapipeWasmPath + "\n" + assets.faceLandmarkerModelPath;
|
|
1258
|
+
}
|
|
1259
|
+
/**
|
|
1260
|
+
* Boot the avatar-2d worker and start its MediaPipe bring-up in the
|
|
1261
|
+
* background. Fire-and-forget and never throws; environments without the
|
|
1262
|
+
* worker path (probe fail, CSP, instrumented source) silently no-op — the
|
|
1263
|
+
* capture-time fallback chain is unchanged.
|
|
1264
|
+
*/
|
|
1265
|
+
function warmAvatar2D(overrides) {
|
|
1266
|
+
try {
|
|
1267
|
+
if (!canUseAvatar2DWorker()) return;
|
|
1268
|
+
const assets = resolveAvatar2DAssets(overrides ?? {});
|
|
1269
|
+
const key = assetsKey(assets);
|
|
1270
|
+
if (entry?.key === key) return;
|
|
1271
|
+
releaseAvatar2DWarmup();
|
|
1272
|
+
const source = buildAvatar2DTrackWorkerSource();
|
|
1273
|
+
if (detectWorkerSourceInstrumentation(source)) return;
|
|
1274
|
+
let blobUrl = null;
|
|
1275
|
+
let worker;
|
|
1276
|
+
try {
|
|
1277
|
+
blobUrl = URL.createObjectURL(new Blob([source], { type: "text/javascript" }));
|
|
1278
|
+
worker = new Worker(blobUrl);
|
|
1279
|
+
} catch {
|
|
1280
|
+
if (blobUrl) URL.revokeObjectURL(blobUrl);
|
|
1281
|
+
return;
|
|
1282
|
+
}
|
|
1283
|
+
if (blobUrl) URL.revokeObjectURL(blobUrl);
|
|
1284
|
+
const ttlId = setTimeout(() => {
|
|
1285
|
+
if (entry?.worker === worker) releaseAvatar2DWarmup();
|
|
1286
|
+
}, WARM_TTL_MS);
|
|
1287
|
+
entry = {
|
|
1288
|
+
worker,
|
|
1289
|
+
key,
|
|
1290
|
+
blobUrl: null,
|
|
1291
|
+
ttlId
|
|
1292
|
+
};
|
|
1293
|
+
worker.onerror = () => {
|
|
1294
|
+
if (entry?.worker === worker) releaseAvatar2DWarmup();
|
|
1295
|
+
};
|
|
1296
|
+
const warmMsg = {
|
|
1297
|
+
type: "warm",
|
|
1298
|
+
mediapipeModulePath: assets.mediapipeModulePath,
|
|
1299
|
+
mediapipeWasmPath: assets.mediapipeWasmPath,
|
|
1300
|
+
faceLandmarkerModelPath: assets.faceLandmarkerModelPath
|
|
1301
|
+
};
|
|
1302
|
+
worker.postMessage(warmMsg);
|
|
1303
|
+
} catch {}
|
|
1304
|
+
}
|
|
1305
|
+
/**
|
|
1306
|
+
* Hand the warm worker (if any, and only for the SAME resolved assets) to the
|
|
1307
|
+
* provider. The caller owns it from here — the registry forgets the entry and
|
|
1308
|
+
* detaches its listeners.
|
|
1309
|
+
*/
|
|
1310
|
+
function takeWarmAvatar2DWorker(assets) {
|
|
1311
|
+
if (!entry || entry.key !== assetsKey(assets)) return null;
|
|
1312
|
+
const { worker, blobUrl, ttlId } = entry;
|
|
1313
|
+
entry = null;
|
|
1314
|
+
clearTimeout(ttlId);
|
|
1315
|
+
if (blobUrl) URL.revokeObjectURL(blobUrl);
|
|
1316
|
+
worker.onmessage = null;
|
|
1317
|
+
worker.onerror = null;
|
|
1318
|
+
return worker;
|
|
1319
|
+
}
|
|
1320
|
+
/** Dispose an un-adopted warm worker (terminal machine states; re-warm). */
|
|
1321
|
+
function releaseAvatar2DWarmup() {
|
|
1322
|
+
if (!entry) return;
|
|
1323
|
+
const { worker, blobUrl, ttlId } = entry;
|
|
1324
|
+
entry = null;
|
|
1325
|
+
clearTimeout(ttlId);
|
|
1326
|
+
if (blobUrl) URL.revokeObjectURL(blobUrl);
|
|
1327
|
+
worker.terminate();
|
|
1328
|
+
}
|
|
1329
|
+
//#endregion
|
|
1330
|
+
export { createAvatar2DPoseCore as a, SatinSmileyRenderer as c, AVATAR2D_POSE_CORE_DEFAULTS as i, resolveAvatar2DAssets as l, buildAvatar2DTrackWorkerSource as n, createAvatar2DPoseCoreDeps as o, canUseAvatar2DWorker as r, releaseAvatar2DWarmup, extractCompactFace as s, buildAvatar2DRenderWorkerSource as t, takeWarmAvatar2DWorker, warmAvatar2D };
|