Typing fixes for the squeezelite provider (#2589)
authorOzGav <gavnosp@hotmail.com>
Fri, 7 Nov 2025 16:16:21 +0000 (02:16 +1000)
committerGitHub <noreply@github.com>
Fri, 7 Nov 2025 16:16:21 +0000 (17:16 +0100)
music_assistant/providers/squeezelite/multi_client_stream.py
music_assistant/providers/squeezelite/player.py
pyproject.toml

index b2b15cacbce5cf7459ce2a7bf4662dec75d192f3..1b1ff112c7be61ac4981922f35650161c13f8c6d 100644 (file)
@@ -7,7 +7,7 @@ from contextlib import suppress
 
 from music_assistant_models.media_items import AudioFormat
 
-from music_assistant.helpers.audio import get_ffmpeg_stream
+from music_assistant.helpers.ffmpeg import get_ffmpeg_stream
 from music_assistant.helpers.util import empty_queue
 
 LOGGER = logging.getLogger(__name__)
index a8fb0ffd061426f70e3b06a4407e3586a075fa39..d581e8c3e513c4271354c64badf1fe6df2ccf81d 100644 (file)
@@ -23,7 +23,10 @@ from music_assistant_models.enums import (
     PlayerType,
     RepeatMode,
 )
-from music_assistant_models.errors import MusicAssistantError
+from music_assistant_models.errors import (
+    InvalidCommand,
+    MusicAssistantError,
+)
 from music_assistant_models.media_items import AudioFormat
 
 from music_assistant.constants import (
@@ -34,7 +37,6 @@ from music_assistant.constants import (
     CONF_ENTRY_OUTPUT_CODEC,
     CONF_ENTRY_SUPPORT_CROSSFADE_DIFFERENT_SAMPLE_RATES,
     CONF_ENTRY_SYNC_ADJUST,
-    CONF_SAMPLE_RATES,
     INTERNAL_PCM_FORMAT,
     VERBOSE_LOG_LEVEL,
     create_sample_rates_config_entry,
@@ -220,7 +222,7 @@ class SqueezelitePlayer(Player):
         """Handle PLAY MEDIA on the player."""
         if self.synced_to:
             msg = "A synced player cannot receive play commands directly"
-            raise RuntimeError(msg)
+            raise InvalidCommand(msg)
 
         if not self.group_members:
             # Simple, single-player playback
@@ -234,32 +236,14 @@ class SqueezelitePlayer(Player):
             return
 
         # this is a syncgroup, we need to handle this with a multi client stream
-        # Get the minimum supported sample rate across all group members (LCD)
-        min_sample_rate = 192000  # Start high
-        for member_id in [self.player_id, *self.group_members]:
-            supported_rates_conf = cast(
-                "list[tuple[str, str]]",
-                await self.mass.config.get_player_config_value(
-                    member_id, CONF_SAMPLE_RATES, unpack_splitted_values=True
-                ),
-            )
-            if supported_rates_conf:
-                member_max_rate = max(int(x[0]) for x in supported_rates_conf)
-                min_sample_rate = min(min_sample_rate, member_max_rate)
-
-        # For queue streams, further cap to content sample rate
-        if media.source_id and media.queue_item_id:
-            queue_item = self.mass.player_queues.get_item(media.source_id, media.queue_item_id)
-            min_sample_rate = min(
-                min_sample_rate, queue_item.streamdetails.audio_format.sample_rate
-            )
-
+        # Use a fixed 96kHz/24-bit format for syncgroup playback
         master_audio_format = AudioFormat(
             content_type=INTERNAL_PCM_FORMAT.content_type,
-            sample_rate=min_sample_rate,
-            bit_depth=INTERNAL_PCM_FORMAT.bit_depth,  # 32-bit float for processing
+            sample_rate=96000,
+            bit_depth=24,
             channels=2,
         )
+
         # select audio source
         audio_source = self.mass.streams.get_stream(media, master_audio_format)
         # start the stream task
@@ -307,7 +291,7 @@ class SqueezelitePlayer(Player):
         """Handle SET_MEMBERS command on the player."""
         if self.synced_to:
             # this should not happen, but guard anyways
-            raise RuntimeError("Player is synced, cannot set members")
+            raise InvalidCommand("Player is synced, cannot set members")
         if not player_ids_to_add and not player_ids_to_remove:
             # nothing to do
             return
@@ -329,7 +313,7 @@ class SqueezelitePlayer(Player):
             if player_id == self.player_id or player_id in self.group_members:
                 # nothing to do: player is already part of the group
                 continue
-            child_player: SqueezelitePlayer | None = self.mass.players.get(player_id)
+            child_player = cast("SqueezelitePlayer | None", self.mass.players.get(player_id))
             if not child_player:
                 # should not happen, but guard against it
                 continue
@@ -421,7 +405,7 @@ class SqueezelitePlayer(Player):
             "source_id": media.source_id,
             "queue_item_id": media.queue_item_id,
         }
-        if queue := self.mass.player_queues.get(media.source_id):
+        if media.source_id and (queue := self.mass.player_queues.get(media.source_id)):
             self.extra_data["playlist repeat"] = REPEATMODE_MAP[queue.repeat_mode]
             self.extra_data["playlist shuffle"] = int(queue.shuffle_enabled)
         await slimplayer.play_url(
@@ -509,6 +493,8 @@ class SqueezelitePlayer(Player):
         # TODO: fix this in the aioslimproto lib
         event_data = cast("str", event.data)
         queue = self.mass.player_queues.get_active_queue(self.player_id)
+        if not queue:
+            return
         if event_data.startswith("button preset_") and event_data.endswith(".single"):
             preset_id = event_data.split("preset_")[1].split(".")[0]
             preset_index = int(preset_id) - 1
@@ -546,7 +532,9 @@ class SqueezelitePlayer(Player):
         if not sync_master_id:
             # we only correct sync members, not the sync master itself
             return
-        if not (sync_master := self.provider.slimproto.get_player(sync_master_id)):
+        if not self._provider.slimproto or not (
+            sync_master := self._provider.slimproto.get_player(sync_master_id)
+        ):
             return  # just here as a guard as bad things can happen
 
         if sync_master.state != SlimPlayerState.PLAYING:
@@ -571,8 +559,8 @@ class SqueezelitePlayer(Player):
             sync_playpoints.clear()
 
         diff = int(
-            self.provider.get_corrected_elapsed_milliseconds(sync_master)
-            - self.provider.get_corrected_elapsed_milliseconds(self.client)
+            self._provider.get_corrected_elapsed_milliseconds(sync_master)
+            - self._provider.get_corrected_elapsed_milliseconds(self.client)
         )
 
         sync_playpoints.append(SyncPlayPoint(now, sync_master.player_id, diff))
@@ -621,7 +609,7 @@ class SqueezelitePlayer(Player):
                 self.player_id, f"preset_{preset_index}"
             ):
                 try:
-                    media_item = await self.mass.music.get_item_by_uri(preset_conf)
+                    media_item = await self.mass.music.get_item_by_uri(cast("str", preset_conf))
                     preset_items.append(
                         SlimPreset(
                             uri=media_item.uri,
index 39bb3bc283ced61a1026f77590920759f470f132..ae96ca3ffd8ea33e6a0fe9eec24a730aa6bcb173 100644 (file)
@@ -149,7 +149,6 @@ exclude = [
   '^music_assistant/providers/chromecast/.*$',
   '^music_assistant/providers/qobuz/.*$',
   '^music_assistant/providers/siriusxm/.*$',
-  '^music_assistant/providers/squeezelite/.*$',
   '^music_assistant/providers/sonos/.*$',
   '^music_assistant/providers/snapcast/.*$',
   '^music_assistant/providers/ytmusic/.*$',