Fix: Config entries
authorMarcel van der Veldt <m.vanderveldt@outlook.com>
Sat, 15 Mar 2025 01:31:03 +0000 (02:31 +0100)
committerMarcel van der Veldt <m.vanderveldt@outlook.com>
Sat, 15 Mar 2025 05:23:44 +0000 (06:23 +0100)
15 files changed:
music_assistant/constants.py
music_assistant/controllers/config.py
music_assistant/controllers/players.py
music_assistant/models/player_provider.py
music_assistant/providers/_template_player_provider/__init__.py
music_assistant/providers/airplay/provider.py
music_assistant/providers/builtin_player/__init__.py
music_assistant/providers/chromecast/__init__.py
music_assistant/providers/fully_kiosk/__init__.py
music_assistant/providers/hass_players/__init__.py
music_assistant/providers/player_group/__init__.py
music_assistant/providers/snapcast/__init__.py
music_assistant/providers/sonos/provider.py
music_assistant/providers/sonos_s1/__init__.py
music_assistant/providers/squeezelite/__init__.py

index fcb346bbe03adf68ecd5b1411c00e4b5dd5ce200..0546904e41463505e26ae88fb881659ae763e293 100644 (file)
@@ -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",
index 47449a3a4402edc6b6aa4c46d9342f5ba062a76a..bdd52e0b4903bfa85de08b79e55009ddb38af7f4 100644 (file)
@@ -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()
 
index 02405c01f6c250e1be57ddec08c6da03439a6b78..65cfcdd9058444112332affd0d912e24ceaec649 100644 (file)
@@ -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:
index ae050dc68f3d6fd98d7bb6329bf25631122144a4..989c68c6fab956f90ad92b00e658be69087db9a9 100644 (file)
@@ -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:
index 33ddfd60475f1b804ea1a3e3f39db3246349edfd..73f33e65f0c8cdc48951d13d7cf3eeb3e213b9cc 100644 (file)
@@ -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."""
index 1c7395aee787c0131603df81a3f257618126cb48..e01aa0fb5e323a91582e531eba6fa1e83b1c9be5 100644 (file)
@@ -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,
index 16a9caf6641a1dc41002047f8bed808c681c0041..55ee3ee6107f6ce80c294c6bef396418a2c8a6b3 100644 (file)
@@ -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:
index 36237c507849e0ab64c21933ebd0c92785f75223..ae8a48b11380bac42e328fb36028ae1fcb97713f 100644 (file)
@@ -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."""
index d25137ec325322ef226320d0fc852164c3eaad09..f97fe44ea418ba372384aa47aae96aabbd902b07 100644 (file)
@@ -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:
index b408ed1e0808f0be27a331a129bd20e70f0a39e8..6ef28ff9731bb5b3b2fe10de16e73ce5fbf2b5e1 100644 (file)
@@ -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."""
index 845f152ad1c0661e35c0808637e401d98e69076d..42f83e2d729998d5e1e83c74d077d5369da10483 100644 (file)
@@ -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(
index a78da442e7698a9288f201b24469b9bafca58222..e3475b65bab961d979dab5767d3b3ce8982a3e42 100644 (file)
@@ -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(
index e0470a6399009b8f3eb3fe5657ac8b578a1a3193..b8e04218a038165e573f4682752fa708e4004ab5 100644 (file)
@@ -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
             ),
index 74d556e4e013595c8cbb5f1a7ec0fc27591d4eaf..787064e6687dd8f1622e69a5e89b46ef16fcfa63 100644 (file)
@@ -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:
index c98f5e1739b91a8a7c0876ea7757566742e1da77..60c9541629b4025579d5b3ab7427bcfc4def15e8 100644 (file)
@@ -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,