Camera autostart fixed
This commit is contained in:
10
.claude/settings.local.json
Normal file
10
.claude/settings.local.json
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"permissions": {
|
||||||
|
"allow": [
|
||||||
|
"Bash(npm run lint)",
|
||||||
|
"WebSearch"
|
||||||
|
],
|
||||||
|
"deny": [],
|
||||||
|
"ask": []
|
||||||
|
}
|
||||||
|
}
|
||||||
53
CLAUDE.md
Normal file
53
CLAUDE.md
Normal 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
|
||||||
78
app/page.tsx
78
app/page.tsx
@@ -32,19 +32,80 @@ export default function HomePage() {
|
|||||||
};
|
};
|
||||||
}, [stream]);
|
}, [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) => {
|
const startStream = async (deviceId: string) => {
|
||||||
// Stop previous stream if it exists
|
// Stop previous stream if it exists
|
||||||
stream?.getTracks().forEach((track) => track.stop());
|
stream?.getTracks().forEach((track) => track.stop());
|
||||||
|
console.log("starting stream....")
|
||||||
try {
|
try {
|
||||||
const newStream = await navigator.mediaDevices.getUserMedia({
|
const newStream = await navigator.mediaDevices.getUserMedia({
|
||||||
video: { deviceId: { exact: deviceId } },
|
video: { deviceId: { exact: deviceId } },
|
||||||
});
|
});
|
||||||
if (videoRef.current) {
|
|
||||||
videoRef.current.srcObject = newStream;
|
console.log(newStream)
|
||||||
}
|
|
||||||
|
// Always save the stream first
|
||||||
setStream(newStream);
|
setStream(newStream);
|
||||||
setCameraStatus('active');
|
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) {
|
} catch (err) {
|
||||||
console.error("Error starting stream: ", err);
|
console.error("Error starting stream: ", err);
|
||||||
setCameraStatus('denied');
|
setCameraStatus('denied');
|
||||||
@@ -61,9 +122,10 @@ export default function HomePage() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setVideoDevices(videoInputs);
|
setVideoDevices(videoInputs);
|
||||||
const firstDeviceId = videoInputs[0].deviceId;
|
const firstDevice = videoInputs[0];
|
||||||
setSelectedDeviceId(firstDeviceId);
|
setSelectedDeviceId(firstDevice.deviceId);
|
||||||
await startStream(firstDeviceId);
|
localStorage.setItem('selectedCameraDeviceId', firstDevice.deviceId); // Save stable deviceId
|
||||||
|
await startStream(firstDevice.deviceId);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Error enumerating devices: ", err);
|
console.error("Error enumerating devices: ", err);
|
||||||
setCameraStatus('denied');
|
setCameraStatus('denied');
|
||||||
@@ -71,7 +133,9 @@ export default function HomePage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleCameraChange = (deviceId: string) => {
|
const handleCameraChange = (deviceId: string) => {
|
||||||
|
console.log("SAVING: ",deviceId)
|
||||||
setSelectedDeviceId(deviceId);
|
setSelectedDeviceId(deviceId);
|
||||||
|
localStorage.setItem('selectedCameraDeviceId', deviceId); // Save stable deviceId
|
||||||
startStream(deviceId);
|
startStream(deviceId);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user