diff --git a/client/src/pages/LiveCamera.tsx b/client/src/pages/LiveCamera.tsx index 04f6940..57ea044 100644 --- a/client/src/pages/LiveCamera.tsx +++ b/client/src/pages/LiveCamera.tsx @@ -33,6 +33,7 @@ export default function LiveCamera() { const streamRef = useRef(null); const poseRef = useRef(null); const animFrameRef = useRef(0); + const analyzingRef = useRef(false); const [cameraActive, setCameraActive] = useState(false); const [facing, setFacing] = useState("environment"); @@ -108,6 +109,7 @@ export default function LiveCamera() { }, [facing]); const stopCamera = useCallback(() => { + analyzingRef.current = false; if (streamRef.current) { streamRef.current.getTracks().forEach(t => t.stop()); streamRef.current = null; @@ -115,9 +117,12 @@ export default function LiveCamera() { if (animFrameRef.current) { cancelAnimationFrame(animFrameRef.current); } + poseRef.current?.close?.(); + poseRef.current = null; setCameraActive(false); setAnalyzing(false); setLiveScore(null); + setFeedback([]); }, []); const switchCamera = useCallback(() => { @@ -131,17 +136,29 @@ export default function LiveCamera() { // Start pose analysis const startAnalysis = useCallback(async () => { - if (!videoRef.current || !canvasRef.current) return; + if (!videoRef.current || !canvasRef.current || !cameraActive) { + toast.error("请先启动摄像头"); + return; + } + if (analyzingRef.current) return; + analyzingRef.current = true; setAnalyzing(true); toast.info("正在加载姿势识别模型..."); try { - const { Pose } = await import("@mediapipe/pose"); - const { drawConnectors, drawLandmarks } = await import("@mediapipe/drawing_utils"); + const testFactory = ( + window as typeof window & { + __TEST_MEDIAPIPE_FACTORY__?: () => Promise<{ Pose: any }>; + } + ).__TEST_MEDIAPIPE_FACTORY__; + const { Pose } = testFactory + ? await testFactory() + : await import("@mediapipe/pose"); + const allowSyntheticFrames = Boolean(testFactory); const pose = new Pose({ - locateFile: (file) => `https://cdn.jsdelivr.net/npm/@mediapipe/pose/${file}`, + locateFile: (file: string) => `https://cdn.jsdelivr.net/npm/@mediapipe/pose/${file}`, }); pose.setOptions({ @@ -216,8 +233,8 @@ export default function LiveCamera() { poseRef.current = pose; const processFrame = async () => { - if (!videoRef.current || !analyzing) return; - if (videoRef.current.readyState >= 2) { + if (!videoRef.current || !analyzingRef.current) return; + if (allowSyntheticFrames || videoRef.current.readyState >= 2) { await pose.send({ image: videoRef.current }); } animFrameRef.current = requestAnimationFrame(processFrame); @@ -228,16 +245,21 @@ export default function LiveCamera() { } catch (err) { console.error("Pose init error:", err); toast.error("姿势识别模型加载失败"); + analyzingRef.current = false; setAnalyzing(false); } - }, [analyzing, exerciseType]); + }, [cameraActive, exerciseType]); const stopAnalysis = useCallback(() => { + analyzingRef.current = false; if (animFrameRef.current) { cancelAnimationFrame(animFrameRef.current); } + poseRef.current?.close?.(); + poseRef.current = null; setAnalyzing(false); setLiveScore(null); + setFeedback([]); }, []); // Cleanup on unmount @@ -328,14 +350,14 @@ export default function LiveCamera() {