Some small follow-up fixes for protocols linking
authorMarcel van der Veldt <m.vanderveldt@outlook.com>
Mon, 16 Feb 2026 09:34:42 +0000 (10:34 +0100)
committerMarcel van der Veldt <m.vanderveldt@outlook.com>
Mon, 16 Feb 2026 09:34:42 +0000 (10:34 +0100)
music_assistant/controllers/players/protocol_linking.py
music_assistant/mass.py
music_assistant/providers/hass_players/provider.py

index f05fc75f93ced25c0e9576fb56c577c1c50dbda9..390af146cc601e2778da4d413f796d38406539d5 100644 (file)
@@ -651,15 +651,18 @@ class ProtocolLinkingMixin:
             if not protocol_provider:
                 continue
 
+            # Extract domain from provider instance_id (e.g., "airplay--uuid" -> "airplay")
+            protocol_domain = protocol_provider.split("--")[0]
+
             # Get provider name for display
             provider_name = "Protocol"  # Default fallback
             for provider in self.mass.get_providers(ProviderType.PLAYER):
-                if provider.domain == protocol_provider:
+                if provider.domain == protocol_domain:
                     provider_name = provider.name
                     break
 
             # Get priority for this protocol
-            priority = PROTOCOL_PRIORITY.get(protocol_provider, 100)
+            priority = PROTOCOL_PRIORITY.get(protocol_domain, 100)
 
             # Check if protocol player is available (registered)
             protocol_player = self.get_player(protocol_id)
@@ -670,7 +673,7 @@ class ProtocolLinkingMixin:
                 OutputProtocol(
                     output_protocol_id=protocol_id,
                     name=provider_name,
-                    protocol_domain=protocol_provider,
+                    protocol_domain=protocol_domain,
                     priority=priority,
                     is_native=False,
                     available=is_available,
index ab36005591a90656561c61545d9b9018d3e5e534..d309342a8f187b3f07b883e41f7d3d6a1468f8fd 100644 (file)
@@ -282,9 +282,13 @@ class MusicAssistant:
         return list(self._provider_manifests.values())
 
     @api_command("providers/manifests/get")
-    def get_provider_manifest(self, domain: str) -> ProviderManifest:
+    def get_provider_manifest(self, instance_id_or_domain: str) -> ProviderManifest:
         """Return Provider manifests of single provider(domain)."""
-        return self._provider_manifests[domain]
+        if instance_id_or_domain in self._provider_manifests:
+            return self._provider_manifests[instance_id_or_domain]
+        if provider := self.get_provider(instance_id_or_domain, return_unavailable=True):
+            return provider.manifest
+        raise KeyError(f"Provider manifest not found for {instance_id_or_domain}")
 
     @api_command("providers")
     def get_providers(
index c74658292e3877b77533db72a71a13be856d6591..1aa4cd093399baf5ef9b075db288dc4149be12e2 100644 (file)
@@ -10,6 +10,8 @@ from __future__ import annotations
 from collections.abc import Callable
 from typing import TYPE_CHECKING, Any, cast
 
+from music_assistant_models.enums import ProviderFeature
+
 from music_assistant.mass import MusicAssistant
 from music_assistant.models.player_provider import PlayerProvider
 
@@ -42,7 +44,8 @@ class HomeAssistantPlayerProvider(PlayerProvider):
         hass_prov: HomeAssistantProvider,
     ) -> None:
         """Initialize MusicProvider."""
-        super().__init__(mass, manifest, config)
+        supported_features = {ProviderFeature.REMOVE_PLAYER}
+        super().__init__(mass, manifest, config, supported_features=supported_features)
         self.hass_prov = hass_prov
 
     async def loaded_in_mass(self) -> None:
@@ -63,6 +66,12 @@ class HomeAssistantPlayerProvider(PlayerProvider):
         self.on_unload_callbacks = [
             await self.hass_prov.hass.subscribe_entities(self._on_entity_state_update, player_ids)
         ]
+        # cleanup any players that are no longer in the config
+        for player_conf in await self.mass.config.get_player_configs(
+            provider=self.instance_id, include_unavailable=True, include_disabled=True
+        ):
+            if player_conf.player_id not in player_ids:
+                await self.mass.players.remove(player_conf.player_id)
 
     async def unload(self, is_removed: bool = False) -> None:
         """
@@ -75,6 +84,16 @@ class HomeAssistantPlayerProvider(PlayerProvider):
             for callback in self.on_unload_callbacks:
                 callback()
 
+    async def remove_player(self, player_id: str) -> None:
+        """Remove a player."""
+        player_ids = cast("list[str]", self.config.get_value(CONF_PLAYERS))
+        if player_id in player_ids:
+            player_ids.remove(player_id)
+            self.mass.config.set_raw_provider_config_value(
+                self.instance_id, CONF_PLAYERS, player_ids
+            )
+        await self.mass.players.unregister(player_id, True)
+
     async def _setup_player(
         self,
         state: HassState,