From f9543c0c7c36aa350be264acdaf6122483131840 Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Sat, 15 Mar 2025 02:31:03 +0100 Subject: [PATCH] Fix: Config entries --- music_assistant/constants.py | 91 +++++++++++++----- music_assistant/controllers/config.py | 10 +- music_assistant/controllers/players.py | 7 +- music_assistant/models/player_provider.py | 95 ++++++++++++++++++- .../_template_player_provider/__init__.py | 2 +- music_assistant/providers/airplay/provider.py | 2 - .../providers/builtin_player/__init__.py | 19 +--- .../providers/chromecast/__init__.py | 55 +++-------- .../providers/fully_kiosk/__init__.py | 2 + .../providers/hass_players/__init__.py | 6 +- .../providers/player_group/__init__.py | 34 +------ .../providers/snapcast/__init__.py | 10 ++ music_assistant/providers/sonos/provider.py | 4 + .../providers/sonos_s1/__init__.py | 14 +-- .../providers/squeezelite/__init__.py | 2 - 15 files changed, 216 insertions(+), 137 deletions(-) diff --git a/music_assistant/constants.py b/music_assistant/constants.py index fcb346bb..0546904e 100644 --- a/music_assistant/constants.py +++ b/music_assistant/constants.py @@ -8,7 +8,7 @@ from music_assistant_models.config_entries import ( ConfigEntry, ConfigValueOption, ) -from music_assistant_models.enums import ConfigEntryType, ContentType +from music_assistant_models.enums import ConfigEntryType, ContentType, HidePlayerOption from music_assistant_models.media_items import AudioFormat API_SCHEMA_VERSION: Final[int] = 26 @@ -63,7 +63,8 @@ CONF_PUBLISH_IP: Final[str] = "publish_ip" CONF_AUTO_PLAY: Final[str] = "auto_play" CONF_CROSSFADE: Final[str] = "crossfade" CONF_GROUP_MEMBERS: Final[str] = "group_members" -CONF_HIDE_PLAYER: Final[str] = "hide_player" +CONF_HIDE_PLAYER_IN_UI: Final[str] = "hide_player_in_ui" +CONF_EXPOSE_PLAYER_TO_HA: Final[str] = "expose_player_to_ha" CONF_SYNC_ADJUST: Final[str] = "sync_adjust" CONF_TTS_PRE_ANNOUNCE: Final[str] = "tts_pre_announce" CONF_ANNOUNCE_VOLUME_STRATEGY: Final[str] = "announce_volume_strategy" @@ -191,6 +192,9 @@ CONF_ENTRY_AUTO_PLAY = ConfigEntry( default_value=False, description="When this player is turned ON, automatically start playing " "(if there are items in the queue).", + depends_on=CONF_POWER_CONTROL, + depends_on_value_not="none", + category="player_controls", ) CONF_ENTRY_OUTPUT_CHANNELS = ConfigEntry( @@ -303,13 +307,62 @@ CONF_ENTRY_CROSSFADE_DURATION = ConfigEntry( category="advanced", ) -CONF_ENTRY_HIDE_PLAYER = ConfigEntry( - key=CONF_HIDE_PLAYER, - type=ConfigEntryType.BOOLEAN, +CONF_ENTRY_CROSSFADE_DURATION_HIDDEN = ConfigEntry.from_dict( + {**CONF_ENTRY_CROSSFADE_DURATION.to_dict(), "hidden": True} +) + +CONF_ENTRY_HIDE_PLAYER_IN_UI = ConfigEntry( + key=CONF_HIDE_PLAYER_IN_UI, + type=ConfigEntryType.STRING, label="Hide this player in the user interface", - default_value=False, + multi_value=True, + options=[ + ConfigValueOption("Always", HidePlayerOption.ALWAYS.value), + ConfigValueOption("When powered off", HidePlayerOption.WHEN_OFF.value), + ConfigValueOption("When group active", HidePlayerOption.WHEN_GROUP_ACTIVE.value), + ConfigValueOption("When synced", HidePlayerOption.WHEN_SYNCED.value), + ConfigValueOption("When unavailable", HidePlayerOption.WHEN_UNAVAILABLE.value), + ], + default_value=[ + HidePlayerOption.WHEN_UNAVAILABLE.value, + HidePlayerOption.WHEN_GROUP_ACTIVE.value, + HidePlayerOption.WHEN_SYNCED.value, + ], +) +CONF_ENTRY_HIDE_PLAYER_IN_UI_ALWAYS_DEFAULT = ConfigEntry.from_dict( + {**CONF_ENTRY_HIDE_PLAYER_IN_UI.to_dict(), "default_value": [HidePlayerOption.ALWAYS.value]} +) + +CONF_ENTRY_HIDE_PLAYER_IN_UI_GROUP_PLAYER = ConfigEntry.from_dict( + { + **CONF_ENTRY_HIDE_PLAYER_IN_UI.to_dict(), + "default_value": [HidePlayerOption.WHEN_UNAVAILABLE.value], + "options": [ + ConfigValueOption("Always", HidePlayerOption.ALWAYS.value).to_dict(), + ConfigValueOption("When powered off", HidePlayerOption.WHEN_OFF.value).to_dict(), + ConfigValueOption( + "When unavailable", HidePlayerOption.WHEN_UNAVAILABLE.value + ).to_dict(), + ], + } ) +CONF_ENTRY_EXPOSE_PLAYER_TO_HA = ConfigEntry( + key=CONF_EXPOSE_PLAYER_TO_HA, + type=ConfigEntryType.BOOLEAN, + label="Expose this player to Home Assistant", + default_value=True, + description="Expose this player to the Home Assistant integration. \n" + "If disabled, this player will not be imported into Home Assistant.", + category="advanced", + # NOTE: This setting is hidden for now, until the HA integration has been updated + hidden=True, +) +CONF_ENTRY_EXPOSE_PLAYER_TO_HA_DEFAULT_DISABLED = ConfigEntry.from_dict( + {**CONF_ENTRY_EXPOSE_PLAYER_TO_HA.to_dict(), "default_value": False} +) + + CONF_ENTRY_OUTPUT_CODEC = ConfigEntry( key=CONF_OUTPUT_CODEC, type=ConfigEntryType.STRING, @@ -497,8 +550,12 @@ CONF_ENTRY_HTTP_PROFILE = ConfigEntry( "other playback related issues. In most cases the default setting is fine.", ) +CONF_ENTRY_HTTP_PROFILE_DEFAULT_1 = ConfigEntry.from_dict( + {**CONF_ENTRY_HTTP_PROFILE.to_dict(), "default_value": "chunked"} +) + CONF_ENTRY_HTTP_PROFILE_FORCED_1 = ConfigEntry.from_dict( - {**CONF_ENTRY_HTTP_PROFILE.to_dict(), "default_value": "chunked", "hidden": True} + {**CONF_ENTRY_HTTP_PROFILE_DEFAULT_1.to_dict(), "hidden": True} ) CONF_ENTRY_HTTP_PROFILE_FORCED_2 = ConfigEntry.from_dict( { @@ -507,6 +564,10 @@ CONF_ENTRY_HTTP_PROFILE_FORCED_2 = ConfigEntry.from_dict( "hidden": True, } ) +CONF_ENTRY_HTTP_PROFILE_HIDDEN = ConfigEntry.from_dict( + {**CONF_ENTRY_HTTP_PROFILE.to_dict(), "hidden": True} +) + CONF_ENTRY_ENABLE_ICY_METADATA = ConfigEntry( key=CONF_ENABLE_ICY_METADATA, @@ -606,22 +667,6 @@ def create_sample_rates_config_entry( return conf_entry -BASE_PLAYER_CONFIG_ENTRIES = ( - # config entries that are valid for all players - CONF_ENTRY_PLAYER_ICON, - CONF_ENTRY_FLOW_MODE, - CONF_ENTRY_VOLUME_NORMALIZATION, - CONF_ENTRY_OUTPUT_LIMITER, - CONF_ENTRY_AUTO_PLAY, - CONF_ENTRY_VOLUME_NORMALIZATION_TARGET, - CONF_ENTRY_HIDE_PLAYER, - CONF_ENTRY_TTS_PRE_ANNOUNCE, - CONF_ENTRY_SAMPLE_RATES, - CONF_ENTRY_HTTP_PROFILE_FORCED_2, - CONF_ENTRY_OUTPUT_CODEC, -) - - DEFAULT_STREAM_HEADERS = { "Server": "Music Assistant", "transferMode.dlna.org": "Streaming", diff --git a/music_assistant/controllers/config.py b/music_assistant/controllers/config.py index 47449a3a..bdd52e0b 100644 --- a/music_assistant/controllers/config.py +++ b/music_assistant/controllers/config.py @@ -798,7 +798,7 @@ class ConfigController: LOGGER.exception("Error while reading persistent storage file %s", filename) LOGGER.debug("Started with empty storage: No persistent storage file found.") - async def _migrate(self) -> None: + async def _migrate(self) -> None: # noqa: PLR0915 changed = False # Older versions of MA can create corrupt entries with no domain if retrying @@ -869,6 +869,14 @@ class ConfigController: self._data[CONF_PROVIDERS][new_instance_id] = provider_config changed = True + # migrate "hide_player" --> "hide_player_in_ui" + for player_id, player_config in list(self._data.get(CONF_PLAYERS, {}).items()): + if not (values := player_config.get("values")): + continue + if values.pop("hide_player", None): + player_config["values"]["hide_player_in_ui"] = ["always"] + changed = True + if changed: await self._async_save() diff --git a/music_assistant/controllers/players.py b/music_assistant/controllers/players.py index 02405c01..65cfcdd9 100644 --- a/music_assistant/controllers/players.py +++ b/music_assistant/controllers/players.py @@ -21,6 +21,7 @@ from music_assistant_models.constants import ( ) from music_assistant_models.enums import ( EventType, + HidePlayerOption, MediaType, PlayerFeature, PlayerState, @@ -47,7 +48,7 @@ from music_assistant.constants import ( CONF_ENTRY_ANNOUNCE_VOLUME_MIN, CONF_ENTRY_ANNOUNCE_VOLUME_STRATEGY, CONF_ENTRY_PLAYER_ICON, - CONF_HIDE_PLAYER, + CONF_HIDE_PLAYER_IN_UI, CONF_MUTE_CONTROL, CONF_PLAYERS, CONF_POWER_CONTROL, @@ -1474,7 +1475,9 @@ class PlayerController(CoreController): async def _set_player_state_from_config(self, player: Player, config: PlayerConfig) -> None: """Set player state from config.""" player.display_name = config.name or player.name or config.default_name or player.player_id - player.hidden = config.get_value(CONF_HIDE_PLAYER) + player.hide_player_in_ui = { + HidePlayerOption(x) for x in config.get_value(CONF_HIDE_PLAYER_IN_UI) + } player.icon = config.get_value(CONF_ENTRY_PLAYER_ICON.key) player.power_control = config.get_value(CONF_POWER_CONTROL) if player.power_control == PLAYER_CONTROL_FAKE: diff --git a/music_assistant/models/player_provider.py b/music_assistant/models/player_provider.py index ae050dc6..989c68c6 100644 --- a/music_assistant/models/player_provider.py +++ b/music_assistant/models/player_provider.py @@ -11,17 +11,34 @@ from music_assistant_models.constants import ( PLAYER_CONTROL_NATIVE, PLAYER_CONTROL_NONE, ) -from music_assistant_models.enums import ConfigEntryType, PlayerFeature +from music_assistant_models.enums import ConfigEntryType, PlayerFeature, PlayerType from music_assistant_models.errors import UnsupportedFeaturedException from zeroconf import ServiceStateChange from zeroconf.asyncio import AsyncServiceInfo from music_assistant.constants import ( - BASE_PLAYER_CONFIG_ENTRIES, CONF_ENTRY_ANNOUNCE_VOLUME, CONF_ENTRY_ANNOUNCE_VOLUME_MAX, CONF_ENTRY_ANNOUNCE_VOLUME_MIN, CONF_ENTRY_ANNOUNCE_VOLUME_STRATEGY, + CONF_ENTRY_AUTO_PLAY, + CONF_ENTRY_CROSSFADE_DURATION, + CONF_ENTRY_CROSSFADE_FLOW_MODE_REQUIRED, + CONF_ENTRY_EXPOSE_PLAYER_TO_HA, + CONF_ENTRY_EXPOSE_PLAYER_TO_HA_DEFAULT_DISABLED, + CONF_ENTRY_FLOW_MODE, + CONF_ENTRY_HIDE_PLAYER_IN_UI, + CONF_ENTRY_HIDE_PLAYER_IN_UI_ALWAYS_DEFAULT, + CONF_ENTRY_HIDE_PLAYER_IN_UI_GROUP_PLAYER, + CONF_ENTRY_OUTPUT_CHANNELS, + CONF_ENTRY_OUTPUT_CODEC, + CONF_ENTRY_OUTPUT_LIMITER, + CONF_ENTRY_PLAYER_ICON, + CONF_ENTRY_PLAYER_ICON_GROUP, + CONF_ENTRY_SAMPLE_RATES, + CONF_ENTRY_TTS_PRE_ANNOUNCE, + CONF_ENTRY_VOLUME_NORMALIZATION, + CONF_ENTRY_VOLUME_NORMALIZATION_TARGET, CONF_MUTE_CONTROL, CONF_POWER_CONTROL, CONF_VOLUME_CONTROL, @@ -48,15 +65,85 @@ class PlayerProvider(Provider): async def get_player_config_entries(self, player_id: str) -> tuple[ConfigEntry, ...]: """Return all (provider/player specific) Config Entries for the given player (if any).""" + base_entries = ( + # config entries that are valid for all/most players + CONF_ENTRY_PLAYER_ICON, + CONF_ENTRY_FLOW_MODE, + CONF_ENTRY_CROSSFADE_FLOW_MODE_REQUIRED, + CONF_ENTRY_CROSSFADE_DURATION, + CONF_ENTRY_VOLUME_NORMALIZATION, + CONF_ENTRY_OUTPUT_LIMITER, + CONF_ENTRY_VOLUME_NORMALIZATION_TARGET, + CONF_ENTRY_TTS_PRE_ANNOUNCE, + ) + if not (player := self.mass.players.get(player_id)): + return base_entries + + if player.type == PlayerType.GROUP: + # return group player specific entries + return ( + *base_entries, + CONF_ENTRY_PLAYER_ICON_GROUP, + CONF_ENTRY_HIDE_PLAYER_IN_UI_GROUP_PLAYER, + # add player control entries as hidden entries + ConfigEntry( + key=CONF_POWER_CONTROL, + type=ConfigEntryType.STRING, + label=CONF_POWER_CONTROL, + default_value=PLAYER_CONTROL_NATIVE, + hidden=True, + ), + ConfigEntry( + key=CONF_VOLUME_CONTROL, + type=ConfigEntryType.STRING, + label=CONF_VOLUME_CONTROL, + default_value=PLAYER_CONTROL_NATIVE, + hidden=True, + ), + ConfigEntry( + key=CONF_MUTE_CONTROL, + type=ConfigEntryType.STRING, + label=CONF_MUTE_CONTROL, + # disable mute control for group players for now + # TODO: work out if all child players support mute control + default_value=PLAYER_CONTROL_NONE, + hidden=True, + ), + CONF_ENTRY_AUTO_PLAY, + ) return ( - *BASE_PLAYER_CONFIG_ENTRIES, + # config entries that are valid for all players + *base_entries, + ( + CONF_ENTRY_HIDE_PLAYER_IN_UI_ALWAYS_DEFAULT + if player and player.hidden_by_default + else CONF_ENTRY_HIDE_PLAYER_IN_UI + ), + ( + CONF_ENTRY_EXPOSE_PLAYER_TO_HA + if player and player.expose_to_ha_by_default + else CONF_ENTRY_EXPOSE_PLAYER_TO_HA_DEFAULT_DISABLED + ), + # add player control entries + *self._create_player_control_config_entries(player), + CONF_ENTRY_AUTO_PLAY, + CONF_ENTRY_SAMPLE_RATES, + CONF_ENTRY_OUTPUT_CODEC, + CONF_ENTRY_OUTPUT_CHANNELS, # add default entries for announce feature CONF_ENTRY_ANNOUNCE_VOLUME_STRATEGY, CONF_ENTRY_ANNOUNCE_VOLUME, CONF_ENTRY_ANNOUNCE_VOLUME_MIN, CONF_ENTRY_ANNOUNCE_VOLUME_MAX, + CONF_ENTRY_HIDE_PLAYER_IN_UI_ALWAYS_DEFAULT + if player and player.hidden_by_default + else CONF_ENTRY_HIDE_PLAYER_IN_UI, + CONF_ENTRY_EXPOSE_PLAYER_TO_HA + if player and player.expose_to_ha_by_default + else CONF_ENTRY_EXPOSE_PLAYER_TO_HA_DEFAULT_DISABLED, # add player control entries - *self._create_player_control_config_entries(self.mass.players.get(player_id)), + *self._create_player_control_config_entries(player), + CONF_ENTRY_AUTO_PLAY, ) async def on_player_config_change(self, config: PlayerConfig, changed_keys: set[str]) -> None: diff --git a/music_assistant/providers/_template_player_provider/__init__.py b/music_assistant/providers/_template_player_provider/__init__.py index 33ddfd60..73f33e65 100644 --- a/music_assistant/providers/_template_player_provider/__init__.py +++ b/music_assistant/providers/_template_player_provider/__init__.py @@ -245,7 +245,7 @@ class MyDemoPlayerprovider(PlayerProvider): # configuration entries. If you do not need player specific configuration entries, # you can leave this method out completely to accept the default implementation. # Please note that you need to call the super() method to get the default entries. - return () + return await super().get_player_config_entries(player_id) async def on_player_config_change(self, config: PlayerConfig, changed_keys: set[str]) -> None: """Call (by config manager) when the configuration of a player changes.""" diff --git a/music_assistant/providers/airplay/provider.py b/music_assistant/providers/airplay/provider.py index 1c7395ae..e01aa0fb 100644 --- a/music_assistant/providers/airplay/provider.py +++ b/music_assistant/providers/airplay/provider.py @@ -31,7 +31,6 @@ from music_assistant.constants import ( CONF_ENTRY_DEPRECATED_EQ_MID, CONF_ENTRY_DEPRECATED_EQ_TREBLE, CONF_ENTRY_FLOW_MODE_ENFORCED, - CONF_ENTRY_OUTPUT_CHANNELS, CONF_ENTRY_OUTPUT_CODEC_HIDDEN, CONF_ENTRY_SYNC_ADJUST, create_sample_rates_config_entry, @@ -71,7 +70,6 @@ PLAYER_CONFIG_ENTRIES = ( CONF_ENTRY_DEPRECATED_EQ_BASS, CONF_ENTRY_DEPRECATED_EQ_MID, CONF_ENTRY_DEPRECATED_EQ_TREBLE, - CONF_ENTRY_OUTPUT_CHANNELS, CONF_ENTRY_OUTPUT_CODEC_HIDDEN, ConfigEntry( key=CONF_ENCRYPTION, diff --git a/music_assistant/providers/builtin_player/__init__.py b/music_assistant/providers/builtin_player/__init__.py index 16a9caf6..55ee3ee6 100644 --- a/music_assistant/providers/builtin_player/__init__.py +++ b/music_assistant/providers/builtin_player/__init__.py @@ -23,10 +23,7 @@ from typing import TYPE_CHECKING, cast import shortuuid from aiohttp import web -from music_assistant_models.builtin_player import ( - BuiltinPlayerEvent, - BuiltinPlayerState, -) +from music_assistant_models.builtin_player import BuiltinPlayerEvent, BuiltinPlayerState from music_assistant_models.constants import PLAYER_CONTROL_NONE from music_assistant_models.enums import ( BuiltinPlayerEventType, @@ -39,16 +36,13 @@ from music_assistant_models.enums import ( ) from music_assistant_models.errors import PlayerUnavailableError from music_assistant_models.media_items import AudioFormat -from music_assistant_models.player import ( - DeviceInfo, - Player, - PlayerMedia, -) +from music_assistant_models.player import DeviceInfo, Player, PlayerMedia from music_assistant.constants import ( CONF_ENTRY_CROSSFADE, CONF_ENTRY_CROSSFADE_DURATION, CONF_ENTRY_FLOW_MODE_ENFORCED, + CONF_ENTRY_HTTP_PROFILE, DEFAULT_PCM_FORMAT, DEFAULT_STREAM_HEADERS, ) @@ -59,11 +53,7 @@ from music_assistant.models import ProviderInstanceType from music_assistant.models.player_provider import PlayerProvider if TYPE_CHECKING: - from music_assistant_models.config_entries import ( - ConfigEntry, - ConfigValueType, - ProviderConfig, - ) + from music_assistant_models.config_entries import ConfigEntry, ConfigValueType, ProviderConfig from music_assistant_models.provider import ProviderManifest @@ -147,6 +137,7 @@ class BuiltinPlayerProvider(PlayerProvider): CONF_ENTRY_FLOW_MODE_ENFORCED, CONF_ENTRY_CROSSFADE, CONF_ENTRY_CROSSFADE_DURATION, + CONF_ENTRY_HTTP_PROFILE, ) async def cmd_stop(self, player_id: str) -> None: diff --git a/music_assistant/providers/chromecast/__init__.py b/music_assistant/providers/chromecast/__init__.py index 36237c50..ae8a48b1 100644 --- a/music_assistant/providers/chromecast/__init__.py +++ b/music_assistant/providers/chromecast/__init__.py @@ -12,15 +12,7 @@ from typing import TYPE_CHECKING, Any from uuid import UUID import pychromecast -from music_assistant_models.config_entries import ConfigEntry -from music_assistant_models.constants import PLAYER_CONTROL_NATIVE -from music_assistant_models.enums import ( - ConfigEntryType, - MediaType, - PlayerFeature, - PlayerState, - PlayerType, -) +from music_assistant_models.enums import MediaType, PlayerFeature, PlayerState, PlayerType from music_assistant_models.errors import PlayerUnavailableError from music_assistant_models.player import DeviceInfo, Player, PlayerMedia from pychromecast.controllers.media import STREAM_TYPE_BUFFERED, STREAM_TYPE_LIVE, MediaController @@ -29,15 +21,12 @@ from pychromecast.discovery import CastBrowser, SimpleCastListener from pychromecast.socket_client import CONNECTION_STATUS_CONNECTED, CONNECTION_STATUS_DISCONNECTED from music_assistant.constants import ( - BASE_PLAYER_CONFIG_ENTRIES, CONF_ENTRY_CROSSFADE_DURATION, CONF_ENTRY_CROSSFADE_FLOW_MODE_REQUIRED, + CONF_ENTRY_HTTP_PROFILE, CONF_ENTRY_MANUAL_DISCOVERY_IPS, CONF_ENTRY_OUTPUT_CODEC, - CONF_MUTE_CONTROL, CONF_PLAYERS, - CONF_POWER_CONTROL, - CONF_VOLUME_CONTROL, MASS_LOGO_ONLINE, VERBOSE_LOG_LEVEL, create_sample_rates_config_entry, @@ -47,7 +36,7 @@ from music_assistant.models.player_provider import PlayerProvider from .helpers import CastStatusListener, ChromecastInfo if TYPE_CHECKING: - from music_assistant_models.config_entries import ConfigValueType, ProviderConfig + from music_assistant_models.config_entries import ConfigEntry, ConfigValueType, ProviderConfig from music_assistant_models.provider import ProviderManifest from pychromecast.controllers.media import MediaStatus from pychromecast.controllers.receiver import CastStatus @@ -58,10 +47,11 @@ if TYPE_CHECKING: from music_assistant.models import ProviderInstanceType -PLAYER_CONFIG_ENTRIES = ( +CAST_PLAYER_CONFIG_ENTRIES = ( CONF_ENTRY_CROSSFADE_FLOW_MODE_REQUIRED, CONF_ENTRY_CROSSFADE_DURATION, CONF_ENTRY_OUTPUT_CODEC, + CONF_ENTRY_HTTP_PROFILE, ) # originally/officially cast supports 96k sample rate (even for groups) @@ -70,10 +60,14 @@ PLAYER_CONFIG_ENTRIES = ( CONF_ENTRY_SAMPLE_RATES_CAST = create_sample_rates_config_entry( max_sample_rate=192000, max_bit_depth=24, + safe_max_sample_rate=48000, + safe_max_bit_depth=16, ) CONF_ENTRY_SAMPLE_RATES_CAST_GROUP = create_sample_rates_config_entry( max_sample_rate=96000, max_bit_depth=24, + safe_max_sample_rate=48000, + safe_max_bit_depth=16, ) @@ -195,36 +189,15 @@ class ChromecastProvider(PlayerProvider): async def get_player_config_entries(self, player_id: str) -> tuple[ConfigEntry, ...]: """Return all (provider/player specific) Config Entries for the given player (if any).""" cast_player = self.castplayers.get(player_id) + base_entries = await super().get_player_config_entries(player_id) if cast_player and cast_player.player.type == PlayerType.GROUP: return ( - *BASE_PLAYER_CONFIG_ENTRIES, - *PLAYER_CONFIG_ENTRIES, + *base_entries, + *CAST_PLAYER_CONFIG_ENTRIES, CONF_ENTRY_SAMPLE_RATES_CAST_GROUP, - # add player control entries as hidden entries - ConfigEntry( - key=CONF_POWER_CONTROL, - type=ConfigEntryType.STRING, - label=CONF_POWER_CONTROL, - default_value=PLAYER_CONTROL_NATIVE, - hidden=True, - ), - ConfigEntry( - key=CONF_VOLUME_CONTROL, - type=ConfigEntryType.STRING, - label=CONF_VOLUME_CONTROL, - default_value=PLAYER_CONTROL_NATIVE, - hidden=True, - ), - ConfigEntry( - key=CONF_MUTE_CONTROL, - type=ConfigEntryType.STRING, - label=CONF_MUTE_CONTROL, - default_value=PLAYER_CONTROL_NATIVE, - hidden=True, - ), ) - base_entries = await super().get_player_config_entries(player_id) - return (*base_entries, *PLAYER_CONFIG_ENTRIES, CONF_ENTRY_SAMPLE_RATES_CAST) + + return (*base_entries, *CAST_PLAYER_CONFIG_ENTRIES, CONF_ENTRY_SAMPLE_RATES_CAST) async def cmd_stop(self, player_id: str) -> None: """Send STOP command to given player.""" diff --git a/music_assistant/providers/fully_kiosk/__init__.py b/music_assistant/providers/fully_kiosk/__init__.py index d25137ec..f97fe44e 100644 --- a/music_assistant/providers/fully_kiosk/__init__.py +++ b/music_assistant/providers/fully_kiosk/__init__.py @@ -17,6 +17,7 @@ from music_assistant.constants import ( CONF_ENTRY_CROSSFADE, CONF_ENTRY_CROSSFADE_DURATION, CONF_ENTRY_FLOW_MODE_ENFORCED, + CONF_ENTRY_HTTP_PROFILE, CONF_ENTRY_OUTPUT_CODEC_DEFAULT_MP3, CONF_IP_ADDRESS, CONF_PASSWORD, @@ -159,6 +160,7 @@ class FullyKioskProvider(PlayerProvider): CONF_ENTRY_CROSSFADE, CONF_ENTRY_CROSSFADE_DURATION, CONF_ENTRY_OUTPUT_CODEC_DEFAULT_MP3, + CONF_ENTRY_HTTP_PROFILE, ) async def cmd_volume_set(self, player_id: str, volume_level: int) -> None: diff --git a/music_assistant/providers/hass_players/__init__.py b/music_assistant/providers/hass_players/__init__.py index b408ed1e..6ef28ff9 100644 --- a/music_assistant/providers/hass_players/__init__.py +++ b/music_assistant/providers/hass_players/__init__.py @@ -20,8 +20,6 @@ from music_assistant_models.errors import InvalidDataError, LoginFailed, SetupFa from music_assistant_models.player import DeviceInfo, Player, PlayerMedia from music_assistant.constants import ( - CONF_ENTRY_CROSSFADE, - CONF_ENTRY_CROSSFADE_DURATION, CONF_ENTRY_ENABLE_ICY_METADATA, CONF_ENTRY_ENABLE_ICY_METADATA_HIDDEN, CONF_ENTRY_FLOW_MODE_DEFAULT_ENABLED, @@ -62,8 +60,6 @@ CONF_PLAYERS = "players" DEFAULT_PLAYER_CONFIG_ENTRIES = ( - CONF_ENTRY_CROSSFADE, - CONF_ENTRY_CROSSFADE_DURATION, CONF_ENTRY_OUTPUT_CODEC_DEFAULT_MP3, CONF_ENTRY_HTTP_PROFILE, CONF_ENTRY_ENABLE_ICY_METADATA, @@ -518,8 +514,8 @@ class HomeAssistantPlayers(PlayerProvider): player.powered = state["state"] not in OFF_STATES player.extra_data["hass_supported_features"] = hass_supported_features - self._update_player_attributes(player, state["attributes"]) await self.mass.players.register_or_update(player) + self._update_player_attributes(player, state["attributes"]) def _on_entity_state_update(self, event: EntityStateEvent) -> None: """Handle Entity State event.""" diff --git a/music_assistant/providers/player_group/__init__.py b/music_assistant/providers/player_group/__init__.py index 845f152a..42f83e2d 100644 --- a/music_assistant/providers/player_group/__init__.py +++ b/music_assistant/providers/player_group/__init__.py @@ -21,7 +21,7 @@ from music_assistant_models.config_entries import ( ConfigValueType, PlayerConfig, ) -from music_assistant_models.constants import PLAYER_CONTROL_NATIVE, PLAYER_CONTROL_NONE +from music_assistant_models.constants import PLAYER_CONTROL_NONE from music_assistant_models.enums import ( ConfigEntryType, ContentType, @@ -42,22 +42,17 @@ from music_assistant_models.media_items import AudioFormat, UniqueList from music_assistant_models.player import DeviceInfo, Player, PlayerMedia from music_assistant.constants import ( - BASE_PLAYER_CONFIG_ENTRIES, CONF_CROSSFADE, CONF_CROSSFADE_DURATION, CONF_ENABLE_ICY_METADATA, CONF_ENTRY_CROSSFADE, CONF_ENTRY_CROSSFADE_DURATION, CONF_ENTRY_FLOW_MODE_ENFORCED, - CONF_ENTRY_PLAYER_ICON_GROUP, CONF_FLOW_MODE, CONF_GROUP_MEMBERS, CONF_HTTP_PROFILE, - CONF_MUTE_CONTROL, CONF_OUTPUT_CODEC, - CONF_POWER_CONTROL, CONF_SAMPLE_RATES, - CONF_VOLUME_CONTROL, DEFAULT_PCM_FORMAT, create_sample_rates_config_entry, ) @@ -233,35 +228,10 @@ class PlayerGroupProvider(PlayerProvider): """Return all (provider/player specific) Config Entries for the given player (if any).""" # default entries for player groups base_entries = ( - *BASE_PLAYER_CONFIG_ENTRIES, - CONF_ENTRY_PLAYER_ICON_GROUP, + *await super().get_player_config_entries(player_id), CONF_ENTRY_GROUP_TYPE, CONF_ENTRY_GROUP_MEMBERS, CONFIG_ENTRY_DYNAMIC_MEMBERS, - # add player control entries as hidden entries - ConfigEntry( - key=CONF_POWER_CONTROL, - type=ConfigEntryType.STRING, - label=CONF_POWER_CONTROL, - default_value=PLAYER_CONTROL_NATIVE, - hidden=True, - ), - ConfigEntry( - key=CONF_VOLUME_CONTROL, - type=ConfigEntryType.STRING, - label=CONF_VOLUME_CONTROL, - default_value=PLAYER_CONTROL_NATIVE, - hidden=True, - ), - ConfigEntry( - key=CONF_MUTE_CONTROL, - type=ConfigEntryType.STRING, - label=CONF_MUTE_CONTROL, - # disable mute control for group players for now - # TODO: work out if all child players support mute control - default_value=PLAYER_CONTROL_NONE, - hidden=True, - ), ) # group type is static and can not be changed. we just grab the existing, stored value group_type: str = self.mass.config.get_raw_player_config_value( diff --git a/music_assistant/providers/snapcast/__init__.py b/music_assistant/providers/snapcast/__init__.py index a78da442..e3475b65 100644 --- a/music_assistant/providers/snapcast/__init__.py +++ b/music_assistant/providers/snapcast/__init__.py @@ -142,6 +142,8 @@ async def get_config_entries( required=False, category=CONF_CATEGORY_BUILT_IN, hidden=not local_snapserver_present, + depends_on=CONF_USE_EXTERNAL_SERVER, + depends_on_value_not=True, help_link=CONF_HELP_LINK, ), ConfigEntry( @@ -153,6 +155,8 @@ async def get_config_entries( required=False, category=CONF_CATEGORY_BUILT_IN, hidden=not local_snapserver_present, + depends_on=CONF_USE_EXTERNAL_SERVER, + depends_on_value_not=True, help_link=CONF_HELP_LINK, ), ConfigEntry( @@ -164,6 +168,8 @@ async def get_config_entries( required=False, category=CONF_CATEGORY_BUILT_IN, hidden=not local_snapserver_present, + depends_on=CONF_USE_EXTERNAL_SERVER, + depends_on_value_not=True, help_link=CONF_HELP_LINK, ), ConfigEntry( @@ -174,6 +180,8 @@ async def get_config_entries( required=False, category=CONF_CATEGORY_BUILT_IN, hidden=not local_snapserver_present, + depends_on=CONF_USE_EXTERNAL_SERVER, + depends_on_value_not=True, help_link=CONF_HELP_LINK, ), ConfigEntry( @@ -202,6 +210,8 @@ async def get_config_entries( required=False, category=CONF_CATEGORY_BUILT_IN, hidden=not local_snapserver_present, + depends_on=CONF_USE_EXTERNAL_SERVER, + depends_on_value_not=True, help_link=CONF_HELP_LINK, ), ConfigEntry( diff --git a/music_assistant/providers/sonos/provider.py b/music_assistant/providers/sonos/provider.py index e0470a63..b8e04218 100644 --- a/music_assistant/providers/sonos/provider.py +++ b/music_assistant/providers/sonos/provider.py @@ -24,7 +24,9 @@ from zeroconf import ServiceStateChange from music_assistant.constants import ( CONF_ENTRY_CROSSFADE, + CONF_ENTRY_CROSSFADE_DURATION_HIDDEN, CONF_ENTRY_FLOW_MODE_HIDDEN_DISABLED, + CONF_ENTRY_HTTP_PROFILE_DEFAULT_1, CONF_ENTRY_MANUAL_DISCOVERY_IPS, CONF_ENTRY_OUTPUT_CODEC, MASS_LOGO_ONLINE, @@ -146,8 +148,10 @@ class SonosPlayerProvider(PlayerProvider): base_entries = ( *await super().get_player_config_entries(player_id), CONF_ENTRY_CROSSFADE, + CONF_ENTRY_CROSSFADE_DURATION_HIDDEN, CONF_ENTRY_FLOW_MODE_HIDDEN_DISABLED, CONF_ENTRY_OUTPUT_CODEC, + CONF_ENTRY_HTTP_PROFILE_DEFAULT_1, create_sample_rates_config_entry( max_sample_rate=48000, max_bit_depth=24, safe_max_bit_depth=24, hidden=True ), diff --git a/music_assistant/providers/sonos_s1/__init__.py b/music_assistant/providers/sonos_s1/__init__.py index 74d556e4..787064e6 100644 --- a/music_assistant/providers/sonos_s1/__init__.py +++ b/music_assistant/providers/sonos_s1/__init__.py @@ -33,8 +33,9 @@ from soco.discovery import discover, scan_network from music_assistant.constants import ( CONF_CROSSFADE, CONF_ENTRY_CROSSFADE, + CONF_ENTRY_CROSSFADE_DURATION_HIDDEN, CONF_ENTRY_FLOW_MODE_HIDDEN_DISABLED, - CONF_ENTRY_HTTP_PROFILE_FORCED_1, + CONF_ENTRY_HTTP_PROFILE_DEFAULT_1, CONF_ENTRY_MANUAL_DISCOVERY_IPS, CONF_ENTRY_OUTPUT_CODEC, VERBOSE_LOG_LEVEL, @@ -179,21 +180,14 @@ class SonosPlayerProvider(PlayerProvider): ) -> tuple[ConfigEntry, ...]: """Return Config Entries for the given player.""" base_entries = await super().get_player_config_entries(player_id) - if not (self.sonosplayers.get(player_id)): - # most probably a syncgroup - return ( - *base_entries, - CONF_ENTRY_CROSSFADE, - CONF_ENTRY_OUTPUT_CODEC, - CONF_ENTRY_FLOW_MODE_HIDDEN_DISABLED, - ) return ( *base_entries, CONF_ENTRY_CROSSFADE, + CONF_ENTRY_CROSSFADE_DURATION_HIDDEN, CONF_ENTRY_SAMPLE_RATES, CONF_ENTRY_OUTPUT_CODEC, CONF_ENTRY_FLOW_MODE_HIDDEN_DISABLED, - CONF_ENTRY_HTTP_PROFILE_FORCED_1, + CONF_ENTRY_HTTP_PROFILE_DEFAULT_1, ) def is_device_invisible(self, ip_address: str) -> bool: diff --git a/music_assistant/providers/squeezelite/__init__.py b/music_assistant/providers/squeezelite/__init__.py index c98f5e17..60c95416 100644 --- a/music_assistant/providers/squeezelite/__init__.py +++ b/music_assistant/providers/squeezelite/__init__.py @@ -48,7 +48,6 @@ from music_assistant.constants import ( CONF_ENTRY_DEPRECATED_EQ_MID, CONF_ENTRY_DEPRECATED_EQ_TREBLE, CONF_ENTRY_HTTP_PROFILE_FORCED_2, - CONF_ENTRY_OUTPUT_CHANNELS, CONF_ENTRY_OUTPUT_CODEC, CONF_ENTRY_SYNC_ADJUST, CONF_PORT, @@ -316,7 +315,6 @@ class SlimprotoProvider(PlayerProvider): CONF_ENTRY_DEPRECATED_EQ_BASS, CONF_ENTRY_DEPRECATED_EQ_MID, CONF_ENTRY_DEPRECATED_EQ_TREBLE, - CONF_ENTRY_OUTPUT_CHANNELS, CONF_ENTRY_CROSSFADE_DURATION, CONF_ENTRY_OUTPUT_CODEC, CONF_ENTRY_SYNC_ADJUST, -- 2.34.1