diff --git a/app.py b/app.py index b19333f..ff84233 100644 --- a/app.py +++ b/app.py @@ -466,6 +466,7 @@ class WebcamGrabber: 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.paused = False # manuell freigegeben -> ESP32-Slot freigeben self.reset_flag = Event() self.line = dict(SAVED_LINE) @@ -507,6 +508,8 @@ class WebcamGrabber: def _wants_stream(self) -> bool: """Soll der Grabber gerade die Kamera bedienen? Mit Karenzzeit, damit kurzes Zuschauer-Aus (z.B. -Reload) die Verbindung nicht kappt.""" + if self.paused: + return False # manuell freigegeben -> ESP32-Slot freigeben if GRABBER_ALWAYS_ON: return True if self._viewer_count() > 0: @@ -514,6 +517,12 @@ class WebcamGrabber: return True return (time.time() - self._last_active_ts) < VIEWER_GRACE_SEC + def set_paused(self, paused: bool) -> bool: + """Grabber manuell pausieren (Kamera freigeben) bzw. wieder aufnehmen.""" + self.paused = bool(paused) + print(f"[webcam-grabber] {'pausiert (Slot frei)' if self.paused else 'aktiv'}", flush=True) + return self.paused + def _current_line(self): with self.lock: line = dict(self.line) @@ -817,7 +826,15 @@ def get_counting_line(): @app.route("/api/webcam_status", methods=["GET"]) def webcam_status(): """Liefert, ob der Webcam-Grabber gerade Frames bekommt (Kamera online).""" - return jsonify({"online": webcam.is_online()}) + return jsonify({"online": webcam.is_online(), "paused": webcam.paused}) + + +@app.route("/api/grabber_toggle", methods=["POST"]) +def grabber_toggle(): + """Grabber pausieren/aufnehmen: pausiert gibt den ESP32-Slot frei + (Zaehlung aus), aktiv verbindet wieder zur Kamera (Zaehlung an).""" + paused = webcam.set_paused(not webcam.paused) + return jsonify({"paused": paused}) @app.route("/api/reset_count", methods=["POST"]) diff --git a/templates/webcam.html b/templates/webcam.html index 00e981a..71d3f60 100644 --- a/templates/webcam.html +++ b/templates/webcam.html @@ -144,14 +144,15 @@
-
đź“·
-
Kamera offline
-
Versuche neu zu verbinden…
+
đź“·
+
Kamera offline
+
Versuche neu zu verbinden…
+
Klicke auf "Zähllinie setzen" und dann zweimal auf das Video, um die Zähllinie zu definieren.
Back to Home @@ -288,20 +289,36 @@ // --- Kamera-Online-Status pollen + Auto-Recovery des Streams --- const videoFeed = document.getElementById('videoFeed'); const offlineOverlay = document.getElementById('offlineOverlay'); + const ovIcon = document.getElementById('ovIcon'); + const ovMsg = document.getElementById('ovMsg'); + const ovSub = document.getElementById('ovSub'); + const grabberBtn = document.getElementById('grabberBtn'); 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" + + function showOverlay(icon, msg, sub) { + ovIcon.textContent = icon; ovMsg.textContent = msg; ovSub.textContent = sub; + offlineOverlay.classList.add('show'); + } + async function checkCamStatus() { - let online = false; + let online = false, paused = false; try { - const res = await fetch('/api/webcam_status', { cache: 'no-store' }); - online = (await res.json()).online; - } catch (e) { - online = false; + const d = await (await fetch('/api/webcam_status', { cache: 'no-store' })).json(); + online = d.online; paused = d.paused; + } catch (e) { online = false; } + + grabberBtn.textContent = paused ? 'Kamera übernehmen' : 'Kamera freigeben'; + grabberBtn.classList.toggle('active', paused); + + if (paused) { + showOverlay('⏸️', 'Kamera freigegeben', 'Slot frei für anderen Zugriff – Zählung pausiert'); + offlineStreak = 0; + reloadPending = true; // beim Wiederaufnehmen Stream neu laden + return; } 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; @@ -311,11 +328,21 @@ } else { offlineStreak++; if (offlineStreak >= OFFLINE_POLLS) { - offlineOverlay.classList.add('show'); + showOverlay('📷', 'Kamera offline', 'Versuche neu zu verbinden…'); reloadPending = true; } } } + + grabberBtn.addEventListener('click', async () => { + grabberBtn.disabled = true; + try { + await fetch('/api/grabber_toggle', { method: 'POST' }); + } catch (e) { /* ignorieren */ } + grabberBtn.disabled = false; + checkCamStatus(); + }); + checkCamStatus(); setInterval(checkCamStatus, 2000);