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);