Another fix for reconnections to signaling server
authorMarcel van der Veldt <m.vanderveldt@outlook.com>
Thu, 18 Dec 2025 17:53:44 +0000 (18:53 +0100)
committerMarcel van der Veldt <m.vanderveldt@outlook.com>
Thu, 18 Dec 2025 17:53:44 +0000 (18:53 +0100)
music_assistant/controllers/webserver/remote_access/__init__.py

index c4520aea1a49018e9ff61c8c6e0456553f3c4544..94015d92fde134d9c933f37241ba3f8fb7f4c67b 100644 (file)
@@ -64,7 +64,6 @@ class RemoteAccessManager:
         self._certificate: RTCCertificate
         self._enabled: bool = False
         self._using_ha_cloud: bool = False
-        self._starting: bool = False  # Prevents concurrent gateway starts
         self._on_unload_callbacks: list[Callable[[], None]] = []
 
     async def setup(self) -> None:
@@ -78,7 +77,7 @@ class RemoteAccessManager:
         self._register_api_commands()
         self.mass.subscribe(self._on_providers_updated, EventType.PROVIDERS_UPDATED)
         if self._enabled:
-            self._schedule_start()
+            await self._schedule_start()
 
     async def close(self) -> None:
         """Cleanup on exit."""
@@ -87,8 +86,13 @@ class RemoteAccessManager:
         for unload_cb in self._on_unload_callbacks:
             unload_cb()
 
-    def _schedule_start(self) -> None:
-        """Schedule a debounced gateway start."""
+    async def _schedule_start(self) -> None:
+        """Schedule a debounced gateway start, cancelling any existing connection first."""
+        # Cancel any pending timer
+        self.mass.cancel_timer(TASK_ID_START_GATEWAY)
+        # Stop any existing gateway
+        await self.stop()
+        # Schedule new start
         self.logger.debug("Scheduling remote access gateway start in %s seconds", STARTUP_DELAY)
         self.mass.call_later(
             STARTUP_DELAY,
@@ -98,42 +102,35 @@ class RemoteAccessManager:
 
     async def _start_gateway(self) -> None:
         """Start the remote access gateway (internal implementation)."""
-        if self.is_running or self._starting:
-            self.logger.debug("Gateway already running or starting, skipping start")
-            return
         if not self._enabled:
             self.logger.debug("Remote access disabled, skipping start")
             return
 
-        self._starting = True
-        try:
-            base_url = self.mass.webserver.base_url
-            local_ws_url = base_url.replace("http", "ws")
-            if not local_ws_url.endswith("/"):
-                local_ws_url += "/"
-            local_ws_url += "ws"
+        base_url = self.mass.webserver.base_url
+        local_ws_url = base_url.replace("http", "ws")
+        if not local_ws_url.endswith("/"):
+            local_ws_url += "/"
+        local_ws_url += "ws"
 
-            ha_cloud_available, ice_servers = await self._get_ha_cloud_status()
-            self._using_ha_cloud = bool(ha_cloud_available and ice_servers)
-
-            mode = "optimized" if self._using_ha_cloud else "basic"
-            self.logger.info("Starting remote access in %s mode", mode)
-
-            self.gateway = WebRTCGateway(
-                http_session=self.mass.http_session,
-                remote_id=self._remote_id,
-                certificate=self._certificate,
-                signaling_url=SIGNALING_SERVER_URL,
-                local_ws_url=local_ws_url,
-                ice_servers=ice_servers,
-                # Pass callback to get fresh ICE servers for each client connection
-                # This ensures TURN credentials are always valid
-                ice_servers_callback=self.get_ice_servers if ha_cloud_available else None,
-            )
+        ha_cloud_available, ice_servers = await self._get_ha_cloud_status()
+        self._using_ha_cloud = bool(ha_cloud_available and ice_servers)
+
+        mode = "optimized" if self._using_ha_cloud else "basic"
+        self.logger.info("Starting remote access in %s mode", mode)
+
+        self.gateway = WebRTCGateway(
+            http_session=self.mass.http_session,
+            remote_id=self._remote_id,
+            certificate=self._certificate,
+            signaling_url=SIGNALING_SERVER_URL,
+            local_ws_url=local_ws_url,
+            ice_servers=ice_servers,
+            # Pass callback to get fresh ICE servers for each client connection
+            # This ensures TURN credentials are always valid
+            ice_servers_callback=self.get_ice_servers if ha_cloud_available else None,
+        )
 
-            await self.gateway.start()
-        finally:
-            self._starting = False
+        await self.gateway.start()
 
     async def stop(self) -> None:
         """Stop the remote access gateway."""
@@ -149,19 +146,13 @@ class RemoteAccessManager:
         if not self._enabled:
             return
 
-        # If not running yet and not currently starting, schedule start (debounced)
-        if not self.is_running and not self._starting:
-            self._schedule_start()
-            return
-
         # Check if HA Cloud status changed
         ha_cloud_available, ice_servers = await self._get_ha_cloud_status()
         new_using_ha_cloud = bool(ha_cloud_available and ice_servers)
 
         if new_using_ha_cloud != self._using_ha_cloud:
             self.logger.info("HA Cloud status changed, restarting remote access")
-            await self.stop()
-            self._schedule_start()
+            await self._schedule_start()
 
     async def _get_ha_cloud_status(self) -> tuple[bool, list[dict[str, str]] | None]:
         """Get Home Assistant Cloud status and ICE servers.