Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion homeassistant/components/music_assistant/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@
)

from .const import ATTR_CONF_EXPOSE_PLAYER_TO_HA, DOMAIN, LOGGER
from .services import get_music_assistant_client, register_actions
from .helpers import get_music_assistant_client
from .services import register_actions

if TYPE_CHECKING:
from music_assistant_models.event import MassEvent
Expand Down
24 changes: 22 additions & 2 deletions homeassistant/components/music_assistant/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,18 @@

from collections.abc import Callable, Coroutine
import functools
from typing import Any
from typing import TYPE_CHECKING, Any

from music_assistant_models.errors import MusicAssistantError

from homeassistant.exceptions import HomeAssistantError
from homeassistant.config_entries import ConfigEntryState
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError

if TYPE_CHECKING:
from music_assistant_client import MusicAssistantClient

from . import MusicAssistantConfigEntry


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

return wrapper


@callback
def get_music_assistant_client(
hass: HomeAssistant, config_entry_id: str
) -> MusicAssistantClient:
"""Get the Music Assistant client for the given config entry."""
entry: MusicAssistantConfigEntry | None
if not (entry := hass.config_entries.async_get_entry(config_entry_id)):
raise ServiceValidationError("Entry not found")
if entry.state is not ConfigEntryState.LOADED:
raise ServiceValidationError("Entry not loaded")
return entry.runtime_data.mass
63 changes: 3 additions & 60 deletions homeassistant/components/music_assistant/media_player.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,9 @@
from music_assistant_models.event import MassEvent
from music_assistant_models.media_items import ItemMapping, MediaItemType, Track
from music_assistant_models.player_queue import PlayerQueue
import voluptuous as vol

from homeassistant.components import media_source
from homeassistant.components.media_player import (
ATTR_MEDIA_ENQUEUE,
ATTR_MEDIA_EXTRA,
BrowseMedia,
MediaPlayerDeviceClass,
Expand All @@ -41,38 +39,26 @@
async_process_play_media_url,
)
from homeassistant.const import ATTR_NAME, STATE_OFF, Platform
from homeassistant.core import HomeAssistant, ServiceResponse, SupportsResponse
from homeassistant.core import HomeAssistant, ServiceResponse
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from homeassistant.helpers import config_validation as cv, entity_registry as er
from homeassistant.helpers.entity_platform import (
AddConfigEntryEntitiesCallback,
async_get_current_platform,
)
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.util.dt import utc_from_timestamp

from . import MusicAssistantConfigEntry
from .const import (
ATTR_ACTIVE,
ATTR_ACTIVE_QUEUE,
ATTR_ALBUM,
ATTR_ANNOUNCE_VOLUME,
ATTR_ARTIST,
ATTR_AUTO_PLAY,
ATTR_CURRENT_INDEX,
ATTR_CURRENT_ITEM,
ATTR_ELAPSED_TIME,
ATTR_ITEMS,
ATTR_MASS_PLAYER_TYPE,
ATTR_MEDIA_ID,
ATTR_MEDIA_TYPE,
ATTR_NEXT_ITEM,
ATTR_QUEUE_ID,
ATTR_RADIO_MODE,
ATTR_REPEAT_MODE,
ATTR_SHUFFLE_ENABLED,
ATTR_SOURCE_PLAYER,
ATTR_URL,
ATTR_USE_PRE_ANNOUNCE,
DOMAIN,
)
from .entity import MusicAssistantEntity
Expand Down Expand Up @@ -122,11 +108,6 @@
# UNKNOWN is intentionally not mapped - will return None
}

SERVICE_PLAY_MEDIA_ADVANCED = "play_media"
SERVICE_PLAY_ANNOUNCEMENT = "play_announcement"
SERVICE_TRANSFER_QUEUE = "transfer_queue"
SERVICE_GET_QUEUE = "get_queue"


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

