// Live Camera JavaScript let detectionCount = 0; let alertCount = 0; let personDetectedCount = 0; let personPresent = false; let availableModels = {}; let selectedModels = []; let cameraRunning = false; let sessionStartTime = null; let sessionData = { startTime: null, endTime: null, duration: 0, selectedModels: [], detections: [], alerts: [], totalDetections: 0, totalAlerts: 0, personDetected: 0, personPresent: false, }; // Initialize model selection on page load document.addEventListener("DOMContentLoaded", async () => { await loadAvailableModels(); await loadSelectedModels(); await loadAvailableCameras(); startDetectionHistoryPolling(); // Start loading detection history // Do NOT auto-start camera - wait for user to click Start // initializeCameraFeed(); }); // Load available cameras and populate dropdown async function loadAvailableCameras() { try { const response = await fetch("/api/cameras"); const result = await response.json(); if (!result.success) { console.error("Failed to load cameras:", result.error); document.getElementById("camera-select").innerHTML = ''; return; } const cameras = result.cameras || []; const selected = result.selected || 0; const cameraSelect = document.getElementById("camera-select"); if (cameras.length === 0) { cameraSelect.innerHTML = ''; return; } // Populate dropdown with available cameras cameraSelect.innerHTML = cameras .map( (cam) => ``, ) .join(""); // Add change listener to camera select cameraSelect.addEventListener("change", handleCameraChange); } catch (error) { console.error("Error loading cameras:", error); document.getElementById("camera-select").innerHTML = ''; } } // Handle camera selection change async function handleCameraChange(event) { const cameraIndex = parseInt(event.target.value); if (isNaN(cameraIndex)) return; // Stop current camera if running if (cameraRunning) { await stopCamera(); } try { const response = await fetch("/api/select-camera", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ camera_index: cameraIndex }), }); const result = await response.json(); if (result.success) { showToast(`π· Camera ${cameraIndex} selected`, "success"); console.log("Camera selected:", result.message); } else { showToast("Failed to select camera: " + result.error, "error"); // Reload cameras list await loadAvailableCameras(); } } catch (error) { console.error("Error selecting camera:", error); showToast("Error selecting camera", "error"); } } // Start camera stream with selected models async function startCamera() { if (cameraRunning) { showToast("Camera is already running", "warning"); return; } // Check if models are selected const checkboxes = document.querySelectorAll(".model-checkbox:checked"); const selected = Array.from(checkboxes).map((cb) => cb.dataset.modelId); if (selected.length === 0) { showToast( "Please select at least one model before starting the camera", "warning", ); return; } try { // Load previous person detection data await initializePersonDetection(); // Apply selected models const response = await fetch("/api/set-models", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ models: selected }), }); const data = await response.json(); if (!data.success) { showToast("Failed to apply model selection", "error"); return; } // Start monitoring session const sessionResponse = await fetch("/api/start-camera", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ selectedModels: selected }), }); const sessionData = await sessionResponse.json(); if (!sessionData.success) { showToast("Failed to start camera session", "error"); return; } // Update UI cameraRunning = true; sessionStartTime = new Date(); sessionData.startTime = sessionStartTime; sessionData.selectedModels = selected; // Show camera feed const cameraFeed = document.getElementById("camera-feed"); const cameraPlaceholder = document.getElementById("camera-placeholder"); const videoOverlay = document.getElementById("video-overlay"); cameraFeed.style.display = "block"; cameraFeed.src = "/camera_feed?" + new Date().getTime(); cameraPlaceholder.style.display = "none"; videoOverlay.style.display = "block"; // Update controls document.getElementById("start-btn").style.display = "none"; document.getElementById("stop-btn").style.display = "block"; // Show session info document.getElementById("camera-status-info").style.display = "block"; document.getElementById("session-models").textContent = selected.join(", "); // Initialize camera feed monitoring initializeCameraFeed(); // Start recording the session automatically try { const recordResponse = await fetch("/api/start-recording", { method: "POST", }); const recordData = await recordResponse.json(); if (recordData.success) { isRecording = true; showToast("Recording live session...", "info"); } } catch (error) { console.error("Error starting recording:", error); } // Update session duration every second setInterval(() => { if (cameraRunning) { const elapsed = Math.floor((new Date() - sessionStartTime) / 1000); const minutes = Math.floor(elapsed / 60); const seconds = elapsed % 60; document.getElementById("session-duration").textContent = `${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`; } }, 1000); showToast("Camera started with selected models", "success"); } catch (error) { console.error("Error starting camera:", error); showToast("Failed to start camera", "error"); } } // Initialize and monitor camera feed function initializeCameraFeed() { const cameraFeed = document.getElementById("camera-feed"); if (!cameraFeed) return; // Add error handling cameraFeed.addEventListener("error", function () { console.error("Camera feed error - reloading..."); if (cameraRunning) { document.getElementById("status-badge").innerHTML = ` RECONNECTING... `; // Reload feed after 2 seconds setTimeout(() => { cameraFeed.src = "/camera_feed?" + new Date().getTime(); }, 2000); } }); // Refresh camera feed periodically to ensure stream stays active const feedRefreshInterval = setInterval(() => { if (!cameraRunning) { clearInterval(feedRefreshInterval); return; } const isStreaming = cameraFeed.src && cameraFeed.src.includes("/camera_feed"); if (!isStreaming && cameraRunning) { console.log("Restarting camera feed..."); cameraFeed.src = "/camera_feed?" + new Date().getTime(); } }, 30000); // Every 30 seconds // Start polling for stats updateLiveStats(); // Initial call const statsInterval = setInterval(() => { if (cameraRunning) { updateLiveStats(); } else { clearInterval(statsInterval); } }, 1000); } async function loadAvailableModels() { try { const response = await fetch("/api/available-models"); const data = await response.json(); availableModels = data.models || {}; renderModelSelection(); } catch (error) { console.error("Error loading models:", error); showToast("Failed to load available models", "error"); } } async function loadSelectedModels() { try { const response = await fetch("/api/get-selected-models"); const data = await response.json(); selectedModels = data.selected_models || []; updateModelCheckboxes(); } catch (error) { console.error("Error loading selected models:", error); } } function renderModelSelection() { const grid = document.getElementById("models-grid"); if (!grid) return; const modelCards = Object.entries(availableModels).map( ([modelKey, modelInfo]) => { const isSelected = selectedModels.includes(modelKey); return `
No models available
"; } function updateModelCheckboxes() { const checkboxes = document.querySelectorAll(".model-checkbox"); checkboxes.forEach((checkbox) => { checkbox.checked = selectedModels.includes(checkbox.dataset.modelId); }); } function toggleModelPanel() { const content = document.getElementById("model-panel-content"); const icon = document.getElementById("model-toggle-icon"); if (!content) return; content.classList.toggle("open"); icon.textContent = content.classList.contains("open") ? "β²" : "βΌ"; } function selectAllModels() { const checkboxes = document.querySelectorAll(".model-checkbox"); checkboxes.forEach((checkbox) => { checkbox.checked = true; }); } function deselectAllModels() { const checkboxes = document.querySelectorAll(".model-checkbox"); checkboxes.forEach((checkbox) => { checkbox.checked = false; }); } async function applyModelSelection() { const checkboxes = document.querySelectorAll(".model-checkbox:checked"); const selected = Array.from(checkboxes).map((cb) => cb.dataset.modelId); try { const response = await fetch("/api/set-models", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ models: selected }), }); const data = await response.json(); if (data.success) { selectedModels = selected; showToast( `Applied ${selected.length > 0 ? selected.length + " model(s)" : "all models"}`, "success", ); } else { showToast("Failed to apply model selection", "error"); } } catch (error) { console.error("Error applying model selection:", error); showToast("Error applying model selection", "error"); } } function showToast(message, type = "info") { const container = document.getElementById("toast-container"); if (!container) return; const toast = document.createElement("div"); toast.className = `toast ${type}`; toast.textContent = message; container.appendChild(toast); setTimeout(() => { toast.remove(); }, 2600); } // ===================== Person Detection Tracking ===================== // Load previous person detection data from database async function loadPersonDetectionHistory() { try { const response = await fetch("/api/person-detection-history"); const data = await response.json(); if (data.success && data.data) { personDetectedCount = data.data.total_detections || 0; personPresent = data.data.currently_present || false; updatePersonDetectionUI(); } } catch (error) { console.error("Error loading person detection history:", error); } } // Save person detection data to database async function savePersonDetection( count, isPresent, confidence = 0.0, detailsObj = null, ) { try { const detailsData = detailsObj || { timestamp: new Date().toISOString(), session_active: cameraRunning, }; const response = await fetch("/api/save-person-detection", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ person_count: count, is_present: isPresent, confidence: confidence, detection_details: JSON.stringify(detailsData), }), }); const data = await response.json(); if (!data.success) { console.error("Failed to save person detection:", data.message); } } catch (error) { console.error("Error saving person detection:", error); } } // Update person detection UI elements function updatePersonDetectionUI() { const detectedEl = document.getElementById("person-detected"); const presentEl = document.getElementById("person-present"); if (detectedEl) { detectedEl.textContent = personDetectedCount; } if (presentEl) { presentEl.textContent = personPresent ? "β Yes" : "β No"; presentEl.style.color = personPresent ? "var(--ok)" : "var(--danger)"; } } // Track person detected in detection results (call this when processing detections) // Supports detections from YOLO with class='person' and bounding boxes function recordPersonDetection(detectionData) { let hasPersonDetection = false; let personCount = 0; let boundingBoxes = []; let avgConfidence = 0.0; if (detectionData && typeof detectionData === "object") { // Check for detections array (from YOLO/frame processing) if (Array.isArray(detectionData)) { for (const detection of detectionData) { if ( detection.class && (detection.class.toLowerCase() === "person" || detection.class.toLowerCase() === "human") ) { hasPersonDetection = true; personCount++; if (detection.bbox) { boundingBoxes.push({ bbox: detection.bbox, confidence: detection.confidence || 0.0, type: detection.type || "object", }); } if (detection.confidence) { avgConfidence += detection.confidence; } } } if (personCount > 0) { avgConfidence = avgConfidence / personCount; } } // Legacy support for object detection structure else if (detectionData.objects) { for (const obj of detectionData.objects) { if ( obj.label && (obj.label.toLowerCase() === "person" || obj.label.toLowerCase() === "human") ) { hasPersonDetection = true; personCount++; if (obj.bbox) { boundingBoxes.push({ bbox: obj.bbox, confidence: obj.confidence || 0.0, label: obj.label, }); } if (obj.confidence) { avgConfidence += obj.confidence; } } } if (personCount > 0) { avgConfidence = avgConfidence / personCount; } } // Update person detected count if person detected if (hasPersonDetection) { personDetectedCount++; personPresent = true; updatePersonDetectionUI(); // Save with bounding box information savePersonDetection(personDetectedCount, true, avgConfidence, { person_count: personCount, bounding_boxes: boundingBoxes, timestamp: new Date().toISOString(), session_active: cameraRunning, }); } } } // Call this on camera start to load previous data async function initializePersonDetection() { await loadPersonDetectionHistory(); } async function stopCamera() { if (!cameraRunning) { showToast("Camera is not running", "warning"); return; } if (!confirm("Stop camera and save the monitoring session?")) { return; } // Stop recording if active and get video filename let recordingFilename = null; if (isRecording) { isRecording = false; clearInterval(recordingInterval); try { const stopResponse = await fetch("/api/stop-recording", { method: "POST", }); const stopData = await stopResponse.json(); if (stopData.success) { recordingFilename = stopData.filename; sessionData.recordingFilename = recordingFilename; showToast("Recording saved", "success"); } } catch (error) { console.error("Error stopping recording:", error); } } // Prepare session data sessionData.endTime = new Date(); sessionData.duration = Math.floor( (sessionData.endTime - sessionData.startTime) / 1000, ); sessionData.totalDetections = detectionCount; sessionData.totalAlerts = alertCount; // Stop camera cameraRunning = false; const cameraFeed = document.getElementById("camera-feed"); cameraFeed.src = ""; cameraFeed.style.display = "none"; // Hide controls document.getElementById("stop-btn").style.display = "none"; document.getElementById("record-btn").style.display = "none"; document.getElementById("start-btn").style.display = "block"; document.getElementById("camera-status-info").style.display = "none"; // Show camera placeholder document.getElementById("camera-placeholder").style.display = "block"; document.getElementById("video-overlay").style.display = "none"; // Save session to database saveMonitoringSession(sessionData); } async function saveMonitoringSession(session) { try { const response = await fetch("/api/save-monitoring-session", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(session), }); const data = await response.json(); if (data.success) { showToast("Monitoring session saved successfully", "success"); // Show session summary const summary = `Duration: ${Math.floor(session.duration / 60)}:${String(session.duration % 60).padStart(2, "0")}
Total Detections: ${session.totalDetections}
Total Alerts: ${session.totalAlerts}
Models Used: ${session.selectedModels.join(", ")}
`; // You could show this in a modal or in the alerts panel setTimeout(() => { alert( `Session saved!\n\nDuration: ${Math.floor(session.duration / 60)}:${String(session.duration % 60).padStart(2, "0")}\nDetections: ${session.totalDetections}\nAlerts: ${session.totalAlerts}`, ); }, 500); // Reset counters detectionCount = 0; alertCount = 0; document.getElementById("total-detections").textContent = "0"; document.getElementById("total-alerts").textContent = "0"; } else { showToast("Failed to save monitoring session: " + data.error, "error"); } } catch (error) { console.error("Error saving monitoring session:", error); showToast("Error saving session to database", "error"); } } function toggleFullscreen() { const videoContainer = document.querySelector(".video-container"); if (!document.fullscreenElement) { if (videoContainer.requestFullscreen) { videoContainer.requestFullscreen(); } else if (videoContainer.webkitRequestFullscreen) { videoContainer.webkitRequestFullscreen(); } else if (videoContainer.msRequestFullscreen) { videoContainer.msRequestFullscreen(); } } else { if (document.exitFullscreen) { document.exitFullscreen(); } } } function addAlert(alertData) { const alertsContainer = document.getElementById("alerts-container"); // Remove placeholder if exists const placeholder = alertsContainer.querySelector(".alert-placeholder"); if (placeholder) { placeholder.remove(); } const alertDiv = document.createElement("div"); alertDiv.className = "alert-item"; if (alertData.severity === "HIGH" || alertData.severity === "HIGH RISK") { alertDiv.classList.add("high"); } const timestamp = new Date().toLocaleTimeString(); alertDiv.innerHTML = `πΈ No detections yet
Detected threats will appear hereType: ${type.toUpperCase()}
Alert Level: ${level}
Detected: ${timestamp}
Loadingβ¦
Failed to load storage data.
No sessions saved yet.
Sessions are recorded automatically when you stop the camera.
No raw recordings yet.
Use the Record button during live monitoring.
No detection images yet.
High-risk detections are automatically captured.