"""Async initialize of controller."""
await self._load()
self.initialized = True
+ #### temp fix issue introduced in b89 ##########
+ # TODO: remove after b92
+ final_player_configs = {}
+ for player_id, player_conf in self.get(CONF_PLAYERS, {}).items():
+ if "provider" in player_conf:
+ final_player_configs[player_id] = player_conf
+ self.set(CONF_PLAYERS, final_player_configs)
+ #### end of temp fix ############################
+
# create default server ID if needed (also used for encrypting passwords)
self.set_default(CONF_SERVER_ID, uuid4().hex)
server_id: str = self.get(CONF_SERVER_ID)
Note that this only stores the (raw) value without any validation or default.
"""
+ if not self.get(f"{CONF_PROVIDERS}/{provider_instance}"):
+ # only allow setting raw values if main entry exists
+ raise KeyError(f"Invalid provider_instance: {provider_instance}")
self.set(f"{CONF_PROVIDERS}/{provider_instance}/{key}", value)
def set_raw_player_config_value(self, player_id: str, key: str, value: ConfigValueType) -> None:
Note that this only stores the (raw) value without any validation or default.
"""
+ if not self.get(f"{CONF_PLAYERS}/{player_id}"):
+ # only allow setting raw values if main entry exists
+ raise KeyError(f"Invalid player_id: {player_id}")
self.set(f"{CONF_PLAYERS}/{player_id}/values/{key}", value)
def save(self, immediate: bool = False) -> None:
self.subscribed_players[player_id] = sub_queue = asyncio.Queue(2)
if self._all_clients_connected.is_set():
- # client subscribes while we're already started
- self.logger.warning(
- "Client %s is joining while the stream is already started", player_id
+ # client subscribes while we're already started - we dont support that (for now?)
+ raise RuntimeError(
+ f"Client {player_id} is joining while the stream is already started"
)
- else:
- self.logger.debug("Subscribed client %s", player_id)
+ self.logger.debug("Subscribed client %s", player_id)
if len(self.subscribed_players) == len(self.expected_players):
# we reached the number of expected subscribers, set event
def _get_corrected_elapsed_milliseconds(self, client: SlimClient) -> int:
"""Return corrected elapsed milliseconds."""
- skipped_millis = 0
- active_queue = self.mass.player_queues.get_active_queue(client.player_id)
- if stream_job := self.mass.streams.multi_client_jobs.get(active_queue.queue_id):
- skipped_millis = stream_job.client_seconds_skipped.get(client.player_id, 0) * 1000
sync_delay = self.mass.config.get_raw_player_config_value(
client.player_id, CONF_SYNC_ADJUST, 0
)
- current_millis = int(skipped_millis + client.elapsed_milliseconds)
+ current_millis = client.elapsed_milliseconds
if sync_delay != 0:
return current_millis - sync_delay
return current_millis
from soco import events_asyncio, zonegroupstate
from soco.core import SoCo
from soco.discovery import discover
+from soco.exceptions import SoCoUPnPException
from music_assistant.common.models.config_entries import (
CONF_ENTRY_CROSSFADE,
) -> 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(
key="sonos_bass",
type=ConfigEntryType.INTEGER,
label="Bass",
- default_value=0,
+ default_value=sonos_player.bass,
+ value=sonos_player.bass,
range=(-10, 10),
description="Set the Bass level for the Sonos player",
advanced=True,
key="sonos_treble",
type=ConfigEntryType.INTEGER,
label="Treble",
- default_value=0,
+ default_value=sonos_player.treble,
+ value=sonos_player.treble,
range=(-10, 10),
description="Set the Treble level for the Sonos player",
advanced=True,
key="sonos_loudness",
type=ConfigEntryType.BOOLEAN,
label="Loudness compensation",
- default_value=True,
+ default_value=sonos_player.loudness,
+ value=sonos_player.loudness,
description="Enable loudness compensation on the Sonos player",
advanced=True,
),
if sonos_player.crossfade != crossfade:
def set_crossfade():
- with suppress(Exception):
- sonos_player.soco.cross_fade = crossfade
- sonos_player.crossfade = crossfade
+ sonos_player.soco.cross_fade = crossfade
+ sonos_player.crossfade = crossfade
- await asyncio.to_thread(set_crossfade)
+ with suppress(SoCoUPnPException):
+ await asyncio.to_thread(set_crossfade)
await self._enqueue_item(sonos_player, url=url, queue_item=queue_item)
self.uri: str | None = None
self.position: int | None = None
self.position_updated_at: datetime.datetime | None = None
+ self.loudness: bool = False
+ self.bass: int = 0
+ self.treble: int = 0
# Subscriptions and events
self._subscriptions: list[SubscriptionBase] = []
self._subscription_lock: asyncio.Lock | None = None
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.loudness = self.soco.loudness
+ self.bass = self.soco.bass
+ self.treble = self.soco.treble
self.update_groups()
if not self.sync_coordinator:
self.poll_media()
if loudness := variables.get("loudness"):
# TODO: handle this is a better way
- self.mass.loop.call_soon_threadsafe(
- self.mass.config.set_raw_player_config_value,
- self.player_id,
- "sonos_loudness",
- loudness["Master"] == "1",
- )
+ self.loudness = loudness["Master"] == "1"
+ with contextlib.suppress(KeyError):
+ 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 (
"bass",
):
if int_var in variables:
# TODO: handle this is a better way
- self.mass.loop.call_soon_threadsafe(
- self.mass.config.set_raw_player_config_value,
- self.player_id,
- f"sonos_{int_var}",
- variables[int_var],
- )
+ setattr(self, int_var, variables[int_var])
+ with contextlib.suppress(KeyError):
+ 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()