React Native
@meeeetup/camera-react-native runs detection on the native thread through a Vision Camera frame processor backed by ML Kit. There is no JavaScript inference and no per-frame JPEG encode, so the pipeline costs a few milliseconds per frame on mid-range Android hardware.
pnpm add @meeeetup/camera-react-native
Peer dependencies, all of which you install yourself:
| Package | Minimum |
|---|---|
react-native-vision-camera |
4.0.0 |
react-native-vision-camera-face-detector |
1.10.0 |
react-native-worklets-core |
1.5.0 |
react-native-fs |
2.20.0 |
Passive capture
import { useRef } from "react";
import { Camera, useCameraDevice } from "react-native-vision-camera";
import { useFaceCaptureSession } from "@meeeetup/camera-react-native";
export function Kiosk({ width, height }: { width: number; height: number }) {
const camera = useRef<Camera>(null);
const device = useCameraDevice("front");
const conf = {
cameraRef: camera,
sessionType: "passive",
minFaceHeight: 0.18,
minFrontalness: 55,
captureWindowMs: 2_000,
cooldownMs: 30_000,
detectionFps: 15,
windowWidth: width,
windowHeight: height,
cameraFacing: "front",
onBatchCapture: async (faces) => {
await upload(faces);
},
} as const;
const { frameProcessor, trackedCount, ready, error } = useFaceCaptureSession(conf);
if (!device) return null;
return (
<Camera
ref={camera}
device={device}
isActive={ready && !error}
photo
frameProcessor={frameProcessor}
style={{ flex: 1 }}
/>
);
}
Every capture gate from Configuration applies here unchanged — the hook forwards them straight to the same FaceCaptureSession the web SDK uses, so a deployment can tune thresholds once and use them on both platforms.
Native-only options
| Option | Default | What it does |
|---|---|---|
cameraRef |
required | Ref to the Vision Camera <Camera>, used to snapshot the JPEG the tracker crops from. |
detectionFps |
15 |
Frame-processor throttle. Vision Camera 4 removed frameProcessorFps, so throttling lives inside the worklet. |
photoCadenceMs |
1000 |
How often the cached snapshot is refreshed. Lower means committed crops match the peak frame more closely, at more GPU cost. |
snapshotQuality |
60 |
JPEG quality of that snapshot. |
windowWidth / windowHeight |
— | Preview size in px. Pass these. They switch the detector into autoMode, which rotates, mirrors and scales landmarks into window coordinates so your overlays line up with the preview. |
cameraFacing |
"front" |
Required for correct mirroring in autoMode. |
What the hook returns
| Field | Meaning |
|---|---|
frameProcessor |
Attach to <Camera frameProcessor={…} />. |
ready / error |
Session status. |
trackedCount |
Faces currently tracked. |
lastDetections |
Normalised detections for drawing your own overlay. |
lastSnapshot |
file:// URI of the cached preview snapshot, plus its size and timestamp. |
lastTimings |
Per-stage latency — native detect time and the worklet-to-JS hop. |
flushBatch() |
Send buffered faces now; call it when the app backgrounds. |
Nose landmark
ML Kit exposes noseBase, not noseTip. The hook sets noseLandmark: "noseBase" on the session for you, which shifts the vertical-tilt reference so scores stay comparable with MediaPipe on web. That is also why UseFaceCaptureSessionOptions omits noseLandmark — setting it by hand would break cross-platform parity.
Android build notes
- The frame processor needs worklets configured in
babel.config.js(react-native-worklets-core/plugin). - Camera permission must be granted before the
<Camera>mounts; Vision Camera will not retry on its own. takeSnapshotis Android-only in Vision Camera 4. On iOS the hook falls back totakePhoto(), which is heavier — raisephotoCadenceMsthere if you see frame drops.