diff --git a/routes/listening_post.py b/routes/listening_post.py index ac1dbe3..0c2be71 100644 --- a/routes/listening_post.py +++ b/routes/listening_post.py @@ -914,29 +914,35 @@ def stream_audio() -> Response: if not audio_running or not audio_process: return Response(b'', mimetype='audio/mpeg', status=204) - def generate(): - # Capture local reference to avoid race condition with stop - proc = audio_process - if not proc or not proc.stdout: - return - try: - while audio_running and proc.poll() is None: - # Use select to avoid blocking forever - ready, _, _ = select.select([proc.stdout], [], [], 2.0) - if ready: - chunk = proc.stdout.read(4096) - if chunk: - yield chunk - else: - break - else: - # Timeout - check if process died - if proc.poll() is not None: - break - except GeneratorExit: - pass - except Exception as e: - logger.error(f"Audio stream error: {e}") + def generate(): + # Capture local reference to avoid race condition with stop + proc = audio_process + if not proc or not proc.stdout: + return + try: + # First byte timeout to avoid hanging clients forever + first_chunk_deadline = time.time() + 3.0 + while audio_running and proc.poll() is None: + # Use select to avoid blocking forever + ready, _, _ = select.select([proc.stdout], [], [], 2.0) + if ready: + chunk = proc.stdout.read(4096) + if chunk: + yield chunk + else: + break + else: + # If no data arrives shortly after start, exit so caller can retry + if time.time() > first_chunk_deadline: + logger.warning("Audio stream timed out waiting for first chunk") + break + # Timeout - check if process died + if proc.poll() is not None: + break + except GeneratorExit: + pass + except Exception as e: + logger.error(f"Audio stream error: {e}") return Response( generate(),