| | import { useState, useEffect, useRef } from 'react'; |
| | import { HandLandmarker, FilesetResolver } from '@mediapipe/tasks-vision'; |
| | import { drawLandmarks, analyzeHandGesture } from '../utils/handUtils'; |
| |
|
| | const useHandDetection = (videoRef, canvasRef, isMobile) => { |
| | const [handLandmarker, setHandLandmarker] = useState(null); |
| | const [handDetected, setHandDetected] = useState(false); |
| | const [isMouthOpen, setIsMouthOpen] = useState(false); |
| | const [isLeftHand, setIsLeftHand] = useState(true); |
| | const [thumbPosition, setThumbPosition] = useState({ x: 0, y: 0 }); |
| | const [isFirstLoad, setIsFirstLoad] = useState(true); |
| | |
| | const requestRef = useRef(null); |
| | const lastDetectionTimeRef = useRef(0); |
| | const isComponentMounted = useRef(true); |
| | |
| | |
| | useEffect(() => { |
| | isComponentMounted.current = true; |
| | |
| | const initializeHandLandmarker = async () => { |
| | try { |
| | const vision = await FilesetResolver.forVisionTasks( |
| | "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest/wasm" |
| | ); |
| | |
| | if (!isComponentMounted.current) return; |
| | |
| | const landmarker = await HandLandmarker.createFromOptions(vision, { |
| | baseOptions: { |
| | modelAssetPath: "https://storage.googleapis.com/mediapipe-models/hand_landmarker/hand_landmarker/float16/latest/hand_landmarker.task", |
| | delegate: "GPU" |
| | }, |
| | runningMode: "VIDEO", |
| | numHands: 1, |
| | minHandDetectionConfidence: 0.5, |
| | minHandPresenceConfidence: 0.5, |
| | minTrackingConfidence: 0.5 |
| | }); |
| | |
| | if (!isComponentMounted.current) return; |
| | |
| | setHandLandmarker(landmarker); |
| | console.log("Hand landmarker initialized successfully"); |
| | |
| | |
| | setTimeout(() => { |
| | if (isComponentMounted.current) { |
| | setIsFirstLoad(false); |
| | } |
| | }, 3000); |
| | } catch (error) { |
| | console.error("Error initializing hand landmarker:", error); |
| | } |
| | }; |
| |
|
| | initializeHandLandmarker(); |
| |
|
| | return () => { |
| | isComponentMounted.current = false; |
| | if (requestRef.current) { |
| | cancelAnimationFrame(requestRef.current); |
| | requestRef.current = null; |
| | } |
| | }; |
| | }, []); |
| |
|
| | |
| | useEffect(() => { |
| | if (!handLandmarker || !videoRef.current || !canvasRef.current) return; |
| |
|
| | const video = videoRef.current; |
| | const canvas = canvasRef.current; |
| | const ctx = canvas.getContext('2d'); |
| |
|
| | const detectHands = async (now) => { |
| | if (!isComponentMounted.current) return; |
| | |
| | if (video.readyState < 2) { |
| | requestRef.current = requestAnimationFrame(detectHands); |
| | return; |
| | } |
| |
|
| | |
| | if (now - lastDetectionTimeRef.current > 100) { |
| | lastDetectionTimeRef.current = now; |
| | |
| | |
| | const results = handLandmarker.detectForVideo(video, now); |
| | |
| | |
| | ctx.clearRect(0, 0, canvas.width, canvas.height); |
| | |
| | |
| | const videoWidth = video.videoWidth; |
| | const videoHeight = video.videoHeight; |
| | |
| | |
| | let drawWidth = canvas.width; |
| | let drawHeight = canvas.height; |
| | let offsetX = 0; |
| | let offsetY = 0; |
| | |
| | |
| | ctx.drawImage(video, offsetX, offsetY, drawWidth, drawHeight); |
| | |
| | |
| | if (results.landmarks && results.landmarks.length > 0) { |
| | const landmarks = results.landmarks[0]; |
| | setHandDetected(true); |
| | |
| | |
| | drawLandmarks(ctx, landmarks, canvas, isMobile); |
| | |
| | |
| | const { isOpen, isLeftHand: isLeft, thumbPosition: thumbPos } = analyzeHandGesture(landmarks); |
| | |
| | |
| | setIsLeftHand(isLeft); |
| | setThumbPosition({ |
| | x: thumbPos.x * canvas.width, |
| | y: thumbPos.y * canvas.height |
| | }); |
| | |
| | |
| | if (isOpen !== isMouthOpen) { |
| | setIsMouthOpen(isOpen); |
| | } |
| | } else { |
| | |
| | setHandDetected(false); |
| | if (isMouthOpen) { |
| | setIsMouthOpen(false); |
| | } |
| | } |
| | } |
| | |
| | requestRef.current = requestAnimationFrame(detectHands); |
| | }; |
| |
|
| | requestRef.current = requestAnimationFrame(detectHands); |
| |
|
| | return () => { |
| | if (requestRef.current) { |
| | cancelAnimationFrame(requestRef.current); |
| | requestRef.current = null; |
| | } |
| | }; |
| | }, [handLandmarker, isMouthOpen, isMobile, videoRef, canvasRef]); |
| |
|
| | return { |
| | handDetected, |
| | isMouthOpen, |
| | isLeftHand, |
| | thumbPosition, |
| | isFirstLoad, |
| | isComponentMounted |
| | }; |
| | }; |
| |
|
| | export default useHandDetection; |