From ef19fef69c5181a17bafa655aa065da8b8ad2b54 Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Thu, 19 Feb 2026 11:21:15 +0100 Subject: [PATCH] Fix universal player availability --- .../controllers/players/protocol_linking.py | 7 +--- .../providers/universal_player/player.py | 36 ++++++------------- 2 files changed, 12 insertions(+), 31 deletions(-) diff --git a/music_assistant/controllers/players/protocol_linking.py b/music_assistant/controllers/players/protocol_linking.py index b8758575..04ff213c 100644 --- a/music_assistant/controllers/players/protocol_linking.py +++ b/music_assistant/controllers/players/protocol_linking.py @@ -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: """ diff --git a/music_assistant/providers/universal_player/player.py b/music_assistant/providers/universal_player/player.py index 9c825dee..d50fc10f 100644 --- a/music_assistant/providers/universal_player/player.py +++ b/music_assistant/providers/universal_player/player.py @@ -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: -- 2.34.1