# add platform service for play_media with advanced options
platform = async_get_current_platform()
platform.async_register_entity_service(
SERVICE_PLAY_MEDIA_ADVANCED,
{
vol.Required(ATTR_MEDIA_ID): vol.All(cv.ensure_list, [cv.string]),
vol.Optional(ATTR_MEDIA_TYPE): vol.Coerce(MediaType),
vol.Optional(ATTR_MEDIA_ENQUEUE): vol.Coerce(QueueOption),
vol.Optional(ATTR_ARTIST): cv.string,
vol.Optional(ATTR_ALBUM): cv.string,
vol.Optional(ATTR_RADIO_MODE): vol.Coerce(bool),
},
"_async_handle_play_media",
)
platform.async_register_entity_service(
SERVICE_PLAY_ANNOUNCEMENT,
{
vol.Required(ATTR_URL): cv.string,
vol.Optional(ATTR_USE_PRE_ANNOUNCE): vol.Coerce(bool),
vol.Optional(ATTR_ANNOUNCE_VOLUME): vol.Coerce(int),
},
"_async_handle_play_announcement",
)
platform.async_register_entity_service(
SERVICE_TRANSFER_QUEUE,
{
vol.Optional(ATTR_SOURCE_PLAYER): cv.entity_id,
vol.Optional(ATTR_AUTO_PLAY): vol.Coerce(bool),
},
"_async_handle_transfer_queue",
)
platform.async_register_entity_service(
SERVICE_GET_QUEUE,
schema=None,
func="_async_handle_get_queue",
supports_response=SupportsResponse.ONLY,
)


class MusicAssistantPlayer(MusicAssistantEntity, MediaPlayerEntity):
"""Representation of MediaPlayerEntity from Music Assistant Player."""
Expand Down
89 changes: 70 additions & 19 deletions homeassistant/components/music_assistant/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@

from typing import TYPE_CHECKING

from music_assistant_models.enums import MediaType
from music_assistant_models.enums import MediaType, QueueOption
import voluptuous as vol

from homeassistant.config_entries import ConfigEntryState
from homeassistant.components.media_player import (
ATTR_MEDIA_ENQUEUE,
DOMAIN as MEDIA_PLAYER_DOMAIN,
)
from homeassistant.const import ATTR_CONFIG_ENTRY_ID
from homeassistant.core import (
HomeAssistant,
Expand All @@ -17,39 +20,48 @@
callback,
)
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers import config_validation as cv, service

from .const import (
ATTR_ALBUM,
ATTR_ALBUM_ARTISTS_ONLY,
ATTR_ALBUM_TYPE,
ATTR_ALBUMS,
ATTR_ANNOUNCE_VOLUME,
ATTR_ARTIST,
ATTR_ARTISTS,
ATTR_AUDIOBOOKS,
ATTR_AUTO_PLAY,
ATTR_FAVORITE,
ATTR_ITEMS,
ATTR_LIBRARY_ONLY,
ATTR_LIMIT,
ATTR_MEDIA_ID,
ATTR_MEDIA_TYPE,
ATTR_OFFSET,
ATTR_ORDER_BY,
ATTR_PLAYLISTS,
ATTR_PODCASTS,
ATTR_RADIO,
ATTR_RADIO_MODE,
ATTR_SEARCH,
ATTR_SEARCH_ALBUM,
ATTR_SEARCH_ARTIST,
ATTR_SEARCH_NAME,
ATTR_SOURCE_PLAYER,
ATTR_TRACKS,
ATTR_URL,
ATTR_USE_PRE_ANNOUNCE,
DOMAIN,
)
from .helpers import get_music_assistant_client
from .schemas import (
LIBRARY_RESULTS_SCHEMA,
SEARCH_RESULT_SCHEMA,
media_item_dict_from_mass_item,
)

if TYPE_CHECKING:
from music_assistant_client import MusicAssistantClient
from music_assistant_models.media_items import (
Album,
Artist,
Expand All @@ -60,28 +72,18 @@
Track,
)

from . import MusicAssistantConfigEntry

SERVICE_SEARCH = "search"
SERVICE_GET_LIBRARY = "get_library"
SERVICE_PLAY_MEDIA_ADVANCED = "play_media"
SERVICE_PLAY_ANNOUNCEMENT = "play_announcement"
SERVICE_TRANSFER_QUEUE = "transfer_queue"
SERVICE_GET_QUEUE = "get_queue"

