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