import { useCallback, useRef, useEffect, useState } from 'react'; import { useRunOnJS } from 'react-native-worklets-core'; import type { Frame } from 'react-native-vision-camera'; import { detectFaces } from '../detection/faceDetector'; import type { Face, FaceDetectionOptions, CameraFacing } from '../types'; /** * Face detection options for useFaceDetector hook * Matches the API of react-native-vision-camera-face-detector */ export interface UseFaceDetectorOptions extends FaceDetectionOptions { /** * Current active camera * @default 'front' */ cameraFacing?: CameraFacing; /** * Should handle auto scale on native side? * If disabled, results are relative to frame coordinates, not screen/preview. * Don't use this if you want to draw with Skia Frame Processor. * @default false */ autoMode?: boolean; /** * Screen width for coordinate scaling (required when autoMode is true) * @default 1.0 */ windowWidth?: number; /** * Screen height for coordinate scaling (required when autoMode is true) * @default 1.0 */ windowHeight?: number; } /** * Return type for useFaceDetector hook */ export interface UseFaceDetectorResult { /** * Detect faces in a frame (for use in frame processor) */ detectFaces: (frame: Frame) => Face[]; } /** * Hook for face detection in VisionCamera frame processors. * * This hook provides a `detectFaces` function that can be used directly * in a frame processor to detect faces in each frame. * * @param options - Face detection options * @returns Object with detectFaces function * * @example * ```tsx * import { useFaceDetector } from '@arfuhad/react-native-smart-camera'; * import { useFrameProcessor } from 'react-native-vision-camera'; * import Worklets from 'react-native-worklets-core'; * * function FaceDetectionCamera() { * const { detectFaces } = useFaceDetector({ * performanceMode: 'fast', * classificationMode: 'all', // Required for blink detection * }); * * const handleFaces = Worklets.createRunOnJS((faces: Face[]) => { * console.log('Detected faces:', faces.length); * // Check for blinks using leftEyeOpenProbability / rightEyeOpenProbability * }); * * const frameProcessor = useFrameProcessor((frame) => { * 'worklet'; * const faces = detectFaces(frame); * handleFaces(faces); * }, [detectFaces, handleFaces]); * * return ; * } * ``` */ export function useFaceDetector(options: UseFaceDetectorOptions = {}): UseFaceDetectorResult { const optionsRef = useRef(options); // Keep options ref updated useEffect(() => { optionsRef.current = options; }, [options]); // Create detect function that uses current options const detect = useCallback((frame: Frame): Face[] => { 'worklet'; return detectFaces(frame, { performanceMode: optionsRef.current.performanceMode, landmarkMode: optionsRef.current.landmarkMode, contourMode: optionsRef.current.contourMode, classificationMode: optionsRef.current.classificationMode, minFaceSize: optionsRef.current.minFaceSize, trackingEnabled: optionsRef.current.trackingEnabled, cameraFacing: optionsRef.current.cameraFacing, autoMode: optionsRef.current.autoMode, windowWidth: optionsRef.current.windowWidth, windowHeight: optionsRef.current.windowHeight, }); }, []); return { detectFaces: detect, }; } /** * Callback type for face detection */ export type FaceDetectionCallback = (faces: Face[]) => void; /** * Options for useFaceDetectorWithCallback hook */ export interface UseFaceDetectorWithCallbackOptions extends UseFaceDetectorOptions { /** * Callback when faces are detected */ onFacesDetected?: FaceDetectionCallback; } /** * Hook for face detection with automatic callback handling * * This is a convenience hook that wraps useFaceDetector and automatically * calls your callback function when faces are detected. * * @param options - Face detection options with callback * @returns Object with detectFaces function for use in frame processor * * @example * ```tsx * import { useFaceDetectorWithCallback } from '@arfuhad/react-native-smart-camera'; * * function FaceDetectionCamera() { * const [faces, setFaces] = useState([]); * * const { detectFaces } = useFaceDetectorWithCallback({ * classificationMode: 'all', * onFacesDetected: (detectedFaces) => { * setFaces(detectedFaces); * }, * }); * * const frameProcessor = useFrameProcessor((frame) => { * 'worklet'; * detectFaces(frame); // Automatically calls onFacesDetected * }, [detectFaces]); * * return ; * } * ``` */ export function useFaceDetectorWithCallback( options: UseFaceDetectorWithCallbackOptions = {} ): UseFaceDetectorResult { const { onFacesDetected, ...detectorOptions } = options; const { detectFaces: baseDet } = useFaceDetector(detectorOptions); const callbackRef = useRef(onFacesDetected); useEffect(() => { callbackRef.current = onFacesDetected; }, [onFacesDetected]); // Create runOnJS callback const handleFacesDetected = useRunOnJS((faces: Face[]) => { callbackRef.current?.(faces); }, []); const detectWithCallback = useCallback((frame: Frame): Face[] => { 'worklet'; const faces = baseDet(frame); if (faces.length > 0 || callbackRef.current) { handleFacesDetected(faces); } return faces; }, [baseDet, handleFacesDetected]); return { detectFaces: detectWithCallback, }; }