Fix AirPlay devices that need pairing not visible in the config (#3060)
authorMarcel van der Veldt <m.vanderveldt@outlook.com>
Fri, 30 Jan 2026 19:49:32 +0000 (20:49 +0100)
committerGitHub <noreply@github.com>
Fri, 30 Jan 2026 19:49:32 +0000 (20:49 +0100)
music_assistant/controllers/config.py
music_assistant/providers/airplay/__init__.py
music_assistant/providers/airplay/player.py

index 1306a0663b293e803899b33966866245d537f8b0..6e76abf1f4d2cd97d4195d58804a5e5bffb9bc5c 100644 (file)
@@ -546,12 +546,12 @@ class ConfigController:
                 continue
             # filter out unavailable players
             # (unless disabled, otherwise there is no way to re-enable them)
+            # note that we only check for missing players in the player controller,
+            # and we do allow players that are temporary unavailable (player.available = false)
+            # because this can also mean that the player needs additional configuration
+            # such as airplay devices that need pairing.
             player = self.mass.players.get(raw_conf["player_id"], False)
-            if (
-                not include_unavailable
-                and (not player or not player.available)
-                and raw_conf.get("enabled", True)
-            ):
+            if not include_unavailable and player is None and raw_conf.get("enabled", True):
                 continue
             # filter out disabled players
             if not include_disabled and not raw_conf.get("enabled", True):
index 9413a4ea3902a2611a1158389de7c4e5b791520d..0e334ee656275bbf62d22600d5012ed4ae953a60 100644 (file)
@@ -53,7 +53,7 @@ async def get_config_entries(
                 "If you experience issues or players are not fully in sync, disable this option. \n"
                 "Also note that a late joining player may take a few seconds to catch up."
             ),
-            category="airplay",
+            category="protocol_generic",
         ),
     )
 
index f276e1d91f6a326a8d76487952432904d6bea133..151f65b5589b8ceff682ce800f4309dffc7b0a0a 100644 (file)
@@ -176,6 +176,7 @@ class AirPlayPlayer(Player):
                     ConfigValueOption("Prefer AirPlay 2", StreamingProtocol.AIRPLAY2.value),
                 ],
                 default_value=0,
+                category="protocol_generic",
             ),
             ConfigEntry(
                 key=CONF_ENCRYPTION,
@@ -187,6 +188,7 @@ class AirPlayPlayer(Player):
                 depends_on=CONF_AIRPLAY_PROTOCOL,
                 depends_on_value=StreamingProtocol.RAOP.value,
                 hidden=self.protocol != StreamingProtocol.RAOP,
+                category="protocol_generic",
                 advanced=True,
             ),
             ConfigEntry(
@@ -199,6 +201,7 @@ class AirPlayPlayer(Player):
                 depends_on=CONF_AIRPLAY_PROTOCOL,
                 depends_on_value=StreamingProtocol.RAOP.value,
                 hidden=self.protocol != StreamingProtocol.RAOP,
+                category="protocol_generic",
                 advanced=True,
             ),
             CONF_ENTRY_SYNC_ADJUST,
@@ -209,6 +212,7 @@ class AirPlayPlayer(Player):
                 required=False,
                 label="Device password",
                 description="Some devices require a password to connect/play.",
+                category="protocol_generic",
                 advanced=True,
             ),
             # airplay has fixed sample rate/bit depth so make this config entry static and hidden
@@ -227,7 +231,7 @@ class AirPlayPlayer(Player):
                     "volume changes. \n"
                     "Enable this option to ignore these reports."
                 ),
-                category="airplay",
+                category="protocol_generic",
                 # TODO: remove depends_on when DACP support is added for AirPlay2
                 depends_on=CONF_AIRPLAY_PROTOCOL,
                 depends_on_value=StreamingProtocol.RAOP.value,
@@ -307,6 +311,7 @@ class AirPlayPlayer(Player):
                         type=ConfigEntryType.STRING,
                         label="Enter the 4-digit PIN shown on the device",
                         required=True,
+                        category="protocol_generic",
                     )
                 )
                 entries.append(
@@ -315,6 +320,7 @@ class AirPlayPlayer(Player):
                         type=ConfigEntryType.ACTION,
                         label=f"Complete {protocol_name} pairing with the PIN",
                         action=CONF_ACTION_FINISH_PAIRING,
+                        category="protocol_generic",
                     )
                 )
             else:
@@ -327,6 +333,7 @@ class AirPlayPlayer(Player):
                             f"This device requires {protocol_name} pairing before it can be used. "
                             "Click the button below to start the pairing process."
                         ),
+                        category="protocol_generic",
                     )
                 )
                 entries.append(
@@ -335,6 +342,7 @@ class AirPlayPlayer(Player):
                         type=ConfigEntryType.ACTION,
                         label=f"Start {protocol_name} pairing",
                         action=CONF_ACTION_START_PAIRING,
+                        category="protocol_generic",
                     )
                 )
         else:
@@ -344,6 +352,7 @@ class AirPlayPlayer(Player):
                     key="pairing_status",
                     type=ConfigEntryType.LABEL,
                     label=f"Device is paired ({protocol_name}) and ready to use.",
+                    category="protocol_generic",
                 )
             )
             # Add reset pairing button
@@ -353,6 +362,7 @@ class AirPlayPlayer(Player):
                     type=ConfigEntryType.ACTION,
                     label=f"Reset {protocol_name} pairing",
                     action=CONF_ACTION_RESET_PAIRING,
+                    category="protocol_generic",
                 )
             )
 
@@ -368,6 +378,7 @@ class AirPlayPlayer(Player):
                     value=values.get(conf_key) if values else None,
                     required=False,
                     hidden=True,
+                    category="protocol_generic",
                 )
             )
         return entries