import { useState, useCallback, useRef, useEffect } from 'react'; import type { Face, EyeStatusResult, UseBlinkDetectionResult } from '../types'; /** * Options for useBlinkDetection hook */ export interface UseBlinkDetectionOptions { /** Whether eye tracking is enabled. Default: true */ enabled?: boolean; /** Threshold below which an eye is considered closed (0-1). Default: 0.5 */ eyeClosedThreshold?: number; /** Callback when eye status changes */ onEyeStatusChange?: (status: EyeStatusResult) => void; } /** * Hook for tracking eye status from detected faces * * This hook provides real-time eye open/closed status based on face detection results. * The user can set their own threshold for determining when an eye is considered closed. * * @param options - Eye tracking options * @returns Eye status and controls * * @example * ```tsx * function EyeTracker() { * const { eyeStatus, processEyeStatus } = useBlinkDetection({ * eyeClosedThreshold: 0.3, // Consider eye closed when probability < 0.3 * onEyeStatusChange: (status) => { * // Handle blink detection yourself based on status * if (status.leftEye.isClosed && status.rightEye.isClosed) { * console.log('Both eyes closed!'); * } * }, * }); * * // In your face detection callback: * const handleFaces = (faces: Face[]) => { * processEyeStatus(faces); * }; * * return ( * * Left Eye: {eyeStatus?.leftEye.openProbability.toFixed(2)} * Right Eye: {eyeStatus?.rightEye.openProbability.toFixed(2)} * * ); * } * ``` */ export function useBlinkDetection( options: UseBlinkDetectionOptions = {} ): UseBlinkDetectionResult { const { enabled = true, eyeClosedThreshold = 0.5, onEyeStatusChange } = options; const [eyeStatus, setEyeStatus] = useState(null); const callbackRef = useRef(onEyeStatusChange); // Keep callback ref updated useEffect(() => { callbackRef.current = onEyeStatusChange; }, [onEyeStatusChange]); // Process faces to get eye status (call this from JS thread with detected faces) const processEyeStatus = useCallback((faces: Face[]) => { if (!enabled || faces.length === 0) { return; } // Use the first face (most prominent) const face = faces[0]; // Ensure we have eye classification data if ( face.leftEyeOpenProbability === undefined || face.rightEyeOpenProbability === undefined ) { return; } const leftOpenProbability = face.leftEyeOpenProbability; const rightOpenProbability = face.rightEyeOpenProbability; const now = Date.now(); const status: EyeStatusResult = { leftEye: { openProbability: leftOpenProbability, isClosed: leftOpenProbability < eyeClosedThreshold, }, rightEye: { openProbability: rightOpenProbability, isClosed: rightOpenProbability < eyeClosedThreshold, }, faceId: face.trackingId, timestamp: now, }; setEyeStatus(status); callbackRef.current?.(status); }, [enabled, eyeClosedThreshold]); // Reset eye status const reset = useCallback(() => { setEyeStatus(null); }, []); // Reset on disable useEffect(() => { if (!enabled) { reset(); } }, [enabled, reset]); return { eyeStatus, processEyeStatus, reset, }; }