Camera autostart fixed

This commit is contained in:
2025-08-27 19:37:56 -06:00
parent 0ea245562d
commit 82785f24c7
3 changed files with 134 additions and 7 deletions

View File

@@ -0,0 +1,10 @@
{
"permissions": {
"allow": [
"Bash(npm run lint)",
"WebSearch"
],
"deny": [],
"ask": []
}
}

53
CLAUDE.md Normal file
View File

@@ -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

View File

@@ -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);
};