@trustchex/react-native-sdk 1.355.1 → 1.357.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/README.md +2 -9
- package/TrustchexSDK.podspec +5 -4
- package/android/build.gradle +6 -4
- package/android/src/main/AndroidManifest.xml +1 -1
- package/android/src/main/java/com/trustchex/reactnativesdk/TrustchexSDKPackage.kt +45 -25
- package/android/src/main/java/com/trustchex/reactnativesdk/camera/TrustchexCameraManager.kt +168 -0
- package/android/src/main/java/com/trustchex/reactnativesdk/camera/TrustchexCameraView.kt +871 -0
- package/android/src/main/java/com/trustchex/reactnativesdk/mlkit/MLKitModule.kt +245 -0
- package/android/src/main/java/com/trustchex/reactnativesdk/mrz/MRZValidationModule.kt +785 -0
- package/android/src/main/java/com/trustchex/reactnativesdk/mrz/MRZValidator.kt +419 -0
- package/android/src/main/java/com/trustchex/reactnativesdk/opencv/OpenCVModule.kt +818 -0
- package/ios/Camera/TrustchexCameraManager.m +37 -0
- package/ios/Camera/TrustchexCameraManager.swift +125 -0
- package/ios/Camera/TrustchexCameraView.swift +1176 -0
- package/ios/MLKit/MLKitModule.m +23 -0
- package/ios/MLKit/MLKitModule.swift +250 -0
- package/ios/MRZValidation.m +39 -0
- package/ios/MRZValidation.swift +802 -0
- package/ios/MRZValidator.swift +466 -0
- package/ios/OpenCV/OpenCVModule.h +4 -0
- package/ios/OpenCV/OpenCVModule.mm +810 -0
- package/lib/module/Screens/Dynamic/IdentityDocumentEIDScanningScreen.js +2 -3
- package/lib/module/Screens/Dynamic/IdentityDocumentScanningScreen.js +1 -2
- package/lib/module/Screens/Dynamic/LivenessDetectionScreen.js +418 -193
- package/lib/module/Screens/Static/OTPVerificationScreen.js +11 -11
- package/lib/module/Screens/Static/QrCodeScanningScreen.js +5 -1
- package/lib/module/Screens/Static/ResultScreen.js +25 -2
- package/lib/module/Screens/Static/VerificationSessionCheckScreen.js +25 -7
- package/lib/module/Shared/Components/DebugNavigationPanel.js +234 -24
- package/lib/module/Shared/Components/EIDScanner.js +99 -9
- package/lib/module/Shared/Components/FaceCamera.js +170 -179
- package/lib/module/Shared/Components/IdentityDocumentCamera.js +2151 -771
- package/lib/module/Shared/Components/QrCodeScannerCamera.js +109 -107
- package/lib/module/Shared/Components/TrustchexCamera.js +122 -0
- package/lib/module/Shared/EIDReader/tlv/tlv.helpers.js +91 -0
- package/lib/module/Shared/EIDReader/tlv/tlv.utils.js +2 -124
- package/lib/module/Shared/EIDReader/tlv/tlvInputStream.js +4 -4
- package/lib/module/Shared/EIDReader/tlv/tlvOutputState.js +4 -4
- package/lib/module/Shared/EIDReader/tlv/tlvOutputStream.js +4 -4
- package/lib/module/Shared/Libs/analytics.utils.js +2 -2
- package/lib/module/Shared/Libs/debug.utils.js +132 -0
- package/lib/module/Shared/Libs/deeplink.utils.js +6 -5
- package/lib/module/Shared/Libs/demo.utils.js +13 -3
- package/lib/module/Shared/Libs/mrz.utils.js +1 -175
- package/lib/module/Shared/Libs/native-device-info.utils.js +12 -6
- package/lib/module/Shared/Libs/tts.utils.js +40 -6
- package/lib/module/Shared/Services/AnalyticsService.js +9 -8
- package/lib/module/Shared/Types/mrzFields.js +1 -0
- package/lib/module/Translation/Resources/en.js +87 -88
- package/lib/module/Translation/Resources/tr.js +84 -85
- package/lib/module/Trustchex.js +9 -2
- package/lib/module/index.js +1 -0
- package/lib/module/version.js +1 -1
- package/lib/typescript/src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Dynamic/IdentityDocumentScanningScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Dynamic/LivenessDetectionScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Static/OTPVerificationScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Static/QrCodeScanningScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Static/ResultScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Static/VerificationSessionCheckScreen.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/DebugNavigationPanel.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/EIDScanner.d.ts +2 -2
- package/lib/typescript/src/Shared/Components/EIDScanner.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/FaceCamera.d.ts +18 -4
- package/lib/typescript/src/Shared/Components/FaceCamera.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.d.ts +3 -4
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/QrCodeScannerCamera.d.ts +2 -1
- package/lib/typescript/src/Shared/Components/QrCodeScannerCamera.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/TrustchexCamera.d.ts +124 -0
- package/lib/typescript/src/Shared/Components/TrustchexCamera.d.ts.map +1 -0
- package/lib/typescript/src/Shared/EIDReader/tlv/tlv.helpers.d.ts +11 -0
- package/lib/typescript/src/Shared/EIDReader/tlv/tlv.helpers.d.ts.map +1 -0
- package/lib/typescript/src/Shared/EIDReader/tlv/tlv.utils.d.ts +2 -39
- package/lib/typescript/src/Shared/EIDReader/tlv/tlv.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/analytics.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/debug.utils.d.ts +42 -0
- package/lib/typescript/src/Shared/Libs/debug.utils.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Libs/deeplink.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/demo.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/http-client.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/mrz.utils.d.ts +0 -4
- package/lib/typescript/src/Shared/Libs/mrz.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/native-device-info.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/tts.utils.d.ts +4 -3
- package/lib/typescript/src/Shared/Libs/tts.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Services/AnalyticsService.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Types/identificationInfo.d.ts +2 -2
- package/lib/typescript/src/Shared/Types/identificationInfo.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Types/mrzFields.d.ts +11 -0
- package/lib/typescript/src/Shared/Types/mrzFields.d.ts.map +1 -0
- package/lib/typescript/src/Translation/Resources/en.d.ts +4 -5
- package/lib/typescript/src/Translation/Resources/en.d.ts.map +1 -1
- package/lib/typescript/src/Translation/Resources/tr.d.ts +4 -5
- package/lib/typescript/src/Translation/Resources/tr.d.ts.map +1 -1
- package/lib/typescript/src/Trustchex.d.ts +2 -0
- package/lib/typescript/src/Trustchex.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +1 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/version.d.ts +1 -1
- package/package.json +4 -35
- package/src/Screens/Dynamic/ContractAcceptanceScreen.tsx +1 -1
- package/src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.tsx +7 -5
- package/src/Screens/Dynamic/IdentityDocumentScanningScreen.tsx +2 -3
- package/src/Screens/Dynamic/LivenessDetectionScreen.tsx +498 -216
- package/src/Screens/Static/OTPVerificationScreen.tsx +37 -31
- package/src/Screens/Static/QrCodeScanningScreen.tsx +8 -1
- package/src/Screens/Static/ResultScreen.tsx +136 -88
- package/src/Screens/Static/VerificationSessionCheckScreen.tsx +46 -13
- package/src/Shared/Components/DebugNavigationPanel.tsx +290 -34
- package/src/Shared/Components/EIDScanner.tsx +94 -16
- package/src/Shared/Components/FaceCamera.tsx +236 -203
- package/src/Shared/Components/IdentityDocumentCamera.tsx +3073 -1030
- package/src/Shared/Components/QrCodeScannerCamera.tsx +133 -127
- package/src/Shared/Components/TrustchexCamera.tsx +289 -0
- package/src/Shared/Config/camera-enhancement.config.ts +2 -2
- package/src/Shared/EIDReader/tlv/tlv.helpers.ts +96 -0
- package/src/Shared/EIDReader/tlv/tlv.utils.ts +2 -125
- package/src/Shared/EIDReader/tlv/tlvInputStream.ts +4 -4
- package/src/Shared/EIDReader/tlv/tlvOutputState.ts +4 -4
- package/src/Shared/EIDReader/tlv/tlvOutputStream.ts +4 -4
- package/src/Shared/Libs/analytics.utils.ts +48 -20
- package/src/Shared/Libs/debug.utils.ts +149 -0
- package/src/Shared/Libs/deeplink.utils.ts +7 -5
- package/src/Shared/Libs/demo.utils.ts +4 -0
- package/src/Shared/Libs/http-client.ts +12 -8
- package/src/Shared/Libs/mrz.utils.ts +1 -163
- package/src/Shared/Libs/native-device-info.utils.ts +12 -6
- package/src/Shared/Libs/tts.utils.ts +48 -6
- package/src/Shared/Services/AnalyticsService.ts +69 -24
- package/src/Shared/Types/identificationInfo.ts +2 -2
- package/src/Shared/Types/mrzFields.ts +29 -0
- package/src/Translation/Resources/en.ts +90 -100
- package/src/Translation/Resources/tr.ts +89 -97
- package/src/Translation/index.ts +1 -1
- package/src/Trustchex.tsx +21 -4
- package/src/index.tsx +14 -0
- package/src/version.ts +1 -1
- package/android/src/main/java/com/trustchex/reactnativesdk/visioncameraplugins/barcodescanner/BarcodeScannerFrameProcessorPlugin.kt +0 -301
- package/android/src/main/java/com/trustchex/reactnativesdk/visioncameraplugins/cropper/BitmapUtils.kt +0 -205
- package/android/src/main/java/com/trustchex/reactnativesdk/visioncameraplugins/cropper/CropperPlugin.kt +0 -72
- package/android/src/main/java/com/trustchex/reactnativesdk/visioncameraplugins/cropper/FrameMetadata.kt +0 -4
- package/android/src/main/java/com/trustchex/reactnativesdk/visioncameraplugins/facedetector/FaceDetectorFrameProcessorPlugin.kt +0 -303
- package/android/src/main/java/com/trustchex/reactnativesdk/visioncameraplugins/textrecognition/TextRecognitionFrameProcessorPlugin.kt +0 -115
- package/ios/VisionCameraPlugins/BarcodeScanner/BarcodeScannerFrameProcessorPlugin-Bridging-Header.h +0 -9
- package/ios/VisionCameraPlugins/BarcodeScanner/BarcodeScannerFrameProcessorPlugin.mm +0 -22
- package/ios/VisionCameraPlugins/BarcodeScanner/BarcodeScannerFrameProcessorPlugin.swift +0 -188
- package/ios/VisionCameraPlugins/Cropper/Cropper-Bridging-Header.h +0 -13
- package/ios/VisionCameraPlugins/Cropper/Cropper.h +0 -20
- package/ios/VisionCameraPlugins/Cropper/Cropper.mm +0 -22
- package/ios/VisionCameraPlugins/Cropper/Cropper.swift +0 -145
- package/ios/VisionCameraPlugins/Cropper/CropperUtils.swift +0 -49
- package/ios/VisionCameraPlugins/FaceDetector/FaceDetectorFrameProcessorPlugin-Bridging-Header.h +0 -4
- package/ios/VisionCameraPlugins/FaceDetector/FaceDetectorFrameProcessorPlugin.mm +0 -22
- package/ios/VisionCameraPlugins/FaceDetector/FaceDetectorFrameProcessorPlugin.swift +0 -320
- package/ios/VisionCameraPlugins/TextRecognition/TextRecognitionFrameProcessorPlugin-Bridging-Header.h +0 -4
- package/ios/VisionCameraPlugins/TextRecognition/TextRecognitionFrameProcessorPlugin.mm +0 -27
- package/ios/VisionCameraPlugins/TextRecognition/TextRecognitionFrameProcessorPlugin.swift +0 -144
- package/lib/module/Shared/Libs/camera.utils.js +0 -308
- package/lib/module/Shared/Libs/frame-enhancement.utils.js +0 -133
- package/lib/module/Shared/Libs/opencv.utils.js +0 -21
- package/lib/module/Shared/Libs/worklet.utils.js +0 -68
- package/lib/module/Shared/VisionCameraPlugins/BarcodeScanner/hooks/useBarcodeScanner.js +0 -46
- package/lib/module/Shared/VisionCameraPlugins/BarcodeScanner/hooks/useCameraPermissions.js +0 -35
- package/lib/module/Shared/VisionCameraPlugins/BarcodeScanner/index.js +0 -19
- package/lib/module/Shared/VisionCameraPlugins/BarcodeScanner/scanCodes.js +0 -26
- package/lib/module/Shared/VisionCameraPlugins/BarcodeScanner/types.js +0 -3
- package/lib/module/Shared/VisionCameraPlugins/BarcodeScanner/utils/convert.js +0 -197
- package/lib/module/Shared/VisionCameraPlugins/BarcodeScanner/utils/geometry.js +0 -101
- package/lib/module/Shared/VisionCameraPlugins/BarcodeScanner/utils/highlights.js +0 -60
- package/lib/module/Shared/VisionCameraPlugins/Cropper/index.js +0 -47
- package/lib/module/Shared/VisionCameraPlugins/FaceDetector/Camera.js +0 -42
- package/lib/module/Shared/VisionCameraPlugins/FaceDetector/detectFaces.js +0 -35
- package/lib/module/Shared/VisionCameraPlugins/FaceDetector/index.js +0 -4
- package/lib/module/Shared/VisionCameraPlugins/FaceDetector/types.js +0 -3
- package/lib/module/Shared/VisionCameraPlugins/TextRecognition/Camera.js +0 -56
- package/lib/module/Shared/VisionCameraPlugins/TextRecognition/PhotoRecognizer.js +0 -20
- package/lib/module/Shared/VisionCameraPlugins/TextRecognition/RemoveLanguageModel.js +0 -9
- package/lib/module/Shared/VisionCameraPlugins/TextRecognition/index.js +0 -6
- package/lib/module/Shared/VisionCameraPlugins/TextRecognition/scanText.js +0 -20
- package/lib/module/Shared/VisionCameraPlugins/TextRecognition/translateText.js +0 -19
- package/lib/module/Shared/VisionCameraPlugins/TextRecognition/types.js +0 -3
- package/lib/typescript/src/Shared/Libs/camera.utils.d.ts +0 -87
- package/lib/typescript/src/Shared/Libs/camera.utils.d.ts.map +0 -1
- package/lib/typescript/src/Shared/Libs/frame-enhancement.utils.d.ts +0 -25
- package/lib/typescript/src/Shared/Libs/frame-enhancement.utils.d.ts.map +0 -1
- package/lib/typescript/src/Shared/Libs/opencv.utils.d.ts +0 -3
- package/lib/typescript/src/Shared/Libs/opencv.utils.d.ts.map +0 -1
- package/lib/typescript/src/Shared/Libs/worklet.utils.d.ts +0 -9
- package/lib/typescript/src/Shared/Libs/worklet.utils.d.ts.map +0 -1
- package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/hooks/useBarcodeScanner.d.ts +0 -13
- package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/hooks/useBarcodeScanner.d.ts.map +0 -1
- package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/hooks/useCameraPermissions.d.ts +0 -6
- package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/hooks/useCameraPermissions.d.ts.map +0 -1
- package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/index.d.ts +0 -12
- package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/index.d.ts.map +0 -1
- package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/scanCodes.d.ts +0 -3
- package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/scanCodes.d.ts.map +0 -1
- package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/types.d.ts +0 -52
- package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/types.d.ts.map +0 -1
- package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/utils/convert.d.ts +0 -62
- package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/utils/convert.d.ts.map +0 -1
- package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/utils/geometry.d.ts +0 -34
- package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/utils/geometry.d.ts.map +0 -1
- package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/utils/highlights.d.ts +0 -32
- package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/utils/highlights.d.ts.map +0 -1
- package/lib/typescript/src/Shared/VisionCameraPlugins/Cropper/index.d.ts +0 -23
- package/lib/typescript/src/Shared/VisionCameraPlugins/Cropper/index.d.ts.map +0 -1
- package/lib/typescript/src/Shared/VisionCameraPlugins/FaceDetector/Camera.d.ts +0 -9
- package/lib/typescript/src/Shared/VisionCameraPlugins/FaceDetector/Camera.d.ts.map +0 -1
- package/lib/typescript/src/Shared/VisionCameraPlugins/FaceDetector/detectFaces.d.ts +0 -3
- package/lib/typescript/src/Shared/VisionCameraPlugins/FaceDetector/detectFaces.d.ts.map +0 -1
- package/lib/typescript/src/Shared/VisionCameraPlugins/FaceDetector/index.d.ts +0 -3
- package/lib/typescript/src/Shared/VisionCameraPlugins/FaceDetector/index.d.ts.map +0 -1
- package/lib/typescript/src/Shared/VisionCameraPlugins/FaceDetector/types.d.ts +0 -79
- package/lib/typescript/src/Shared/VisionCameraPlugins/FaceDetector/types.d.ts.map +0 -1
- package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/Camera.d.ts +0 -6
- package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/Camera.d.ts.map +0 -1
- package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/PhotoRecognizer.d.ts +0 -3
- package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/PhotoRecognizer.d.ts.map +0 -1
- package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/RemoveLanguageModel.d.ts +0 -3
- package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/RemoveLanguageModel.d.ts.map +0 -1
- package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/index.d.ts +0 -5
- package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/index.d.ts.map +0 -1
- package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/scanText.d.ts +0 -3
- package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/scanText.d.ts.map +0 -1
- package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/translateText.d.ts +0 -3
- package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/translateText.d.ts.map +0 -1
- package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/types.d.ts +0 -67
- package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/types.d.ts.map +0 -1
- package/src/Shared/Libs/camera.utils.ts +0 -345
- package/src/Shared/Libs/frame-enhancement.utils.ts +0 -217
- package/src/Shared/Libs/opencv.utils.ts +0 -40
- package/src/Shared/Libs/worklet.utils.ts +0 -72
- package/src/Shared/VisionCameraPlugins/BarcodeScanner/hooks/useBarcodeScanner.ts +0 -79
- package/src/Shared/VisionCameraPlugins/BarcodeScanner/hooks/useCameraPermissions.ts +0 -46
- package/src/Shared/VisionCameraPlugins/BarcodeScanner/index.ts +0 -60
- package/src/Shared/VisionCameraPlugins/BarcodeScanner/scanCodes.ts +0 -32
- package/src/Shared/VisionCameraPlugins/BarcodeScanner/types.ts +0 -82
- package/src/Shared/VisionCameraPlugins/BarcodeScanner/utils/convert.ts +0 -195
- package/src/Shared/VisionCameraPlugins/BarcodeScanner/utils/geometry.ts +0 -135
- package/src/Shared/VisionCameraPlugins/BarcodeScanner/utils/highlights.ts +0 -84
- package/src/Shared/VisionCameraPlugins/Cropper/index.ts +0 -78
- package/src/Shared/VisionCameraPlugins/FaceDetector/Camera.tsx +0 -63
- package/src/Shared/VisionCameraPlugins/FaceDetector/detectFaces.ts +0 -44
- package/src/Shared/VisionCameraPlugins/FaceDetector/index.ts +0 -3
- package/src/Shared/VisionCameraPlugins/FaceDetector/types.ts +0 -99
- package/src/Shared/VisionCameraPlugins/TextRecognition/Camera.tsx +0 -76
- package/src/Shared/VisionCameraPlugins/TextRecognition/PhotoRecognizer.ts +0 -18
- package/src/Shared/VisionCameraPlugins/TextRecognition/RemoveLanguageModel.ts +0 -7
- package/src/Shared/VisionCameraPlugins/TextRecognition/index.ts +0 -7
- package/src/Shared/VisionCameraPlugins/TextRecognition/scanText.ts +0 -27
- package/src/Shared/VisionCameraPlugins/TextRecognition/translateText.ts +0 -21
- package/src/Shared/VisionCameraPlugins/TextRecognition/types.ts +0 -141
|
@@ -0,0 +1,871 @@
|
|
|
1
|
+
package com.trustchex.reactnativesdk.camera
|
|
2
|
+
|
|
3
|
+
import android.annotation.SuppressLint
|
|
4
|
+
import android.graphics.BitmapFactory
|
|
5
|
+
import android.graphics.ImageFormat
|
|
6
|
+
import android.graphics.Matrix
|
|
7
|
+
import android.graphics.Rect
|
|
8
|
+
import android.graphics.YuvImage
|
|
9
|
+
import android.util.Size
|
|
10
|
+
import android.widget.FrameLayout
|
|
11
|
+
import androidx.annotation.OptIn
|
|
12
|
+
import androidx.camera.core.*
|
|
13
|
+
import androidx.camera.lifecycle.ProcessCameraProvider
|
|
14
|
+
import androidx.camera.video.*
|
|
15
|
+
import androidx.camera.view.PreviewView
|
|
16
|
+
import androidx.core.content.ContextCompat
|
|
17
|
+
import androidx.lifecycle.LifecycleOwner
|
|
18
|
+
import com.facebook.react.bridge.Arguments
|
|
19
|
+
import com.facebook.react.bridge.ReactContext
|
|
20
|
+
import com.facebook.react.bridge.WritableMap
|
|
21
|
+
import com.facebook.react.uimanager.ThemedReactContext
|
|
22
|
+
import com.facebook.react.uimanager.events.RCTEventEmitter
|
|
23
|
+
import com.google.android.gms.tasks.Tasks
|
|
24
|
+
import com.google.mlkit.vision.barcode.BarcodeScanning
|
|
25
|
+
import com.google.mlkit.vision.barcode.BarcodeScannerOptions
|
|
26
|
+
import com.google.mlkit.vision.barcode.common.Barcode
|
|
27
|
+
import com.google.mlkit.vision.common.InputImage
|
|
28
|
+
import com.trustchex.reactnativesdk.mrz.MRZValidator
|
|
29
|
+
import com.google.mlkit.vision.face.FaceDetection
|
|
30
|
+
import com.google.mlkit.vision.face.FaceDetectorOptions
|
|
31
|
+
import com.google.mlkit.vision.text.TextRecognition
|
|
32
|
+
import com.google.mlkit.vision.text.latin.TextRecognizerOptions
|
|
33
|
+
import java.io.ByteArrayOutputStream
|
|
34
|
+
import kotlin.math.max
|
|
35
|
+
import java.io.File
|
|
36
|
+
import java.util.concurrent.ExecutorService
|
|
37
|
+
import java.util.concurrent.Executors
|
|
38
|
+
import java.util.concurrent.atomic.AtomicBoolean
|
|
39
|
+
|
|
40
|
+
@SuppressLint("ViewConstructor")
|
|
41
|
+
class TrustchexCameraView(context: ThemedReactContext) : FrameLayout(context) {
|
|
42
|
+
|
|
43
|
+
// ==================================================================================
|
|
44
|
+
// PORTRAIT-ONLY CAMERA VIEW
|
|
45
|
+
// All camera use cases are locked to ROTATION_0 (portrait) orientation.
|
|
46
|
+
// Preview: 720x1280 (Portrait HD)
|
|
47
|
+
// ImageAnalysis: 720x1280 (Portrait HD for ML Kit)
|
|
48
|
+
// ImageCapture: 720x1280 (Portrait HD for frame capture during recording)
|
|
49
|
+
// VideoCapture: Quality.HD (CameraX auto-selects portrait HD resolution)
|
|
50
|
+
// Activity must be locked to portrait in AndroidManifest.xml
|
|
51
|
+
// ==================================================================================
|
|
52
|
+
|
|
53
|
+
private val reactContext: ReactContext = context
|
|
54
|
+
private val previewView: PreviewView = PreviewView(context).apply {
|
|
55
|
+
implementationMode = PreviewView.ImplementationMode.COMPATIBLE
|
|
56
|
+
scaleType = PreviewView.ScaleType.FILL_CENTER
|
|
57
|
+
}
|
|
58
|
+
private var cameraExecutor: ExecutorService = Executors.newSingleThreadExecutor()
|
|
59
|
+
private var cameraProvider: ProcessCameraProvider? = null
|
|
60
|
+
private var camera: Camera? = null
|
|
61
|
+
private var preview: Preview? = null
|
|
62
|
+
private var imageAnalyzer: ImageAnalysis? = null
|
|
63
|
+
private var videoCapture: VideoCapture<Recorder>? = null
|
|
64
|
+
private var activeRecording: Recording? = null
|
|
65
|
+
private var currentRecordingFile: File? = null
|
|
66
|
+
|
|
67
|
+
private var cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
|
|
68
|
+
private var torchEnabled = false
|
|
69
|
+
private var frameProcessingEnabled = false
|
|
70
|
+
private var targetFps = 6
|
|
71
|
+
private var isCameraInitialized = false
|
|
72
|
+
private var isStoppingRecording = false // Track if stopRecording was called to prevent cancelRecording from deleting the file
|
|
73
|
+
|
|
74
|
+
// ML Kit detection modes — controlled via props from JS
|
|
75
|
+
private var faceDetectionEnabled = false
|
|
76
|
+
private var textRecognitionEnabled = false
|
|
77
|
+
private var barcodeScanningEnabled = false
|
|
78
|
+
private var mrzValidationEnabled = false
|
|
79
|
+
private var includeBase64 = false
|
|
80
|
+
|
|
81
|
+
// ML Kit detector instances (lazy, created once)
|
|
82
|
+
private val textRecognizer by lazy {
|
|
83
|
+
TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS)
|
|
84
|
+
}
|
|
85
|
+
private val faceDetector by lazy {
|
|
86
|
+
FaceDetection.getClient(
|
|
87
|
+
FaceDetectorOptions.Builder()
|
|
88
|
+
.setPerformanceMode(FaceDetectorOptions.PERFORMANCE_MODE_ACCURATE)
|
|
89
|
+
.setLandmarkMode(FaceDetectorOptions.LANDMARK_MODE_NONE)
|
|
90
|
+
.setClassificationMode(FaceDetectorOptions.CLASSIFICATION_MODE_ALL)
|
|
91
|
+
.setMinFaceSize(0.1f) // 10% - small enough to detect faces reliably
|
|
92
|
+
.build()
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
private val barcodeScanner by lazy {
|
|
96
|
+
BarcodeScanning.getClient(
|
|
97
|
+
BarcodeScannerOptions.Builder()
|
|
98
|
+
.setBarcodeFormats(
|
|
99
|
+
Barcode.FORMAT_PDF417,
|
|
100
|
+
Barcode.FORMAT_QR_CODE,
|
|
101
|
+
Barcode.FORMAT_CODE_128,
|
|
102
|
+
Barcode.FORMAT_CODE_39,
|
|
103
|
+
Barcode.FORMAT_EAN_13,
|
|
104
|
+
Barcode.FORMAT_EAN_8,
|
|
105
|
+
Barcode.FORMAT_AZTEC,
|
|
106
|
+
Barcode.FORMAT_DATA_MATRIX
|
|
107
|
+
)
|
|
108
|
+
.build()
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Prevent concurrent ML Kit processing (drop frames while busy)
|
|
113
|
+
private val isProcessing = AtomicBoolean(false)
|
|
114
|
+
|
|
115
|
+
private var lastFrameTime = 0L
|
|
116
|
+
private val minFrameInterval get() = 1000L / targetFps
|
|
117
|
+
|
|
118
|
+
// React Native suppresses layout passes for custom views.
|
|
119
|
+
// Without this override, PreviewView's internal SurfaceView/TextureView
|
|
120
|
+
// stays at 0x0 dimensions and the preview is black.
|
|
121
|
+
private val measureAndLayout = Runnable {
|
|
122
|
+
measure(
|
|
123
|
+
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
|
|
124
|
+
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)
|
|
125
|
+
)
|
|
126
|
+
layout(left, top, right, bottom)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
override fun requestLayout() {
|
|
130
|
+
super.requestLayout()
|
|
131
|
+
// Post a forced layout pass so the PreviewView gets measured
|
|
132
|
+
post(measureAndLayout)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
init {
|
|
136
|
+
addView(previewView, LayoutParams(
|
|
137
|
+
LayoutParams.MATCH_PARENT,
|
|
138
|
+
LayoutParams.MATCH_PARENT
|
|
139
|
+
))
|
|
140
|
+
|
|
141
|
+
// Wait for view to be laid out before initializing camera
|
|
142
|
+
post {
|
|
143
|
+
initializeCamera()
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
fun setCameraType(type: String) {
|
|
148
|
+
val newSelector = selectBestCamera(type)
|
|
149
|
+
// Only reinitialize if the selector actually changed and camera is already running
|
|
150
|
+
if (newSelector != cameraSelector || !isCameraInitialized) {
|
|
151
|
+
cameraSelector = newSelector
|
|
152
|
+
if (isCameraInitialized) {
|
|
153
|
+
initializeCamera()
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
private fun selectBestCamera(type: String): CameraSelector {
|
|
159
|
+
if (type == "front") {
|
|
160
|
+
return CameraSelector.DEFAULT_FRONT_CAMERA
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// For back camera, try to select Ultra Wide camera for document scanning
|
|
164
|
+
val cameraProvider = cameraProvider ?: return CameraSelector.DEFAULT_BACK_CAMERA
|
|
165
|
+
|
|
166
|
+
try {
|
|
167
|
+
// Check for Ultra Wide camera
|
|
168
|
+
val ultraWideSelector = CameraSelector.Builder()
|
|
169
|
+
.requireLensFacing(CameraSelector.LENS_FACING_BACK)
|
|
170
|
+
.build()
|
|
171
|
+
|
|
172
|
+
val cameras = cameraProvider.availableCameraInfos.filter {
|
|
173
|
+
ultraWideSelector.filter(listOf(it)).isNotEmpty()
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Heuristic: If we have 3+ back cameras, index 2 is often Ultra Wide
|
|
177
|
+
if (cameras.size >= 3) {
|
|
178
|
+
// Heuristic: If we have 3+ back cameras, index 2 is often Ultra Wide
|
|
179
|
+
}
|
|
180
|
+
} catch (e: Exception) {
|
|
181
|
+
android.util.Log.e("TrustchexCamera", "Failed to enumerate cameras: ${e.message}")
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return CameraSelector.DEFAULT_BACK_CAMERA
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
fun setTorchEnabled(enabled: Boolean) {
|
|
188
|
+
torchEnabled = enabled
|
|
189
|
+
camera?.cameraControl?.enableTorch(enabled)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
fun setFrameProcessingEnabled(enabled: Boolean) {
|
|
193
|
+
frameProcessingEnabled = enabled
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
fun setTargetFps(fps: Int) {
|
|
197
|
+
targetFps = fps.coerceIn(1, 30)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
fun setFaceDetectionEnabled(enabled: Boolean) {
|
|
201
|
+
faceDetectionEnabled = enabled
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
fun setTextRecognitionEnabled(enabled: Boolean) {
|
|
205
|
+
textRecognitionEnabled = enabled
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
fun setBarcodeScanningEnabled(enabled: Boolean) {
|
|
209
|
+
barcodeScanningEnabled = enabled
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
fun setMrzValidationEnabled(enabled: Boolean) {
|
|
213
|
+
mrzValidationEnabled = enabled
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
fun setIncludeBase64(enabled: Boolean) {
|
|
217
|
+
includeBase64 = enabled
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
private fun initializeCamera() {
|
|
221
|
+
val cameraProviderFuture = ProcessCameraProvider.getInstance(context)
|
|
222
|
+
|
|
223
|
+
cameraProviderFuture.addListener({
|
|
224
|
+
try {
|
|
225
|
+
cameraProvider = cameraProviderFuture.get()
|
|
226
|
+
bindCamera()
|
|
227
|
+
} catch (e: Exception) {
|
|
228
|
+
sendErrorEvent("Camera initialization failed: ${e.message}")
|
|
229
|
+
}
|
|
230
|
+
}, ContextCompat.getMainExecutor(context))
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
private fun bindCamera() {
|
|
234
|
+
val provider = cameraProvider ?: return
|
|
235
|
+
|
|
236
|
+
// Unbind previous use cases
|
|
237
|
+
provider.unbindAll()
|
|
238
|
+
|
|
239
|
+
try {
|
|
240
|
+
// Determine resolution based on camera type
|
|
241
|
+
// Front camera (liveness): HD (720x1280) for optimal performance
|
|
242
|
+
// Back camera (documents): Full HD (1080x1920) for sharp document capture
|
|
243
|
+
val isFrontCamera = cameraSelector == CameraSelector.DEFAULT_FRONT_CAMERA
|
|
244
|
+
val targetResolution = if (isFrontCamera) {
|
|
245
|
+
Size(720, 1280) // Portrait HD for front camera
|
|
246
|
+
} else {
|
|
247
|
+
Size(1080, 1920) // Portrait Full HD for back camera
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Preview use case
|
|
251
|
+
preview = Preview.Builder()
|
|
252
|
+
.setTargetResolution(targetResolution)
|
|
253
|
+
.setTargetRotation(android.view.Surface.ROTATION_0) // Portrait
|
|
254
|
+
.build()
|
|
255
|
+
.also {
|
|
256
|
+
it.setSurfaceProvider(previewView.surfaceProvider)
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Image analysis use case
|
|
260
|
+
imageAnalyzer = ImageAnalysis.Builder()
|
|
261
|
+
.setTargetResolution(targetResolution)
|
|
262
|
+
.setTargetRotation(android.view.Surface.ROTATION_0) // Portrait
|
|
263
|
+
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
|
|
264
|
+
.setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_YUV_420_888)
|
|
265
|
+
.build()
|
|
266
|
+
.also {
|
|
267
|
+
it.setAnalyzer(cameraExecutor, FrameAnalyzer())
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Image capture use case is NOT used in the current version of the SDK
|
|
271
|
+
// (Liveness detection uses ImageAnalyzer and VideoCapture)
|
|
272
|
+
// Removing it allows us to bind VideoCapture + Analyzer + Preview simultaneously
|
|
273
|
+
// within the 3-use-case limit of many Android devices.
|
|
274
|
+
|
|
275
|
+
// Video capture use case — PORTRAIT HD
|
|
276
|
+
val recorder = Recorder.Builder()
|
|
277
|
+
.setQualitySelector(QualitySelector.from(Quality.HD))
|
|
278
|
+
.build()
|
|
279
|
+
videoCapture = VideoCapture.withOutput(recorder)
|
|
280
|
+
|
|
281
|
+
// Bind all 3 essential use cases (Preview + Analyzer + Video)
|
|
282
|
+
// This ensures instant recording start without camera restart/jump
|
|
283
|
+
val useCases = mutableListOf<androidx.camera.core.UseCase>()
|
|
284
|
+
preview?.let { useCases.add(it) }
|
|
285
|
+
imageAnalyzer?.let { useCases.add(it) }
|
|
286
|
+
videoCapture?.let { useCases.add(it) }
|
|
287
|
+
|
|
288
|
+
val lifecycleOwner = (reactContext.currentActivity as? LifecycleOwner)
|
|
289
|
+
?: run {
|
|
290
|
+
sendErrorEvent("Activity not available")
|
|
291
|
+
return
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
camera = provider.bindToLifecycle(
|
|
295
|
+
lifecycleOwner,
|
|
296
|
+
cameraSelector,
|
|
297
|
+
*useCases.toTypedArray()
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
isCameraInitialized = true
|
|
301
|
+
|
|
302
|
+
// Apply torch setting
|
|
303
|
+
if (torchEnabled) {
|
|
304
|
+
camera?.cameraControl?.enableTorch(true)
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Apply stable exposure and focus settings
|
|
308
|
+
applyCameraSettings()
|
|
309
|
+
|
|
310
|
+
// Force a layout pass so the preview surface gets sized correctly
|
|
311
|
+
post(measureAndLayout)
|
|
312
|
+
|
|
313
|
+
// Get exposure and focus capabilities
|
|
314
|
+
val cameraInfo = camera?.cameraInfo
|
|
315
|
+
val exposureState = cameraInfo?.exposureState
|
|
316
|
+
val minExposure = exposureState?.exposureCompensationRange?.lower ?: -2
|
|
317
|
+
val maxExposure = exposureState?.exposureCompensationRange?.upper ?: 2
|
|
318
|
+
|
|
319
|
+
sendReadyEvent(minExposure.toDouble(), maxExposure.toDouble())
|
|
320
|
+
|
|
321
|
+
} catch (e: Exception) {
|
|
322
|
+
sendErrorEvent("Camera binding failed: ${e.message}")
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
private inner class FrameAnalyzer : ImageAnalysis.Analyzer {
|
|
327
|
+
|
|
328
|
+
@OptIn(ExperimentalGetImage::class)
|
|
329
|
+
override fun analyze(imageProxy: ImageProxy) {
|
|
330
|
+
val currentTime = System.currentTimeMillis()
|
|
331
|
+
|
|
332
|
+
// Throttle to target FPS (6 frames per second = ~167ms per frame)
|
|
333
|
+
val timeSinceLastFrame = currentTime - lastFrameTime
|
|
334
|
+
if (timeSinceLastFrame < minFrameInterval) {
|
|
335
|
+
imageProxy.close()
|
|
336
|
+
return
|
|
337
|
+
}
|
|
338
|
+
lastFrameTime = currentTime
|
|
339
|
+
|
|
340
|
+
// Prevent concurrent ML Kit processing
|
|
341
|
+
val canProcess = isProcessing.compareAndSet(false, true)
|
|
342
|
+
if (!canProcess) {
|
|
343
|
+
imageProxy.close()
|
|
344
|
+
return
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
val mediaImage = imageProxy.image ?: run {
|
|
348
|
+
isProcessing.set(false)
|
|
349
|
+
imageProxy.close()
|
|
350
|
+
return
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
val hasAnyDetection = frameProcessingEnabled && (faceDetectionEnabled || textRecognitionEnabled || barcodeScanningEnabled)
|
|
354
|
+
if (!hasAnyDetection) {
|
|
355
|
+
isProcessing.set(false)
|
|
356
|
+
imageProxy.close()
|
|
357
|
+
return
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Create InputImage directly from camera frame — zero-copy, no base64 for ML Kit
|
|
361
|
+
val inputImage = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)
|
|
362
|
+
|
|
363
|
+
val rotationDegrees = imageProxy.imageInfo.rotationDegrees
|
|
364
|
+
|
|
365
|
+
// Calculate portrait-oriented dimensions early (JPEG is already rotated)
|
|
366
|
+
val isRotated = rotationDegrees == 90 || rotationDegrees == 270
|
|
367
|
+
val reportedWidth = if (isRotated) imageProxy.height else imageProxy.width
|
|
368
|
+
val reportedHeight = if (isRotated) imageProxy.width else imageProxy.height
|
|
369
|
+
|
|
370
|
+
// Compute brightness natively from Y plane (no base64 or bitmap needed)
|
|
371
|
+
// Brightness calculation restricted to scanning frame area (between 36% from top and 36% from bottom, 5% margins on sides)
|
|
372
|
+
val averageBrightness = computeYPlaneBrightness(imageProxy, reportedWidth, reportedHeight)
|
|
373
|
+
|
|
374
|
+
// Generate JPEG base64 only when JS side explicitly needs the image
|
|
375
|
+
// NOTE: Do NOT auto-generate for face detection - too expensive, causes frame drops
|
|
376
|
+
val jpegBase64: String? = if (includeBase64) {
|
|
377
|
+
yuvImageProxyToJpegBase64(imageProxy, rotationDegrees)
|
|
378
|
+
} else null
|
|
379
|
+
|
|
380
|
+
// Launch all enabled detectors in parallel using Google Tasks API
|
|
381
|
+
val tasks = mutableListOf<com.google.android.gms.tasks.Task<*>>()
|
|
382
|
+
|
|
383
|
+
val faceTask = if (faceDetectionEnabled) {
|
|
384
|
+
faceDetector.process(inputImage).also { tasks.add(it) }
|
|
385
|
+
} else null
|
|
386
|
+
|
|
387
|
+
val textTask = if (textRecognitionEnabled) {
|
|
388
|
+
textRecognizer.process(inputImage).also { tasks.add(it) }
|
|
389
|
+
} else null
|
|
390
|
+
|
|
391
|
+
val barcodeTask = if (barcodeScanningEnabled) {
|
|
392
|
+
barcodeScanner.process(inputImage).also { tasks.add(it) }
|
|
393
|
+
} else null
|
|
394
|
+
|
|
395
|
+
// Wait for all detectors, then build + send a single event to JS
|
|
396
|
+
Tasks.whenAllComplete(tasks).addOnCompleteListener { _ ->
|
|
397
|
+
try {
|
|
398
|
+
val frameData = Arguments.createMap()
|
|
399
|
+
frameData.putInt("width", reportedWidth)
|
|
400
|
+
frameData.putInt("height", reportedHeight)
|
|
401
|
+
frameData.putInt("orientation", 0) // Already corrected
|
|
402
|
+
frameData.putDouble("timestamp", currentTime.toDouble())
|
|
403
|
+
frameData.putDouble("brightness", averageBrightness)
|
|
404
|
+
|
|
405
|
+
if (jpegBase64 != null) {
|
|
406
|
+
frameData.putString("base64Image", jpegBase64)
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Face detection results
|
|
410
|
+
// ML Kit with InputImage.fromMediaImage(img, rotationDegrees) returns
|
|
411
|
+
// coordinates in the rotated (portrait) coordinate space — no transform needed.
|
|
412
|
+
if (faceTask != null) {
|
|
413
|
+
val facesArray = Arguments.createArray()
|
|
414
|
+
if (faceTask.isSuccessful) {
|
|
415
|
+
for (face in faceTask.result) {
|
|
416
|
+
val faceMap = Arguments.createMap()
|
|
417
|
+
val bounds = Arguments.createMap()
|
|
418
|
+
bounds.putInt("x", face.boundingBox.left)
|
|
419
|
+
bounds.putInt("y", face.boundingBox.top)
|
|
420
|
+
bounds.putInt("width", face.boundingBox.width())
|
|
421
|
+
bounds.putInt("height", face.boundingBox.height())
|
|
422
|
+
faceMap.putMap("bounds", bounds)
|
|
423
|
+
faceMap.putDouble("yawAngle", face.headEulerAngleY.toDouble())
|
|
424
|
+
faceMap.putDouble("pitchAngle", face.headEulerAngleX.toDouble())
|
|
425
|
+
faceMap.putDouble("rollAngle", face.headEulerAngleZ.toDouble())
|
|
426
|
+
if (face.trackingId != null) {
|
|
427
|
+
faceMap.putInt("trackingId", face.trackingId!!)
|
|
428
|
+
}
|
|
429
|
+
if (face.smilingProbability != null) {
|
|
430
|
+
faceMap.putDouble("smilingProbability", face.smilingProbability!!.toDouble())
|
|
431
|
+
}
|
|
432
|
+
if (face.leftEyeOpenProbability != null) {
|
|
433
|
+
faceMap.putDouble("leftEyeOpenProbability", face.leftEyeOpenProbability!!.toDouble())
|
|
434
|
+
}
|
|
435
|
+
if (face.rightEyeOpenProbability != null) {
|
|
436
|
+
faceMap.putDouble("rightEyeOpenProbability", face.rightEyeOpenProbability!!.toDouble())
|
|
437
|
+
}
|
|
438
|
+
facesArray.pushMap(faceMap)
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
frameData.putArray("faces", facesArray)
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Text recognition results
|
|
445
|
+
if (textTask != null) {
|
|
446
|
+
if (textTask.isSuccessful) {
|
|
447
|
+
val result = textTask.result
|
|
448
|
+
frameData.putString("resultText", result.text)
|
|
449
|
+
|
|
450
|
+
val blocksArray = Arguments.createArray()
|
|
451
|
+
for (block in result.textBlocks) {
|
|
452
|
+
val blockMap = Arguments.createMap()
|
|
453
|
+
blockMap.putString("text", block.text)
|
|
454
|
+
val bb = block.boundingBox
|
|
455
|
+
if (bb != null) {
|
|
456
|
+
val boxMap = Arguments.createMap()
|
|
457
|
+
boxMap.putInt("x", bb.left)
|
|
458
|
+
boxMap.putInt("y", bb.top)
|
|
459
|
+
boxMap.putInt("width", bb.width())
|
|
460
|
+
boxMap.putInt("height", bb.height())
|
|
461
|
+
boxMap.putInt("boundingCenterX", bb.centerX())
|
|
462
|
+
boxMap.putInt("boundingCenterY", bb.centerY())
|
|
463
|
+
blockMap.putMap("blockFrame", boxMap)
|
|
464
|
+
}
|
|
465
|
+
blocksArray.pushMap(blockMap)
|
|
466
|
+
}
|
|
467
|
+
frameData.putArray("textBlocks", blocksArray)
|
|
468
|
+
} else {
|
|
469
|
+
frameData.putString("resultText", "")
|
|
470
|
+
frameData.putArray("textBlocks", Arguments.createArray())
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// MRZ validation (if enabled and text was recognized)
|
|
475
|
+
if (mrzValidationEnabled && textTask != null && textTask.isSuccessful) {
|
|
476
|
+
val rawText = textTask.result.text
|
|
477
|
+
val mrzResult = MRZValidator.validateWithCorrections(rawText)
|
|
478
|
+
val mrzMap = Arguments.createMap()
|
|
479
|
+
mrzMap.putBoolean("valid", mrzResult.valid)
|
|
480
|
+
mrzMap.putString("format", mrzResult.format)
|
|
481
|
+
mrzResult.error?.let { mrzMap.putString("error", it) }
|
|
482
|
+
mrzResult.documentCode?.let { mrzMap.putString("documentCode", it) }
|
|
483
|
+
mrzResult.issuingState?.let { mrzMap.putString("issuingState", it) }
|
|
484
|
+
mrzResult.documentNumber?.let { mrzMap.putString("documentNumber", it) }
|
|
485
|
+
mrzResult.lastName?.let { mrzMap.putString("lastName", it) }
|
|
486
|
+
mrzResult.firstName?.let { mrzMap.putString("firstName", it) }
|
|
487
|
+
mrzResult.birthDate?.let { mrzMap.putString("birthDate", it) }
|
|
488
|
+
mrzResult.sex?.let { mrzMap.putString("sex", it) }
|
|
489
|
+
mrzResult.expirationDate?.let { mrzMap.putString("expirationDate", it) }
|
|
490
|
+
mrzResult.nationality?.let { mrzMap.putString("nationality", it) }
|
|
491
|
+
mrzResult.optional1?.let { mrzMap.putString("optional1", it) }
|
|
492
|
+
mrzResult.optional2?.let { mrzMap.putString("optional2", it) }
|
|
493
|
+
mrzResult.rawLines?.let { mrzMap.putString("rawLines", it) }
|
|
494
|
+
frameData.putMap("mrzResult", mrzMap)
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// Barcode scanning results
|
|
498
|
+
if (barcodeTask != null) {
|
|
499
|
+
val barcodesArray = Arguments.createArray()
|
|
500
|
+
if (barcodeTask.isSuccessful) {
|
|
501
|
+
for (barcode in barcodeTask.result) {
|
|
502
|
+
val barcodeMap = Arguments.createMap()
|
|
503
|
+
barcodeMap.putString("rawValue", barcode.rawValue ?: "")
|
|
504
|
+
barcodeMap.putString("displayValue", barcode.displayValue ?: "")
|
|
505
|
+
barcodeMap.putInt("format", barcode.format)
|
|
506
|
+
val bb = barcode.boundingBox
|
|
507
|
+
if (bb != null) {
|
|
508
|
+
val boxMap = Arguments.createMap()
|
|
509
|
+
boxMap.putInt("left", bb.left)
|
|
510
|
+
boxMap.putInt("top", bb.top)
|
|
511
|
+
boxMap.putInt("right", bb.right)
|
|
512
|
+
boxMap.putInt("bottom", bb.bottom)
|
|
513
|
+
barcodeMap.putMap("boundingBox", boxMap)
|
|
514
|
+
}
|
|
515
|
+
val cornerPoints = Arguments.createArray()
|
|
516
|
+
barcode.cornerPoints?.forEach { point ->
|
|
517
|
+
val pointMap = Arguments.createMap()
|
|
518
|
+
pointMap.putInt("x", point.x)
|
|
519
|
+
pointMap.putInt("y", point.y)
|
|
520
|
+
cornerPoints.pushMap(pointMap)
|
|
521
|
+
}
|
|
522
|
+
barcodeMap.putArray("cornerPoints", cornerPoints)
|
|
523
|
+
barcodesArray.pushMap(barcodeMap)
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
frameData.putArray("barcodes", barcodesArray)
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
sendFrameEvent(frameData)
|
|
530
|
+
} finally {
|
|
531
|
+
imageProxy.close()
|
|
532
|
+
isProcessing.set(false)
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* Fast brightness calculation directly from the Y plane — no allocation, no base64.
|
|
541
|
+
* Samples within the scanning frame area only (~100 points).
|
|
542
|
+
* Scanning frame: top 36%, bottom 36%, left 5%, right 5% of portrait dimensions.
|
|
543
|
+
* Returns average luma value (0-255).
|
|
544
|
+
*/
|
|
545
|
+
private fun computeYPlaneBrightness(imageProxy: ImageProxy, portraitWidth: Int, portraitHeight: Int): Double {
|
|
546
|
+
val yPlane = imageProxy.planes[0]
|
|
547
|
+
val yBuffer = yPlane.buffer
|
|
548
|
+
val rowStride = yPlane.rowStride
|
|
549
|
+
val bufferWidth = imageProxy.width
|
|
550
|
+
val bufferHeight = imageProxy.height
|
|
551
|
+
|
|
552
|
+
// Define scanning frame area (from IdentityDocumentCamera.tsx styles)
|
|
553
|
+
// top: 36%, left: 5%, right: 5%, bottom: 36%
|
|
554
|
+
val scanTopPercent = 0.36
|
|
555
|
+
val scanBottomPercent = 0.36
|
|
556
|
+
val scanLeftPercent = 0.05
|
|
557
|
+
val scanRightPercent = 0.05
|
|
558
|
+
|
|
559
|
+
// Convert percentages to pixel coordinates in the portrait frame
|
|
560
|
+
val scanTop = (portraitHeight * scanTopPercent).toInt()
|
|
561
|
+
val scanBottom = (portraitHeight * (1.0 - scanBottomPercent)).toInt()
|
|
562
|
+
val scanLeft = (portraitWidth * scanLeftPercent).toInt()
|
|
563
|
+
val scanRight = (portraitWidth * (1.0 - scanRightPercent)).toInt()
|
|
564
|
+
|
|
565
|
+
// Map from portrait coordinates to buffer coordinates
|
|
566
|
+
// Portrait frame dimensions and buffer dimensions may differ in rotation
|
|
567
|
+
// Sample points proportionally within the scanning frame
|
|
568
|
+
val scanWidth = scanRight - scanLeft
|
|
569
|
+
val scanHeight = scanBottom - scanTop
|
|
570
|
+
|
|
571
|
+
var sum = 0L
|
|
572
|
+
var count = 0
|
|
573
|
+
val sampleCount = 100
|
|
574
|
+
|
|
575
|
+
for (i in 0..<sampleCount) {
|
|
576
|
+
// Distribute samples evenly within the scanning frame
|
|
577
|
+
val sampleOffsetX = (i * 17) % sampleCount
|
|
578
|
+
val sampleOffsetY = (i * 23) % sampleCount
|
|
579
|
+
|
|
580
|
+
val portraitX = scanLeft + (sampleOffsetX * scanWidth) / max(1, sampleCount - 1)
|
|
581
|
+
val portraitY = scanTop + (sampleOffsetY * scanHeight) / max(1, sampleCount - 1)
|
|
582
|
+
|
|
583
|
+
// Map portrait coordinates to buffer coordinates (accounting for rotation)
|
|
584
|
+
val bufferX = (portraitX * bufferWidth) / portraitWidth
|
|
585
|
+
val bufferY = (portraitY * bufferHeight) / portraitHeight
|
|
586
|
+
|
|
587
|
+
val clampedX = bufferX.coerceIn(0, bufferWidth - 1)
|
|
588
|
+
val clampedY = bufferY.coerceIn(0, bufferHeight - 1)
|
|
589
|
+
|
|
590
|
+
val bufferIndex = clampedY * rowStride + clampedX
|
|
591
|
+
if (bufferIndex < yBuffer.capacity()) {
|
|
592
|
+
sum += (yBuffer.get(bufferIndex).toInt() and 0xFF)
|
|
593
|
+
count++
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
return if (count > 0) sum.toDouble() / count else 0.0
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
private fun yuvImageProxyToJpegBase64(imageProxy: ImageProxy, rotationDegrees: Int): String? {
|
|
601
|
+
try {
|
|
602
|
+
val width = imageProxy.width
|
|
603
|
+
val height = imageProxy.height
|
|
604
|
+
|
|
605
|
+
// Check plane count
|
|
606
|
+
val planeCount = imageProxy.planes.size
|
|
607
|
+
if (planeCount < 3) {
|
|
608
|
+
android.util.Log.e("TrustchexCamera", "[yuvImageProxyToJpegBase64] ERROR: Expected 3 planes (YUV), got $planeCount")
|
|
609
|
+
return null
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
val yPlane = imageProxy.planes[0]
|
|
613
|
+
val uPlane = imageProxy.planes[1]
|
|
614
|
+
val vPlane = imageProxy.planes[2]
|
|
615
|
+
|
|
616
|
+
val yRowStride = yPlane.rowStride
|
|
617
|
+
val uvRowStride = uPlane.rowStride
|
|
618
|
+
val uvPixelStride = uPlane.pixelStride
|
|
619
|
+
val nv21 = ByteArray(width * height + width * (height / 2))
|
|
620
|
+
|
|
621
|
+
// Copy Y plane row-by-row, stripping any row padding
|
|
622
|
+
val yBuffer = yPlane.buffer
|
|
623
|
+
for (row in 0 until height) {
|
|
624
|
+
yBuffer.position(row * yRowStride)
|
|
625
|
+
yBuffer.get(nv21, row * width, width)
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// Copy UV planes into interleaved VU (NV21) format
|
|
629
|
+
val uBuffer = uPlane.buffer
|
|
630
|
+
val vBuffer = vPlane.buffer
|
|
631
|
+
val uvHeight = height / 2
|
|
632
|
+
val uvWidth = width / 2
|
|
633
|
+
var nv21Offset = width * height
|
|
634
|
+
|
|
635
|
+
// Reset buffer positions to start
|
|
636
|
+
if (uvPixelStride == 2) {
|
|
637
|
+
val actualUvRowStride = if (uvRowStride >= uvWidth * 2) uvRowStride else uvWidth * 2
|
|
638
|
+
|
|
639
|
+
for (row in 0 until uvHeight) {
|
|
640
|
+
val rowOffset = row * actualUvRowStride
|
|
641
|
+
for (col in 0 until uvWidth) {
|
|
642
|
+
val uvIndex = rowOffset + col * uvPixelStride
|
|
643
|
+
if (uvIndex < vBuffer.capacity() && uvIndex < uBuffer.capacity()) {
|
|
644
|
+
nv21[nv21Offset++] = vBuffer.get(uvIndex)
|
|
645
|
+
nv21[nv21Offset++] = uBuffer.get(uvIndex)
|
|
646
|
+
} else {
|
|
647
|
+
android.util.Log.w("TrustchexCamera", "UV index out of bounds: $uvIndex at row=$row, col=$col")
|
|
648
|
+
nv21[nv21Offset++] = 128.toByte()
|
|
649
|
+
nv21[nv21Offset++] = 128.toByte()
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
// Convert YUV to JPEG with optimized quality (75% for faster encoding)
|
|
656
|
+
val strides = intArrayOf(width, width)
|
|
657
|
+
val yuvImage = YuvImage(nv21, ImageFormat.NV21, width, height, strides)
|
|
658
|
+
val jpegStream = ByteArrayOutputStream()
|
|
659
|
+
yuvImage.compressToJpeg(Rect(0, 0, width, height), 75, jpegStream)
|
|
660
|
+
|
|
661
|
+
// Rotate to portrait if needed
|
|
662
|
+
if (rotationDegrees != 0) {
|
|
663
|
+
val jpegBytes = jpegStream.toByteArray()
|
|
664
|
+
val bitmap = BitmapFactory.decodeByteArray(jpegBytes, 0, jpegBytes.size)
|
|
665
|
+
val matrix = Matrix().apply { postRotate(rotationDegrees.toFloat()) }
|
|
666
|
+
val rotated = android.graphics.Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
|
|
667
|
+
bitmap.recycle()
|
|
668
|
+
val rotatedStream = ByteArrayOutputStream()
|
|
669
|
+
rotated.compress(android.graphics.Bitmap.CompressFormat.JPEG, 60, rotatedStream)
|
|
670
|
+
rotated.recycle()
|
|
671
|
+
return android.util.Base64.encodeToString(rotatedStream.toByteArray(), android.util.Base64.NO_WRAP)
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
return android.util.Base64.encodeToString(jpegStream.toByteArray(), android.util.Base64.NO_WRAP)
|
|
675
|
+
} catch (e: Exception) {
|
|
676
|
+
android.util.Log.e("TrustchexCamera", "[yuvImageProxyToJpegBase64] ERROR: ${e.message}", e)
|
|
677
|
+
return null
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
fun setFocusPoint(x: Double, y: Double) {
|
|
682
|
+
val factory = previewView.meteringPointFactory
|
|
683
|
+
val point = factory.createPoint(x.toFloat(), y.toFloat())
|
|
684
|
+
val action = FocusMeteringAction.Builder(
|
|
685
|
+
point,
|
|
686
|
+
FocusMeteringAction.FLAG_AF or FocusMeteringAction.FLAG_AE
|
|
687
|
+
).build()
|
|
688
|
+
|
|
689
|
+
camera?.cameraControl?.startFocusAndMetering(action)
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
fun setExposureOffset(offset: Double) {
|
|
693
|
+
val exposureState = camera?.cameraInfo?.exposureState
|
|
694
|
+
val range = exposureState?.exposureCompensationRange
|
|
695
|
+
if (range != null) {
|
|
696
|
+
val index = offset.toInt().coerceIn(range.lower, range.upper)
|
|
697
|
+
camera?.cameraControl?.setExposureCompensationIndex(index)
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
@SuppressLint("MissingPermission")
|
|
702
|
+
fun startRecording() {
|
|
703
|
+
if (activeRecording != null) {
|
|
704
|
+
return
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
val videoCapture = videoCapture ?: run {
|
|
708
|
+
sendRecordingErrorEvent("VideoCapture not initialized")
|
|
709
|
+
return
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
try {
|
|
713
|
+
// Apply torch setting if needed
|
|
714
|
+
if (torchEnabled) {
|
|
715
|
+
camera?.cameraControl?.enableTorch(true)
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
// Apply stable camera settings
|
|
719
|
+
applyCameraSettings()
|
|
720
|
+
|
|
721
|
+
// Create output file in cache directory (not visible in gallery)
|
|
722
|
+
val fileName = "liveness_${System.currentTimeMillis()}.mp4"
|
|
723
|
+
val videoFile = File(context.cacheDir, fileName)
|
|
724
|
+
currentRecordingFile = videoFile
|
|
725
|
+
isStoppingRecording = false // Reset flag for new recording
|
|
726
|
+
|
|
727
|
+
val fileOutputOptions = FileOutputOptions.Builder(videoFile).build()
|
|
728
|
+
|
|
729
|
+
activeRecording = videoCapture.output
|
|
730
|
+
.prepareRecording(reactContext, fileOutputOptions)
|
|
731
|
+
.start(ContextCompat.getMainExecutor(reactContext)) { event ->
|
|
732
|
+
when (event) {
|
|
733
|
+
is VideoRecordEvent.Finalize -> {
|
|
734
|
+
activeRecording = null
|
|
735
|
+
|
|
736
|
+
if (event.hasError()) {
|
|
737
|
+
currentRecordingFile?.delete()
|
|
738
|
+
currentRecordingFile = null
|
|
739
|
+
sendRecordingErrorEvent("Recording error: ${event.cause?.message}")
|
|
740
|
+
} else {
|
|
741
|
+
val filePath = videoFile.absolutePath
|
|
742
|
+
sendRecordingFinishedEvent(filePath)
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
else -> {}
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
} catch (e: Exception) {
|
|
749
|
+
activeRecording = null
|
|
750
|
+
sendRecordingErrorEvent("Failed to start recording: ${e.message}")
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
fun stopRecording() {
|
|
755
|
+
isStoppingRecording = true // Mark that we're stopping normally
|
|
756
|
+
activeRecording?.stop()
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
fun cancelRecording() {
|
|
760
|
+
// Don't delete the file if stopRecording was already called - let it finalize normally
|
|
761
|
+
if (isStoppingRecording) {
|
|
762
|
+
return
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
activeRecording?.close()
|
|
766
|
+
activeRecording = null
|
|
767
|
+
|
|
768
|
+
// Delete incomplete recording file
|
|
769
|
+
currentRecordingFile?.let { file ->
|
|
770
|
+
try {
|
|
771
|
+
if (file.exists()) {
|
|
772
|
+
file.delete()
|
|
773
|
+
}
|
|
774
|
+
} catch (e: Exception) {
|
|
775
|
+
// Silently handle
|
|
776
|
+
}
|
|
777
|
+
currentRecordingFile = null
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
@OptIn(ExperimentalGetImage::class)
|
|
782
|
+
private fun applyCameraSettings() {
|
|
783
|
+
// CameraX manages continuous autofocus automatically
|
|
784
|
+
// We only need to set initial focus point at center
|
|
785
|
+
val camera = camera ?: return
|
|
786
|
+
|
|
787
|
+
try {
|
|
788
|
+
val centerPoint = previewView.meteringPointFactory.createPoint(0.5f, 0.5f)
|
|
789
|
+
val afAction = FocusMeteringAction.Builder(
|
|
790
|
+
centerPoint,
|
|
791
|
+
FocusMeteringAction.FLAG_AF or FocusMeteringAction.FLAG_AE
|
|
792
|
+
).build()
|
|
793
|
+
|
|
794
|
+
camera.cameraControl.startFocusAndMetering(afAction)
|
|
795
|
+
} catch (e: Exception) {
|
|
796
|
+
// Silently handle - not critical
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
private fun sendFrameEvent(frameData: WritableMap) {
|
|
800
|
+
val event = Arguments.createMap()
|
|
801
|
+
event.putMap("frame", frameData)
|
|
802
|
+
|
|
803
|
+
reactContext
|
|
804
|
+
.getJSModule(RCTEventEmitter::class.java)
|
|
805
|
+
.receiveEvent(id, TrustchexCameraManager.EVENT_FRAME_AVAILABLE, event)
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
private fun sendReadyEvent(minExposure: Double, maxExposure: Double) {
|
|
809
|
+
val event = Arguments.createMap()
|
|
810
|
+
event.putDouble("minExposureOffset", minExposure)
|
|
811
|
+
event.putDouble("maxExposureOffset", maxExposure)
|
|
812
|
+
|
|
813
|
+
reactContext
|
|
814
|
+
.getJSModule(RCTEventEmitter::class.java)
|
|
815
|
+
.receiveEvent(id, TrustchexCameraManager.EVENT_CAMERA_READY, event)
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
private fun sendErrorEvent(error: String) {
|
|
819
|
+
val event = Arguments.createMap()
|
|
820
|
+
event.putString("error", error)
|
|
821
|
+
|
|
822
|
+
reactContext
|
|
823
|
+
.getJSModule(RCTEventEmitter::class.java)
|
|
824
|
+
.receiveEvent(id, TrustchexCameraManager.EVENT_CAMERA_ERROR, event)
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
private fun sendRecordingFinishedEvent(path: String) {
|
|
828
|
+
val event = Arguments.createMap()
|
|
829
|
+
event.putString("path", path)
|
|
830
|
+
|
|
831
|
+
try {
|
|
832
|
+
reactContext
|
|
833
|
+
.getJSModule(RCTEventEmitter::class.java)
|
|
834
|
+
.receiveEvent(id, TrustchexCameraManager.EVENT_RECORDING_FINISHED, event)
|
|
835
|
+
} catch (e: Exception) {
|
|
836
|
+
android.util.Log.e("TrustchexCamera", "Error sending recording finished event: ${e.message}", e)
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
private fun sendRecordingErrorEvent(error: String) {
|
|
841
|
+
val event = Arguments.createMap()
|
|
842
|
+
event.putString("error", error)
|
|
843
|
+
|
|
844
|
+
reactContext
|
|
845
|
+
.getJSModule(RCTEventEmitter::class.java)
|
|
846
|
+
.receiveEvent(id, TrustchexCameraManager.EVENT_RECORDING_ERROR, event)
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
fun deleteRecording(filePath: String) {
|
|
850
|
+
try {
|
|
851
|
+
val file = File(filePath)
|
|
852
|
+
if (file.exists()) {
|
|
853
|
+
file.delete()
|
|
854
|
+
}
|
|
855
|
+
} catch (e: Exception) {
|
|
856
|
+
// Silently handle
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
fun cleanup() {
|
|
861
|
+
// Cancel any active recording and delete file
|
|
862
|
+
if (activeRecording != null) {
|
|
863
|
+
activeRecording?.close()
|
|
864
|
+
currentRecordingFile?.delete()
|
|
865
|
+
currentRecordingFile = null
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
cameraProvider?.unbindAll()
|
|
869
|
+
cameraExecutor.shutdown()
|
|
870
|
+
}
|
|
871
|
+
}
|