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]);
|
||||
|
||||
// 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);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user