Use bind ip of the stream server for Sendspin url (#2930)
authorMarvin Schenkel <marvinschenkel@gmail.com>
Mon, 19 Jan 2026 06:46:37 +0000 (07:46 +0100)
committerGitHub <noreply@github.com>
Mon, 19 Jan 2026 06:46:37 +0000 (07:46 +0100)
music_assistant/controllers/webserver/remote_access/__init__.py
music_assistant/controllers/webserver/remote_access/gateway.py
music_assistant/controllers/webserver/sendspin_proxy.py

index 94015d92fde134d9c933f37241ba3f8fb7f4c67b..1cff751b55aa3c8e0054d61e389ced601b6f3e67 100644 (file)
@@ -118,12 +118,15 @@ class RemoteAccessManager:
         mode = "optimized" if self._using_ha_cloud else "basic"
         self.logger.info("Starting remote access in %s mode", mode)
 
+        sendspin_url = f"ws://{self.mass.streams.publish_ip}:8927/sendspin"
+
         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,
+            sendspin_url=sendspin_url,
             ice_servers=ice_servers,
             # Pass callback to get fresh ICE servers for each client connection
             # This ensures TURN credentials are always valid
index 4ded35556d105017a1fe9a127514fcb64ca95207..4ec9261ddbe7225c2234eb3a068f896bc5d5e9e7 100644 (file)
@@ -81,6 +81,7 @@ class WebRTCGateway:
         certificate: RTCCertificate,
         signaling_url: str = "wss://signaling.music-assistant.io/ws",
         local_ws_url: str = "ws://localhost:8095/ws",
+        sendspin_url: str = "ws://localhost:8927/sendspin",
         ice_servers: list[dict[str, Any]] | None = None,
         ice_servers_callback: Callable[[], Awaitable[list[dict[str, Any]]]] | None = None,
     ) -> None:
@@ -92,12 +93,14 @@ class WebRTCGateway:
         :param certificate: Persistent RTCCertificate for DTLS, enabling client-side pinning.
         :param signaling_url: WebSocket URL of the signaling server.
         :param local_ws_url: Local WebSocket URL to bridge to.
+        :param sendspin_url: Internal Sendspin WebSocket URL to bridge to.
         :param ice_servers: List of ICE server configurations (used at registration time).
         :param ice_servers_callback: Optional callback to fetch fresh ICE servers for each session.
         """
         self.http_session = http_session
         self.signaling_url = signaling_url
         self.local_ws_url = local_ws_url
+        self.sendspin_url = sendspin_url
         self._remote_id = remote_id
         self._certificate = certificate
         self.logger = LOGGER
@@ -729,7 +732,7 @@ class WebRTCGateway:
                 if session.sendspin_ws and not session.sendspin_ws.closed:
                     asyncio.run_coroutine_threadsafe(session.sendspin_ws.close(), loop)
 
-            session.sendspin_ws = await self.http_session.ws_connect("ws://127.0.0.1:8927/sendspin")
+            session.sendspin_ws = await self.http_session.ws_connect(self.sendspin_url)
             self.logger.debug("Sendspin channel connected for session %s", session.session_id)
 
             # Start forwarding tasks - queued messages will be processed
index 6c8e5362b4f4870c07c71883ac8e508fa3bf8d19..3e55154db05b8516dee1d0f1f3bbf40585359a07 100644 (file)
@@ -28,7 +28,6 @@ if TYPE_CHECKING:
     from music_assistant.controllers.webserver import WebserverController
 
 LOGGER = logging.getLogger(f"{MASS_LOGGER_NAME}.sendspin_proxy")
-INTERNAL_SENDSPIN_URL = "ws://127.0.0.1:8927/sendspin"
 
 
 class SendspinProxyHandler:
@@ -43,6 +42,11 @@ class SendspinProxyHandler:
         self.mass = webserver.mass
         self.logger = LOGGER
 
+    @property
+    def internal_sendspin_url(self) -> str:
+        """Return the internal sendspin URL for connecting to the internal Sendspin server."""
+        return f"ws://{self.mass.streams.publish_ip}:8927/sendspin"
+
     async def handle_sendspin_proxy(self, request: web.Request) -> web.WebSocketResponse:
         """
         Handle incoming WebSocket connection and proxy to internal Sendspin server.
@@ -86,7 +90,7 @@ class SendspinProxyHandler:
                 return wsock
 
         try:
-            internal_ws = await self.mass.http_session.ws_connect(INTERNAL_SENDSPIN_URL)
+            internal_ws = await self.mass.http_session.ws_connect(self.internal_sendspin_url)
         except Exception:
             self.logger.exception("Failed to connect to internal Sendspin server")
             await wsock.close(code=1011, message=b"Internal server error")