diff --git a/routes/audio_websocket.py b/routes/audio_websocket.py index 6d70d0b..4e2acf5 100644 --- a/routes/audio_websocket.py +++ b/routes/audio_websocket.py @@ -1,10 +1,11 @@ """WebSocket-based audio streaming for SDR.""" +import json +import shutil +import socket import subprocess import threading import time -import shutil -import json from flask import Flask # Try to import flask-sock @@ -251,4 +252,19 @@ def init_audio_websocket(app: Flask): finally: with process_lock: kill_audio_processes() + # Complete WebSocket close handshake, then shut down the + # raw socket so Werkzeug cannot write its HTTP 200 response + # on top of the WebSocket stream. + try: + ws.close() + except Exception: + pass + try: + ws.sock.shutdown(socket.SHUT_RDWR) + except Exception: + pass + try: + ws.sock.close() + except Exception: + pass logger.info("WebSocket audio client disconnected") diff --git a/routes/waterfall_websocket.py b/routes/waterfall_websocket.py index 87a5fbc..c144f30 100644 --- a/routes/waterfall_websocket.py +++ b/routes/waterfall_websocket.py @@ -2,6 +2,7 @@ import json import queue +import socket import subprocess import threading import time @@ -348,4 +349,20 @@ def init_waterfall_websocket(app: Flask): unregister_process(iq_process) if claimed_device is not None: app_module.release_sdr_device(claimed_device) + # Complete WebSocket close handshake, then shut down the + # raw socket so Werkzeug cannot write its HTTP 200 response + # on top of the WebSocket stream (which browsers see as + # "Invalid frame header"). + try: + ws.close() + except Exception: + pass + try: + ws.sock.shutdown(socket.SHUT_RDWR) + except Exception: + pass + try: + ws.sock.close() + except Exception: + pass logger.info("WebSocket waterfall client disconnected")