@hexar/biometric-identity-sdk-react-native 1.0.19 → 1.0.20
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;AAaxE,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;AA8CD,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;AAaxE,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;AA8CD,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAqzBtD,CAAC;AA4OF,eAAe,aAAa,CAAC"}
|
|
@@ -100,6 +100,7 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
|
|
|
100
100
|
const [overallProgress, setOverallProgress] = (0, react_1.useState)(0);
|
|
101
101
|
const [completedChallenges, setCompletedChallenges] = (0, react_1.useState)([]);
|
|
102
102
|
const [frames, setFrames] = (0, react_1.useState)([]);
|
|
103
|
+
const framesRef = (0, react_1.useRef)([]);
|
|
103
104
|
const [hasPermission, setHasPermission] = (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)();
|
|
@@ -257,6 +258,7 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
|
|
|
257
258
|
}, [arrowAnim]);
|
|
258
259
|
const resetAndRetry = (0, react_1.useCallback)(() => {
|
|
259
260
|
setFrames([]);
|
|
261
|
+
framesRef.current = [];
|
|
260
262
|
setCompletedChallenges([]);
|
|
261
263
|
setCurrentChallengeIndex(0);
|
|
262
264
|
setChallengeProgress(0);
|
|
@@ -293,16 +295,18 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
|
|
|
293
295
|
biometric_identity_sdk_core_1.logger.warn('Could not read video file, using captured frames');
|
|
294
296
|
}
|
|
295
297
|
}
|
|
296
|
-
|
|
298
|
+
const currentFrames = framesRef.current.length > 0 ? framesRef.current : frames;
|
|
299
|
+
let finalFrames = currentFrames.length > 0 ? currentFrames : [];
|
|
297
300
|
if (finalFrames.length === 0 && videoBase64) {
|
|
298
301
|
finalFrames = [videoBase64];
|
|
299
302
|
}
|
|
300
303
|
if (finalFrames.length === 0) {
|
|
301
|
-
biometric_identity_sdk_core_1.logger.error('No frames or video available, cannot complete');
|
|
304
|
+
biometric_identity_sdk_core_1.logger.error('No frames or video available, cannot complete. Frames ref:', framesRef.current.length, 'Frames state:', frames.length);
|
|
302
305
|
setPhase('recording');
|
|
303
306
|
handleRecordingError(new Error('No video frames captured'));
|
|
304
307
|
return;
|
|
305
308
|
}
|
|
309
|
+
biometric_identity_sdk_core_1.logger.info('Using frames for completion:', finalFrames.length, 'frames');
|
|
306
310
|
const result = {
|
|
307
311
|
frames: finalFrames,
|
|
308
312
|
duration: actualDuration,
|
|
@@ -324,7 +328,16 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
|
|
|
324
328
|
}, [frames, completedChallenges, challenges, sessionId, onComplete, resetAndRetry, handleRecordingError, strings, minDurationMs, phase]);
|
|
325
329
|
const startFrameCapture = (0, react_1.useCallback)(() => {
|
|
326
330
|
if (cameraRef.current && device) {
|
|
331
|
+
biometric_identity_sdk_core_1.logger.info('Starting frame capture');
|
|
332
|
+
framesRef.current = [];
|
|
327
333
|
frameCaptureInterval.current = setInterval(async () => {
|
|
334
|
+
if (!isRecordingRef.current) {
|
|
335
|
+
if (frameCaptureInterval.current) {
|
|
336
|
+
clearInterval(frameCaptureInterval.current);
|
|
337
|
+
frameCaptureInterval.current = null;
|
|
338
|
+
}
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
328
341
|
try {
|
|
329
342
|
const photo = await cameraRef.current?.takePhoto({
|
|
330
343
|
flash: 'off',
|
|
@@ -334,26 +347,32 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
|
|
|
334
347
|
const RNFS = require('react-native-fs');
|
|
335
348
|
const base64 = await RNFS.readFile(photo.path, 'base64');
|
|
336
349
|
setFrames(prev => {
|
|
337
|
-
|
|
338
|
-
|
|
350
|
+
const newFrames = prev.length < 100 ? [...prev, base64] : prev;
|
|
351
|
+
framesRef.current = newFrames;
|
|
352
|
+
if (newFrames.length % 10 === 0) {
|
|
353
|
+
biometric_identity_sdk_core_1.logger.info('Captured frames:', newFrames.length);
|
|
339
354
|
}
|
|
340
|
-
return
|
|
355
|
+
return newFrames;
|
|
341
356
|
});
|
|
342
357
|
}
|
|
343
358
|
catch (fsError) {
|
|
359
|
+
biometric_identity_sdk_core_1.logger.warn('Could not read photo file, using path:', fsError);
|
|
344
360
|
setFrames(prev => {
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
return prev;
|
|
361
|
+
const newFrames = prev.length < 100 ? [...prev, photo.path] : prev;
|
|
362
|
+
framesRef.current = newFrames;
|
|
363
|
+
return newFrames;
|
|
349
364
|
});
|
|
350
365
|
}
|
|
351
366
|
}
|
|
352
367
|
}
|
|
353
368
|
catch (error) {
|
|
369
|
+
biometric_identity_sdk_core_1.logger.warn('Frame capture error:', error);
|
|
354
370
|
}
|
|
355
371
|
}, 100);
|
|
356
372
|
}
|
|
373
|
+
else {
|
|
374
|
+
biometric_identity_sdk_core_1.logger.warn('Cannot start frame capture: camera or device not available');
|
|
375
|
+
}
|
|
357
376
|
}, [device]);
|
|
358
377
|
const stopRecording = (0, react_1.useCallback)(async () => {
|
|
359
378
|
if (recordingTimeoutRef.current) {
|
|
@@ -377,23 +396,26 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
|
|
|
377
396
|
catch (error) {
|
|
378
397
|
biometric_identity_sdk_core_1.logger.error('Error stopping video recording:', error);
|
|
379
398
|
const actualDuration = Date.now() - recordingStartTime.current;
|
|
380
|
-
|
|
399
|
+
const currentFrames = framesRef.current.length > 0 ? framesRef.current : frames;
|
|
400
|
+
if (actualDuration >= minDurationMs && currentFrames.length > 0) {
|
|
401
|
+
biometric_identity_sdk_core_1.logger.info('Stopping recording with frames:', currentFrames.length);
|
|
381
402
|
const result = {
|
|
382
|
-
frames,
|
|
403
|
+
frames: currentFrames,
|
|
383
404
|
duration: actualDuration,
|
|
384
405
|
instructionsFollowed: completedChallenges.length === challenges.length,
|
|
385
|
-
qualityScore: Math.min(100, (
|
|
406
|
+
qualityScore: Math.min(100, (currentFrames.length / 30) * 100),
|
|
386
407
|
challengesCompleted: completedChallenges,
|
|
387
408
|
sessionId,
|
|
388
409
|
};
|
|
389
410
|
onComplete(result);
|
|
390
411
|
}
|
|
391
412
|
else {
|
|
413
|
+
biometric_identity_sdk_core_1.logger.info('Stopping recording with frames (fallback):', currentFrames.length);
|
|
392
414
|
const result = {
|
|
393
|
-
frames:
|
|
415
|
+
frames: currentFrames.length > 0 ? currentFrames : [],
|
|
394
416
|
duration: actualDuration,
|
|
395
417
|
instructionsFollowed: completedChallenges.length === challenges.length,
|
|
396
|
-
qualityScore:
|
|
418
|
+
qualityScore: currentFrames.length > 0 ? Math.min(100, (currentFrames.length / 30) * 100) : 0,
|
|
397
419
|
challengesCompleted: completedChallenges,
|
|
398
420
|
sessionId,
|
|
399
421
|
};
|
|
@@ -403,19 +425,21 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
|
|
|
403
425
|
}
|
|
404
426
|
else {
|
|
405
427
|
const actualDuration = Date.now() - recordingStartTime.current;
|
|
406
|
-
|
|
407
|
-
|
|
428
|
+
const currentFrames = framesRef.current.length > 0 ? framesRef.current : frames;
|
|
429
|
+
if (currentFrames.length === 0 && actualDuration < minDurationMs) {
|
|
430
|
+
biometric_identity_sdk_core_1.logger.error('No frames and duration too short, cannot complete. Frames ref:', framesRef.current.length, 'Frames state:', frames.length);
|
|
408
431
|
setPhase('recording');
|
|
409
432
|
react_native_1.Alert.alert(strings.errors.videoTooShort?.title || 'Recording Too Short', strings.errors.videoTooShort?.message || `Video must be at least ${minDurationMs / 1000} seconds. Please try again.`, [{ text: strings.common.retry || 'OK', onPress: resetAndRetry }]);
|
|
410
433
|
return;
|
|
411
434
|
}
|
|
412
435
|
setPhase('processing');
|
|
413
436
|
setOverallProgress(100);
|
|
437
|
+
biometric_identity_sdk_core_1.logger.info('Completing recording with frames:', currentFrames.length);
|
|
414
438
|
const result = {
|
|
415
|
-
frames:
|
|
439
|
+
frames: currentFrames.length > 0 ? currentFrames : [],
|
|
416
440
|
duration: actualDuration,
|
|
417
441
|
instructionsFollowed: completedChallenges.length === challenges.length,
|
|
418
|
-
qualityScore:
|
|
442
|
+
qualityScore: currentFrames.length > 0 ? Math.min(100, (currentFrames.length / 30) * 100) : 0,
|
|
419
443
|
challengesCompleted: completedChallenges,
|
|
420
444
|
sessionId,
|
|
421
445
|
};
|
package/package.json
CHANGED
|
@@ -127,6 +127,7 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
|
|
|
127
127
|
const [overallProgress, setOverallProgress] = useState(0);
|
|
128
128
|
const [completedChallenges, setCompletedChallenges] = useState<string[]>([]);
|
|
129
129
|
const [frames, setFrames] = useState<string[]>([]);
|
|
130
|
+
const framesRef = useRef<string[]>([]);
|
|
130
131
|
const [hasPermission, setHasPermission] = useState(false);
|
|
131
132
|
|
|
132
133
|
const cameraRef = useRef<Camera>(null);
|
|
@@ -304,6 +305,7 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
|
|
|
304
305
|
|
|
305
306
|
const resetAndRetry = useCallback(() => {
|
|
306
307
|
setFrames([]);
|
|
308
|
+
framesRef.current = [];
|
|
307
309
|
setCompletedChallenges([]);
|
|
308
310
|
setCurrentChallengeIndex(0);
|
|
309
311
|
setChallengeProgress(0);
|
|
@@ -354,19 +356,22 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
|
|
|
354
356
|
}
|
|
355
357
|
}
|
|
356
358
|
|
|
357
|
-
|
|
359
|
+
const currentFrames = framesRef.current.length > 0 ? framesRef.current : frames;
|
|
360
|
+
let finalFrames = currentFrames.length > 0 ? currentFrames : [];
|
|
358
361
|
|
|
359
362
|
if (finalFrames.length === 0 && videoBase64) {
|
|
360
363
|
finalFrames = [videoBase64];
|
|
361
364
|
}
|
|
362
365
|
|
|
363
366
|
if (finalFrames.length === 0) {
|
|
364
|
-
logger.error('No frames or video available, cannot complete');
|
|
367
|
+
logger.error('No frames or video available, cannot complete. Frames ref:', framesRef.current.length, 'Frames state:', frames.length);
|
|
365
368
|
setPhase('recording');
|
|
366
369
|
handleRecordingError(new Error('No video frames captured'));
|
|
367
370
|
return;
|
|
368
371
|
}
|
|
369
372
|
|
|
373
|
+
logger.info('Using frames for completion:', finalFrames.length, 'frames');
|
|
374
|
+
|
|
370
375
|
const result: VideoRecordingResult = {
|
|
371
376
|
frames: finalFrames,
|
|
372
377
|
duration: actualDuration,
|
|
@@ -390,7 +395,17 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
|
|
|
390
395
|
|
|
391
396
|
const startFrameCapture = useCallback(() => {
|
|
392
397
|
if (cameraRef.current && device) {
|
|
398
|
+
logger.info('Starting frame capture');
|
|
399
|
+
framesRef.current = [];
|
|
393
400
|
frameCaptureInterval.current = setInterval(async () => {
|
|
401
|
+
if (!isRecordingRef.current) {
|
|
402
|
+
if (frameCaptureInterval.current) {
|
|
403
|
+
clearInterval(frameCaptureInterval.current);
|
|
404
|
+
frameCaptureInterval.current = null;
|
|
405
|
+
}
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
|
|
394
409
|
try {
|
|
395
410
|
const photo = await cameraRef.current?.takePhoto({
|
|
396
411
|
flash: 'off',
|
|
@@ -401,23 +416,28 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
|
|
|
401
416
|
const RNFS = require('react-native-fs');
|
|
402
417
|
const base64 = await RNFS.readFile(photo.path, 'base64');
|
|
403
418
|
setFrames(prev => {
|
|
404
|
-
|
|
405
|
-
|
|
419
|
+
const newFrames = prev.length < 100 ? [...prev, base64] : prev;
|
|
420
|
+
framesRef.current = newFrames;
|
|
421
|
+
if (newFrames.length % 10 === 0) {
|
|
422
|
+
logger.info('Captured frames:', newFrames.length);
|
|
406
423
|
}
|
|
407
|
-
return
|
|
424
|
+
return newFrames;
|
|
408
425
|
});
|
|
409
426
|
} catch (fsError) {
|
|
427
|
+
logger.warn('Could not read photo file, using path:', fsError);
|
|
410
428
|
setFrames(prev => {
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
return prev;
|
|
429
|
+
const newFrames = prev.length < 100 ? [...prev, photo.path] : prev;
|
|
430
|
+
framesRef.current = newFrames;
|
|
431
|
+
return newFrames;
|
|
415
432
|
});
|
|
416
433
|
}
|
|
417
434
|
}
|
|
418
435
|
} catch (error) {
|
|
436
|
+
logger.warn('Frame capture error:', error);
|
|
419
437
|
}
|
|
420
438
|
}, 100);
|
|
439
|
+
} else {
|
|
440
|
+
logger.warn('Cannot start frame capture: camera or device not available');
|
|
421
441
|
}
|
|
422
442
|
}, [device]);
|
|
423
443
|
|
|
@@ -447,22 +467,26 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
|
|
|
447
467
|
logger.error('Error stopping video recording:', error);
|
|
448
468
|
|
|
449
469
|
const actualDuration = Date.now() - recordingStartTime.current;
|
|
450
|
-
|
|
470
|
+
const currentFrames = framesRef.current.length > 0 ? framesRef.current : frames;
|
|
471
|
+
|
|
472
|
+
if (actualDuration >= minDurationMs && currentFrames.length > 0) {
|
|
473
|
+
logger.info('Stopping recording with frames:', currentFrames.length);
|
|
451
474
|
const result: VideoRecordingResult = {
|
|
452
|
-
frames,
|
|
475
|
+
frames: currentFrames,
|
|
453
476
|
duration: actualDuration,
|
|
454
477
|
instructionsFollowed: completedChallenges.length === challenges.length,
|
|
455
|
-
qualityScore: Math.min(100, (
|
|
478
|
+
qualityScore: Math.min(100, (currentFrames.length / 30) * 100),
|
|
456
479
|
challengesCompleted: completedChallenges,
|
|
457
480
|
sessionId,
|
|
458
481
|
};
|
|
459
482
|
onComplete(result);
|
|
460
483
|
} else {
|
|
484
|
+
logger.info('Stopping recording with frames (fallback):', currentFrames.length);
|
|
461
485
|
const result: VideoRecordingResult = {
|
|
462
|
-
frames:
|
|
486
|
+
frames: currentFrames.length > 0 ? currentFrames : [],
|
|
463
487
|
duration: actualDuration,
|
|
464
488
|
instructionsFollowed: completedChallenges.length === challenges.length,
|
|
465
|
-
qualityScore:
|
|
489
|
+
qualityScore: currentFrames.length > 0 ? Math.min(100, (currentFrames.length / 30) * 100) : 0,
|
|
466
490
|
challengesCompleted: completedChallenges,
|
|
467
491
|
sessionId,
|
|
468
492
|
};
|
|
@@ -471,9 +495,10 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
|
|
|
471
495
|
}
|
|
472
496
|
} else {
|
|
473
497
|
const actualDuration = Date.now() - recordingStartTime.current;
|
|
498
|
+
const currentFrames = framesRef.current.length > 0 ? framesRef.current : frames;
|
|
474
499
|
|
|
475
|
-
if (
|
|
476
|
-
logger.error('No frames and duration too short, cannot complete');
|
|
500
|
+
if (currentFrames.length === 0 && actualDuration < minDurationMs) {
|
|
501
|
+
logger.error('No frames and duration too short, cannot complete. Frames ref:', framesRef.current.length, 'Frames state:', frames.length);
|
|
477
502
|
setPhase('recording');
|
|
478
503
|
Alert.alert(
|
|
479
504
|
strings.errors.videoTooShort?.title || 'Recording Too Short',
|
|
@@ -486,11 +511,12 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
|
|
|
486
511
|
setPhase('processing');
|
|
487
512
|
setOverallProgress(100);
|
|
488
513
|
|
|
514
|
+
logger.info('Completing recording with frames:', currentFrames.length);
|
|
489
515
|
const result: VideoRecordingResult = {
|
|
490
|
-
frames:
|
|
516
|
+
frames: currentFrames.length > 0 ? currentFrames : [],
|
|
491
517
|
duration: actualDuration,
|
|
492
518
|
instructionsFollowed: completedChallenges.length === challenges.length,
|
|
493
|
-
qualityScore:
|
|
519
|
+
qualityScore: currentFrames.length > 0 ? Math.min(100, (currentFrames.length / 30) * 100) : 0,
|
|
494
520
|
challengesCompleted: completedChallenges,
|
|
495
521
|
sessionId,
|
|
496
522
|
};
|