/** * Camera button rendered on the canvas header for ESP32-CAM boards. * * Click → asks for webcam permission → starts streaming JPEG frames at * 10 fps over the existing simulator WebSocket. The frames flow into * the QEMU OV2640+I²S peripheral so user sketches that call * `esp_camera_fb_get()` see real webcam content (subject to the * upstream-driver caveats documented in test/test-esp32-cam/autosearch/10). * * Visual states: * idle grey camera icon * requesting orange (pulsing) — browser permission prompt visible * streaming green + frame counter — frames flowing * denied red, click-to-retry * error red, hover for message */ import React from 'react'; import { useWebcamFrames } from '../../hooks/useWebcamFrames'; interface CameraToggleProps { boardId: string | null; } export const CameraToggle: React.FC = ({ boardId }) => { const { status, errorMessage, framesSent, lastFrameBytes, lastQualityUsed, lastDownscaled, start, stop, } = useWebcamFrames(); const handleClick = () => { if (!boardId) return; if (status === 'streaming') { stop(); } else { void start(boardId); } }; const isOn = status === 'streaming'; // Build a streaming tooltip that exposes the adaptive encoder state. // Users see "auto-tuned" hints when the encoder had to drop quality // or downscale — useful diagnostic for HD/4K webcams. const streamingTooltip = () => { const kb = (lastFrameBytes / 1024).toFixed(1); const q = lastQualityUsed.toFixed(2); const tuneNote = lastDownscaled ? ` (auto-downscaled, q=${q})` : lastQualityUsed < 0.3 ? ` (auto-tuned to q=${q})` : ` (q=${q})`; return `Streaming webcam (${framesSent} frames, last=${kb} KB${tuneNote}) — click to stop`; }; const tooltip = status === 'streaming' ? streamingTooltip() : status === 'requesting' ? 'Asking for camera permission…' : status === 'denied' ? `Permission denied. Click to retry.` : status === 'error' ? `Webcam error: ${errorMessage ?? 'unknown'}` : 'Use webcam as ESP32-CAM camera'; const color = status === 'streaming' ? '#3fb950' : status === 'requesting' ? '#f0883e' : status === 'denied' || status === 'error' ? '#f85149' : '#ccc'; return ( ); };