/
/
/
1"""Helpers for building shared configuration entries."""
2
3from __future__ import annotations
4
5from typing import TYPE_CHECKING
6
7from music_assistant_models.config_entries import ConfigEntry, ConfigValueOption
8from music_assistant_models.enums import ConfigEntryType, PlayerType
9
10if TYPE_CHECKING:
11 from music_assistant_models.config_entries import ConfigValueType
12
13 from music_assistant.mass import MusicAssistant
14
15# the Music Assistant players a per-player plugin exposes a device for
16CONF_CONNECTED_PLAYERS = "connected_players"
17# how the advertised device name is composed from the connected player's name
18CONF_PUBLISH_NAME_TEMPLATE = "publish_name_template"
19PUBLISH_NAME_PLAYER_MASS = "player_mass"
20PUBLISH_NAME_PLAYER = "player"
21PUBLISH_NAME_MASS_PLAYER = "mass_player"
22PUBLISH_NAME_TEMPLATES = (
23 PUBLISH_NAME_PLAYER_MASS,
24 PUBLISH_NAME_PLAYER,
25 PUBLISH_NAME_MASS_PLAYER,
26)
27# Player types that can render audio, so capture-only, display and lighting
28# clients are never offered as a playback target.
29PLAYBACK_TARGET_TYPES = {
30 PlayerType.PLAYER,
31 PlayerType.STEREO_PAIR,
32 PlayerType.GROUP,
33}
34
35
36def create_player_selector(
37 mass: MusicAssistant,
38 key: str,
39 selected_value: ConfigValueType = None,
40) -> ConfigEntry:
41 """
42 Return a required single-player selector populated from the available players.
43
44 :param mass: The Music Assistant instance providing the current players.
45 :param key: The config entry key for the selected player.
46 :param selected_value: Previously selected player id to prefill when still available.
47 """
48 options = _player_options(mass)
49 selected = selected_value if any(option.value == selected_value for option in options) else None
50 return ConfigEntry(
51 key=key,
52 type=ConfigEntryType.STRING,
53 required=True,
54 default_value=selected,
55 value=selected,
56 options=options,
57 )
58
59
60def create_connected_players_entry(
61 mass: MusicAssistant, selected: list[str] | None = None
62) -> ConfigEntry:
63 """
64 Return the multi-select of players a per-player plugin exposes a device for.
65
66 :param mass: The Music Assistant instance providing the current players.
67 :param selected: Currently selected player ids; ids not registered right now stay
68 listed (with the id as title) so saving the form does not silently drop them.
69 """
70 options = _player_options(mass)
71 known_ids = {option.value for option in options}
72 options += [
73 ConfigValueOption(player_id, title=player_id)
74 for player_id in selected or []
75 if player_id not in known_ids
76 ]
77 return ConfigEntry(
78 key=CONF_CONNECTED_PLAYERS,
79 type=ConfigEntryType.STRING,
80 multi_value=True,
81 required=False,
82 default_value=[],
83 value=list(selected or []),
84 options=options,
85 )
86
87
88def create_publish_name_template_entry(current: ConfigValueType = None) -> ConfigEntry:
89 """
90 Return the dropdown selecting how the advertised device name is composed.
91
92 :param current: Previously stored template value to preselect.
93 """
94 selected = current if current in PUBLISH_NAME_TEMPLATES else PUBLISH_NAME_PLAYER_MASS
95 return ConfigEntry(
96 key=CONF_PUBLISH_NAME_TEMPLATE,
97 type=ConfigEntryType.STRING,
98 required=True,
99 default_value=PUBLISH_NAME_PLAYER_MASS,
100 value=selected,
101 options=[ConfigValueOption(template) for template in PUBLISH_NAME_TEMPLATES],
102 )
103
104
105def resolve_publish_name(template: ConfigValueType, player_name: str) -> str:
106 """
107 Render the advertised device name for a player from the selected template.
108
109 :param template: One of the PUBLISH_NAME_TEMPLATES option values.
110 :param player_name: The connected player's display name.
111 """
112 if template == PUBLISH_NAME_PLAYER:
113 return player_name
114 if template == PUBLISH_NAME_MASS_PLAYER:
115 return f"Music Assistant | {player_name}"
116 return f"{player_name} | Music Assistant"
117
118
119def _player_options(mass: MusicAssistant) -> list[ConfigValueOption]:
120 """Return a picker option for every registered playback-capable player."""
121 return [
122 ConfigValueOption(player.player_id, title=player.display_name)
123 for player in sorted(
124 mass.players.all_players(False, False),
125 key=lambda player: player.display_name.lower(),
126 )
127 if player.type in PLAYBACK_TARGET_TYPES
128 ]
129