Fix universal player availability
authorMarcel van der Veldt <m.vanderveldt@outlook.com>
Thu, 19 Feb 2026 10:21:15 +0000 (11:21 +0100)
committerMarcel van der Veldt <m.vanderveldt@outlook.com>
Thu, 19 Feb 2026 10:21:15 +0000 (11:21 +0100)
music_assistant/controllers/players/protocol_linking.py
music_assistant/providers/universal_player/player.py

index b8758575e38c967af7ae9415d4a6017f6887ac2d..04ff213cf380d03dea2df8fac5c8677b9284c881 100644 (file)
@@ -230,8 +230,6 @@ class ProtocolLinkingMixin:
                             native_player.device_info.add_identifier(conn_type, value)
                         # Update model/manufacturer if universal player has generic values
                         self._update_universal_device_info(native_player, protocol_player)
-                        # Update availability from protocol players
-                        native_player.update_from_protocol_players()
                         # Persist updated data to config (async via task)
                         self._save_universal_player_data(native_player)
                         protocol_player.update_state()
@@ -372,8 +370,6 @@ class ProtocolLinkingMixin:
                 universal_player.device_info.add_identifier(conn_type, value)
             # Update model/manufacturer if universal player has generic values
             self._update_universal_device_info(universal_player, protocol_player)
-            # Update availability from protocol players
-            universal_player.update_from_protocol_players()
 
             # Persist all player data (protocol IDs, identifiers, device info) to config
             for provider in self.mass.get_providers(ProviderType.PLAYER):
@@ -440,8 +436,7 @@ class ProtocolLinkingMixin:
             player.update_state()
 
         # Update availability from protocol players
-        if isinstance(universal_player, UniversalPlayer):
-            universal_player.update_from_protocol_players()
+        universal_player.update_state()
 
     async def _create_or_update_universal_player(self, protocol_players: list[Player]) -> None:
         """
index 9c825dee636fcf306895a4d646ced489d0728d3d..d50fc10f62f02084bba708045c0dff4ac44c76cc 100644 (file)
@@ -14,12 +14,12 @@ from __future__ import annotations
 
 from typing import TYPE_CHECKING
 
-from music_assistant_models.enums import PlayerFeature
-
 from music_assistant.constants import CONF_PREFERRED_OUTPUT_PROTOCOL
 from music_assistant.models.player import DeviceInfo, Player
 
 if TYPE_CHECKING:
+    from music_assistant_models.enums import PlayerFeature
+
     from .provider import UniversalPlayerProvider
 
 
@@ -55,8 +55,6 @@ class UniversalPlayer(Player):
         # Set player attributes
         self._attr_name = name
         self._attr_device_info = device_info
-        # Start as unavailable - will be updated when protocol players are linked
-        self._attr_available = False
         # a universal player does not have any features on its own,
         # it delegates to protocol players
         self._attr_supported_features = set()
@@ -72,6 +70,15 @@ class UniversalPlayer(Player):
             return True
         return False
 
+    @property
+    def available(self) -> bool:
+        """Return if the player is currently available."""
+        # A universal player is available if any of its linked protocol players are available
+        return any(
+            (p := self.mass.players.get_player(pid)) and p.available
+            for pid in self._protocol_player_ids
+        )
+
     @property
     def expose_to_ha_by_default(self) -> bool:
         """Return if the player should be exposed to Home Assistant by default."""
@@ -115,27 +122,6 @@ class UniversalPlayer(Player):
 
         return None
 
-    def update_from_protocol_players(self) -> None:
-        """
-        Update state from linked protocol players.
-
-        Called to sync state like volume, availability from protocol players.
-        """
-        # Aggregate availability - available if any protocol is available
-        self._attr_available = any(
-            (p := self.mass.players.get_player(pid)) and p.available
-            for pid in self._protocol_player_ids
-        )
-        # Get volume from best control target
-        if target := self._get_control_target(PlayerFeature.VOLUME_SET):
-            if target.volume_level is not None:
-                self._attr_volume_level = target.volume_level
-        if target := self._get_control_target(PlayerFeature.VOLUME_MUTE):
-            if target.volume_muted is not None:
-                self._attr_volume_muted = target.volume_muted
-
-        self.update_state()
-
     def add_protocol_player(self, protocol_player_id: str) -> None:
         """Add a protocol player to this universal player."""
         if protocol_player_id not in self._protocol_player_ids: