Initial commit
This commit is contained in:
116
js/ui/camera.js
Normal file
116
js/ui/camera.js
Normal file
@@ -0,0 +1,116 @@
|
||||
// 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
|
||||
};
|
||||
Reference in New Issue
Block a user