DEFAULT_OFFSET = 0
DEFAULT_LIMIT = 25
DEFAULT_SORT_ORDER = "name"


@callback
def get_music_assistant_client(
hass: HomeAssistant, config_entry_id: str
) -> MusicAssistantClient:
"""Get the Music Assistant client for the given config entry."""
entry: MusicAssistantConfigEntry | None
if not (entry := hass.config_entries.async_get_entry(config_entry_id)):
raise ServiceValidationError("Entry not found")
if entry.state is not ConfigEntryState.LOADED:
raise ServiceValidationError("Entry not loaded")
return entry.runtime_data.mass


@callback
def register_actions(hass: HomeAssistant) -> None:
"""Register custom actions."""
Expand Down Expand Up @@ -124,6 +126,55 @@ def register_actions(hass: HomeAssistant) -> None:
supports_response=SupportsResponse.ONLY,
)

# Platform entity services
service.async_register_platform_entity_service(
hass,
DOMAIN,
SERVICE_PLAY_MEDIA_ADVANCED,
entity_domain=MEDIA_PLAYER_DOMAIN,
schema={
vol.Required(ATTR_MEDIA_ID): vol.All(cv.ensure_list, [cv.string]),
vol.Optional(ATTR_MEDIA_TYPE): vol.Coerce(MediaType),
vol.Optional(ATTR_MEDIA_ENQUEUE): vol.Coerce(QueueOption),
vol.Optional(ATTR_ARTIST): cv.string,
vol.Optional(ATTR_ALBUM): cv.string,
vol.Optional(ATTR_RADIO_MODE): vol.Coerce(bool),
},
func="_async_handle_play_media",
)
service.async_register_platform_entity_service(
hass,
DOMAIN,
SERVICE_PLAY_ANNOUNCEMENT,
entity_domain=MEDIA_PLAYER_DOMAIN,
schema={
vol.Required(ATTR_URL): cv.string,
vol.Optional(ATTR_USE_PRE_ANNOUNCE): vol.Coerce(bool),
vol.Optional(ATTR_ANNOUNCE_VOLUME): vol.Coerce(int),
},
func="_async_handle_play_announcement",
)
service.async_register_platform_entity_service(
hass,
DOMAIN,
SERVICE_TRANSFER_QUEUE,
entity_domain=MEDIA_PLAYER_DOMAIN,
schema={
vol.Optional(ATTR_SOURCE_PLAYER): cv.entity_id,
vol.Optional(ATTR_AUTO_PLAY): vol.Coerce(bool),
},
func="_async_handle_transfer_queue",
)
service.async_register_platform_entity_service(
hass,
DOMAIN,
SERVICE_GET_QUEUE,
entity_domain=MEDIA_PLAYER_DOMAIN,
schema=None,
func="_async_handle_get_queue",
supports_response=SupportsResponse.ONLY,
)


async def handle_search(call: ServiceCall) -> ServiceResponse:
"""Handle queue_command action."""
Expand Down
6 changes: 4 additions & 2 deletions tests/components/music_assistant/test_media_player.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,7 @@
SERVICE_UNJOIN,
MediaPlayerEntityFeature,
)
from homeassistant.components.music_assistant.const import DOMAIN
from homeassistant.components.music_assistant.media_player import (
from homeassistant.components.music_assistant.const import (
ATTR_ALBUM,
ATTR_ANNOUNCE_VOLUME,
ATTR_ARTIST,
Expand All @@ -42,6 +41,9 @@
ATTR_SOURCE_PLAYER,
ATTR_URL,
ATTR_USE_PRE_ANNOUNCE,
DOMAIN,
)
from homeassistant.components.music_assistant.services import (
SERVICE_GET_QUEUE,
SERVICE_PLAY_ANNOUNCEMENT,
SERVICE_PLAY_MEDIA_ADVANCED,
Expand Down
Loading