Fix player sources in Sonos S1 (#3030)
authorMarvin Schenkel <marvinschenkel@gmail.com>
Tue, 27 Jan 2026 10:24:03 +0000 (11:24 +0100)
committerGitHub <noreply@github.com>
Tue, 27 Jan 2026 10:24:03 +0000 (11:24 +0100)
* Set active source to Line in source when selected.

* Add PlayerSources for S1.

* Add PlayerSources for S1.

* Disable media when linein is active. Switch back to MA on stop.

music_assistant/providers/sonos_s1/constants.py
music_assistant/providers/sonos_s1/player.py

index 9ca1c1905cb45ae4c005f62eba27e5f51767da11..44dee4c3844df6af9d8c0fc064c25eae544bb9ec 100644 (file)
@@ -11,6 +11,8 @@ from soco.core import (
     MUSIC_SRC_TV,
 )
 
+from music_assistant.models.player import PlayerSource
+
 # Configuration Keys
 CONF_NETWORK_SCAN = "network_scan"
 CONF_HOUSEHOLD_ID = "household_id"
@@ -22,6 +24,7 @@ PLAYER_FEATURES = (
     PlayerFeature.VOLUME_SET,
     PlayerFeature.ENQUEUE,
     PlayerFeature.GAPLESS_PLAYBACK,
+    PlayerFeature.SELECT_SOURCE,
 )
 
 # Source Mapping
@@ -46,6 +49,42 @@ SOURCE_MAPPING = {
 }
 
 LINEIN_SOURCES = (MUSIC_SRC_TV, MUSIC_SRC_LINE_IN)
+LINEIN_SOURCE_IDS = (SOURCE_TV, SOURCE_LINEIN)
+
+PLAYER_SOURCE_MAP = {
+    SOURCE_LINEIN: PlayerSource(
+        id=SOURCE_LINEIN,
+        name="Line-in",
+        passive=False,
+        can_play_pause=False,
+        can_next_previous=False,
+        can_seek=False,
+    ),
+    SOURCE_TV: PlayerSource(
+        id=SOURCE_TV,
+        name="TV",
+        passive=False,
+        can_play_pause=False,
+        can_next_previous=False,
+        can_seek=False,
+    ),
+    SOURCE_AIRPLAY: PlayerSource(
+        id=SOURCE_AIRPLAY,
+        name="AirPlay",
+        passive=True,
+        can_play_pause=True,
+        can_next_previous=True,
+        can_seek=True,
+    ),
+    SOURCE_SPOTIFY_CONNECT: PlayerSource(
+        id=SOURCE_SPOTIFY_CONNECT,
+        name="Spotify Connect",
+        passive=True,
+        can_play_pause=True,
+        can_next_previous=True,
+        can_seek=True,
+    ),
+}
 
 # Playback State Mapping
 PLAYBACK_STATE_MAP = {
index 1e4e57e29fa77b7c4cdc257fb3ce74aaf8aa876f..821d9c45b6793ec2e503f00e27154374545ae5b7 100644 (file)
@@ -26,9 +26,11 @@ from music_assistant.models.player import DeviceInfo, Player, PlayerMedia
 
 from .constants import (
     DURATION_SECONDS,
+    LINEIN_SOURCE_IDS,
     LINEIN_SOURCES,
     NEVER_TIME,
     PLAYER_FEATURES,
+    PLAYER_SOURCE_MAP,
     POSITION_SECONDS,
     RESUB_COOLDOWN_SECONDS,
     SONOS_STATE_TRANSITIONING,
@@ -140,7 +142,12 @@ class SonosPlayer(Player):
                 self.player_id,
             )
             return
-        await asyncio.to_thread(self.soco.stop)
+        if self._attr_active_source in LINEIN_SOURCE_IDS:
+            # Play an invalid URI to force stop line-in sources
+            with contextlib.suppress(SoCoException):
+                await asyncio.to_thread(self.soco.play_uri, "")
+        else:
+            await asyncio.to_thread(self.soco.stop)
         self.mass.call_later(2, self.poll)
         self.update_state()
 
@@ -538,9 +545,13 @@ class SonosPlayer(Player):
         uri = track_info["uri"]
 
         audio_source = self.soco.music_source_from_uri(uri)
-        if SOURCE_MAPPING.get(audio_source) and audio_source in LINEIN_SOURCES:
+        if (source_id := SOURCE_MAPPING.get(audio_source)) and audio_source in LINEIN_SOURCES:
             self._attr_elapsed_time = None
             self._attr_elapsed_time_last_updated = None
+            self._attr_active_source = source_id
+            self._attr_current_media = None
+            if source_id not in [x.id for x in self._attr_source_list]:
+                self._attr_source_list.append(PLAYER_SOURCE_MAP[source_id])
             return
 
         current_media = PlayerMedia(
@@ -551,6 +562,7 @@ class SonosPlayer(Player):
             image_url=track_info.get("album_art"),
         )
         self._attr_current_media = current_media
+        self._attr_active_source = None
         self._update_media_position(track_info, force_update=update_position)
 
     def _update_media_position(