@hexar/biometric-identity-sdk-react-native 1.20.0 → 1.22.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.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VideoRecorder.d.ts","sourceRoot":"","sources":["../../src/components/VideoRecorder.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAmD,MAAM,OAAO,CAAC;AAexE,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,iBAAiB,EAAmC,MAAM,oCAAoC,CAAC;AAE1I,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAC7B,sEAAsE;IACtE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,sCAAsC;IACtC,YAAY,CAAC,EAAE,mBAAmB,EAAE,CAAC;IACrC,0CAA0C;IAC1C,UAAU,CAAC,EAAE,eAAe,EAAE,CAAC;IAC/B,wCAAwC;IACxC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kDAAkD;IAClD,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,wCAAwC;IACxC,UAAU,EAAE,CAAC,SAAS,EAAE,oBAAoB,KAAK,IAAI,CAAC;IACtD,iCAAiC;IACjC,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,gDAAgD;IAChD,iBAAiB,CAAC,EAAE,MAAM,OAAO,CAAC,eAAe,EAAE,CAAC,CAAC;CACtD;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,oBAAoB,EAAE,OAAO,CAAC;IAC9B,YAAY,EAAE,MAAM,CAAC;IACrB,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA2CD,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,
|
|
1
|
+
{"version":3,"file":"VideoRecorder.d.ts","sourceRoot":"","sources":["../../src/components/VideoRecorder.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAmD,MAAM,OAAO,CAAC;AAexE,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,iBAAiB,EAAmC,MAAM,oCAAoC,CAAC;AAE1I,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAC7B,sEAAsE;IACtE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,sCAAsC;IACtC,YAAY,CAAC,EAAE,mBAAmB,EAAE,CAAC;IACrC,0CAA0C;IAC1C,UAAU,CAAC,EAAE,eAAe,EAAE,CAAC;IAC/B,wCAAwC;IACxC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kDAAkD;IAClD,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,wCAAwC;IACxC,UAAU,EAAE,CAAC,SAAS,EAAE,oBAAoB,KAAK,IAAI,CAAC;IACtD,iCAAiC;IACjC,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,gDAAgD;IAChD,iBAAiB,CAAC,EAAE,MAAM,OAAO,CAAC,eAAe,EAAE,CAAC,CAAC;CACtD;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,oBAAoB,EAAE,OAAO,CAAC;IAC9B,YAAY,EAAE,MAAM,CAAC;IACrB,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA2CD,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAwhCtD,CAAC;AA4PF,eAAe,aAAa,CAAC"}
|
|
@@ -101,6 +101,7 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
|
|
|
101
101
|
const framesRef = (0, react_1.useRef)([]);
|
|
102
102
|
const [guidanceText, setGuidanceText] = (0, react_1.useState)(null);
|
|
103
103
|
const [hasPermission, setHasPermission] = (0, react_1.useState)(false);
|
|
104
|
+
const [isCameraReady, setIsCameraReady] = (0, react_1.useState)(false);
|
|
104
105
|
const cameraRef = (0, react_1.useRef)(null);
|
|
105
106
|
const { hasPermission: cameraPermission, requestPermission } = (0, react_native_vision_camera_1.useCameraPermission)();
|
|
106
107
|
// Prefer wide-angle (main) front camera — some Android devices expose
|
|
@@ -233,7 +234,11 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
|
|
|
233
234
|
initChallenges();
|
|
234
235
|
}, [propChallenges, instructions, onFetchChallenges, smartMode, duration, language]);
|
|
235
236
|
(0, react_1.useEffect)(() => {
|
|
236
|
-
|
|
237
|
+
// Don't start countdown until the native camera session is ready.
|
|
238
|
+
// On Motorola and other slow-to-initialize Android OEMs, isActive becoming
|
|
239
|
+
// true before the native view is registered causes IllegalViewOperationException
|
|
240
|
+
// inside CameraViewModule.findCameraView. We wait for onInitialized to fire.
|
|
241
|
+
if (phase !== 'countdown' || !isCameraReady)
|
|
237
242
|
return;
|
|
238
243
|
// Trigger focus at the start of countdown so the camera has ~3s to lock focus
|
|
239
244
|
if (countdown === 3)
|
|
@@ -257,7 +262,7 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
|
|
|
257
262
|
else {
|
|
258
263
|
startRecording();
|
|
259
264
|
}
|
|
260
|
-
}, [countdown, phase]);
|
|
265
|
+
}, [countdown, phase, isCameraReady]);
|
|
261
266
|
(0, react_1.useEffect)(() => {
|
|
262
267
|
if (phase === 'recording') {
|
|
263
268
|
react_native_1.Animated.loop(react_native_1.Animated.sequence([
|
|
@@ -309,6 +314,19 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
|
|
|
309
314
|
setPhase('loading');
|
|
310
315
|
react_native_1.Alert.alert('Recording Error', 'Failed to record video. Please try again.', [{ text: 'OK', onPress: onCancel }]);
|
|
311
316
|
}, [onCancel]);
|
|
317
|
+
// Safety timeout: if the camera session never fires onInitialized within
|
|
318
|
+
// 10 seconds, treat it as a hardware failure and send the user back.
|
|
319
|
+
(0, react_1.useEffect)(() => {
|
|
320
|
+
if (isCameraReady)
|
|
321
|
+
return;
|
|
322
|
+
const timeout = setTimeout(() => {
|
|
323
|
+
if (!isCameraReady) {
|
|
324
|
+
biometric_identity_sdk_core_1.logger.error('Camera onInitialized timed out — hardware may be unavailable');
|
|
325
|
+
handleRecordingError(new Error('Camera initialization timed out'));
|
|
326
|
+
}
|
|
327
|
+
}, 10000);
|
|
328
|
+
return () => clearTimeout(timeout);
|
|
329
|
+
}, [isCameraReady, handleRecordingError]);
|
|
312
330
|
const handleVideoComplete = (0, react_1.useCallback)(async (video) => {
|
|
313
331
|
if (phase === 'loading' && !video) {
|
|
314
332
|
return;
|
|
@@ -362,7 +380,12 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
|
|
|
362
380
|
};
|
|
363
381
|
biometric_identity_sdk_core_1.logger.info('Video recording completed:', result.frames.length, 'frames,', (actualDuration / 1000).toFixed(1) + 's', 'challenges:', currentCompletedChallenges.length);
|
|
364
382
|
isRecordingRef.current = false;
|
|
365
|
-
onComplete(
|
|
383
|
+
// Delay onComplete so Vision Camera's native session (especially
|
|
384
|
+
// multi-physical-camera sessions opened via physicalDevices) has time
|
|
385
|
+
// to fully close before the parent unmounts the <Camera> view.
|
|
386
|
+
// Without this delay, CameraViewModule.findCameraView throws
|
|
387
|
+
// IllegalViewOperationException on Android 13 (SDK 33).
|
|
388
|
+
setTimeout(() => onComplete(result), 250);
|
|
366
389
|
}
|
|
367
390
|
catch (error) {
|
|
368
391
|
biometric_identity_sdk_core_1.logger.error('Error processing video:', error);
|
|
@@ -512,7 +535,8 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
|
|
|
512
535
|
challengesCompleted: currentCompletedChallenges,
|
|
513
536
|
sessionId,
|
|
514
537
|
};
|
|
515
|
-
|
|
538
|
+
setPhase('processing');
|
|
539
|
+
setTimeout(() => onComplete(result), 250);
|
|
516
540
|
}
|
|
517
541
|
else {
|
|
518
542
|
biometric_identity_sdk_core_1.logger.info('Stopping recording with frames (fallback):', currentFrames.length);
|
|
@@ -524,7 +548,8 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
|
|
|
524
548
|
challengesCompleted: currentCompletedChallenges,
|
|
525
549
|
sessionId,
|
|
526
550
|
};
|
|
527
|
-
|
|
551
|
+
setPhase('processing');
|
|
552
|
+
setTimeout(() => onComplete(result), 250);
|
|
528
553
|
}
|
|
529
554
|
}
|
|
530
555
|
}
|
|
@@ -557,7 +582,7 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
|
|
|
557
582
|
};
|
|
558
583
|
setTimeout(() => {
|
|
559
584
|
onComplete(result);
|
|
560
|
-
},
|
|
585
|
+
}, 250);
|
|
561
586
|
}
|
|
562
587
|
}, [frames, completedChallenges, challenges, sessionId, onComplete, minDurationMs, resetAndRetry, strings]);
|
|
563
588
|
const runChallenge = (0, react_1.useCallback)((index) => {
|
|
@@ -600,7 +625,8 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
|
|
|
600
625
|
challengesCompleted: currentCompletedChallenges,
|
|
601
626
|
sessionId,
|
|
602
627
|
};
|
|
603
|
-
|
|
628
|
+
setPhase('processing');
|
|
629
|
+
setTimeout(() => onComplete(result), 250);
|
|
604
630
|
}
|
|
605
631
|
}
|
|
606
632
|
return;
|
|
@@ -781,7 +807,10 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
|
|
|
781
807
|
}
|
|
782
808
|
return (react_1.default.createElement(react_native_1.View, { style: styles.container },
|
|
783
809
|
react_1.default.createElement(react_native_1.View, { style: styles.cameraContainer },
|
|
784
|
-
react_1.default.createElement(react_native_vision_camera_1.Camera, { ref: cameraRef, style: react_native_1.StyleSheet.absoluteFill, device: device, isActive:
|
|
810
|
+
react_1.default.createElement(react_native_vision_camera_1.Camera, { ref: cameraRef, style: react_native_1.StyleSheet.absoluteFill, device: device, isActive: true, video: true, photo: true, audio: false, onInitialized: () => setIsCameraReady(true), onError: (error) => {
|
|
811
|
+
biometric_identity_sdk_core_1.logger.error('Camera hardware error:', error);
|
|
812
|
+
handleRecordingError(error);
|
|
813
|
+
} }),
|
|
785
814
|
react_1.default.createElement(react_native_1.View, { style: styles.overlay },
|
|
786
815
|
react_1.default.createElement(react_native_1.View, { style: [
|
|
787
816
|
styles.faceOval,
|
package/package.json
CHANGED
|
@@ -130,6 +130,7 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
|
|
|
130
130
|
const framesRef = useRef<string[]>([]);
|
|
131
131
|
const [guidanceText, setGuidanceText] = useState<string | null>(null);
|
|
132
132
|
const [hasPermission, setHasPermission] = useState(false);
|
|
133
|
+
const [isCameraReady, setIsCameraReady] = useState(false);
|
|
133
134
|
const cameraRef = useRef<Camera>(null);
|
|
134
135
|
const { hasPermission: cameraPermission, requestPermission } = useCameraPermission();
|
|
135
136
|
// Prefer wide-angle (main) front camera — some Android devices expose
|
|
@@ -274,7 +275,11 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
|
|
|
274
275
|
}, [propChallenges, instructions, onFetchChallenges, smartMode, duration, language]);
|
|
275
276
|
|
|
276
277
|
useEffect(() => {
|
|
277
|
-
|
|
278
|
+
// Don't start countdown until the native camera session is ready.
|
|
279
|
+
// On Motorola and other slow-to-initialize Android OEMs, isActive becoming
|
|
280
|
+
// true before the native view is registered causes IllegalViewOperationException
|
|
281
|
+
// inside CameraViewModule.findCameraView. We wait for onInitialized to fire.
|
|
282
|
+
if (phase !== 'countdown' || !isCameraReady) return;
|
|
278
283
|
|
|
279
284
|
// Trigger focus at the start of countdown so the camera has ~3s to lock focus
|
|
280
285
|
if (countdown === 3) triggerFocus();
|
|
@@ -298,7 +303,7 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
|
|
|
298
303
|
} else {
|
|
299
304
|
startRecording();
|
|
300
305
|
}
|
|
301
|
-
}, [countdown, phase]);
|
|
306
|
+
}, [countdown, phase, isCameraReady]);
|
|
302
307
|
|
|
303
308
|
useEffect(() => {
|
|
304
309
|
if (phase === 'recording') {
|
|
@@ -364,6 +369,19 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
|
|
|
364
369
|
);
|
|
365
370
|
}, [onCancel]);
|
|
366
371
|
|
|
372
|
+
// Safety timeout: if the camera session never fires onInitialized within
|
|
373
|
+
// 10 seconds, treat it as a hardware failure and send the user back.
|
|
374
|
+
useEffect(() => {
|
|
375
|
+
if (isCameraReady) return;
|
|
376
|
+
const timeout = setTimeout(() => {
|
|
377
|
+
if (!isCameraReady) {
|
|
378
|
+
logger.error('Camera onInitialized timed out — hardware may be unavailable');
|
|
379
|
+
handleRecordingError(new Error('Camera initialization timed out'));
|
|
380
|
+
}
|
|
381
|
+
}, 10000);
|
|
382
|
+
return () => clearTimeout(timeout);
|
|
383
|
+
}, [isCameraReady, handleRecordingError]);
|
|
384
|
+
|
|
367
385
|
const handleVideoComplete = useCallback(async (video: any) => {
|
|
368
386
|
if (phase === 'loading' && !video) {
|
|
369
387
|
return;
|
|
@@ -432,7 +450,12 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
|
|
|
432
450
|
logger.info('Video recording completed:', result.frames.length, 'frames,', (actualDuration / 1000).toFixed(1) + 's', 'challenges:', currentCompletedChallenges.length);
|
|
433
451
|
|
|
434
452
|
isRecordingRef.current = false;
|
|
435
|
-
onComplete(
|
|
453
|
+
// Delay onComplete so Vision Camera's native session (especially
|
|
454
|
+
// multi-physical-camera sessions opened via physicalDevices) has time
|
|
455
|
+
// to fully close before the parent unmounts the <Camera> view.
|
|
456
|
+
// Without this delay, CameraViewModule.findCameraView throws
|
|
457
|
+
// IllegalViewOperationException on Android 13 (SDK 33).
|
|
458
|
+
setTimeout(() => onComplete(result), 250);
|
|
436
459
|
} catch (error) {
|
|
437
460
|
logger.error('Error processing video:', error);
|
|
438
461
|
setPhase('recording');
|
|
@@ -598,7 +621,8 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
|
|
|
598
621
|
challengesCompleted: currentCompletedChallenges,
|
|
599
622
|
sessionId,
|
|
600
623
|
};
|
|
601
|
-
|
|
624
|
+
setPhase('processing');
|
|
625
|
+
setTimeout(() => onComplete(result), 250);
|
|
602
626
|
} else {
|
|
603
627
|
logger.info('Stopping recording with frames (fallback):', currentFrames.length);
|
|
604
628
|
const result: VideoRecordingResult = {
|
|
@@ -609,7 +633,8 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
|
|
|
609
633
|
challengesCompleted: currentCompletedChallenges,
|
|
610
634
|
sessionId,
|
|
611
635
|
};
|
|
612
|
-
|
|
636
|
+
setPhase('processing');
|
|
637
|
+
setTimeout(() => onComplete(result), 250);
|
|
613
638
|
}
|
|
614
639
|
}
|
|
615
640
|
} else {
|
|
@@ -649,7 +674,7 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
|
|
|
649
674
|
|
|
650
675
|
setTimeout(() => {
|
|
651
676
|
onComplete(result);
|
|
652
|
-
},
|
|
677
|
+
}, 250);
|
|
653
678
|
}
|
|
654
679
|
}, [frames, completedChallenges, challenges, sessionId, onComplete, minDurationMs, resetAndRetry, strings]);
|
|
655
680
|
|
|
@@ -697,7 +722,8 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
|
|
|
697
722
|
challengesCompleted: currentCompletedChallenges,
|
|
698
723
|
sessionId,
|
|
699
724
|
};
|
|
700
|
-
|
|
725
|
+
setPhase('processing');
|
|
726
|
+
setTimeout(() => onComplete(result), 250);
|
|
701
727
|
}
|
|
702
728
|
}
|
|
703
729
|
return;
|
|
@@ -952,10 +978,15 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
|
|
|
952
978
|
ref={cameraRef}
|
|
953
979
|
style={StyleSheet.absoluteFill}
|
|
954
980
|
device={device}
|
|
955
|
-
isActive={
|
|
981
|
+
isActive={true}
|
|
956
982
|
video={true}
|
|
957
983
|
photo={true}
|
|
958
984
|
audio={false}
|
|
985
|
+
onInitialized={() => setIsCameraReady(true)}
|
|
986
|
+
onError={(error) => {
|
|
987
|
+
logger.error('Camera hardware error:', error);
|
|
988
|
+
handleRecordingError(error);
|
|
989
|
+
}}
|
|
959
990
|
/>
|
|
960
991
|
|
|
961
992
|
{/* Face Oval Overlay */}
|