diff --git a/recorder.py b/recorder.py index af4d867..96fc29c 100644 --- a/recorder.py +++ b/recorder.py @@ -9,7 +9,7 @@ from pathlib import Path from zoneinfo import ZoneInfo import requests -from flask import Flask, render_template_string, send_from_directory, abort, Response, request +from flask import Flask, render_template_string, send_from_directory, abort, Response, request, stream_with_context # --------------------------------------------------------------------------- @@ -70,6 +70,7 @@ NORMALIZE_TRUE_PEAK = os.environ.get("NORMALIZE_TRUE_PEAK", "-1.5") NORMALIZE_LRA = os.environ.get("NORMALIZE_LRA", "11") + # --------------------------------------------------------------------------- # Application state # --------------------------------------------------------------------------- @@ -90,6 +91,9 @@ last_status_ok = False last_status_check = None last_status_error = None +SSE_CLIENTS = set() +SSE_LOCK = threading.Lock() + # --------------------------------------------------------------------------- # Helpers @@ -294,6 +298,43 @@ def query_icecast(): return None + +def broadcast_event(event, filename=None): + payload = json.dumps({ + "event": event, + "filename": filename, + }, ensure_ascii=False) + dead = [] + with SSE_LOCK: + for client_queue in list(SSE_CLIENTS): + try: + client_queue.put_nowait(payload) + except Exception: + dead.append(client_queue) + for client_queue in dead: + SSE_CLIENTS.discard(client_queue) + + +def sse_stream(): + import queue + + client_queue = queue.Queue() + with SSE_LOCK: + SSE_CLIENTS.add(client_queue) + + try: + yield ": connected\n\n" + while True: + try: + payload = client_queue.get(timeout=20) + yield f"data: {payload}\n\n" + except queue.Empty: + yield ": keepalive\n\n" + finally: + with SSE_LOCK: + SSE_CLIENTS.discard(client_queue) + + # --------------------------------------------------------------------------- # Recording # --------------------------------------------------------------------------- @@ -333,6 +374,8 @@ def start_recording(): recording_filename = filename recording_started = time.time() + broadcast_event("recording_started", filename) + def normalize_recording(filename): """Normalize a completed MP3 in place in RECORDINGS_DIR.""" @@ -435,6 +478,8 @@ def stop_recording(): finally: normalizing_filename = None + broadcast_event("recording_stopped", filename) + # --------------------------------------------------------------------------- # Monitoring loop @@ -732,6 +777,7 @@ a.button:hover {