diff --git a/src/data/handTrackingConfig.ts b/src/data/handTrackingConfig.ts index 6027d12..2b28dda 100644 --- a/src/data/handTrackingConfig.ts +++ b/src/data/handTrackingConfig.ts @@ -9,3 +9,8 @@ export const HAND_TRACKING_BROWSER_WASM_URL = export const HAND_TRACKING_BROWSER_MODEL_URL = "https://storage.googleapis.com/mediapipe-models/hand_landmarker/hand_landmarker/float16/1/hand_landmarker.task"; export const HAND_TRACKING_BROWSER_DELEGATE: "CPU" | "GPU" = "CPU"; + +// Delay before the runtime actually starts after `enabled` flips to true. +// Absorbs React StrictMode's mount/unmount/mount cycle in dev and rapid +// `nearby` toggles at trigger borders. Invisible to the user (~5 frames). +export const HAND_TRACKING_RUNTIME_START_DELAY_MS = 80; diff --git a/src/hooks/handTracking/useBrowserHandTracking.ts b/src/hooks/handTracking/useBrowserHandTracking.ts index 7310f99..285fa2f 100644 --- a/src/hooks/handTracking/useBrowserHandTracking.ts +++ b/src/hooks/handTracking/useBrowserHandTracking.ts @@ -2,6 +2,7 @@ import { useEffect, useRef, useState } from "react"; import { HAND_TRACKING_FRAME_HEIGHT, HAND_TRACKING_FRAME_WIDTH, + HAND_TRACKING_RUNTIME_START_DELAY_MS, HAND_TRACKING_TARGET_FPS, } from "@/data/handTrackingConfig"; import { @@ -169,10 +170,17 @@ export function useBrowserHandTracking({ } }; - void start(); + // Delay the actual start so that a StrictMode mount/unmount/mount + // cycle, or a rapid `enabled` toggle at a trigger border, does not + // spin up the camera + MediaPipe twice in a few milliseconds. + const startTimer = window.setTimeout(() => { + if (cancelled) return; + void start(); + }, HAND_TRACKING_RUNTIME_START_DELAY_MS); return () => { cancelled = true; + window.clearTimeout(startTimer); cleanup(); }; }, [enabled]); diff --git a/src/hooks/handTracking/useRemoteHandTracking.ts b/src/hooks/handTracking/useRemoteHandTracking.ts index e9dc1db..fafa568 100644 --- a/src/hooks/handTracking/useRemoteHandTracking.ts +++ b/src/hooks/handTracking/useRemoteHandTracking.ts @@ -4,6 +4,7 @@ import { HAND_TRACKING_FRAME_WIDTH, HAND_TRACKING_JPEG_QUALITY, HAND_TRACKING_RESPONSE_TIMEOUT_MS, + HAND_TRACKING_RUNTIME_START_DELAY_MS, HAND_TRACKING_TARGET_FPS, } from "@/data/handTrackingConfig"; import { getHandTrackingWsUrl } from "@/utils/handTracking/handTrackingEndpoint"; @@ -330,10 +331,17 @@ export function useRemoteHandTracking({ } }; - void start(); + // Delay the actual start so that a StrictMode mount/unmount/mount + // cycle, or a rapid `enabled` toggle at a trigger border, does not + // open the camera + WebSocket twice in a few milliseconds. + const startTimer = window.setTimeout(() => { + if (cancelled) return; + void start(); + }, HAND_TRACKING_RUNTIME_START_DELAY_MS); return () => { cancelled = true; + window.clearTimeout(startTimer); cleanup(); }; }, [enabled, websocketUrl]);