auraehealth-facescan
Version:
Face scanning package
827 lines (743 loc) • 28.5 kB
JavaScript
// facescan.js
window.initializeFaceScan = function (config) {
const videoElement = config.videoElement;
const canvasElement = config.canvasElement;
const canvasCtx = canvasElement.getContext('2d');
const colorDisplay = config.colorDisplayElement;
const errorMessageElement = config.errorMessageElement;
const progressBox1 = config.progressBox1;
const progressBox2 = config.progressBox2;
const startScanButton = config.startScanButton;
const backToHomeButton = config.backButton;
const progressCircle = config.progressCircleElement;
const progressValue = config.progressValueElement;
const scanProgressMessage = config.scanProgressMessageElement;
const container = config.container;
const EYE_AR_THRESH = 0.35; // Try a higher threshold
const EYE_AR_CONSEC_FRAMES = 2; // Check more consecutive frames
const BLINK_TIMEOUT = 10000;
const BLINK_COOLDOWN = 500;
let faceMesh, camera;
let scanPaused = false;
let analysisActive = false;
let rgbValues = { r: [], g: [], b: [] };
let frameCount = 0;
let startTime;
let capturedFrame = null;
let fpsStartTime = null;
let currentFacingMode = 'user';
let blinkDetected = false;
let lastBlinkTime = 0;
let eyeStates = [];
let progressInterval = null;
const switchCameraButton = document.createElement('button');
switchCameraButton.className = 'camera-switch-button';
switchCameraButton.innerHTML = `
<svg width="56" height="56" viewBox="0 0 56 56" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="28" cy="28" r="28" fill="#4F4D4D" fill-opacity="0.83"/>
<path d="M21.3852 14.2882C22.0978 11.2588 24.8688 9 28.1786 9C31.4884 9 34.2594 11.2588 34.9719 14.2882L35.2696 14.3489C39.8225 15.2786 43.4481 18.6476 44.6347 23.0513C45.598 26.626 45.598 30.3843 44.6347 33.959C43.4481 38.3627 39.8225 41.7318 35.2696 42.6614L34.3997 42.839C30.2964 43.6769 26.0608 43.6769 21.9575 42.839L21.0875 42.6614C16.5347 41.7318 12.9091 38.3627 11.7225 33.959C10.7592 30.3843 10.7592 26.626 11.7225 23.0513C12.9091 18.6476 16.5347 15.2786 21.0875 14.3489L21.3852 14.2882Z" stroke="#E0DFDF" stroke-width="1.5" stroke-linejoin="round"/>
<path d="M25.7636 26.8114H22.6199C22.4809 26.8114 22.3476 26.7562 22.2494 26.658C22.1511 26.5597 22.0959 26.4264 22.0959 26.2875V23.1437C22.0959 23.0047 22.1511 22.8714 22.2494 22.7732C22.3476 22.6749 22.4809 22.6197 22.6199 22.6197C22.7588 22.6197 22.8921 22.6749 22.9904 22.7732C23.0886 22.8714 23.1438 23.0047 23.1438 23.1437V25.0227L24.102 24.0645C25.2706 22.8901 26.8577 22.2276 28.5145 22.2228H28.5492C30.1919 22.2186 31.7701 22.8617 32.942 24.0128C33.0377 24.1107 33.0912 24.2421 33.0912 24.379C33.0913 24.5159 33.0377 24.6474 32.942 24.7453C32.8463 24.8432 32.7162 24.8997 32.5793 24.9029C32.4424 24.906 32.3098 24.8555 32.2097 24.7621C31.2329 23.8033 29.9179 23.2675 28.5492 23.2707H28.5197C27.1392 23.275 25.8167 23.8269 24.8428 24.8053L23.8846 25.7635H25.7636C25.9026 25.7635 26.0359 25.8187 26.1341 25.917C26.2324 26.0152 26.2876 26.1485 26.2876 26.2875C26.2876 26.4264 26.2324 26.5597 26.1341 26.658C26.0359 26.7562 25.9026 26.8114 25.7636 26.8114ZM34.1471 29.9552H31.0033C30.8643 29.9552 30.7311 30.0104 30.6328 30.1087C30.5345 30.207 30.4793 30.3402 30.4793 30.4792C30.4793 30.6182 30.5345 30.7514 30.6328 30.8497C30.7311 30.948 30.8643 31.0032 31.0033 31.0032H32.8824L31.9242 31.9614C30.9504 32.9396 29.6282 33.4915 28.2479 33.4959H28.2184C26.8497 33.4991 25.5347 32.9634 24.5579 32.0046C24.5091 31.9547 24.4508 31.915 24.3864 31.8879C24.3221 31.8608 24.253 31.8469 24.1831 31.8469C24.1133 31.8469 24.0442 31.8608 23.9799 31.8879C23.9155 31.915 23.8572 31.9547 23.8084 32.0046C23.7596 32.0546 23.7213 32.1137 23.6957 32.1787C23.6701 32.2436 23.6577 32.3131 23.6593 32.3829C23.6609 32.4527 23.6765 32.5214 23.705 32.5851C23.7336 32.6489 23.7746 32.7062 23.8256 32.7539C24.9975 33.905 26.5757 34.5481 28.2184 34.5439H28.2525C29.909 34.5388 31.4959 33.8764 32.6643 32.7021L33.6231 31.7439V33.623C33.6231 33.7619 33.6783 33.8952 33.7766 33.9935C33.8749 34.0917 34.0081 34.147 34.1471 34.147C34.2861 34.147 34.4193 34.0917 34.5176 33.9935C34.6159 33.8952 34.6711 33.7619 34.6711 33.623V30.4792C34.6711 30.3402 34.6159 30.207 34.5176 30.1087C34.4193 30.0104 34.2861 29.9552 34.1471 29.9552Z" fill="#FCF0F0"/>
</svg>
`;
const CONFIG = {
requiredFields: ['uid', 'email', 'name', 'gender'],
numberFields: ['waist', 'height', 'weight', 'age'],
};
if (container) {
container.appendChild(switchCameraButton);
}
// Get and validate user data from URL
function validateAndGetUserData() {
const urlParams = new URLSearchParams(window.location.search);
const userData = {
waist: Number(urlParams.get('waist')) || null,
height: Number(urlParams.get('height')) || null,
weight: Number(urlParams.get('weight')) || null,
age: Number(urlParams.get('age')) || null,
email: urlParams.get('email') || null,
name: urlParams.get('name') || null,
gender: urlParams.get('gender') || null,
uid: urlParams.get('uid') || null,
};
// Check required fields
const missingFields = CONFIG.requiredFields.filter(
(field) => !userData[field]
);
if (missingFields.length > 0) {
throw new Error(
`Missing required parameters: ${missingFields.join(', ')}`
);
}
// Validate number fields (if they are provided)
CONFIG.numberFields.forEach((field) => {
const value = urlParams.get(field);
if (value && isNaN(Number(value))) {
throw new Error(`Invalid value for ${field}: must be a number`);
}
});
return userData;
}
// Initialize application with validated user data
function initializeApp() {
try {
// Validate and get user data
const userData = validateAndGetUserData();
// Destructure user data into global variables
({
waist: userWaist,
height: userHeight,
weight: userWeight,
age: userAge,
email: userEmail,
name: userName,
gender: userGender,
uid: userUid,
} = userData);
console.log(`User data: ${userData}`);
// Initialize face mesh and camera
showLoadingIndicator();
initializeFaceMesh();
initializeCamera()
.then(() => {
setTimeout(() => {
hideLoadingIndicator();
document.getElementsByClassName('progress-box-1')[0].style.display =
'block';
}, 2000);
})
.catch((error) => {
console.error('Error initializing camera:');
hideLoadingIndicator();
showErrorDialog('Failed to start camera: ' + error.message);
});
} catch (error) {
console.error('Initialization error:', error);
showErrorDialog(error.message);
// Optionally disable scan functionality
if (startScanButton) {
startScanButton.disabled = true;
}
}
}
function sendMessageToFlutter(message) {
window.parent.postMessage(message, '*');
}
function showErrorDialog(message) {
const errorDialog = document.getElementById('error-dialog');
const errorDialogMessage = document.getElementById('error-dialog-message');
errorDialogMessage.textContent = message;
errorDialog.style.display = 'block';
// Add a class to identify auto-closeable errors
if (message.includes('closer') || message.includes('straight')) {
errorDialog.classList.add('auto-closeable');
} else {
errorDialog.classList.remove('auto-closeable');
}
}
function closeErrorDialog() {
const errorDialog = document.getElementById('error-dialog');
errorDialog.style.display = 'none';
// If this was an auto-closeable error and scan was active, continue
if (errorDialog.classList.contains('auto-closeable') && analysisActive) {
errorDialog.classList.remove('auto-closeable');
}
}
function updateVideoTransform() {
// Always mirror for front camera, never for back camera
if (currentFacingMode === 'user') {
videoElement.style.transform = 'scaleX(-1)';
canvasElement.style.transform = 'scaleX(-1)';
} else {
videoElement.style.transform = 'none';
canvasElement.style.transform = 'none';
}
}
// Initialize face mesh
function initializeFaceMesh() {
try {
faceMesh = new FaceMesh({
locateFile: (file) => {
return `https://cdn.jsdelivr.net/npm/@mediapipe/face_mesh/${file}`;
},
});
faceMesh.setOptions({
maxNumFaces: 1,
refineLandmarks: true,
minDetectionConfidence: 0.5,
minTrackingConfidence: 0.5,
});
faceMesh.onResults(onResults);
} catch (error) {
console.error('Error initializing FaceMesh:');
}
}
function hideLoadingIndicator() {
document.getElementById('loading-indicator').style.display = 'none';
document.querySelector('body').style.overflow = 'auto';
}
function showLoadingIndicator() {
document.getElementById('loading-indicator').style.display = 'block';
document.querySelector('body').style.overflow = 'hidden';
}
function showNetworkErrorOverlay() {
document.getElementById('network-error-overlay').style.display = 'flex';
}
// Function to hide network error overlay
function hideNetworkErrorOverlay() {
document.getElementById('network-error-overlay').style.display = 'none';
}
// Add event listener to the "Back to Home" button in the error overlay
document.addEventListener('DOMContentLoaded', function () {
document
.getElementById('network-error-home-button')
.addEventListener('click', function () {
hideNetworkErrorOverlay();
sendMessageToFlutter('backToHomePage');
});
});
// Function to capture, compress, and convert frame to Base64
function captureCompressAndEncode(image, quality = 0.5) {
return new Promise((resolve) => {
const canvas = document.createElement('canvas');
canvas.width = image.width;
canvas.height = image.height;
const ctx = canvas.getContext('2d');
ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
// Compress and convert to Base64
canvas.toBlob(
(blob) => {
const reader = new FileReader();
reader.onloadend = () => resolve(reader.result);
reader.readAsDataURL(blob);
},
'image/jpeg',
quality
);
});
}
function getDistance(p1, p2, landmarks) {
const dx = landmarks[p1].x - landmarks[p2].x;
const dy = landmarks[p1].y - landmarks[p2].y;
const dz = landmarks[p1].z - landmarks[p2].z;
return Math.sqrt(dx * dx + dy * dy + dz * dz);
}
function getEyeAspectRatio(landmarks, eyeIndices) {
try {
// Vertical distances
const v1 = getDistance(eyeIndices[1], eyeIndices[3], landmarks);
const v2 = getDistance(eyeIndices[4], eyeIndices[5], landmarks);
// Horizontal distance
const h = getDistance(eyeIndices[0], eyeIndices[2], landmarks);
const ear = h !== 0 ? (v1 + v2) / (2.0 * h) : 0;
return ear;
} catch (error) {
console.error('Error calculating EAR:', error);
return 0;
}
}
const LEFT_EYE = [
33, // left corner
159, // top
133, // right corner
145, // bottom
158, // outer top
144, // outer bottom
];
const RIGHT_EYE = [
362, // left corner
386, // top
263, // right corner
374, // bottom
385, // outer top
380, // outer bottom
];
// Function to detect blinks
function detectBlink(landmarks) {
try {
const leftEAR = getEyeAspectRatio(landmarks, LEFT_EYE);
const rightEAR = getEyeAspectRatio(landmarks, RIGHT_EYE);
const ear = (leftEAR + rightEAR) / 2.0;
// Check if eyes are closed
const isClosed = ear < EYE_AR_THRESH;
eyeStates.push(isClosed);
if (eyeStates.length > EYE_AR_CONSEC_FRAMES) {
eyeStates.shift();
}
// Modified blink detection pattern
if (eyeStates.length === EYE_AR_CONSEC_FRAMES && !blinkDetected) {
// Look for any closed state in recent frames
const hasClosed = eyeStates.some((state) => state);
if (hasClosed && Date.now() - lastBlinkTime > BLINK_COOLDOWN) {
blinkDetected = true;
lastBlinkTime = Date.now();
return true;
}
}
return false;
} catch (error) {
console.error('Error in detectBlink:', error);
return false;
}
}
function resetScan() {
analysisActive = false;
scanPaused = false;
switchCameraButton.style.display = 'flex';
clearCanvas();
if (progressInterval) {
clearInterval(progressInterval);
}
progressInterval = null;
progressValue.textContent = '0%';
progressCircle.style.background = `conic-gradient(#ccc 0deg, #ccc 360deg)`;
startScanButton.textContent = 'Start Scan';
scanProgressMessage.style.display = 'none';
progressBox1.style.display = 'flex';
progressBox2.style.display = 'none';
}
function handleInvalidDistance() {
showErrorDialog('Please keep the camera closer for better scanning.');
scanPaused = true;
clearCanvas();
}
function calculateDistanceFromCamera(landmarks) {
const leftEyeCorner = landmarks[33];
const rightEyeCorner = landmarks[263];
const faceWidth = Math.sqrt(
Math.pow(rightEyeCorner.x - leftEyeCorner.x, 2) +
Math.pow(rightEyeCorner.y - leftEyeCorner.y, 2)
);
return (0.1 / faceWidth) * 100; // Adjust scale factor if needed
}
function checkFaceOrientation(landmarks) {
// Use nose tip and both cheeks for orientation check
const noseTip = landmarks[1];
const leftCheek = landmarks[234];
const rightCheek = landmarks[454];
// Calculate distances from nose to each cheek
const leftCheekDist = Math.abs(noseTip.x - leftCheek.x);
const rightCheekDist = Math.abs(rightCheek.x - noseTip.x);
// Calculate the difference ratio
const distanceRatio =
Math.abs(leftCheekDist - rightCheekDist) /
((leftCheekDist + rightCheekDist) / 2);
// Lower threshold means more sensitive detection
const orientationThreshold = 0.5;
return distanceRatio <= orientationThreshold;
}
function handleFaceNotStraight() {
showErrorDialog('Please keep your face straight for better scanning.');
scanPaused = true;
clearCanvas();
}
function isLaptop() {
const userAgent = navigator.userAgent.toLowerCase();
const isMobile = /mobile|android|iphone|ipad|tablet/.test(userAgent);
return !isMobile; // If not a mobile device, assume it's a laptop/desktop
}
function onResults(results) {
if (!analysisActive) return;
canvasCtx.save();
canvasCtx.clearRect(0, 0, canvasElement.width, canvasElement.height);
// Always draw the camera feed
canvasCtx.drawImage(
results.image,
0,
0,
canvasElement.width,
canvasElement.height
);
if (results.multiFaceLandmarks.length === 0) {
showErrorDialog('No face detected.\nPlace your face in front of camera.');
scanPaused = true;
canvasCtx.restore();
return;
}
// Get landmarks and check distance
const landmarks = results.multiFaceLandmarks[0];
// Check face orientation first
if (!checkFaceOrientation(landmarks)) {
handleFaceNotStraight();
canvasCtx.restore();
return;
}
// Then check distance
const distance = calculateDistanceFromCamera(landmarks);
const distanceThreshold = isLaptop() ? 92 : 30;
// Check distance before proceeding
if (distance > distanceThreshold) {
handleInvalidDistance();
canvasCtx.restore();
return;
}
// If we get here, both face orientation and distance are okay
if (scanPaused) {
scanPaused = false;
closeErrorDialog();
}
// Process frames and draw facemesh only if not paused
if (!scanPaused) {
// Blink detection
const blinkOccurred = detectBlink(landmarks);
// Draw facemesh
drawConnectors(canvasCtx, landmarks, FACEMESH_TESSELATION, {
color: '#C0C0C070',
lineWidth: 1,
});
drawConnectors(canvasCtx, landmarks, FACEMESH_RIGHT_EYE, {
color: '#E0E0E0',
lineWidth: 1,
});
drawConnectors(canvasCtx, landmarks, FACEMESH_RIGHT_EYEBROW, {
color: '#E0E0E0',
lineWidth: 1,
});
drawConnectors(canvasCtx, landmarks, FACEMESH_RIGHT_IRIS, {
color: '#E0E0E0',
lineWidth: 1,
});
drawConnectors(canvasCtx, landmarks, FACEMESH_LEFT_EYE, {
color: '#E0E0E0',
lineWidth: 1,
});
drawConnectors(canvasCtx, landmarks, FACEMESH_LEFT_EYEBROW, {
color: '#E0E0E0',
lineWidth: 1,
});
drawConnectors(canvasCtx, landmarks, FACEMESH_LEFT_IRIS, {
color: '#E0E0E0',
lineWidth: 1,
});
drawConnectors(canvasCtx, landmarks, FACEMESH_FACE_OVAL, {
color: '#E0E0E0',
});
drawConnectors(canvasCtx, landmarks, FACEMESH_LIPS, {
color: '#E0E0E0',
lineWidth: 1,
});
// Process frames if appropriate
if (frameCount >= 100 && frameCount <= 600) {
const color = processFrame(results.image, landmarks);
if (color) {
rgbValues.r.push(color[0]);
rgbValues.g.push(color[1]);
rgbValues.b.push(color[2]);
}
}
// Capture frame if needed
if (frameCount === 101 && !capturedFrame) {
captureCompressAndEncode(results.image).then((base64Image) => {
capturedFrame = base64Image.replace('data:image/jpeg;base64,', '');
});
}
frameCount++;
}
canvasCtx.restore();
}
function processFrame(image, landmarks) {
const regions = {
forehead: [10, 108, 151, 9, 336, 337, 338, 339, 340],
rightCheek: [36, 31, 39, 0, 267, 269, 270, 409],
leftCheek: [266, 261, 269, 230, 37, 39, 40, 185],
};
const allRegionsColors = [];
for (const [regionName, indices] of Object.entries(regions)) {
const regionPoints = indices.map((index) => ({
x: landmarks[index].x * image.width,
y: landmarks[index].y * image.height,
}));
const minX = Math.min(...regionPoints.map((p) => p.x));
const maxX = Math.max(...regionPoints.map((p) => p.x));
const minY = Math.min(...regionPoints.map((p) => p.y));
const maxY = Math.max(...regionPoints.map((p) => p.y));
const tempCanvas = document.createElement('canvas');
tempCanvas.width = maxX - minX;
tempCanvas.height = maxY - minY;
const tempCtx = tempCanvas.getContext('2d');
tempCtx.drawImage(
image,
minX,
minY,
tempCanvas.width,
tempCanvas.height,
0,
0,
tempCanvas.width,
tempCanvas.height
);
const imageData = tempCtx.getImageData(
0,
0,
tempCanvas.width,
tempCanvas.height
);
const data = imageData.data;
let sumR = 0,
sumG = 0,
sumB = 0,
count = 0;
for (let i = 0; i < data.length; i += 4) {
sumR += data[i];
sumG += data[i + 1];
sumB += data[i + 2];
count++;
}
if (count > 0) {
allRegionsColors.push([sumR / count, sumG / count, sumB / count]);
}
}
if (allRegionsColors.length > 0) {
const avgColor = allRegionsColors.reduce(
(acc, color) => [
acc[0] + color[0] / allRegionsColors.length,
acc[1] + color[1] / allRegionsColors.length,
acc[2] + color[2] / allRegionsColors.length,
],
[0, 0, 0]
);
return avgColor.map(Math.round);
}
return null;
}
function startScan() {
if (analysisActive) {
// Cancel scan
analysisActive = false;
scanPaused = false;
blinkDetected = false;
lastBlinkTime = 0;
eyeStates = [];
updateVideoTransform();
switchCameraButton.style.display = 'flex';
clearCanvas();
if (progressInterval) {
clearInterval(progressInterval);
}
// Reset the progress indicator
progressValue.textContent = '0%';
progressCircle.style.background = `conic-gradient(#ccc 0deg, #ccc 360deg)`;
startScanButton.textContent = 'Start Scan';
scanProgressMessage.style.display = 'none';
progressBox1.style.display = 'flex';
progressBox2.style.display = 'none';
} else {
// Start scan
initValues();
scanPaused = false;
analysisActive = true;
blinkDetected = false;
lastBlinkTime = 0;
updateVideoTransform();
eyeStates = [];
startTime = Date.now();
frameCount = 0;
fpsStartTime = Date.now();
capturedFrame = null;
rgbValues = { r: [], g: [], b: [] };
scanProgressMessage.style.display = 'block';
progressBox2.style.display = 'none';
startScanButton.textContent = 'Cancel';
switchCameraButton.style.display = 'none';
progressInterval = animateProgressBar();
}
}
function initValues() {
frameCount = 0;
rgbValues = { r: [], g: [], b: [] };
analysisActive = true;
startTime = Date.now();
frameCount = 0;
fpsStartTime = Date.now();
capturedFrame = null;
rgbValues = { r: [], g: [], b: [] };
scanProgressMessage.style.display = 'block';
progressBox2.style.display = 'none';
}
function resetValues() {
frameCount = 0;
rgbValues = { r: [], g: [], b: [] };
capturedFrame = null;
progressValue.textContent = '0%';
progressCircle.style.background = 'conic-gradient(#ccc 0deg, #ccc 360deg)';
colorDisplay.textContent = '';
scanProgressMessage.textContent = 'Scan in progress...';
}
function animateProgressBar() {
const totalTime = 30000;
const interval = 100;
let progress = 0;
let pauseStartTime = null;
let totalPausedTime = 0;
return setInterval(() => {
// If scan is paused, store the pause start time
if (scanPaused && !pauseStartTime) {
pauseStartTime = Date.now();
return;
}
// If scan is resumed, add the paused duration to total paused time
if (!scanPaused && pauseStartTime) {
totalPausedTime += Date.now() - pauseStartTime;
pauseStartTime = null;
}
if (!analysisActive || progress >= 100) {
clearInterval(progressInterval);
canvasCtx.clearRect(0, 0, canvasElement.width, canvasElement.height);
return;
}
// Calculate progress accounting for paused time
const currentTime = Date.now();
const effectiveTime = currentTime - startTime - totalPausedTime;
progress = Math.min((effectiveTime / totalTime) * 100, 100);
// Update UI only if not paused
if (!scanPaused) {
progressValue.textContent = `${Math.floor(progress)}%`;
progressCircle.style.background = `conic-gradient(
#4caf50 ${progress * 3.6}deg,
#ccc ${progress * 3.6}deg
)`;
}
if (effectiveTime >= totalTime) {
analysisActive = false;
checkCapturedFrameAndCallApi();
canvasCtx.clearRect(0, 0, canvasElement.width, canvasElement.height);
}
}, interval);
}
function checkCapturedFrameAndCallApi() {
if (capturedFrame != null) {
callAPI();
switchCameraButton.style.display = 'flex';
} else {
//keep calling after every 2 seconds
setTimeout(checkCapturedFrameAndCallApi, 2000);
}
}
function checkImage() {
if (capturedFrame != null) {
callAPI();
}
}
function callAPI() {
const elapsedTime = (Date.now() - fpsStartTime) / 1000;
const fps = frameCount / elapsedTime;
const apiUrl =
'https://ok67gldbh5.execute-api.ap-south-1.amazonaws.com/prod/process-rppg';
const data = {
redChannel: rgbValues.r,
greenChannel: rgbValues.g,
blueChannel: rgbValues.b,
image: capturedFrame,
metadata: {
fps: Math.round(fps),
user_id: userUid,
gender: userGender,
email: userEmail,
fullname: userName,
height: userHeight,
weight: userWeight,
waist: userWaist,
age: userAge,
},
};
console.log('data--');
console.log({ data });
fetch(apiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
})
.then((response) => response.json())
.then((data) => {
console.log('API Response:', data);
if (camera) {
camera
.stop()
.then(() => console.log('Camera stopped successfully after scan'))
.catch((err) => console.error('Error stopping camera:', err));
}
switchProgressBoxes();
sendMessageToFlutter('DONE');
})
.catch((error) => {
console.error('Error calling API:', error);
const errorDialog = document.getElementById('error-dialog');
if (errorDialog) {
errorDialog.style.display = 'none';
}
// Show network error overlay instead of the error dialog
showNetworkErrorOverlay();
// Reset scan state
analysisActive = false;
scanPaused = false;
if (progressInterval) {
clearInterval(progressInterval);
}
switchCameraButton.style.display = 'flex';
startScanButton.textContent = 'Start Scan';
// Reset UI elements
progressBox1.style.display = 'flex';
progressBox2.style.display = 'none';
});
}
function initializeCamera() {
return new Promise((resolve, reject) => {
camera = new Camera(videoElement, {
onFrame: async () => {
await faceMesh.send({ image: videoElement });
},
width: 1280,
height: 720,
facingMode: currentFacingMode,
});
camera
.start()
.then(() => {
updateVideoTransform(); // Set initial transform
resolve();
})
.catch((error) => {
console.error('Error starting camera:', error);
reject(error);
showErrorDialog('Failed to start camera: Try updating your Browser');
});
});
}
async function switchCamera() {
if (camera) {
await camera.stop();
}
currentFacingMode = currentFacingMode === 'user' ? 'environment' : 'user';
updateVideoTransform(); // Update transform when switching cameras
try {
await initializeCamera();
} catch (error) {
console.error('Error switching camera:', error);
showErrorDialog('Failed to switch camera: ' + error.message);
}
}
// Add click event listener for the switch camera button
switchCameraButton.addEventListener('click', switchCamera);
function switchProgressBoxes() {
progressBox1.style.display = 'none';
progressBox2.style.display = 'flex';
}
startScanButton.addEventListener('click', startScan);
backToHomeButton.addEventListener('click', backToHome);
function backToHome() {
sendMessageToFlutter('backToHomePage');
}
function clearCanvas() {
canvasCtx.clearRect(0, 0, canvasElement.width, canvasElement.height);
}
// Clear the canvas and reset to normal view when canceling or finishing the scan
function stopFaceMeshProcessing() {
analysisActive = false;
clearCanvas();
}
document
.getElementById('error-dialog-close')
.addEventListener('click', closeErrorDialog);
initializeApp()
};