Some small bugfixes and optimizations (#1039)
authorMarcel van der Veldt <m.vanderveldt@outlook.com>
Sun, 28 Jan 2024 23:20:25 +0000 (00:20 +0100)
committerGitHub <noreply@github.com>
Sun, 28 Jan 2024 23:20:25 +0000 (00:20 +0100)
* Fix radio mode error in queue controller

* fix clear queue

* get rid of some blocking IO in sonos

* fix syncgroup power state

* filter self from sync with list in sonos

* change poll logic a bit

music_assistant/server/controllers/player_queues.py
music_assistant/server/controllers/players.py
music_assistant/server/providers/sonos/__init__.py
music_assistant/server/providers/sonos/player.py

index f29f5c453e1115a72834863903c7cbd1a02d0af2..163df9e000e38ed3ccbcefa1ef26bde5ad822c27 100755 (executable)
@@ -302,6 +302,9 @@ class PlayerQueuesController(CoreController):
                         self.domain, f"default_enqueue_action_{media_item.media_type.value}"
                     )
                 )
+            # clear queue if needed
+            if option == QueueOption.REPLACE:
+                self.clear(queue_id)
 
             # collect tracks to play
             ctrl = self.mass.music.get_controller(media_item.media_type)
@@ -340,9 +343,9 @@ class PlayerQueuesController(CoreController):
 
         # overwrite or append radio source items
         if option not in (QueueOption.ADD, QueueOption.PLAY, QueueOption.NEXT):
-            queue.radio_source = radio_mode
+            queue.radio_source = radio_source
         else:
-            queue.radio_source += radio_mode
+            queue.radio_source += radio_source
         # Use collected media items to calculate the radio if radio mode is on
         if radio_mode:
             tracks = await self._get_radio_tracks(queue_id)
@@ -359,7 +362,6 @@ class PlayerQueuesController(CoreController):
 
         # handle replace: clear all items and replace with the new items
         if option == QueueOption.REPLACE:
-            self.clear(queue_id)
             self.load(
                 queue_id,
                 queue_items=queue_items,
index 012e20cc3d63d626b472d7a284ae9573809f6942..1f36c346b3cb1e04a4289cb4fdb99b664fd2cb99 100755 (executable)
@@ -253,7 +253,7 @@ class PlayerController(CoreController):
         )
         # handle syncgroup - get attributes from first player that has this group as source
         if player.player_id.startswith(SYNCGROUP_PREFIX):
-            if sync_leader := self.get_sync_leader(player):
+            if player.powered and (sync_leader := self.get_sync_leader(player)):
                 player.state = sync_leader.state
                 player.current_item_id = sync_leader.current_item_id
                 player.elapsed_time = sync_leader.elapsed_time
@@ -835,14 +835,10 @@ class PlayerController(CoreController):
                 # - every 30 seconds if the player is powered
                 # - every 10 seconds if the player is playing
                 if (
-                    (player.available or count == 360)
-                    and (
-                        (player.powered and count % 30 == 0)
-                        or (player_playing and count % 10 == 0)
-                        or count == 360
-                    )
-                    and (player_prov := self.get_player_provider(player_id))
-                ):
+                    (player.powered and count % 30 == 0)
+                    or (player_playing and count % 10 == 0)
+                    or count == 360
+                ) and (player_prov := self.get_player_provider(player_id)):
                     try:
                         await player_prov.poll_player(player_id)
                     except PlayerUnavailableError:
index 814f1f9c50045766627f83d345842a53c5cc6f01..420b0159d4f047aa9912cc0bc37dfd5db719d7bb 100644 (file)
@@ -177,8 +177,6 @@ class SonosPlayerProvider(PlayerProvider):
     ) -> tuple[ConfigEntry, ...]:
         """Return Config Entries for the given player."""
         base_entries = await super().get_player_config_entries(player_id)
-        if not (sonos_player := self.sonosplayers.get(player_id)):
-            return base_entries
         return base_entries + (
             CONF_ENTRY_CROSSFADE,
             ConfigEntry(
@@ -188,7 +186,6 @@ class SonosPlayerProvider(PlayerProvider):
                 default_value=0,
                 range=(-10, 10),
                 description="Set the Bass level for the Sonos player",
-                value=sonos_player.soco.bass,
                 advanced=True,
             ),
             ConfigEntry(
@@ -198,7 +195,6 @@ class SonosPlayerProvider(PlayerProvider):
                 default_value=0,
                 range=(-10, 10),
                 description="Set the Treble level for the Sonos player",
-                value=sonos_player.soco.treble,
                 advanced=True,
             ),
             ConfigEntry(
@@ -207,7 +203,6 @@ class SonosPlayerProvider(PlayerProvider):
                 label="Loudness compensation",
                 default_value=True,
                 description="Enable loudness compensation on the Sonos player",
-                value=sonos_player.soco.loudness,
                 advanced=True,
             ),
         )
@@ -256,7 +251,7 @@ class SonosPlayerProvider(PlayerProvider):
                 player_id,
             )
             return
-        await asyncio.to_thread(sonos_player.soco.stop)
+        await self.mass.create_task(sonos_player.soco.stop)
 
     async def cmd_play(self, player_id: str) -> None:
         """Send PLAY command to given player."""
@@ -267,7 +262,7 @@ class SonosPlayerProvider(PlayerProvider):
                 player_id,
             )
             return
-        await asyncio.to_thread(sonos_player.soco.play)
+        await self.mass.create_task(sonos_player.soco.play)
 
     async def cmd_pause(self, player_id: str) -> None:
         """Send PAUSE command to given player."""
