/** * Stream frames from the user's webcam to the simulator's ESP32-CAM * peripheral over WebSocket. * * Lifecycle: * - start(boardId): asks for camera permission, starts capture loop. * - stop(): turns the webcam off, tells backend to detach. * - status: 'idle' | 'requesting' | 'streaming' | 'denied' | 'error'. * * The frame transport goes through Esp32Bridge.sendCameraFrame(), which * is also what the test suite uses. The ctypes binding in the worker * pushes the bytes into the QEMU OV2640+I²S peripheral. * * Implementation notes: * - QVGA (320×240) at 10 fps. Larger sizes work but bandwidth * scales linearly and the firmware's DMA buffer is fixed-size. * - JPEG quality 0.6 keeps each frame in the 8–14 KB range. * - We use OffscreenCanvas when available (Chrome/Edge); fall back * to a hidden DOM canvas for Safari < 17. */ import { useCallback, useEffect, useRef, useState } from 'react'; import { getEsp32Bridge } from '../store/useSimulatorStore'; export type WebcamStatus = | 'idle' | 'requesting' | 'streaming' | 'denied' | 'error'; export interface UseWebcamFramesResult { status: WebcamStatus; errorMessage: string | null; /** Frames sent since the last start(). Useful for live counter UI. */ framesSent: number; /** Last frame payload size (bytes). */ lastFrameBytes: number; start: (boardId: string) => Promise; stop: () => void; /** A `