From 82785f24c7cb231cbc718b63f4e7622f6f07e337 Mon Sep 17 00:00:00 2001 From: Jose Andres Date: Wed, 27 Aug 2025 19:37:56 -0600 Subject: [PATCH] Camera autostart fixed --- .claude/settings.local.json | 10 +++++ CLAUDE.md | 53 +++++++++++++++++++++++++ app/page.tsx | 78 +++++++++++++++++++++++++++++++++---- 3 files changed, 134 insertions(+), 7 deletions(-) create mode 100644 .claude/settings.local.json create mode 100644 CLAUDE.md diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..8b6f275 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,10 @@ +{ + "permissions": { + "allow": [ + "Bash(npm run lint)", + "WebSearch" + ], + "deny": [], + "ask": [] + } +} \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..efe6eb4 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,53 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Development Commands + +- `npm run dev` - Start development server with Turbopack (opens at http://localhost:3000) +- `npm run build` - Build for production +- `npm start` - Start production server +- `npm run lint` - Run ESLint + +## Architecture Overview + +This is a Next.js 15 App Router application with TypeScript, implementing a shoe detection camera app with the following structure: + +### Core Architecture +- **Frontend**: Next.js with App Router, React 19, TypeScript +- **Styling**: Tailwind CSS v4 with shadcn/ui components (New York style) +- **State Management**: React hooks with local storage for persistence +- **UI Components**: Radix UI primitives with custom variants + +### Key Features +- **Camera Integration**: WebRTC camera access with device selection and localStorage persistence +- **Shoe Detection**: Simulated ML classification (currently returns random shoes from database) +- **History System**: Local storage-based history with sidebar navigation +- **Responsive UI**: Mobile-first design with overlay controls + +### File Structure +- `app/` - Next.js App Router pages and layouts +- `components/` - React components including shadcn/ui components +- `lib/` - Business logic and utilities: + - `shoe-database.ts` - Static shoe data with type definitions + - `ml-classification.ts` - Mock ML detection logic + - `history-storage.ts` - Local storage management + - `utils.ts` - Utility functions (cn helper) + +### Component Architecture +- Uses shadcn/ui component library with Radix UI primitives +- Component variants managed with `class-variance-authority` +- Icons from `lucide-react` +- Custom components: `ShoeResultsPopup`, `HistorySidebar` + +### State Management Patterns +- Camera state managed in main page component +- Local storage for device preferences and history +- Popup and sidebar state via boolean flags +- No external state management library used + +### Configuration +- TypeScript with strict mode and ES2017 target +- ESLint with Next.js and TypeScript rules +- Path aliases configured: `@/*` maps to project root +- Turbopack enabled for faster development \ No newline at end of file diff --git a/app/page.tsx b/app/page.tsx index 2e8cac0..88fb3fe 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -32,19 +32,80 @@ export default function HomePage() { }; }, [stream]); + // Effect to assign stream to video when videoRef becomes available + useEffect(() => { + if (videoRef.current && stream && cameraStatus === 'active') { + console.log("Assigning saved stream to video element"); + videoRef.current.srcObject = stream; + } + }, [stream, cameraStatus]); // Runs when stream or camera status changes + + // Effect to enumerate devices and auto-load camera on mount + useEffect(() => { + const loadDevicesAndCamera = async () => { + try { + // First request permissions to get stable deviceIds + const permissionStream = await navigator.mediaDevices.getUserMedia({ video: true }); + permissionStream.getTracks().forEach(track => track.stop()); // Stop immediately + + // Now enumerate devices - deviceIds will be stable after permission granted + const devices = await navigator.mediaDevices.enumerateDevices(); + const videoInputs = devices.filter(d => d.kind === 'videoinput'); + setVideoDevices(videoInputs); // Always populate the dropdown + console.log(videoInputs) + const storedDeviceId = localStorage.getItem('selectedCameraDeviceId'); + if (storedDeviceId && videoInputs.length > 0) { + // Try to find camera by stable deviceId + const matchedDevice = videoInputs.find(d => d.deviceId === storedDeviceId); + if (matchedDevice) { + setSelectedDeviceId(matchedDevice.deviceId); + startStream(matchedDevice.deviceId); + } else { + // If stored device not found, use first available + const firstDevice = videoInputs[0]; + setSelectedDeviceId(firstDevice.deviceId); + localStorage.setItem('selectedCameraDeviceId', firstDevice.deviceId); + startStream(firstDevice.deviceId); + } + } else if (videoInputs.length > 0) { + // No stored device, start with first available + const firstDevice = videoInputs[0]; + setSelectedDeviceId(firstDevice.deviceId); + localStorage.setItem('selectedCameraDeviceId', firstDevice.deviceId); + startStream(firstDevice.deviceId); + } + } catch (err) { + // Permission denied or no camera + console.error("Error accessing camera or enumerating devices:", err); + setCameraStatus('denied'); + } + }; + + loadDevicesAndCamera(); + }, []); // Empty dependency array ensures this runs only once on mount + const startStream = async (deviceId: string) => { // Stop previous stream if it exists stream?.getTracks().forEach((track) => track.stop()); - + console.log("starting stream....") try { const newStream = await navigator.mediaDevices.getUserMedia({ video: { deviceId: { exact: deviceId } }, }); - if (videoRef.current) { - videoRef.current.srcObject = newStream; - } + + console.log(newStream) + + // Always save the stream first setStream(newStream); setCameraStatus('active'); + + if (videoRef.current) { + // Video element is ready, assign stream immediately + videoRef.current.srcObject = newStream; + } else { + // Video element not ready yet, stream will be assigned when it mounts + console.log("Video element not ready, stream will be assigned when video mounts"); + } } catch (err) { console.error("Error starting stream: ", err); setCameraStatus('denied'); @@ -61,9 +122,10 @@ export default function HomePage() { return; } setVideoDevices(videoInputs); - const firstDeviceId = videoInputs[0].deviceId; - setSelectedDeviceId(firstDeviceId); - await startStream(firstDeviceId); + const firstDevice = videoInputs[0]; + setSelectedDeviceId(firstDevice.deviceId); + localStorage.setItem('selectedCameraDeviceId', firstDevice.deviceId); // Save stable deviceId + await startStream(firstDevice.deviceId); } catch (err) { console.error("Error enumerating devices: ", err); setCameraStatus('denied'); @@ -71,7 +133,9 @@ export default function HomePage() { }; const handleCameraChange = (deviceId: string) => { + console.log("SAVING: ",deviceId) setSelectedDeviceId(deviceId); + localStorage.setItem('selectedCameraDeviceId', deviceId); // Save stable deviceId startStream(deviceId); };