Skip to content

Commit c32a471

Browse files
authored
Register music assistant services in async setup (home-assistant#155963)
1 parent 97b7e51 commit c32a471

File tree

5 files changed

+101
-84
lines changed

5 files changed

+101
-84
lines changed

homeassistant/components/music_assistant/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@
2727
)
2828

2929
from .const import ATTR_CONF_EXPOSE_PLAYER_TO_HA, DOMAIN, LOGGER
30-
from .services import get_music_assistant_client, register_actions
30+
from .helpers import get_music_assistant_client
31+
from .services import register_actions
3132

3233
if TYPE_CHECKING:
3334
from music_assistant_models.event import MassEvent

homeassistant/components/music_assistant/helpers.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,18 @@
44

55
from collections.abc import Callable, Coroutine
66
import functools
7-
from typing import Any
7+
from typing import TYPE_CHECKING, Any
88

99
from music_assistant_models.errors import MusicAssistantError
1010

11-
from homeassistant.exceptions import HomeAssistantError
11+
from homeassistant.config_entries import ConfigEntryState
12+
from homeassistant.core import HomeAssistant, callback
13+
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
14+
15+
if TYPE_CHECKING:
16+
from music_assistant_client import MusicAssistantClient
17+
18+
from . import MusicAssistantConfigEntry
1219

1320

1421
def catch_musicassistant_error[**_P, _R](
@@ -26,3 +33,16 @@ async def wrapper(*args: _P.args, **kwargs: _P.kwargs) -> _R:
2633
raise HomeAssistantError(error_msg) from err
2734

2835
return wrapper
36+
37+
38+
@callback
39+
def get_music_assistant_client(
40+
hass: HomeAssistant, config_entry_id: str
41+
) -> MusicAssistantClient:
42+
"""Get the Music Assistant client for the given config entry."""
43+
entry: MusicAssistantConfigEntry | None
44+
if not (entry := hass.config_entries.async_get_entry(config_entry_id)):
45+
raise ServiceValidationError("Entry not found")
46+
if entry.state is not ConfigEntryState.LOADED:
47+
raise ServiceValidationError("Entry not loaded")
48+
return entry.runtime_data.mass

homeassistant/components/music_assistant/media_player.py

Lines changed: 3 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,9 @@
2222
from music_assistant_models.event import MassEvent
2323
from music_assistant_models.media_items import ItemMapping, MediaItemType, Track
2424
from music_assistant_models.player_queue import PlayerQueue
25-
import voluptuous as vol
2625

2726
from homeassistant.components import media_source
2827
from homeassistant.components.media_player import (
29-
ATTR_MEDIA_ENQUEUE,
3028
ATTR_MEDIA_EXTRA,
3129
BrowseMedia,
3230
MediaPlayerDeviceClass,
@@ -41,38 +39,26 @@
4139
async_process_play_media_url,
4240
)
4341
from homeassistant.const import ATTR_NAME, STATE_OFF, Platform
44-
from homeassistant.core import HomeAssistant, ServiceResponse, SupportsResponse
42+
from homeassistant.core import HomeAssistant, ServiceResponse
4543
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
46-
from homeassistant.helpers import config_validation as cv, entity_registry as er
47-
from homeassistant.helpers.entity_platform import (
48-
AddConfigEntryEntitiesCallback,
49-
async_get_current_platform,
50-
)
44+
from homeassistant.helpers import entity_registry as er
45+
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
5146
from homeassistant.util.dt import utc_from_timestamp
5247

5348
from . import MusicAssistantConfigEntry
5449
from .const import (
5550
ATTR_ACTIVE,
5651
ATTR_ACTIVE_QUEUE,
57-
ATTR_ALBUM,
58-
ATTR_ANNOUNCE_VOLUME,
59-
ATTR_ARTIST,
60-
ATTR_AUTO_PLAY,
6152
ATTR_CURRENT_INDEX,
6253
ATTR_CURRENT_ITEM,
6354
ATTR_ELAPSED_TIME,
6455
ATTR_ITEMS,
6556
ATTR_MASS_PLAYER_TYPE,
66-
ATTR_MEDIA_ID,
67-
ATTR_MEDIA_TYPE,
6857
ATTR_NEXT_ITEM,
6958
ATTR_QUEUE_ID,
7059
ATTR_RADIO_MODE,
7160
ATTR_REPEAT_MODE,
7261
ATTR_SHUFFLE_ENABLED,
73-
ATTR_SOURCE_PLAYER,
74-
ATTR_URL,
75-
ATTR_USE_PRE_ANNOUNCE,
7662
DOMAIN,
7763
)
7864
from .entity import MusicAssistantEntity
@@ -122,11 +108,6 @@
122108
# UNKNOWN is intentionally not mapped - will return None
123109
}
124110

125-
SERVICE_PLAY_MEDIA_ADVANCED = "play_media"
126-
SERVICE_PLAY_ANNOUNCEMENT = "play_announcement"
127-
SERVICE_TRANSFER_QUEUE = "transfer_queue"
128-
SERVICE_GET_QUEUE = "get_queue"
129-
130111

