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).
|
||||
# 1 = dauerhaft verbinden + zaehlen, egal ob jemand zuschaut.
|
||||
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.
|
||||
try:
|
||||
@@ -460,6 +465,7 @@ class WebcamGrabber:
|
||||
self.frame_seq = 0
|
||||
self.last_frame_ts = 0.0 # time.time() des letzten gelieferten Frames
|
||||
self.viewers = 0
|
||||
self._last_active_ts = 0.0 # letzter Zeitpunkt mit Zuschauer (Karenzzeit)
|
||||
self.reset_flag = Event()
|
||||
self.line = dict(SAVED_LINE)
|
||||
|
||||
@@ -498,6 +504,16 @@ class WebcamGrabber:
|
||||
with self.lock:
|
||||
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):
|
||||
with self.lock:
|
||||
line = dict(self.line)
|
||||
@@ -564,8 +580,8 @@ class WebcamGrabber:
|
||||
state = new_state()
|
||||
|
||||
while True:
|
||||
# Ohne Always-on: nur verbinden, wenn jemand zuschaut (gibt ESP32-Slot frei).
|
||||
if not GRABBER_ALWAYS_ON and self._viewer_count() == 0:
|
||||
# Ohne Always-on: nur verbinden, wenn jemand zuschaut (mit Karenzzeit).
|
||||
if not self._wants_stream():
|
||||
if self.latest_jpeg is not None:
|
||||
self._clear()
|
||||
time.sleep(0.3)
|
||||
@@ -587,8 +603,8 @@ class WebcamGrabber:
|
||||
|
||||
buf = b""
|
||||
for chunk in resp.iter_content(chunk_size=8192):
|
||||
if not GRABBER_ALWAYS_ON and self._viewer_count() == 0:
|
||||
break # letzter Viewer weg -> Verbindung freigeben
|
||||
if not self._wants_stream():
|
||||
break # Zuschauer (inkl. Karenz) weg -> Verbindung freigeben
|
||||
if not chunk:
|
||||
continue
|
||||
|
||||
|
||||
@@ -288,7 +288,9 @@
|
||||
// --- Kamera-Online-Status pollen + Auto-Recovery des Streams ---
|
||||
const videoFeed = document.getElementById('videoFeed');
|
||||
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() {
|
||||
let online = false;
|
||||
try {
|
||||
@@ -297,12 +299,22 @@
|
||||
} catch (e) {
|
||||
online = false;
|
||||
}
|
||||
if (online && camWasOffline) {
|
||||
// MJPEG-<img> reconnectet nach Stream-Abbruch nicht von selbst -> neu anstossen
|
||||
videoFeed.src = "{{ url_for('webcam_feed') }}?t=" + Date.now();
|
||||
if (online) {
|
||||
// Nur nach einem laengeren Ausfall den MJPEG-Stream neu anstossen
|
||||
// (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();
|
||||
setInterval(checkCamStatus, 2000);
|
||||
|
||||
Reference in New Issue
Block a user