@rick427/react-native-liveness 0.1.4 → 0.1.6
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/android/src/main/java/com/livenesscamera/LivenessCameraPlugin.kt +14 -14
- package/lib/module/LivenessCamera.js +31 -19
- package/lib/module/LivenessCamera.js.map +1 -1
- package/lib/module/useLivenessCamera.js +9 -3
- package/lib/module/useLivenessCamera.js.map +1 -1
- package/lib/typescript/src/LivenessCamera.d.ts.map +1 -1
- package/lib/typescript/src/useLivenessCamera.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/LivenessCamera.tsx +28 -15
- package/src/useLivenessCamera.ts +8 -3
|
@@ -8,8 +8,6 @@ import com.google.mlkit.vision.face.FaceDetectorOptions
|
|
|
8
8
|
import com.mrousavy.camera.frameprocessors.Frame
|
|
9
9
|
import com.mrousavy.camera.frameprocessors.FrameProcessorPlugin
|
|
10
10
|
|
|
11
|
-
// Vision Camera v4.5+ removed VisionCameraProxyHolder and made FrameProcessorPlugin
|
|
12
|
-
// a no-arg constructor. The proxy/options are no longer passed at construction time.
|
|
13
11
|
class LivenessCameraPlugin : FrameProcessorPlugin() {
|
|
14
12
|
|
|
15
13
|
private val faceDetector = FaceDetection.getClient(
|
|
@@ -23,8 +21,7 @@ class LivenessCameraPlugin : FrameProcessorPlugin() {
|
|
|
23
21
|
|
|
24
22
|
/**
|
|
25
23
|
* Convert Vision Camera's Orientation to ML Kit rotation degrees.
|
|
26
|
-
* Uses toString()
|
|
27
|
-
* where the Orientation API (toDegrees / toSurfaceRotation) may vary.
|
|
24
|
+
* Uses toString() to stay resilient across VC patch-version API churn.
|
|
28
25
|
*/
|
|
29
26
|
private fun orientationDegrees(frame: Frame): Int {
|
|
30
27
|
val name = frame.orientation.toString().uppercase()
|
|
@@ -52,20 +49,23 @@ class LivenessCameraPlugin : FrameProcessorPlugin() {
|
|
|
52
49
|
val face = faces.first()
|
|
53
50
|
val box = face.boundingBox
|
|
54
51
|
|
|
52
|
+
// IMPORTANT: All numeric values must be Double, not Float.
|
|
53
|
+
// JSI (Vision Camera's JS bridge) cannot convert Java Float → jsi::Value
|
|
54
|
+
// and throws "Cannot convert Java type class java.lang.Float" at runtime.
|
|
55
55
|
mapOf(
|
|
56
56
|
"detected" to true,
|
|
57
57
|
"bounds" to mapOf(
|
|
58
|
-
"x" to box.left.
|
|
59
|
-
"y" to box.top.
|
|
60
|
-
"width" to box.width().
|
|
61
|
-
"height" to box.height().
|
|
58
|
+
"x" to box.left.toDouble(),
|
|
59
|
+
"y" to box.top.toDouble(),
|
|
60
|
+
"width" to box.width().toDouble(),
|
|
61
|
+
"height" to box.height().toDouble()
|
|
62
62
|
),
|
|
63
|
-
"yawAngle" to face.headEulerAngleY,
|
|
64
|
-
"pitchAngle" to face.headEulerAngleX,
|
|
65
|
-
"rollAngle" to face.headEulerAngleZ,
|
|
66
|
-
"leftEyeOpenProbability" to (face.leftEyeOpenProbability ?: -
|
|
67
|
-
"rightEyeOpenProbability" to (face.rightEyeOpenProbability ?: -
|
|
68
|
-
"smilingProbability" to (face.smilingProbability ?: -
|
|
63
|
+
"yawAngle" to face.headEulerAngleY.toDouble(),
|
|
64
|
+
"pitchAngle" to face.headEulerAngleX.toDouble(),
|
|
65
|
+
"rollAngle" to face.headEulerAngleZ.toDouble(),
|
|
66
|
+
"leftEyeOpenProbability" to (face.leftEyeOpenProbability?.toDouble() ?: -1.0),
|
|
67
|
+
"rightEyeOpenProbability" to (face.rightEyeOpenProbability?.toDouble() ?: -1.0),
|
|
68
|
+
"smilingProbability" to (face.smilingProbability?.toDouble() ?: -1.0)
|
|
69
69
|
)
|
|
70
70
|
} catch (e: Exception) {
|
|
71
71
|
mapOf("detected" to false)
|
|
@@ -3,23 +3,36 @@
|
|
|
3
3
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
4
4
|
import { Animated, StyleSheet, Text, View } from 'react-native';
|
|
5
5
|
import { Camera, useCameraDevice, useCameraPermission } from 'react-native-vision-camera';
|
|
6
|
-
import {
|
|
6
|
+
import { Ellipse, Path, Svg } from 'react-native-svg';
|
|
7
7
|
import { useLivenessCamera } from "./useLivenessCamera.js";
|
|
8
8
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
// Oval is sized relative to container WIDTH only, so it stays face-shaped
|
|
10
|
+
// on any screen. ry = rx * FACE_RATIO gives a natural portrait face oval.
|
|
11
|
+
const OVAL_WIDTH_RATIO = 0.72; // oval width = 72 % of container width
|
|
12
|
+
const FACE_RATIO = 1.35; // height-to-width ratio of the oval (~3:4 face)
|
|
11
13
|
const STROKE_WIDTH = 3;
|
|
12
14
|
// Cubic bezier approximation constant for a smooth ellipse
|
|
13
15
|
const K = 0.5523;
|
|
14
|
-
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Returns the stroke colour for the oval guide.
|
|
19
|
+
*
|
|
20
|
+
* ● White – no face / scanning (score < 0.4)
|
|
21
|
+
* ● Yellow – face detected, confidence building (0.4 ≤ score < threshold)
|
|
22
|
+
* ● Green – liveness confirmed / countdown / capture
|
|
23
|
+
* ● Red – error
|
|
24
|
+
*/
|
|
25
|
+
function getOvalColor(state, score) {
|
|
15
26
|
switch (state) {
|
|
27
|
+
case 'error':
|
|
28
|
+
return '#FF3B30';
|
|
16
29
|
case 'confirmed':
|
|
17
30
|
case 'countdown':
|
|
18
31
|
case 'capturing':
|
|
19
32
|
case 'done':
|
|
20
33
|
return '#4CAF50';
|
|
21
34
|
default:
|
|
22
|
-
return '#FFFFFF';
|
|
35
|
+
return score >= 0.4 ? '#FFD60A' : '#FFFFFF';
|
|
23
36
|
}
|
|
24
37
|
}
|
|
25
38
|
|
|
@@ -34,18 +47,19 @@ function ellipsePath(cx, cy, rx, ry) {
|
|
|
34
47
|
function OvalOverlay({
|
|
35
48
|
width,
|
|
36
49
|
height,
|
|
37
|
-
state
|
|
50
|
+
state,
|
|
51
|
+
score
|
|
38
52
|
}) {
|
|
39
53
|
if (width === 0 || height === 0) return null;
|
|
40
54
|
const cx = width / 2;
|
|
41
|
-
|
|
42
|
-
const
|
|
43
|
-
const
|
|
44
|
-
const
|
|
55
|
+
// Shift centre slightly above midpoint so the face sits naturally in frame
|
|
56
|
+
const cy = height * 0.45;
|
|
57
|
+
const rx = width * OVAL_WIDTH_RATIO / 2;
|
|
58
|
+
const ry = rx * FACE_RATIO;
|
|
59
|
+
const color = getOvalColor(state, score);
|
|
45
60
|
|
|
46
61
|
// Compound path: outer rect + oval. evenodd fill rule makes the oval transparent.
|
|
47
62
|
const scrimD = `M0 0H${width}V${height}H0Z ${ellipsePath(cx, cy, rx, ry)}`;
|
|
48
|
-
const showDot = state === 'confirmed' || state === 'countdown' || state === 'capturing';
|
|
49
63
|
return /*#__PURE__*/_jsxs(Svg, {
|
|
50
64
|
style: StyleSheet.absoluteFill,
|
|
51
65
|
width: width,
|
|
@@ -62,11 +76,6 @@ function OvalOverlay({
|
|
|
62
76
|
fill: "none",
|
|
63
77
|
stroke: color,
|
|
64
78
|
strokeWidth: STROKE_WIDTH
|
|
65
|
-
}), showDot && /*#__PURE__*/_jsx(Circle, {
|
|
66
|
-
cx: cx,
|
|
67
|
-
cy: cy - ry - 8,
|
|
68
|
-
r: 5,
|
|
69
|
-
fill: color
|
|
70
79
|
})]
|
|
71
80
|
});
|
|
72
81
|
}
|
|
@@ -138,6 +147,7 @@ export function LivenessCamera({
|
|
|
138
147
|
const {
|
|
139
148
|
frameProcessor,
|
|
140
149
|
livenessState,
|
|
150
|
+
livenessScore,
|
|
141
151
|
countdown,
|
|
142
152
|
feedback
|
|
143
153
|
} = useLivenessCamera({
|
|
@@ -195,11 +205,13 @@ export function LivenessCamera({
|
|
|
195
205
|
isActive: livenessState !== 'done' && livenessState !== 'error',
|
|
196
206
|
frameProcessor: frameProcessor,
|
|
197
207
|
photo: true,
|
|
198
|
-
pixelFormat: "yuv"
|
|
208
|
+
pixelFormat: "yuv",
|
|
209
|
+
fps: 60
|
|
199
210
|
}), /*#__PURE__*/_jsx(OvalOverlay, {
|
|
200
211
|
width: containerSize.width,
|
|
201
212
|
height: containerSize.height,
|
|
202
|
-
state: livenessState
|
|
213
|
+
state: livenessState,
|
|
214
|
+
score: livenessScore
|
|
203
215
|
}), livenessState !== 'done' && /*#__PURE__*/_jsx(View, {
|
|
204
216
|
style: styles.feedbackContainer,
|
|
205
217
|
children: /*#__PURE__*/_jsx(Text, {
|
|
@@ -235,7 +247,7 @@ const styles = StyleSheet.create({
|
|
|
235
247
|
},
|
|
236
248
|
feedbackContainer: {
|
|
237
249
|
position: 'absolute',
|
|
238
|
-
bottom: '
|
|
250
|
+
bottom: '12%',
|
|
239
251
|
left: 0,
|
|
240
252
|
right: 0,
|
|
241
253
|
alignItems: 'center',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["useCallback","useEffect","useRef","useState","Animated","StyleSheet","Text","View","Camera","useCameraDevice","useCameraPermission","
|
|
1
|
+
{"version":3,"names":["useCallback","useEffect","useRef","useState","Animated","StyleSheet","Text","View","Camera","useCameraDevice","useCameraPermission","Ellipse","Path","Svg","useLivenessCamera","jsx","_jsx","jsxs","_jsxs","OVAL_WIDTH_RATIO","FACE_RATIO","STROKE_WIDTH","K","getOvalColor","state","score","ellipsePath","cx","cy","rx","ry","join","OvalOverlay","width","height","color","scrimD","style","absoluteFill","children","d","fill","fillRule","stroke","strokeWidth","CountdownBubble","value","scale","Value","current","opacity","parallel","sequence","spring","toValue","stiffness","damping","useNativeDriver","timing","duration","start","styles","countdownBubble","transform","countdownText","LivenessCamera","onCapture","onLivenessConfirmed","onError","countdownFrom","livenessThreshold","confirmFrames","soundEnabled","hasPermission","requestPermission","device","cameraRef","containerSize","setContainerSize","frameProcessor","livenessState","livenessScore","countdown","feedback","handleLayout","e","nativeEvent","layout","catch","Error","root","centered","permissionText","onLayout","ref","isActive","photo","pixelFormat","fps","feedbackContainer","feedbackText","countdownContainer","captureFlash","pointerEvents","create","flex","backgroundColor","overflow","justifyContent","alignItems","fontSize","textAlign","paddingHorizontal","position","bottom","left","right","fontWeight","textShadowColor","textShadowOffset","textShadowRadius","absoluteFillObject","borderRadius","borderWidth","borderColor","lineHeight"],"sourceRoot":"../../src","sources":["LivenessCamera.tsx"],"mappings":"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","ignoreList":[]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
4
|
-
import { useFrameProcessor } from 'react-native-vision-camera';
|
|
4
|
+
import { runAtTargetFps, useFrameProcessor } from 'react-native-vision-camera';
|
|
5
5
|
import { Worklets } from 'react-native-worklets-core';
|
|
6
6
|
import { useLivenessPlugin } from "./LivenessDetector.js";
|
|
7
7
|
import { getFeedback, rollingAverage, scoreFrame } from "./livenessScoring.js";
|
|
@@ -146,8 +146,14 @@ export function useLivenessCamera(options) {
|
|
|
146
146
|
const frameProcessor = useFrameProcessor(frame => {
|
|
147
147
|
'worklet';
|
|
148
148
|
|
|
149
|
-
|
|
150
|
-
|
|
149
|
+
// Camera preview renders at full fps (60). ML Kit only needs ~20fps —
|
|
150
|
+
// running it on every frame would block the render thread unnecessarily.
|
|
151
|
+
runAtTargetFps(20, () => {
|
|
152
|
+
'worklet';
|
|
153
|
+
|
|
154
|
+
const face = plugin.detectLiveness(frame);
|
|
155
|
+
handleFaceDataJS(face, frame.width);
|
|
156
|
+
});
|
|
151
157
|
}, [plugin, handleFaceDataJS]);
|
|
152
158
|
useEffect(() => {
|
|
153
159
|
return () => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["useCallback","useEffect","useMemo","useRef","useState","useFrameProcessor","Worklets","useLivenessPlugin","getFeedback","rollingAverage","scoreFrame","WINDOW_SIZE","useLivenessCamera","options","livenessThreshold","confirmFrames","countdownFrom","soundEnabled","cameraRef","onCapture","onLivenessConfirmed","onError","plugin","frameScores","consecutiveGood","frameWidth","stateRef","isCaptured","state","setState","livenessState","livenessScore","countdown","feedback","setLivenessState","next","current","prev","capture","photo","takePhoto","flash","enableShutterSound","score","timestamp","Date","now","err","Error","String","startCountdown","tick","interval","setInterval","clearInterval","handleFaceData","face","width","safeFace","detected","bounds","x","y","height","yawAngle","pitchAngle","rollAngle","leftEyeOpenProbability","rightEyeOpenProbability","smilingProbability","total","push","length","shift","avgScore","isLive","handleFaceDataJS","createRunOnJS","frameProcessor","frame","detectLiveness"],"sourceRoot":"../../src","sources":["useLivenessCamera.ts"],"mappings":";;AAAA,SAASA,WAAW,EAAEC,SAAS,EAAEC,OAAO,EAAEC,MAAM,EAAEC,QAAQ,QAAQ,OAAO;AAEzE,SAASC,iBAAiB,QAAQ,4BAA4B;
|
|
1
|
+
{"version":3,"names":["useCallback","useEffect","useMemo","useRef","useState","runAtTargetFps","useFrameProcessor","Worklets","useLivenessPlugin","getFeedback","rollingAverage","scoreFrame","WINDOW_SIZE","useLivenessCamera","options","livenessThreshold","confirmFrames","countdownFrom","soundEnabled","cameraRef","onCapture","onLivenessConfirmed","onError","plugin","frameScores","consecutiveGood","frameWidth","stateRef","isCaptured","state","setState","livenessState","livenessScore","countdown","feedback","setLivenessState","next","current","prev","capture","photo","takePhoto","flash","enableShutterSound","score","timestamp","Date","now","err","Error","String","startCountdown","tick","interval","setInterval","clearInterval","handleFaceData","face","width","safeFace","detected","bounds","x","y","height","yawAngle","pitchAngle","rollAngle","leftEyeOpenProbability","rightEyeOpenProbability","smilingProbability","total","push","length","shift","avgScore","isLive","handleFaceDataJS","createRunOnJS","frameProcessor","frame","detectLiveness"],"sourceRoot":"../../src","sources":["useLivenessCamera.ts"],"mappings":";;AAAA,SAASA,WAAW,EAAEC,SAAS,EAAEC,OAAO,EAAEC,MAAM,EAAEC,QAAQ,QAAQ,OAAO;AAEzE,SAASC,cAAc,EAAEC,iBAAiB,QAAQ,4BAA4B;AAC9E,SAASC,QAAQ,QAAQ,4BAA4B;AACrD,SAASC,iBAAiB,QAAQ,uBAAoB;AACtD,SAASC,WAAW,EAAEC,cAAc,EAAEC,UAAU,QAAQ,sBAAmB;AAQ3E,MAAMC,WAAW,GAAG,EAAE;AAoBtB,OAAO,SAASC,iBAAiBA,CAACC,OAAgB,EAAE;EAClD,MAAM;IACJC,iBAAiB;IACjBC,aAAa;IACbC,aAAa;IACbC,YAAY;IACZC,SAAS;IACTC,SAAS;IACTC,mBAAmB;IACnBC;EACF,CAAC,GAAGR,OAAO;EAEX,MAAMS,MAAM,GAAGf,iBAAiB,CAAC,CAAC;;EAElC;EACA,MAAMgB,WAAW,GAAGrB,MAAM,CAAW,EAAE,CAAC;EACxC,MAAMsB,eAAe,GAAGtB,MAAM,CAAC,CAAC,CAAC;EACjC,MAAMuB,UAAU,GAAGvB,MAAM,CAAC,CAAC,CAAC;EAC5B,MAAMwB,QAAQ,GAAGxB,MAAM,CAAgB,UAAU,CAAC;EAClD,MAAMyB,UAAU,GAAGzB,MAAM,CAAC,KAAK,CAAC;EAEhC,MAAM,CAAC0B,KAAK,EAAEC,QAAQ,CAAC,GAAG1B,QAAQ,CAAsB;IACtD2B,aAAa,EAAE,UAAU;IACzBC,aAAa,EAAE,CAAC;IAChBC,SAAS,EAAE,IAAI;IACfC,QAAQ,EAAE;EACZ,CAAC,CAAC;EAEF,MAAMC,gBAAgB,GAAGnC,WAAW,CAAEoC,IAAmB,IAAK;IAC5DT,QAAQ,CAACU,OAAO,GAAGD,IAAI;IACvBN,QAAQ,CAAEQ,IAAI,KAAM;MAAE,GAAGA,IAAI;MAAEP,aAAa,EAAEK;IAAK,CAAC,CAAC,CAAC;EACxD,CAAC,EAAE,EAAE,CAAC;;EAEN;;EAEA,MAAMG,OAAO,GAAGvC,WAAW,CAAC,YAAY;IACtC,IAAI4B,UAAU,CAACS,OAAO,IAAI,CAAClB,SAAS,CAACkB,OAAO,EAAE;IAC9CT,UAAU,CAACS,OAAO,GAAG,IAAI;IAEzBF,gBAAgB,CAAC,WAAW,CAAC;IAE7B,IAAI;MACF,MAAMK,KAAK,GAAG,MAAMrB,SAAS,CAACkB,OAAO,CAACI,SAAS,CAAC;QAC9CC,KAAK,EAAE,KAAK;QACZC,kBAAkB,EAAEzB;MACtB,CAAC,CAAC;MACF,MAAM0B,KAAK,GAAGlC,cAAc,CAACc,WAAW,CAACa,OAAO,CAAC;MAEjDF,gBAAgB,CAAC,MAAM,CAAC;MACxBf,SAAS,CAAC;QAAEoB,KAAK;QAAER,aAAa,EAAEY,KAAK;QAAEC,SAAS,EAAEC,IAAI,CAACC,GAAG,CAAC;MAAE,CAAC,CAAC;IACnE,CAAC,CAAC,OAAOC,GAAG,EAAE;MACZb,gBAAgB,CAAC,OAAO,CAAC;MACzBb,OAAO,GAAG0B,GAAG,YAAYC,KAAK,GAAGD,GAAG,GAAG,IAAIC,KAAK,CAACC,MAAM,CAACF,GAAG,CAAC,CAAC,CAAC;IAChE;EACF,CAAC,EAAE,CAAC7B,SAAS,EAAEC,SAAS,EAAEE,OAAO,EAAEJ,YAAY,EAAEiB,gBAAgB,CAAC,CAAC;;EAEnE;;EAEA,MAAMgB,cAAc,GAAGnD,WAAW,CAAC,MAAM;IACvCmC,gBAAgB,CAAC,WAAW,CAAC;IAC7B,IAAIiB,IAAI,GAAGnC,aAAa;IAExBa,QAAQ,CAAEQ,IAAI,KAAM;MAAE,GAAGA,IAAI;MAAEL,SAAS,EAAEmB;IAAK,CAAC,CAAC,CAAC;IAElD,MAAMC,QAAQ,GAAGC,WAAW,CAAC,MAAM;MACjCF,IAAI,IAAI,CAAC;MACT,IAAIA,IAAI,IAAI,CAAC,EAAE;QACbG,aAAa,CAACF,QAAQ,CAAC;QACvBvB,QAAQ,CAAEQ,IAAI,KAAM;UAAE,GAAGA,IAAI;UAAEL,SAAS,EAAE;QAAK,CAAC,CAAC,CAAC;QAClDM,OAAO,CAAC,CAAC;MACX,CAAC,MAAM;QACLT,QAAQ,CAAEQ,IAAI,KAAM;UAAE,GAAGA,IAAI;UAAEL,SAAS,EAAEmB;QAAK,CAAC,CAAC,CAAC;MACpD;IACF,CAAC,EAAE,IAAI,CAAC;IAER,OAAO,MAAMG,aAAa,CAACF,QAAQ,CAAC;EACtC,CAAC,EAAE,CAACd,OAAO,EAAEtB,aAAa,EAAEkB,gBAAgB,CAAC,CAAC;;EAE9C;;EAEA,MAAMqB,cAAc,GAAGxD,WAAW,CAChC,CAACyD,IAAqB,EAAEC,KAAa,KAAK;IACxC,IACE/B,QAAQ,CAACU,OAAO,KAAK,WAAW,IAChCV,QAAQ,CAACU,OAAO,KAAK,MAAM,IAC3BV,QAAQ,CAACU,OAAO,KAAK,OAAO,EAC5B;MACA;IACF;IAEAX,UAAU,CAACW,OAAO,GAAGqB,KAAK;IAE1B,MAAMC,QAAkB,GAAGF,IAAI,IAAI;MACjCG,QAAQ,EAAE,KAAK;MACfC,MAAM,EAAE;QAAEC,CAAC,EAAE,CAAC;QAAEC,CAAC,EAAE,CAAC;QAAEL,KAAK,EAAE,CAAC;QAAEM,MAAM,EAAE;MAAE,CAAC;MAC3CC,QAAQ,EAAE,CAAC;MACXC,UAAU,EAAE,CAAC;MACbC,SAAS,EAAE,CAAC;MACZC,sBAAsB,EAAE,CAAC,CAAC;MAC1BC,uBAAuB,EAAE,CAAC,CAAC;MAC3BC,kBAAkB,EAAE,CAAC;IACvB,CAAC;IAED,MAAM;MAAEC;IAAM,CAAC,GAAG5D,UAAU,CAACgD,QAAQ,EAAED,KAAK,CAAC;IAE7ClC,WAAW,CAACa,OAAO,CAACmC,IAAI,CAACD,KAAK,CAAC;IAC/B,IAAI/C,WAAW,CAACa,OAAO,CAACoC,MAAM,GAAG7D,WAAW,EAAE;MAC5CY,WAAW,CAACa,OAAO,CAACqC,KAAK,CAAC,CAAC;IAC7B;IAEA,MAAMC,QAAQ,GAAGjE,cAAc,CAACc,WAAW,CAACa,OAAO,CAAC;IAEpD,IAAIkC,KAAK,IAAIxD,iBAAiB,EAAE;MAC9BU,eAAe,CAACY,OAAO,IAAI,CAAC;IAC9B,CAAC,MAAM;MACLZ,eAAe,CAACY,OAAO,GAAG,CAAC;IAC7B;IAEA,MAAMuC,MAAM,GACVnD,eAAe,CAACY,OAAO,IAAIrB,aAAa,IACxC2D,QAAQ,IAAI5D,iBAAiB;IAE/B,MAAMmB,QAAQ,GAAGzB,WAAW,CAACkD,QAAQ,EAAED,KAAK,EAAEkB,MAAM,CAAC;IAErD9C,QAAQ,CAAEQ,IAAI,KAAM;MAAE,GAAGA,IAAI;MAAEN,aAAa,EAAE2C,QAAQ;MAAEzC;IAAS,CAAC,CAAC,CAAC;IAEpE,IAAI0C,MAAM,IAAIjD,QAAQ,CAACU,OAAO,KAAK,UAAU,EAAE;MAC7CF,gBAAgB,CAAC,WAAW,CAAC;MAC7Bd,mBAAmB,GAAG,CAAC;MACvB8B,cAAc,CAAC,CAAC;IAClB;EACF,CAAC,EACD,CACEnC,aAAa,EACbD,iBAAiB,EACjBM,mBAAmB,EACnBc,gBAAgB,EAChBgB,cAAc,CAElB,CAAC;;EAED;;EAEA,MAAM0B,gBAAgB,GAAG3E,OAAO,CAC9B,MAAMK,QAAQ,CAACuE,aAAa,CAACtB,cAAc,CAAC,EAC5C,CAACA,cAAc,CACjB,CAAC;EAED,MAAMuB,cAAc,GAAGzE,iBAAiB,CACrC0E,KAAY,IAAK;IAChB,SAAS;;IACT;IACA;IACA3E,cAAc,CAAC,EAAE,EAAE,MAAM;MACvB,SAAS;;MACT,MAAMoD,IAAI,GAAGlC,MAAM,CAAC0D,cAAc,CAACD,KAAK,CAAC;MACzCH,gBAAgB,CAACpB,IAAI,EAAEuB,KAAK,CAACtB,KAAK,CAAC;IACrC,CAAC,CAAC;EACJ,CAAC,EACD,CAACnC,MAAM,EAAEsD,gBAAgB,CAC3B,CAAC;EAED5E,SAAS,CAAC,MAAM;IACd,OAAO,MAAM;MACXuB,WAAW,CAACa,OAAO,GAAG,EAAE;MACxBZ,eAAe,CAACY,OAAO,GAAG,CAAC;MAC3BT,UAAU,CAACS,OAAO,GAAG,KAAK;IAC5B,CAAC;EACH,CAAC,EAAE,EAAE,CAAC;EAEN,OAAO;IACL0C,cAAc;IACdhD,aAAa,EAAEF,KAAK,CAACE,aAAa;IAClCC,aAAa,EAAEH,KAAK,CAACG,aAAa;IAClCC,SAAS,EAAEJ,KAAK,CAACI,SAAS;IAC1BC,QAAQ,EAAEL,KAAK,CAACK;EAClB,CAAC;AACH","ignoreList":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LivenessCamera.d.ts","sourceRoot":"","sources":["../../../src/LivenessCamera.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,mBAAmB,EAAiB,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"LivenessCamera.d.ts","sourceRoot":"","sources":["../../../src/LivenessCamera.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,mBAAmB,EAAiB,MAAM,SAAS,CAAC;AAsIlE,wBAAgB,cAAc,CAAC,EAC7B,SAAS,EACT,mBAAmB,EACnB,OAAO,EACP,aAAiB,EACjB,iBAAwB,EACxB,aAAkB,EAClB,YAAmB,EACnB,KAAK,GACN,EAAE,mBAAmB,2CAmFrB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useLivenessCamera.d.ts","sourceRoot":"","sources":["../../../src/useLivenessCamera.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAS,MAAM,4BAA4B,CAAC;AAKhE,OAAO,KAAK,EACV,aAAa,EAEb,eAAe,EACf,aAAa,EACd,MAAM,SAAS,CAAC;AAIjB,KAAK,OAAO,GAAG;IACb,iBAAiB,EAAE,MAAM,CAAC;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,OAAO,CAAC;IACtB,SAAS,EAAE,KAAK,CAAC,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC1C,SAAS,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,IAAI,CAAC;IAC3C,mBAAmB,CAAC,EAAE,MAAM,IAAI,CAAC;IACjC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC,CAAC;AASF,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO;;;;;;
|
|
1
|
+
{"version":3,"file":"useLivenessCamera.d.ts","sourceRoot":"","sources":["../../../src/useLivenessCamera.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAS,MAAM,4BAA4B,CAAC;AAKhE,OAAO,KAAK,EACV,aAAa,EAEb,eAAe,EACf,aAAa,EACd,MAAM,SAAS,CAAC;AAIjB,KAAK,OAAO,GAAG;IACb,iBAAiB,EAAE,MAAM,CAAC;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,OAAO,CAAC;IACtB,SAAS,EAAE,KAAK,CAAC,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC1C,SAAS,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,IAAI,CAAC;IAC3C,mBAAmB,CAAC,EAAE,MAAM,IAAI,CAAC;IACjC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC,CAAC;AASF,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO;;;;;;EAiLjD"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rick427/react-native-liveness",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "Liveness detection library for React Native using Vision Camera v4 and ML Kit",
|
|
5
5
|
"main": "./lib/module/index.js",
|
|
6
6
|
"types": "./lib/typescript/src/index.d.ts",
|
package/src/LivenessCamera.tsx
CHANGED
|
@@ -5,25 +5,37 @@ import {
|
|
|
5
5
|
useCameraDevice,
|
|
6
6
|
useCameraPermission,
|
|
7
7
|
} from 'react-native-vision-camera';
|
|
8
|
-
import {
|
|
8
|
+
import { Ellipse, Path, Svg } from 'react-native-svg';
|
|
9
9
|
import { useLivenessCamera } from './useLivenessCamera';
|
|
10
10
|
import type { LivenessCameraProps, LivenessState } from './types';
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
// Oval is sized relative to container WIDTH only, so it stays face-shaped
|
|
13
|
+
// on any screen. ry = rx * FACE_RATIO gives a natural portrait face oval.
|
|
14
|
+
const OVAL_WIDTH_RATIO = 0.72; // oval width = 72 % of container width
|
|
15
|
+
const FACE_RATIO = 1.35; // height-to-width ratio of the oval (~3:4 face)
|
|
14
16
|
const STROKE_WIDTH = 3;
|
|
15
17
|
// Cubic bezier approximation constant for a smooth ellipse
|
|
16
18
|
const K = 0.5523;
|
|
17
19
|
|
|
18
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Returns the stroke colour for the oval guide.
|
|
22
|
+
*
|
|
23
|
+
* ● White – no face / scanning (score < 0.4)
|
|
24
|
+
* ● Yellow – face detected, confidence building (0.4 ≤ score < threshold)
|
|
25
|
+
* ● Green – liveness confirmed / countdown / capture
|
|
26
|
+
* ● Red – error
|
|
27
|
+
*/
|
|
28
|
+
function getOvalColor(state: LivenessState, score: number): string {
|
|
19
29
|
switch (state) {
|
|
30
|
+
case 'error':
|
|
31
|
+
return '#FF3B30';
|
|
20
32
|
case 'confirmed':
|
|
21
33
|
case 'countdown':
|
|
22
34
|
case 'capturing':
|
|
23
35
|
case 'done':
|
|
24
36
|
return '#4CAF50';
|
|
25
37
|
default:
|
|
26
|
-
return '#FFFFFF';
|
|
38
|
+
return score >= 0.4 ? '#FFD60A' : '#FFFFFF';
|
|
27
39
|
}
|
|
28
40
|
}
|
|
29
41
|
|
|
@@ -47,25 +59,25 @@ function OvalOverlay({
|
|
|
47
59
|
width,
|
|
48
60
|
height,
|
|
49
61
|
state,
|
|
62
|
+
score,
|
|
50
63
|
}: {
|
|
51
64
|
width: number;
|
|
52
65
|
height: number;
|
|
53
66
|
state: LivenessState;
|
|
67
|
+
score: number;
|
|
54
68
|
}) {
|
|
55
69
|
if (width === 0 || height === 0) return null;
|
|
56
70
|
|
|
57
71
|
const cx = width / 2;
|
|
58
|
-
|
|
59
|
-
const
|
|
60
|
-
const
|
|
61
|
-
const
|
|
72
|
+
// Shift centre slightly above midpoint so the face sits naturally in frame
|
|
73
|
+
const cy = height * 0.45;
|
|
74
|
+
const rx = (width * OVAL_WIDTH_RATIO) / 2;
|
|
75
|
+
const ry = rx * FACE_RATIO;
|
|
76
|
+
const color = getOvalColor(state, score);
|
|
62
77
|
|
|
63
78
|
// Compound path: outer rect + oval. evenodd fill rule makes the oval transparent.
|
|
64
79
|
const scrimD = `M0 0H${width}V${height}H0Z ${ellipsePath(cx, cy, rx, ry)}`;
|
|
65
80
|
|
|
66
|
-
const showDot =
|
|
67
|
-
state === 'confirmed' || state === 'countdown' || state === 'capturing';
|
|
68
|
-
|
|
69
81
|
return (
|
|
70
82
|
<Svg style={StyleSheet.absoluteFill} width={width} height={height}>
|
|
71
83
|
<Path d={scrimD} fill="rgba(0,0,0,0.55)" fillRule="evenodd" />
|
|
@@ -78,7 +90,6 @@ function OvalOverlay({
|
|
|
78
90
|
stroke={color}
|
|
79
91
|
strokeWidth={STROKE_WIDTH}
|
|
80
92
|
/>
|
|
81
|
-
{showDot && <Circle cx={cx} cy={cy - ry - 8} r={5} fill={color} />}
|
|
82
93
|
</Svg>
|
|
83
94
|
);
|
|
84
95
|
}
|
|
@@ -145,7 +156,7 @@ export function LivenessCamera({
|
|
|
145
156
|
const cameraRef = useRef<Camera>(null);
|
|
146
157
|
const [containerSize, setContainerSize] = useState({ width: 0, height: 0 });
|
|
147
158
|
|
|
148
|
-
const { frameProcessor, livenessState, countdown, feedback } =
|
|
159
|
+
const { frameProcessor, livenessState, livenessScore, countdown, feedback } =
|
|
149
160
|
useLivenessCamera({
|
|
150
161
|
livenessThreshold,
|
|
151
162
|
confirmFrames,
|
|
@@ -199,11 +210,13 @@ export function LivenessCamera({
|
|
|
199
210
|
frameProcessor={frameProcessor}
|
|
200
211
|
photo
|
|
201
212
|
pixelFormat="yuv"
|
|
213
|
+
fps={60}
|
|
202
214
|
/>
|
|
203
215
|
<OvalOverlay
|
|
204
216
|
width={containerSize.width}
|
|
205
217
|
height={containerSize.height}
|
|
206
218
|
state={livenessState}
|
|
219
|
+
score={livenessScore}
|
|
207
220
|
/>
|
|
208
221
|
{livenessState !== 'done' && (
|
|
209
222
|
<View style={styles.feedbackContainer}>
|
|
@@ -240,7 +253,7 @@ const styles = StyleSheet.create({
|
|
|
240
253
|
},
|
|
241
254
|
feedbackContainer: {
|
|
242
255
|
position: 'absolute',
|
|
243
|
-
bottom: '
|
|
256
|
+
bottom: '12%',
|
|
244
257
|
left: 0,
|
|
245
258
|
right: 0,
|
|
246
259
|
alignItems: 'center',
|
package/src/useLivenessCamera.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
2
|
import type { Camera, Frame } from 'react-native-vision-camera';
|
|
3
|
-
import { useFrameProcessor } from 'react-native-vision-camera';
|
|
3
|
+
import { runAtTargetFps, useFrameProcessor } from 'react-native-vision-camera';
|
|
4
4
|
import { Worklets } from 'react-native-worklets-core';
|
|
5
5
|
import { useLivenessPlugin } from './LivenessDetector';
|
|
6
6
|
import { getFeedback, rollingAverage, scoreFrame } from './livenessScoring';
|
|
@@ -182,8 +182,13 @@ export function useLivenessCamera(options: Options) {
|
|
|
182
182
|
const frameProcessor = useFrameProcessor(
|
|
183
183
|
(frame: Frame) => {
|
|
184
184
|
'worklet';
|
|
185
|
-
|
|
186
|
-
|
|
185
|
+
// Camera preview renders at full fps (60). ML Kit only needs ~20fps —
|
|
186
|
+
// running it on every frame would block the render thread unnecessarily.
|
|
187
|
+
runAtTargetFps(20, () => {
|
|
188
|
+
'worklet';
|
|
189
|
+
const face = plugin.detectLiveness(frame);
|
|
190
|
+
handleFaceDataJS(face, frame.width);
|
|
191
|
+
});
|
|
187
192
|
},
|
|
188
193
|
[plugin, handleFaceDataJS]
|
|
189
194
|
);
|