import React, { useState } from "react"; import { RefreshCw } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Label } from "@/components/ui/label"; import { Switch } from "@/components/ui/switch"; import { useRobots } from "@/hooks/useRobots"; import CameraFeed from "./CameraFeed"; /** * Optional live camera panel for the teleoperation page. Off by default so we * never call getUserMedia just by landing on the page (same consent pattern as * the calibration camera toggle). Teleoperation opens no cv2 cameras, so the * browser can stream them directly while the arm runs. * * A strict mirror of the selected robot's configured cameras: one live feed per * camera on the robot record (e.g. "wrist_cam", "webcam"), stacked vertically. * If the robot has none configured it shows nothing — teleop never surfaces a * device that wasn't deliberately added to the robot. */ const TeleopCameraPanel: React.FC = () => { const [enabled, setEnabled] = useState(false); // Bumped by the retry button to remount the feeds (a fresh getUserMedia // attempt) — useful if a camera was unplugged and reconnected. const [reloadKey, setReloadKey] = useState(0); const { selectedRecord, isLoading: robotsLoading } = useRobots(); // Feeds come solely from the robot's configured cameras; each carries a stored // browser device_id we stream directly. A configured camera whose device is // currently absent still shows (name + failed-preview placeholder), so the // user can tell it's expected but not detected. const configured = selectedRecord?.cameras ?? []; const feeds = configured.map((c) => ({ key: c.id, name: c.name, deviceId: c.device_id, })); return (

Cameras

{enabled && feeds.length > 0 && ( )}
{enabled ? ( feeds.length > 0 ? (
{feeds.map((feed) => ( ))}
) : (

{robotsLoading ? "Loading robot..." : "No cameras configured for this robot. Add them during calibration to see live feeds here."}

) ) : (

Turn on to watch your cameras while you teleoperate.

)}
); }; export default TeleopCameraPanel;