Fix: Offline-Erkennung loeste Reconnect-Resets aus (Zaehlung gestoert)
Bei GRABBER_ALWAYS_ON=0 kappte kurzes Zuschauer-Aus (<img>-Reload der Auto-Recovery) die Kameraverbindung -> Reconnect -> Tracker/Zaehlzustand wurde zurueckgesetzt. Bei ~1 FPS riss das die Zaehlung auseinander. - Grabber: Karenzzeit (VIEWER_GRACE_SEC, Default 15s) bevor die Kamera bei fehlenden Zuschauern freigegeben wird -> kein Reconnect-Churn - Frontend: Overlay/Reload erst nach ~6s echtem Ausfall (3 Polls), nicht bei einzelnen langsamen Frames -> kein Verbindungs-Churn Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
24
app.py
24
app.py
@@ -53,6 +53,11 @@ MOTION_PIXELS = int(os.environ.get("MOTION_PIXELS", "500"))
|
|||||||
# 0 = nur zaehlen/streamen, wenn ein Browser zuschaut (gibt ESP32-Slot frei).
|
# 0 = nur zaehlen/streamen, wenn ein Browser zuschaut (gibt ESP32-Slot frei).
|
||||||
# 1 = dauerhaft verbinden + zaehlen, egal ob jemand zuschaut.
|
# 1 = dauerhaft verbinden + zaehlen, egal ob jemand zuschaut.
|
||||||
GRABBER_ALWAYS_ON = os.environ.get("GRABBER_ALWAYS_ON", "0") == "1"
|
GRABBER_ALWAYS_ON = os.environ.get("GRABBER_ALWAYS_ON", "0") == "1"
|
||||||
|
# Karenzzeit: nach dem letzten Zuschauer noch so viele Sekunden weiterstreamen,
|
||||||
|
# bevor die Kameraverbindung freigegeben wird. Verhindert, dass kurzes
|
||||||
|
# Zuschauer-Aus (z.B. <img>-Reload durch die Offline-Erkennung) staendig
|
||||||
|
# Reconnects samt Tracker-/Zaehlzustand-Reset ausloest.
|
||||||
|
VIEWER_GRACE_SEC = float(os.environ.get("VIEWER_GRACE_SEC", "15"))
|
||||||
|
|
||||||
# FP16 nur auf CUDA sinnvoll -> automatisch erkennen, per Env erzwingbar.
|
# FP16 nur auf CUDA sinnvoll -> automatisch erkennen, per Env erzwingbar.
|
||||||
try:
|
try:
|
||||||
@@ -460,6 +465,7 @@ class WebcamGrabber:
|
|||||||
self.frame_seq = 0
|
self.frame_seq = 0
|
||||||
self.last_frame_ts = 0.0 # time.time() des letzten gelieferten Frames
|
self.last_frame_ts = 0.0 # time.time() des letzten gelieferten Frames
|
||||||
self.viewers = 0
|
self.viewers = 0
|
||||||
|
self._last_active_ts = 0.0 # letzter Zeitpunkt mit Zuschauer (Karenzzeit)
|
||||||
self.reset_flag = Event()
|
self.reset_flag = Event()
|
||||||
self.line = dict(SAVED_LINE)
|
self.line = dict(SAVED_LINE)
|
||||||
|
|
||||||
@@ -498,6 +504,16 @@ class WebcamGrabber:
|
|||||||
with self.lock:
|
with self.lock:
|
||||||
return self.viewers
|
return self.viewers
|
||||||
|
|
||||||
|
def _wants_stream(self) -> bool:
|
||||||
|
"""Soll der Grabber gerade die Kamera bedienen? Mit Karenzzeit, damit
|
||||||
|
kurzes Zuschauer-Aus (z.B. <img>-Reload) die Verbindung nicht kappt."""
|
||||||
|
if GRABBER_ALWAYS_ON:
|
||||||
|
return True
|
||||||
|
if self._viewer_count() > 0:
|
||||||
|
self._last_active_ts = time.time()
|
||||||
|
return True
|
||||||
|
return (time.time() - self._last_active_ts) < VIEWER_GRACE_SEC
|
||||||
|
|
||||||
def _current_line(self):
|
def _current_line(self):
|
||||||
with self.lock:
|
with self.lock:
|
||||||
line = dict(self.line)
|
line = dict(self.line)
|
||||||
@@ -564,8 +580,8 @@ class WebcamGrabber:
|
|||||||
state = new_state()
|
state = new_state()
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
# Ohne Always-on: nur verbinden, wenn jemand zuschaut (gibt ESP32-Slot frei).
|
# Ohne Always-on: nur verbinden, wenn jemand zuschaut (mit Karenzzeit).
|
||||||
if not GRABBER_ALWAYS_ON and self._viewer_count() == 0:
|
if not self._wants_stream():
|
||||||
if self.latest_jpeg is not None:
|
if self.latest_jpeg is not None:
|
||||||
self._clear()
|
self._clear()
|
||||||
time.sleep(0.3)
|
time.sleep(0.3)
|
||||||
@@ -587,8 +603,8 @@ class WebcamGrabber:
|
|||||||
|
|
||||||
buf = b""
|
buf = b""
|
||||||
for chunk in resp.iter_content(chunk_size=8192):
|
for chunk in resp.iter_content(chunk_size=8192):
|
||||||
if not GRABBER_ALWAYS_ON and self._viewer_count() == 0:
|
if not self._wants_stream():
|
||||||
break # letzter Viewer weg -> Verbindung freigeben
|
break # Zuschauer (inkl. Karenz) weg -> Verbindung freigeben
|
||||||
if not chunk:
|
if not chunk:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|||||||
@@ -288,7 +288,9 @@
|
|||||||
// --- Kamera-Online-Status pollen + Auto-Recovery des Streams ---
|
// --- Kamera-Online-Status pollen + Auto-Recovery des Streams ---
|
||||||
const videoFeed = document.getElementById('videoFeed');
|
const videoFeed = document.getElementById('videoFeed');
|
||||||
const offlineOverlay = document.getElementById('offlineOverlay');
|
const offlineOverlay = document.getElementById('offlineOverlay');
|
||||||
let camWasOffline = false;
|
let offlineStreak = 0; // aufeinanderfolgende Offline-Abfragen
|
||||||
|
let reloadPending = false; // erst nach echtem Ausfall den Stream neu laden
|
||||||
|
const OFFLINE_POLLS = 3; // ~6s -> erst dann "wirklich offline"
|
||||||
async function checkCamStatus() {
|
async function checkCamStatus() {
|
||||||
let online = false;
|
let online = false;
|
||||||
try {
|
try {
|
||||||
@@ -297,12 +299,22 @@
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
online = false;
|
online = false;
|
||||||
}
|
}
|
||||||
if (online && camWasOffline) {
|
if (online) {
|
||||||
// MJPEG-<img> reconnectet nach Stream-Abbruch nicht von selbst -> neu anstossen
|
// Nur nach einem laengeren Ausfall den MJPEG-Stream neu anstossen
|
||||||
videoFeed.src = "{{ url_for('webcam_feed') }}?t=" + Date.now();
|
// (sonst Verbindungs-/Reconnect-Churn bei kurzen Aussetzern).
|
||||||
|
if (reloadPending) {
|
||||||
|
videoFeed.src = "{{ url_for('webcam_feed') }}?t=" + Date.now();
|
||||||
|
reloadPending = false;
|
||||||
|
}
|
||||||
|
offlineStreak = 0;
|
||||||
|
offlineOverlay.classList.remove('show');
|
||||||
|
} else {
|
||||||
|
offlineStreak++;
|
||||||
|
if (offlineStreak >= OFFLINE_POLLS) {
|
||||||
|
offlineOverlay.classList.add('show');
|
||||||
|
reloadPending = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
camWasOffline = !online;
|
|
||||||
offlineOverlay.classList.toggle('show', !online);
|
|
||||||
}
|
}
|
||||||
checkCamStatus();
|
checkCamStatus();
|
||||||
setInterval(checkCamStatus, 2000);
|
setInterval(checkCamStatus, 2000);
|
||||||
|
|||||||
Reference in New Issue
Block a user