@@ -282,7 +277,7 @@ class SonosPlayerProvider(PlayerProvider):
             # pause not possible
             await self.cmd_stop(player_id)
             return
-        await asyncio.to_thread(sonos_player.soco.pause)
+        await self.mass.create_task(sonos_player.soco.pause)
 
     async def cmd_volume_set(self, player_id: str, volume_level: int) -> None:
         """Send VOLUME_SET command to given player."""
@@ -291,7 +286,7 @@ class SonosPlayerProvider(PlayerProvider):
             sonos_player = self.sonosplayers[player_id]
             sonos_player.soco.volume = volume_level
 
-        await asyncio.to_thread(set_volume_level, player_id, volume_level)
+        await self.mass.create_task(set_volume_level, player_id, volume_level)
 
     async def cmd_volume_mute(self, player_id: str, muted: bool) -> None:
         """Send VOLUME MUTE command to given player."""
@@ -300,7 +295,7 @@ class SonosPlayerProvider(PlayerProvider):
             sonos_player = self.sonosplayers[player_id]
             sonos_player.soco.mute = muted
 
-        await asyncio.to_thread(set_volume_mute, player_id, muted)
+        await self.mass.create_task(set_volume_mute, player_id, muted)
 
     async def cmd_sync(self, player_id: str, target_player: str) -> None:
         """Handle SYNC command for given player.
@@ -356,7 +351,7 @@ class SonosPlayerProvider(PlayerProvider):
                 "accept play_media command, it is synced to another player."
             )
         metadata = create_didl_metadata(self.mass, url, queue_item)
-        self.mass.create_task(sonos_player.soco.play_uri, url, meta=metadata)
+        await self.mass.create_task(sonos_player.soco.play_uri, url, meta=metadata)
 
     async def play_stream(self, player_id: str, stream_job: MultiClientStreamJob) -> None:
         """Handle PLAY STREAM on given player.
@@ -403,11 +398,12 @@ class SonosPlayerProvider(PlayerProvider):
         )
         # set crossfade according to player setting
         crossfade = await self.mass.config.get_player_config_value(player_id, CONF_CROSSFADE)
-        if sonos_player.soco.cross_fade != crossfade:
+        if sonos_player.crossfade != crossfade:
 
             def set_crossfade():
                 with suppress(Exception):
                     sonos_player.soco.cross_fade = crossfade
+                    sonos_player.crossfade = crossfade
 
             await asyncio.to_thread(set_crossfade)
 
index dba6733c1783924acb831fc7a006429375d9ae34..1637d1c9ca2b38b2d09cec805ffbc740407aa4aa 100644 (file)
@@ -125,6 +125,7 @@ class SonosPlayer:
         self.mass_player: Player = mass_player
         self.available: bool = True
         # cached attributes
+        self.crossfade: bool = False
         self.play_mode: str | None = None
         self.playback_status: str | None = None
         self.channel: str | None = None
@@ -173,9 +174,27 @@ class SonosPlayer:
 
     def setup(self) -> None:
         """Run initial setup of the speaker (NOT async friendly)."""
-        # update volume
+        self.crossfade = self.soco.cross_fade
         self.mass_player.volume_level = self.soco.volume
         self.mass_player.volume_muted = self.soco.mute
+        self.mass.loop.call_soon_threadsafe(
+            self.mass.config.set_raw_player_config_value,
+            self.player_id,
+            "sonos_loudness",
+            self.soco.loudness,
+        )
+        self.mass.loop.call_soon_threadsafe(
+            self.mass.config.set_raw_player_config_value,
+            self.player_id,
+            "sonos_bass",
+            self.soco.bass,
+        )
+        self.mass.loop.call_soon_threadsafe(
+            self.mass.config.set_raw_player_config_value,
+            self.player_id,
+            "sonos_treble",
+            self.soco.treble,
+        )
         self.update_groups()
         if not self.sync_coordinator:
             self.poll_media()
@@ -415,7 +434,7 @@ class SonosPlayer:
             return
 
         if crossfade := event.variables.get("current_crossfade_mode"):
-            self.logger.debug("crossfade changed to %s", crossfade)
+            self.crossfade = bool(int(crossfade))
 
         # Missing transport_state indicates a transient error
         if (new_status := event.variables.get("transport_state")) is None:
@@ -476,8 +495,11 @@ class SonosPlayer:
 
         if loudness := variables.get("loudness"):
             # TODO: handle this is a better way
-            self.mass.config.set_raw_player_config_value(
-                self.player_id, "sonos_loudness", loudness["Master"] == "1"
+            self.mass.loop.call_soon_threadsafe(
+                self.mass.config.set_raw_player_config_value,
+                self.player_id,
+                "sonos_loudness",
+                loudness["Master"] == "1",
             )
 
         for int_var in (
@@ -486,8 +508,11 @@ class SonosPlayer:
         ):
             if int_var in variables:
                 # TODO: handle this is a better way
-                self.mass.config.set_raw_player_config_value(
-                    self.player_id, f"sonos_{int_var}", variables[int_var]
+                self.mass.loop.call_soon_threadsafe(
+                    self.mass.config.set_raw_player_config_value,
+                    self.player_id,
+                    f"sonos_{int_var}",
+                    variables[int_var],
                 )
 
         self.update_player()
@@ -676,7 +701,9 @@ class SonosPlayer:
 
         # zone topology (syncing/grouping) details
         self.mass_player.can_sync_with = tuple(
-            x.player_id for x in self.sonos_prov.sonosplayers.values() if x.sync_coordinator is None
+            x.player_id
+            for x in self.sonos_prov.sonosplayers.values()
+            if x.sync_coordinator is None and x.player_id != self.player_id
         )
         if self.sync_coordinator:
             # player is syned to another player