diff --git a/Misc/multiCameraViewer.htm b/Misc/multiCameraViewer.htm index 11484cc..4b80b47 100644 --- a/Misc/multiCameraViewer.htm +++ b/Misc/multiCameraViewer.htm @@ -11,8 +11,10 @@ Add camera IPs under "ADD YOUR CAMERA IPs HERE" Modify load settings keys under 'load settings with keypress' + + Specify config on url: http://x.x.x.x?config=xxx - 26May25 + 03Jun25 --> @@ -20,7 +22,7 @@ - Camera Viewer with Motion Detection + Camera Viewer @@ -89,8 +91,8 @@ position: absolute; top: 4px; right: 4px; - background: #333; - color: white; + background: rgba(0,0,0,0); + color: #00A; border: none; font-size: 16px; cursor: pointer; @@ -101,11 +103,11 @@ .cam-title { position: absolute; top: 4px; - left: 8px; - color: #fff; - background: rgba(0,0,0,0.5); + left: 5px; + color: #00A; + background: rgba(0,0,0,0); padding: 2px 6px; - font-size: 14px; + font-size: 16px; z-index: 5; pointer-events: none; } @@ -113,31 +115,31 @@ /* Styles for motion detection controls */ .motion-controls { position: absolute; - bottom: 4px; - left: 8px; - background: rgba(0,0,0,0.5); - padding: 3px 6px; - border-radius: 3px; + top: 26px; + left: 5px; + background: rgba(0,0,0,0); + padding: 0px 0px; + border-radius: 0px; z-index: 10; - color: white; - font-size: 12px; + color: #00A; + font-size: 8px; display: flex; align-items: center; } .motion-controls label { - margin-right: 5px; + margin-right: 0px; cursor: pointer; } .motion-controls input[type="checkbox"] { - margin-right: 5px; + margin-right: 0px; cursor: pointer; } .motion-controls input[type="range"] { - width: 80px; - height: 10px; + width: 120px; + height: 8px; cursor: pointer; display: none; /* Hidden by default */ margin-left: 5px; @@ -188,7 +190,7 @@ } // --- END DEBUGGING --- - + // load settings with keypress ('r' and 'b' unavailable) const keyConfigMap = [ { key: 'm', config: 'main' }, { key: 's', config: 'small' }, @@ -203,7 +205,7 @@ const currentDisplay = window.getComputedStyle(tb).display; tb.style.display = (currentDisplay === 'none' || tb.style.display === 'none') ? 'block' : 'none'; } - if (e.key === 'c') resetCameras(); + if (e.key === 'r') resetCameras(); const match = keyConfigMap.find(item => e.key === item.key); if (match) { @@ -223,7 +225,7 @@ const cameras = [ { src: "http://192.168.1.100/jpg", id: "Front" }, { src: "http://192.168.1.101/jpg", id: "Side" }, - { src: "http://192.168.1.102/jpg", id: "Back" }, + { src: "http://192.168.1.102/jpg", id: "Back" } ]; const toolbar = document.getElementById('toolbar'); @@ -310,15 +312,17 @@ function showInstructions() { alert(`CCTV Camera Viewer with Motion Detection - - Click a camera button to open its feed. - - Save/load/import/export configurations. - - Drag/resize windows. + - Click camera button to open its feed. + - Save/Load/Import/Export configuration. + - Drag/Resize windows. - SHIFT + Scroll to zoom, SHIFT + drag to pan. - - Motion Detection: Check box to enable, + - Motion Detection: Check box to enable; use slider for sensitivity. - - Press 'b' to hide/show the toolbar. - - Quick load configs: 'm', 's', 'f'. - - Reset to close all feeds (or press 'C'). + - 'b' hide/show the toolbar. + - Quick load configs: + 'm' Main, 's' Side, 'f' Front. + - 'r'/Reset close all feeds. + - Specify config on URL: ?config= `); } @@ -370,20 +374,27 @@ debugLog('State saved.'); } - function loadState() { + function getQueryParam(name) { + const params = new URLSearchParams(window.location.search); + return params.get(name); + } + + function loadState(configName) { debugLog('Loading state...'); - // First, clear all existing cameras and their loops properly + + // Clear existing cameras document.querySelectorAll('.img-container').forEach(el => { - if (el._zoomPanState) { - debugLog(`Load: Setting isActive=false for old ${el.id}`); - el._zoomPanState.isActive = false; - } + if (el._zoomPanState) el._zoomPanState.isActive = false; el.remove(); }); - const stateString = localStorage.getItem('cameraState'); + // Get state from URL config or fallback to default + const stateString = configName + ? localStorage.getItem('cameraState_' + configName) + : localStorage.getItem('cameraState'); + if (!stateString) { - debugLog('No state found in localStorage to load.'); + debugLog('No state found to load.'); return; } @@ -392,28 +403,21 @@ stateToLoad = JSON.parse(stateString); } catch (e) { console.error("Error parsing saved state:", e); - alert("Error loading saved state. It might be corrupted."); - localStorage.removeItem('cameraState'); // Clear corrupted state + alert("Error loading saved state."); return; } - if (Object.keys(stateToLoad).length === 0) { - debugLog('Saved state is empty. Nothing to load.'); - return; + for (const camDef of cameras) { + (stateToLoad[camDef.id] || []).forEach(cfg => { + createCameraWindow(camDef, cfg); + }); } - for (const camDef of cameras) { // Iterate over defined cameras - if (stateToLoad[camDef.id]) { // If this camera type has saved instances - (stateToLoad[camDef.id] || []).forEach(cfg => { - debugLog(`Loading config for ${camDef.id} (unique: ${cfg.uniqueId})`); - createCameraWindow(camDef, cfg); - }); - } - } debugLog('State loaded.'); } + function initAudio() { if (!audioContext) { try { @@ -491,7 +495,7 @@
- +
@@ -500,12 +504,36 @@ initContainer(div, cam.src, saved); } - function compareFrames(ctx1, ctx2, width, height, sensitivity) { + function compareFrames(ctx1, ctx2, width, height, sensitivity, containerId = "unknown_cam") { // Added containerId for logging const imgData1 = ctx1.getImageData(0, 0, width, height).data; const imgData2 = ctx2.getImageData(0, 0, width, height).data; let diffCount = 0; - const threshold = 25; - const requiredDiffPixels = (101 - sensitivity) * (width * height / 400); + + // --- MODIFIED PARAMETERS --- + // Original: const threshold = 10; + const threshold = 8; // Lowered from 10. Defines how much a single pixel's grayscale value needs to change. + // This makes individual pixel changes easier to detect. + + // Original calculation was: (101 - sensitivity) * (width * height / 400) + // New calculation aims for a less steep sensitivity curve and generally more sensitivity. + + // N_scaler: Compresses the effect of the sensitivity slider. + // A higher N_scaler means the slider has a less dramatic effect (curve is flatter). + // Original formula implicitly had N_scaler around 1.0 relative to its 1-100 range. + const N_scaler = 2.0; + + // baseDivisor: Affects overall sensitivity. Higher baseDivisor = more sensitive (fewer pixels needed). + // Original was 400.0. + const baseDivisor = 800.0; + + // 'sensitivity' is the slider value from 1 (low sensitivity) to 100 (high sensitivity). + // We want 'effectiveSensitivityFactor' to be low for high slider sensitivity, and high for low slider sensitivity. + // When slider sensitivity = 100 (max), effectiveSensitivityFactor = ((100-100)/2.0) + 1.0 = 1.0 (most sensitive factor part). + // When slider sensitivity = 1 (min), effectiveSensitivityFactor = ((100-1)/2.0) + 1.0 = 49.5 + 1.0 = 50.5 (least sensitive factor part). + const effectiveSensitivityFactor = ((100.0 - parseFloat(sensitivity)) / N_scaler) + 1.0; + + const requiredDiffPixels = effectiveSensitivityFactor * (width * height / baseDivisor); + // --- END MODIFIED PARAMETERS --- for (let i = 0; i < imgData1.length; i += 4) { const gray1 = (imgData1[i] + imgData1[i + 1] + imgData1[i + 2]) / 3; @@ -513,7 +541,12 @@ if (Math.abs(gray1 - gray2) > threshold) diffCount++; } const motionDetected = diffCount > requiredDiffPixels; - // debugLog(`compareFrames: diffCount=${diffCount}, required=${requiredDiffPixels.toFixed(2)}, detected=${motionDetected}`); // Very spammy + + // For debugging the new values: + // Only log ~10% of the calls to reduce console spam, if DEBUG_MOTION is true. + if (DEBUG_MOTION && Math.random() < 0.1) { // Adjust 0.1 (10%) as needed for logging frequency + debugLog(`[${containerId}] compareFrames (slider: ${sensitivity}): diffCount=${diffCount}, reqDiffPixels=${requiredDiffPixels.toFixed(2)}, effectiveSensFactor=${effectiveSensitivityFactor.toFixed(2)}, threshold=${threshold}, detected=${motionDetected}`); + } return motionDetected; } @@ -743,7 +776,7 @@ if (prevImageLoaded) { // debugLog(`[${container.id}] Comparing frames.`); // Spammy - if (compareFrames(ctx1, ctx2, motionWidth, motionHeight, state.motionSensitivity)) { + if (compareFrames(ctx1, ctx2, motionWidth, motionHeight, state.motionSensitivity, container.id)) { debugLog(`[${container.id}] Motion DETECTED! Calling playBeep.`); playBeep(container.id); motionIndicator.style.display = 'block'; @@ -805,7 +838,8 @@ } // Initial load of state from localStorage when the page loads - loadState(); + const startupConfig = getQueryParam('config'); + loadState(startupConfig);