From c7926724e2ad30c969c579eb47b3002e543b76e5 Mon Sep 17 00:00:00 2001 From: pstruebi Date: Wed, 18 Jun 2025 12:29:13 +0200 Subject: [PATCH] fix start restart bugs --- src/auracast/multicast.py | 5 +-- src/auracast/server/multicast_frontend.py | 6 +++- src/auracast/server/multicast_server.py | 43 +++++++++++++++++------ 3 files changed, 38 insertions(+), 16 deletions(-) diff --git a/src/auracast/multicast.py b/src/auracast/multicast.py index 4fd6d5a..c9aa32b 100644 --- a/src/auracast/multicast.py +++ b/src/auracast/multicast.py @@ -336,10 +336,7 @@ class Streamer(): self.is_streaming = False if self.task is not None: self.task.cancel() - try: - await self.task - except asyncio.CancelledError: - pass + self.task = None # Close audio inputs (await to ensure ALSA devices are released) diff --git a/src/auracast/server/multicast_frontend.py b/src/auracast/server/multicast_frontend.py index 5bd1645..c3cf809 100644 --- a/src/auracast/server/multicast_frontend.py +++ b/src/auracast/server/multicast_frontend.py @@ -51,7 +51,7 @@ if audio_mode in ["Webapp", "USB"]: # Input device selection for USB mode if audio_mode == "USB": try: - resp = requests.get(f"{BACKEND_URL}/audio_inputs", timeout=1) + resp = requests.get(f"{BACKEND_URL}/audio_inputs") if resp.status_code == 200: input_options = [f"{d['id']}:{d['name']}" for d in resp.json().get('inputs', [])] else: @@ -71,6 +71,10 @@ if audio_mode in ["Webapp", "USB"]: selected_option = st.selectbox("Input Device", input_options, index=input_options.index(default_input)) with col2: if st.button("Refresh"): + try: + requests.post(f"{BACKEND_URL}/refresh_audio_inputs", timeout=3) + except Exception as e: + st.error(f"Failed to refresh devices: {e}") st.rerun() # We send only the numeric/card identifier (before :) or 'default' input_device = selected_option.split(":", 1)[0] if ":" in selected_option else selected_option diff --git a/src/auracast/server/multicast_server.py b/src/auracast/server/multicast_server.py index 62ee6d7..06b69cb 100644 --- a/src/auracast/server/multicast_server.py +++ b/src/auracast/server/multicast_server.py @@ -15,12 +15,13 @@ from aiortc import RTCPeerConnection, RTCSessionDescription, MediaStreamTrack import av import av.audio.layout import sounddevice as sd # type: ignore -from typing import Set +from typing import Set, List, Dict, Any import traceback PTIME = 40 # TODO: seems to have no effect at all pcs: Set[RTCPeerConnection] = set() # keep refs so they don’t GC early +AUDIO_INPUT_DEVICES_CACHE: List[Dict[str, Any]] = [] class Offer(BaseModel): sdp: str @@ -169,6 +170,7 @@ async def stop_audio(): return {"status": "stopped", "was_running": running} except Exception as e: + log.error("Exception in /stop_audio: %s", traceback.format_exc()) raise HTTPException(status_code=500, detail=str(e)) @@ -182,11 +184,12 @@ async def get_status(): status.update(load_stream_settings()) return status -@app.get("/audio_inputs") -async def list_audio_inputs(): - """Return available hardware audio input devices for USB mode.""" + +async def scan_audio_devices(): + """Scans for available audio devices and updates the cache.""" + global AUDIO_INPUT_DEVICES_CACHE + log.info("Scanning for audio input devices...") try: - # Re-scan devices on Linux, see https://github.com/spatialaudio/python-sounddevice/issues/16 if sys.platform == 'linux': log.info("Re-initializing sounddevice to scan for new devices") sd._terminate() @@ -197,12 +200,30 @@ async def list_audio_inputs(): for idx, d in enumerate(devs) if d.get("max_input_channels", 0) > 0 and ("(hw:" in d["name"].lower() or "usb" in d["name"].lower()) ] - log.info('Found %d audio input devices:', len(inputs)) - for i in inputs: - log.info(' %s', i) - return {"inputs": inputs} - except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) + log.info('Found %d audio input devices: %s', len(inputs), inputs) + AUDIO_INPUT_DEVICES_CACHE = inputs + except Exception: + log.error("Exception while scanning audio devices:", exc_info=True) + # Do not clear cache on error, keep the last known good list + + +@app.on_event("startup") +async def startup_event(): + """Pre-scans audio devices on startup.""" + await scan_audio_devices() + + +@app.get("/audio_inputs") +async def list_audio_inputs(): + """Return available hardware audio input devices from cache.""" + return {"inputs": AUDIO_INPUT_DEVICES_CACHE} + + +@app.post("/refresh_audio_inputs") +async def refresh_audio_inputs(): + """Triggers a re-scan of audio devices.""" + await scan_audio_devices() + return {"status": "ok", "inputs": AUDIO_INPUT_DEVICES_CACHE} @app.post("/offer")