131112
async def async_setup_entry(
132113
hass: HomeAssistant,
@@ -143,44 +124,6 @@ def add_player(player_id: str) -> None:
143124
# register callback to add players when they are discovered
144125
entry.runtime_data.platform_handlers.setdefault(Platform.MEDIA_PLAYER, add_player)
145126

146-
# add platform service for play_media with advanced options
147-
platform = async_get_current_platform()
148-
platform.async_register_entity_service(
149-
SERVICE_PLAY_MEDIA_ADVANCED,
150-
{
151-
vol.Required(ATTR_MEDIA_ID): vol.All(cv.ensure_list, [cv.string]),
152-
vol.Optional(ATTR_MEDIA_TYPE): vol.Coerce(MediaType),
153-
vol.Optional(ATTR_MEDIA_ENQUEUE): vol.Coerce(QueueOption),
154-
vol.Optional(ATTR_ARTIST): cv.string,
155-
vol.Optional(ATTR_ALBUM): cv.string,
156-
vol.Optional(ATTR_RADIO_MODE): vol.Coerce(bool),
157-
},
158-
"_async_handle_play_media",
159-
)
160-
platform.async_register_entity_service(
161-
SERVICE_PLAY_ANNOUNCEMENT,
162-
{
163-
vol.Required(ATTR_URL): cv.string,
164-
vol.Optional(ATTR_USE_PRE_ANNOUNCE): vol.Coerce(bool),
165-
vol.Optional(ATTR_ANNOUNCE_VOLUME): vol.Coerce(int),
166-
},
167-
"_async_handle_play_announcement",
168-
)
169-
platform.async_register_entity_service(
170-
SERVICE_TRANSFER_QUEUE,
171-
{
172-
vol.Optional(ATTR_SOURCE_PLAYER): cv.entity_id,
173-
vol.Optional(ATTR_AUTO_PLAY): vol.Coerce(bool),
174-
},
175-
"_async_handle_transfer_queue",
176-
)
177-
platform.async_register_entity_service(
178-
SERVICE_GET_QUEUE,
179-
schema=None,
180-
func="_async_handle_get_queue",
181-
supports_response=SupportsResponse.ONLY,
182-
)
183-
184127

185128
class MusicAssistantPlayer(MusicAssistantEntity, MediaPlayerEntity):
186129
"""Representation of MediaPlayerEntity from Music Assistant Player."""

homeassistant/components/music_assistant/services.py

Lines changed: 70 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@
44

55
from typing import TYPE_CHECKING
66

7-
from music_assistant_models.enums import MediaType
7+
from music_assistant_models.enums import MediaType, QueueOption
88
import voluptuous as vol
99

10-
from homeassistant.config_entries import ConfigEntryState
10+
from homeassistant.components.media_player import (
11+
ATTR_MEDIA_ENQUEUE,
12+
DOMAIN as MEDIA_PLAYER_DOMAIN,
13+
)
1114
from homeassistant.const import ATTR_CONFIG_ENTRY_ID
1215
from homeassistant.core import (
1316
HomeAssistant,
@@ -17,39 +20,48 @@
1720
callback,
1821
)
1922
from homeassistant.exceptions import ServiceValidationError
20-
from homeassistant.helpers import config_validation as cv
23+
from homeassistant.helpers import config_validation as cv, service
2124

2225
from .const import (
26+
ATTR_ALBUM,
2327
ATTR_ALBUM_ARTISTS_ONLY,
2428
ATTR_ALBUM_TYPE,
2529
ATTR_ALBUMS,
30+
ATTR_ANNOUNCE_VOLUME,
31+
ATTR_ARTIST,
2632
ATTR_ARTISTS,
2733
ATTR_AUDIOBOOKS,
34+
ATTR_AUTO_PLAY,
2835
ATTR_FAVORITE,
2936
ATTR_ITEMS,
3037
ATTR_LIBRARY_ONLY,
3138
ATTR_LIMIT,
39+
ATTR_MEDIA_ID,
3240
ATTR_MEDIA_TYPE,
3341
ATTR_OFFSET,
3442
ATTR_ORDER_BY,
3543
ATTR_PLAYLISTS,
3644
ATTR_PODCASTS,
3745
ATTR_RADIO,
46+
ATTR_RADIO_MODE,
3847
ATTR_SEARCH,
3948
ATTR_SEARCH_ALBUM,
4049
ATTR_SEARCH_ARTIST,
4150
ATTR_SEARCH_NAME,
51+
ATTR_SOURCE_PLAYER,
4252
ATTR_TRACKS,
53+
ATTR_URL,
54+
ATTR_USE_PRE_ANNOUNCE,
4355
DOMAIN,
4456
)
57+
from .helpers import get_music_assistant_client
4558
from .schemas import (
4659
LIBRARY_RESULTS_SCHEMA,
4760
SEARCH_RESULT_SCHEMA,
4861
media_item_dict_from_mass_item,
4962
)
5063

5164
if TYPE_CHECKING:
52-
from music_assistant_client import MusicAssistantClient
5365
from music_assistant_models.media_items import (
5466
Album,
5567
Artist,
@@ -60,28 +72,18 @@
6072
Track,
6173
)
6274

63-
from . import MusicAssistantConfigEntry
64-
6575
SERVICE_SEARCH = "search"
6676
SERVICE_GET_LIBRARY = "get_library"
77+
SERVICE_PLAY_MEDIA_ADVANCED = "play_media"
78+
SERVICE_PLAY_ANNOUNCEMENT = "play_announcement"
79+
SERVICE_TRANSFER_QUEUE = "transfer_queue"
80+
SERVICE_GET_QUEUE = "get_queue"
81+
6782
DEFAULT_OFFSET = 0
6883
DEFAULT_LIMIT = 25
6984
DEFAULT_SORT_ORDER = "name"
7085

7186

72-
@callback
73-
def get_music_assistant_client(
74-
hass: HomeAssistant, config_entry_id: str
75-
) -> MusicAssistantClient:
76-
"""Get the Music Assistant client for the given config entry."""
77-
entry: MusicAssistantConfigEntry | None
78-
if not (entry := hass.config_entries.async_get_entry(config_entry_id)):
79-
raise ServiceValidationError("Entry not found")
80-
if entry.state is not ConfigEntryState.LOADED:
81-
raise ServiceValidationError("Entry not loaded")
82-
return entry.runtime_data.mass
83-
84-
8587
@callback
8688
def register_actions(hass: HomeAssistant) -> None:
8789
"""Register custom actions."""
@@ -124,6 +126,55 @@ def register_actions(hass: HomeAssistant) -> None:
124126
supports_response=SupportsResponse.ONLY,
125127
)
126128

