From 6be017c4f03f6b96e24dc50bddbe957752f0b25c Mon Sep 17 00:00:00 2001 From: dsutanto Date: Mon, 10 Aug 2026 09:53:55 +0700 Subject: [PATCH] Auto-on live preview when recount starts (all recounting templates) --- SPEC.md | 2 ++ templates/recounting.html | 25 ++++++++++++++++---- templates/recounting_lamborghini.html | 25 ++++++++++++++++---- templates/recounting_upload.html | 25 ++++++++++++++++---- templates/recounting_upload_lamborghini.html | 25 ++++++++++++++++---- 5 files changed, 82 insertions(+), 20 deletions(-) diff --git a/SPEC.md b/SPEC.md index f86865a..0282517 100644 --- a/SPEC.md +++ b/SPEC.md @@ -159,6 +159,7 @@ V18: .env missing → scripts fail at import (os.getenv falls back to defaults, V19: stream reconnect → reset motion detection state (prev_gray = None) V20: reset → delete STATE_FILE + touch {SHM_DIR}/.reset; batch_store._check_reset_signal() watches marker & clears in-memory state before next crossing V21: upload filename batch_XX_YYYYMMDD_HHmmSS.mp4 → batch# + date parsed; batch-result queries /api/day-detail/ on live API +V22: preview auto-on when recount running — enablePreview() on START RECOUNT + applyState streaming branch; manual toggle overrides; img.onerror retries cache-busted src while streaming ``` ## §T — Tasks @@ -181,6 +182,7 @@ T13|x|add template system — DASHBOARD_TEMPLATE env var selects dashboard.html| T14|x|harden reset API — separate SHM marker creation from state file cleanup; return 500 on SHM failure|V20 T15|x|add upload recounting dashboard — MP4 upload, progress bar, RECOUNT_CMD replay|V21 T16|x|add cross-dashboard header links — UPLOAD_DASHBOARD_URL (live→upload) & LIVE_DASHBOARD_URL (upload→live)|I.config +T17|x|auto-on live preview when recount running — enablePreview() on START + applyState; onerror retry while streaming|V22 ``` ## §B — Bugs diff --git a/templates/recounting.html b/templates/recounting.html index cd9a9b1..22bbad4 100644 --- a/templates/recounting.html +++ b/templates/recounting.html @@ -483,6 +483,7 @@ function applyState(data) { document.getElementById('video-status').innerHTML = ' streaming ' + (data.file_name || ''); document.getElementById('video-status').className = 'video-status connected'; + enablePreview(); loadBatchResult(data.file); } else if (data.file && selectedFile === data.file) { @@ -669,6 +670,7 @@ async function startRecount() { document.getElementById('video-status').innerHTML = ' streaming ' + data.file; document.getElementById('video-status').className = 'video-status connected'; + enablePreview(); } catch (err) { alert('Error: ' + err.message); btnStart.disabled = false; @@ -732,11 +734,7 @@ function togglePreview() { btn.textContent = 'STREAM OFF'; btn.classList.remove('active'); } else { - img.src = streamUrl + '?' + Date.now(); - img.style.display = 'block'; - if (placeholder) placeholder.style.display = 'none'; - btn.textContent = 'STREAM ON'; - btn.classList.add('active'); + enablePreview(); } } @@ -751,6 +749,23 @@ function resetVideoToggle() { btn.classList.remove('active'); } +function enablePreview() { + const btn = document.getElementById('videoToggle'); + const img = document.getElementById('stream-img'); + const placeholder = document.getElementById('video-placeholder'); + if (!streamUrl) return; + img.onerror = function() { + if (streaming && streamUrl) { + img.src = streamUrl + '?' + Date.now(); + } + }; + img.src = streamUrl + '?' + Date.now(); + img.style.display = 'block'; + if (placeholder) placeholder.style.display = 'none'; + btn.textContent = 'STREAM ON'; + btn.classList.add('active'); +} + let lastRecountCount = null; let lastRecountTime = null; diff --git a/templates/recounting_lamborghini.html b/templates/recounting_lamborghini.html index e42d69c..fa3058f 100644 --- a/templates/recounting_lamborghini.html +++ b/templates/recounting_lamborghini.html @@ -405,6 +405,7 @@ function applyState(data) { document.getElementById('video-status').innerHTML = ' streaming ' + (data.file_name || ''); document.getElementById('video-status').className = 'video-status connected'; + enablePreview(); loadBatchResult(data.file); } else if (data.file && selectedFile === data.file) { @@ -595,6 +596,7 @@ async function startRecount() { document.getElementById('video-status').innerHTML = ' streaming ' + data.file; document.getElementById('video-status').className = 'video-status connected'; + enablePreview(); } catch (err) { alert('Error: ' + err.message); btnStart.disabled = false; @@ -655,11 +657,7 @@ function togglePreview() { btn.textContent = 'STREAM OFF'; btn.classList.remove('active'); } else { - img.src = streamUrl + '?' + Date.now(); - img.style.display = 'block'; - if (placeholder) placeholder.style.display = 'none'; - btn.textContent = 'STREAM ON'; - btn.classList.add('active'); + enablePreview(); } } @@ -674,6 +672,23 @@ function resetVideoToggle() { btn.classList.remove('active'); } +function enablePreview() { + const btn = document.getElementById('videoToggle'); + const img = document.getElementById('stream-img'); + const placeholder = document.getElementById('video-placeholder'); + if (!streamUrl) return; + img.onerror = function() { + if (streaming && streamUrl) { + img.src = streamUrl + '?' + Date.now(); + } + }; + img.src = streamUrl + '?' + Date.now(); + img.style.display = 'block'; + if (placeholder) placeholder.style.display = 'none'; + btn.textContent = 'STREAM ON'; + btn.classList.add('active'); +} + // --- Recount counter --- let lastRecountCount = null; diff --git a/templates/recounting_upload.html b/templates/recounting_upload.html index d9e24d8..8ee5863 100644 --- a/templates/recounting_upload.html +++ b/templates/recounting_upload.html @@ -782,6 +782,7 @@ async function startRecount() { document.getElementById('video-status').innerHTML = ' streaming ' + data.file; document.getElementById('video-status').className = 'video-status connected'; + enablePreview(); } catch (err) { alert('Error: ' + err.message); btnStart.disabled = false; @@ -845,11 +846,7 @@ function togglePreview() { btn.textContent = 'STREAM OFF'; btn.classList.remove('active'); } else { - img.src = streamUrl + '?' + Date.now(); - img.style.display = 'block'; - if (placeholder) placeholder.style.display = 'none'; - btn.textContent = 'STREAM ON'; - btn.classList.add('active'); + enablePreview(); } } @@ -864,6 +861,23 @@ function resetVideoToggle() { btn.classList.remove('active'); } +function enablePreview() { + var btn = document.getElementById('videoToggle'); + var img = document.getElementById('stream-img'); + var placeholder = document.getElementById('video-placeholder'); + if (!streamUrl) return; + img.onerror = function() { + if (streaming && streamUrl) { + img.src = streamUrl + '?' + Date.now(); + } + }; + img.src = streamUrl + '?' + Date.now(); + img.style.display = 'block'; + if (placeholder) placeholder.style.display = 'none'; + btn.textContent = 'STREAM ON'; + btn.classList.add('active'); +} + async function syncState() { try { var res = await fetch('/api/state'); @@ -888,6 +902,7 @@ function applyState(data) { document.getElementById('video-status').innerHTML = ' streaming ' + (data.file_name || ''); document.getElementById('video-status').className = 'video-status connected'; + enablePreview(); loadBatchResult(data.file); } else if (data.file && selectedFile === data.file) { diff --git a/templates/recounting_upload_lamborghini.html b/templates/recounting_upload_lamborghini.html index cf66103..bbdee5d 100644 --- a/templates/recounting_upload_lamborghini.html +++ b/templates/recounting_upload_lamborghini.html @@ -661,6 +661,7 @@ async function startRecount() { document.getElementById('video-status').innerHTML = ' streaming ' + data.file; document.getElementById('video-status').className = 'video-status connected'; + enablePreview(); } catch (err) { alert('Error: ' + err.message); btnStart.disabled = false; @@ -724,11 +725,7 @@ function togglePreview() { btn.textContent = 'STREAM OFF'; btn.classList.remove('active'); } else { - img.src = streamUrl + '?' + Date.now(); - img.style.display = 'block'; - if (placeholder) placeholder.style.display = 'none'; - btn.textContent = 'STREAM ON'; - btn.classList.add('active'); + enablePreview(); } } @@ -743,6 +740,23 @@ function resetVideoToggle() { btn.classList.remove('active'); } +function enablePreview() { + var btn = document.getElementById('videoToggle'); + var img = document.getElementById('stream-img'); + var placeholder = document.getElementById('video-placeholder'); + if (!streamUrl) return; + img.onerror = function() { + if (streaming && streamUrl) { + img.src = streamUrl + '?' + Date.now(); + } + }; + img.src = streamUrl + '?' + Date.now(); + img.style.display = 'block'; + if (placeholder) placeholder.style.display = 'none'; + btn.textContent = 'STREAM ON'; + btn.classList.add('active'); +} + async function syncState() { try { var res = await fetch('/api/state'); @@ -767,6 +781,7 @@ function applyState(data) { document.getElementById('video-status').innerHTML = ' streaming ' + (data.file_name || ''); document.getElementById('video-status').className = 'video-status connected'; + enablePreview(); loadBatchResult(data.file); } else if (data.file && selectedFile === data.file) {