116 lines
4.2 KiB
JavaScript
116 lines
4.2 KiB
JavaScript
// 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
|
|
}; |