129+
# Platform entity services
130+
service.async_register_platform_entity_service(
131+
hass,
132+
DOMAIN,
133+
SERVICE_PLAY_MEDIA_ADVANCED,
134+
entity_domain=MEDIA_PLAYER_DOMAIN,
135+
schema={
136+
vol.Required(ATTR_MEDIA_ID): vol.All(cv.ensure_list, [cv.string]),
137+
vol.Optional(ATTR_MEDIA_TYPE): vol.Coerce(MediaType),
138+
vol.Optional(ATTR_MEDIA_ENQUEUE): vol.Coerce(QueueOption),
139+
vol.Optional(ATTR_ARTIST): cv.string,
140+
vol.Optional(ATTR_ALBUM): cv.string,
141+
vol.Optional(ATTR_RADIO_MODE): vol.Coerce(bool),
142+
},
143+
func="_async_handle_play_media",
144+
)
145+
service.async_register_platform_entity_service(
146+
hass,
147+
DOMAIN,
148+
SERVICE_PLAY_ANNOUNCEMENT,
149+
entity_domain=MEDIA_PLAYER_DOMAIN,
150+
schema={
151+
vol.Required(ATTR_URL): cv.string,
152+
vol.Optional(ATTR_USE_PRE_ANNOUNCE): vol.Coerce(bool),
153+
vol.Optional(ATTR_ANNOUNCE_VOLUME): vol.Coerce(int),
154+
},
155+
func="_async_handle_play_announcement",
156+
)
157+
service.async_register_platform_entity_service(
158+
hass,
159+
DOMAIN,
160+
SERVICE_TRANSFER_QUEUE,
161+
entity_domain=MEDIA_PLAYER_DOMAIN,
162+
schema={
163+
vol.Optional(ATTR_SOURCE_PLAYER): cv.entity_id,
164+
vol.Optional(ATTR_AUTO_PLAY): vol.Coerce(bool),
165+
},
166+
func="_async_handle_transfer_queue",
167+
)
168+
service.async_register_platform_entity_service(
169+
hass,
170+
DOMAIN,
171+
SERVICE_GET_QUEUE,
172+
entity_domain=MEDIA_PLAYER_DOMAIN,
173+
schema=None,
174+
func="_async_handle_get_queue",
175+
supports_response=SupportsResponse.ONLY,
176+
)
177+
127178

128179
async def handle_search(call: ServiceCall) -> ServiceResponse:
129180
"""Handle queue_command action."""

tests/components/music_assistant/test_media_player.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,7 @@
3030
SERVICE_UNJOIN,
3131
MediaPlayerEntityFeature,
3232
)
33-
from homeassistant.components.music_assistant.const import DOMAIN
34-
from homeassistant.components.music_assistant.media_player import (
33+
from homeassistant.components.music_assistant.const import (
3534
ATTR_ALBUM,
3635
ATTR_ANNOUNCE_VOLUME,
3736
ATTR_ARTIST,
@@ -42,6 +41,9 @@
4241
ATTR_SOURCE_PLAYER,
4342
ATTR_URL,
4443
ATTR_USE_PRE_ANNOUNCE,
44+
DOMAIN,
45+
)
46+
from homeassistant.components.music_assistant.services import (
4547
SERVICE_GET_QUEUE,
4648
SERVICE_PLAY_ANNOUNCEMENT,
4749
SERVICE_PLAY_MEDIA_ADVANCED,

0 commit comments

Comments
 (0)