// camera.js import { CSS_CLASSES } from '../config.js'; let stream = null; let elements = {}; // To store references to DOM elements passed during init let onCaptureCallback = null; // Callback when image is captured export function initCamera(cameraElements, options) { elements = cameraElements; // Store refs like { cameraContainer, cameraView, etc. } onCaptureCallback = options.onCapture; // Add internal listeners for capture/cancel buttons elements.cameraCaptureButton?.addEventListener('click', handleCapture); elements.cameraCancelButton?.addEventListener('click', closeCamera); // Handle orientation change to potentially reset stream dimensions window.addEventListener('orientationchange', handleOrientationChange); } async function openCamera() { if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) { alert('Camera access not supported or available on this device.'); return false; // Indicate failure } try { stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: 'user', // Prefer front camera width: { ideal: 1280 }, height: { ideal: 720 } } }); elements.cameraContainer?.classList.add(CSS_CLASSES.CAMERA_ACTIVE); if (elements.cameraView) { elements.cameraView.srcObject = stream; // Wait for video metadata to load to get correct dimensions elements.cameraView.onloadedmetadata = () => { elements.cameraView.play(); // Start playing the video stream }; } return true; // Indicate success } catch (error) { console.error('Error accessing camera:', error); alert('Could not access camera: ' + error.message); closeCamera(); // Ensure cleanup if opening failed return false; // Indicate failure } } function handleCapture() { if (!elements.cameraView || !elements.cameraCanvas || !stream) return; // Set canvas dimensions to match video's actual dimensions elements.cameraCanvas.width = elements.cameraView.videoWidth; elements.cameraCanvas.height = elements.cameraView.videoHeight; // Draw the current video frame to the canvas const context = elements.cameraCanvas.getContext('2d'); // Flip horizontally for front camera to make it mirror-like if (stream.getVideoTracks()[0]?.getSettings()?.facingMode === 'user') { context.translate(elements.cameraCanvas.width, 0); context.scale(-1, 1); } context.drawImage(elements.cameraView, 0, 0, elements.cameraCanvas.width, elements.cameraCanvas.height); // Convert canvas to data URL (JPEG format) const imageDataUrl = elements.cameraCanvas.toDataURL('image/jpeg', 0.9); // Quality 0.9 // Call the callback provided during init with the image data if (onCaptureCallback) { onCaptureCallback(imageDataUrl); } // Stop stream and hide UI after capture closeCamera(); } function stopCameraStream() { if (stream) { stream.getTracks().forEach(track => track.stop()); stream = null; } // Also clear the srcObject if (elements.cameraView) { elements.cameraView.srcObject = null; } } function closeCamera() { stopCameraStream(); elements.cameraContainer?.classList.remove(CSS_CLASSES.CAMERA_ACTIVE); } function handleOrientationChange() { // If camera is active, restart stream to potentially adjust aspect ratio/resolution if (elements.cameraContainer?.classList.contains(CSS_CLASSES.CAMERA_ACTIVE) && stream) { console.log("Orientation changed, re-evaluating camera stream..."); // Short delay to allow layout to settle setTimeout(async () => { // Stop existing stream before requesting new one // This might cause a flicker but ensures constraints are re-evaluated stopCameraStream(); await openCamera(); // Attempt to reopen with potentially new constraints }, 300); } } // Public API for camera module export default { init: initCamera, open: openCamera, close: closeCamera, stopStream: stopCameraStream // Expose if needed externally, e.g., when modal closes };