diff --git a/src/system/audio.py b/src/system/audio.py index af4c39e..7288b44 100644 --- a/src/system/audio.py +++ b/src/system/audio.py @@ -371,6 +371,15 @@ class AudioSystem: def stop_jack(self) -> None: """Gracefully stop the JACK server.""" + # Stop the Bluetooth A2DP bridge first — it restarts jackd + # independently and can race with our profile changes. + try: + subprocess.run( + ["systemctl", "stop", "bt-a2dp-jack"], + capture_output=True, timeout=5, + ) + except Exception: + pass try: subprocess.run( ["killall", "jackd"], diff --git a/src/web/server.py b/src/web/server.py index 7c3720d..5d3d069 100644 --- a/src/web/server.py +++ b/src/web/server.py @@ -1739,6 +1739,12 @@ class WebServer: jack_client.start() except Exception: pass + # Restart bt-a2dp bridge even on rollback so Bluetooth audio recovers + try: + import subprocess + subprocess.run(["systemctl", "start", "bt-a2dp-jack"], capture_output=True, timeout=5) + except Exception: + pass raise HTTPException( status_code=500, detail=f"Failed to start JACK with period={target_profile['period']}, rate={target_profile['rate']} — rolled back", @@ -1787,6 +1793,16 @@ class WebServer: except Exception as e: logger.warning("connect_fx_ports() failed after profile switch: %s", e) + # Restart Bluetooth A2DP bridge so it can connect to our JACK + try: + import subprocess + subprocess.run( + ["systemctl", "start", "bt-a2dp-jack"], + capture_output=True, timeout=5, + ) + except Exception: + pass + # ── Persist to config.yaml ────────────────────────────── if self.deps.config is not None: audio_cfg = self.deps.config.setdefault("audio", {})