diff --git a/routes/listening_post.py b/routes/listening_post.py index 028aa13..c2282f8 100644 --- a/routes/listening_post.py +++ b/routes/listening_post.py @@ -1606,14 +1606,25 @@ def audio_probe() -> Response: return jsonify({'status': 'ok', 'bytes': size}) -@receiver_bp.route('/audio/stream') -def stream_audio() -> Response: - """Stream WAV audio.""" - if audio_source == 'waterfall': - for _ in range(40): - if audio_running: - break - time.sleep(0.05) +@receiver_bp.route('/audio/stream') +def stream_audio() -> Response: + """Stream WAV audio.""" + request_token_raw = request.args.get('request_token') + request_token = None + if request_token_raw is not None: + try: + request_token = int(request_token_raw) + except (ValueError, TypeError): + request_token = None + + if request_token is not None and request_token < audio_start_token: + return Response(b'', mimetype='audio/wav', status=204) + + if audio_source == 'waterfall': + for _ in range(40): + if audio_running: + break + time.sleep(0.05) if not audio_running: return Response(b'', mimetype='audio/wav', status=204) @@ -1633,6 +1644,8 @@ def stream_audio() -> Response: inactive_since: float | None = None while audio_running and audio_source == 'waterfall': + if request_token is not None and request_token < audio_start_token: + break chunk = read_shared_monitor_audio_chunk(timeout=1.0) if chunk: inactive_since = None @@ -1672,11 +1685,11 @@ def stream_audio() -> Response: if not audio_running or not audio_process: return Response(b'', mimetype='audio/wav', status=204) - def generate(): - # Capture local reference to avoid race condition with stop - proc = audio_process - if not proc or not proc.stdout: - return + def generate(): + # Capture local reference to avoid race condition with stop + proc = audio_process + if not proc or not proc.stdout: + return try: # Drain stale audio that accumulated in the pipe buffer # between pipeline start and stream connection. Keep the @@ -1695,15 +1708,17 @@ def stream_audio() -> Response: if header_chunk: yield header_chunk - # Stream real-time audio - first_chunk_deadline = time.time() + 20.0 - warned_wait = False - 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(8192) - if chunk: + # Stream real-time audio + first_chunk_deadline = time.time() + 20.0 + warned_wait = False + while audio_running and proc.poll() is None: + if request_token is not None and request_token < audio_start_token: + break + # Use select to avoid blocking forever + ready, _, _ = select.select([proc.stdout], [], [], 2.0) + if ready: + chunk = proc.stdout.read(8192) + if chunk: warned_wait = False yield chunk else: diff --git a/static/js/modes/waterfall.js b/static/js/modes/waterfall.js index f428d6e..e202ccf 100644 --- a/static/js/modes/waterfall.js +++ b/static/js/modes/waterfall.js @@ -2603,7 +2603,7 @@ const Waterfall = (function () { player.load(); } - async function _attachMonitorAudio(nonce) { + async function _attachMonitorAudio(nonce, streamToken = null) { const player = document.getElementById('wfAudioPlayer'); if (!player) { return { ok: false, reason: 'player_missing', message: 'Audio player is unavailable.' }; @@ -2622,7 +2622,10 @@ const Waterfall = (function () { } await _pauseMonitorAudioElement(); - player.src = `/receiver/audio/stream?fresh=1&t=${Date.now()}-${attempt}`; + const tokenQuery = (streamToken !== null && streamToken !== undefined && String(streamToken).length > 0) + ? `&request_token=${encodeURIComponent(String(streamToken))}` + : ''; + player.src = `/receiver/audio/stream?fresh=1&t=${Date.now()}-${attempt}${tokenQuery}`; player.load(); try { @@ -2886,7 +2889,7 @@ const Waterfall = (function () { return; } - const attach = await _attachMonitorAudio(nonce); + const attach = await _attachMonitorAudio(nonce, payload?.request_token); if (nonce !== _audioConnectNonce) return; _monitorSource = payload?.source === 'waterfall' ? 'waterfall' : 'process'; if (