Files
game-timer/camera.js
2025-03-28 05:04:25 +01:00

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