2323 PlayerType ,
2424 RepeatMode ,
2525)
26- from music_assistant_models .errors import MusicAssistantError
26+ from music_assistant_models .errors import (
27+ InvalidCommand ,
28+ MusicAssistantError ,
29+ )
2730from music_assistant_models .media_items import AudioFormat
2831
2932from music_assistant .constants import (
3437 CONF_ENTRY_OUTPUT_CODEC ,
3538 CONF_ENTRY_SUPPORT_CROSSFADE_DIFFERENT_SAMPLE_RATES ,
3639 CONF_ENTRY_SYNC_ADJUST ,
37- CONF_SAMPLE_RATES ,
3840 INTERNAL_PCM_FORMAT ,
3941 VERBOSE_LOG_LEVEL ,
4042 create_sample_rates_config_entry ,
@@ -220,7 +222,7 @@ async def play_media(self, media: PlayerMedia) -> None:
220222 """Handle PLAY MEDIA on the player."""
221223 if self .synced_to :
222224 msg = "A synced player cannot receive play commands directly"
223- raise RuntimeError (msg )
225+ raise InvalidCommand (msg )
224226
225227 if not self .group_members :
226228 # Simple, single-player playback
@@ -234,32 +236,14 @@ async def play_media(self, media: PlayerMedia) -> None:
234236 return
235237
236238 # this is a syncgroup, we need to handle this with a multi client stream
237- # Get the minimum supported sample rate across all group members (LCD)
238- min_sample_rate = 192000 # Start high
239- for member_id in [self .player_id , * self .group_members ]:
240- supported_rates_conf = cast (
241- "list[tuple[str, str]]" ,
242- await self .mass .config .get_player_config_value (
243- member_id , CONF_SAMPLE_RATES , unpack_splitted_values = True
244- ),
245- )
246- if supported_rates_conf :
247- member_max_rate = max (int (x [0 ]) for x in supported_rates_conf )
248- min_sample_rate = min (min_sample_rate , member_max_rate )
249-
250- # For queue streams, further cap to content sample rate
251- if media .source_id and media .queue_item_id :
252- queue_item = self .mass .player_queues .get_item (media .source_id , media .queue_item_id )
253- min_sample_rate = min (
254- min_sample_rate , queue_item .streamdetails .audio_format .sample_rate
255- )
256-
239+ # Use a fixed 96kHz/24-bit format for syncgroup playback
257240 master_audio_format = AudioFormat (
258241 content_type = INTERNAL_PCM_FORMAT .content_type ,
259- sample_rate = min_sample_rate ,
260- bit_depth = INTERNAL_PCM_FORMAT . bit_depth , # 32-bit float for processing
242+ sample_rate = 96000 ,
243+ bit_depth = 24 ,
261244 channels = 2 ,
262245 )
246+
263247 # select audio source
264248 audio_source = self .mass .streams .get_stream (media , master_audio_format )
265249 # start the stream task
@@ -307,7 +291,7 @@ async def set_members(
307291 """Handle SET_MEMBERS command on the player."""
308292 if self .synced_to :
309293 # this should not happen, but guard anyways
310- raise RuntimeError ("Player is synced, cannot set members" )
294+ raise InvalidCommand ("Player is synced, cannot set members" )
311295 if not player_ids_to_add and not player_ids_to_remove :
312296 # nothing to do
313297 return
@@ -329,7 +313,7 @@ async def set_members(
329313 if player_id == self .player_id or player_id in self .group_members :
330314 # nothing to do: player is already part of the group
331315 continue
332- child_player : SqueezelitePlayer | None = self .mass .players .get (player_id )
316+ child_player = cast ( " SqueezelitePlayer | None" , self .mass .players .get (player_id ) )
333317 if not child_player :
334318 # should not happen, but guard against it
335319 continue
@@ -421,7 +405,7 @@ async def _handle_play_url_for_slimplayer(
421405 "source_id" : media .source_id ,
422406 "queue_item_id" : media .queue_item_id ,
423407 }
424- if queue := self .mass .player_queues .get (media .source_id ):
408+ if media . source_id and ( queue := self .mass .player_queues .get (media .source_id ) ):
425409 self .extra_data ["playlist repeat" ] = REPEATMODE_MAP [queue .repeat_mode ]
426410 self .extra_data ["playlist shuffle" ] = int (queue .shuffle_enabled )
427411 await slimplayer .play_url (
@@ -509,6 +493,8 @@ async def _handle_player_cli_event(self, event: SlimEvent) -> None:
509493 # TODO: fix this in the aioslimproto lib
510494 event_data = cast ("str" , event .data )
511495 queue = self .mass .player_queues .get_active_queue (self .player_id )
496+ if not queue :
497+ return
512498 if event_data .startswith ("button preset_" ) and event_data .endswith (".single" ):
513499 preset_id = event_data .split ("preset_" )[1 ].split ("." )[0 ]
514500 preset_index = int (preset_id ) - 1
@@ -546,7 +532,9 @@ def _handle_sync(self) -> None:
546532 if not sync_master_id :
547533 # we only correct sync members, not the sync master itself
548534 return
549- if not (sync_master := self .provider .slimproto .get_player (sync_master_id )):
535+ if not self ._provider .slimproto or not (
536+ sync_master := self ._provider .slimproto .get_player (sync_master_id )
537+ ):
550538 return # just here as a guard as bad things can happen
551539
552540 if sync_master .state != SlimPlayerState .PLAYING :
@@ -571,8 +559,8 @@ def _handle_sync(self) -> None:
571559 sync_playpoints .clear ()
572560
573561 diff = int (
574- self .provider .get_corrected_elapsed_milliseconds (sync_master )
575- - self .provider .get_corrected_elapsed_milliseconds (self .client )
562+ self ._provider .get_corrected_elapsed_milliseconds (sync_master )
563+ - self ._provider .get_corrected_elapsed_milliseconds (self .client )
576564 )
577565
578566 sync_playpoints .append (SyncPlayPoint (now , sync_master .player_id , diff ))
@@ -621,7 +609,7 @@ async def _set_preset_items(self) -> None:
621609 self .player_id , f"preset_{ preset_index } "
622610 ):
623611 try :
624- media_item = await self .mass .music .get_item_by_uri (preset_conf )
612+ media_item = await self .mass .music .get_item_by_uri (cast ( "str" , preset_conf ) )
625613 preset_items .append (
626614 SlimPreset (
627615 uri = media_item .uri ,
0 commit comments