Add select source action to Music Assistant (#145619)

This commit is contained in:
Marcel van der Veldt 2025-05-26 16:28:18 +02:00 committed by GitHub
parent 0d81694640
commit 6f9a39ab89
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 79 additions and 4 deletions

View File

@ -292,6 +292,10 @@ class MusicAssistantPlayer(MusicAssistantEntity, MediaPlayerEntity):
self._attr_state = MediaPlayerState(player.state.value) self._attr_state = MediaPlayerState(player.state.value)
else: else:
self._attr_state = MediaPlayerState(STATE_OFF) self._attr_state = MediaPlayerState(STATE_OFF)
self._attr_source = player.active_source
self._attr_source_list = [
source.name for source in player.source_list if not source.passive
]
group_members: list[str] = [] group_members: list[str] = []
if player.group_childs: if player.group_childs:
@ -459,6 +463,11 @@ class MusicAssistantPlayer(MusicAssistantEntity, MediaPlayerEntity):
"""Remove this player from any group.""" """Remove this player from any group."""
await self.mass.players.player_command_ungroup(self.player_id) await self.mass.players.player_command_ungroup(self.player_id)
@catch_musicassistant_error
async def async_select_source(self, source: str) -> None:
"""Select input source."""
await self.mass.players.player_command_select_source(self.player_id, source)
@catch_musicassistant_error @catch_musicassistant_error
async def _async_handle_play_media( async def _async_handle_play_media(
self, self,
@ -735,4 +744,6 @@ class MusicAssistantPlayer(MusicAssistantEntity, MediaPlayerEntity):
if self.player.power_control != PLAYER_CONTROL_NONE: if self.player.power_control != PLAYER_CONTROL_NONE:
supported_features |= MediaPlayerEntityFeature.TURN_ON supported_features |= MediaPlayerEntityFeature.TURN_ON
supported_features |= MediaPlayerEntityFeature.TURN_OFF supported_features |= MediaPlayerEntityFeature.TURN_OFF
if PlayerFeature.SELECT_SOURCE in self.player.supported_features:
supported_features |= MediaPlayerEntityFeature.SELECT_SOURCE
self._attr_supported_features = supported_features self._attr_supported_features = supported_features

View File

@ -18,7 +18,8 @@
"pause", "pause",
"set_members", "set_members",
"power", "power",
"enqueue" "enqueue",
"select_source"
], ],
"elapsed_time": null, "elapsed_time": null,
"elapsed_time_last_updated": 0, "elapsed_time_last_updated": 0,
@ -43,7 +44,32 @@
"hide_player_in_ui": ["when_unavailable"], "hide_player_in_ui": ["when_unavailable"],
"expose_to_ha": true, "expose_to_ha": true,
"can_group_with": ["00:00:00:00:00:02"], "can_group_with": ["00:00:00:00:00:02"],
"source_list": [] "source_list": [
{
"id": "00:00:00:00:00:01",
"name": "Music Assistant Queue",
"passive": false,
"can_play_pause": true,
"can_seek": true,
"can_next_previous": true
},
{
"id": "spotify",
"name": "Spotify Connect",
"passive": true,
"can_play_pause": true,
"can_seek": true,
"can_next_previous": true
},
{
"id": "linein",
"name": "Line-In",
"passive": false,
"can_play_pause": false,
"can_seek": false,
"can_next_previous": false
}
]
}, },
{ {
"player_id": "00:00:00:00:00:02", "player_id": "00:00:00:00:00:02",

View File

@ -54,6 +54,7 @@
'media_duration': 300, 'media_duration': 300,
'media_position': 0, 'media_position': 0,
'media_title': 'Test Track', 'media_title': 'Test Track',
'source': 'spotify',
'supported_features': <MediaPlayerEntityFeature: 8320959>, 'supported_features': <MediaPlayerEntityFeature: 8320959>,
'volume_level': 0.2, 'volume_level': 0.2,
}), }),
@ -125,6 +126,7 @@
'media_title': 'November Rain', 'media_title': 'November Rain',
'repeat': 'all', 'repeat': 'all',
'shuffle': True, 'shuffle': True,
'source': 'test_group_player_1',
'supported_features': <MediaPlayerEntityFeature: 8320959>, 'supported_features': <MediaPlayerEntityFeature: 8320959>,
'volume_level': 0.06, 'volume_level': 0.06,
}), }),
@ -142,6 +144,10 @@
}), }),
'area_id': None, 'area_id': None,
'capabilities': dict({ 'capabilities': dict({
'source_list': list([
'Music Assistant Queue',
'Line-In',
]),
}), }),
'config_entry_id': <ANY>, 'config_entry_id': <ANY>,
'config_subentry_id': <ANY>, 'config_subentry_id': <ANY>,
@ -165,7 +171,7 @@
'original_name': None, 'original_name': None,
'platform': 'music_assistant', 'platform': 'music_assistant',
'previous_unique_id': None, 'previous_unique_id': None,
'supported_features': <MediaPlayerEntityFeature: 8320959>, 'supported_features': <MediaPlayerEntityFeature: 8323007>,
'translation_key': None, 'translation_key': None,
'unique_id': '00:00:00:00:00:01', 'unique_id': '00:00:00:00:00:01',
'unit_of_measurement': None, 'unit_of_measurement': None,
@ -181,7 +187,11 @@
]), ]),
'icon': 'mdi:speaker', 'icon': 'mdi:speaker',
'mass_player_type': 'player', 'mass_player_type': 'player',
'supported_features': <MediaPlayerEntityFeature: 8320959>, 'source_list': list([
'Music Assistant Queue',
'Line-In',
]),
'supported_features': <MediaPlayerEntityFeature: 8323007>,
}), }),
'context': <ANY>, 'context': <ANY>,
'entity_id': 'media_player.test_player_1', 'entity_id': 'media_player.test_player_1',

View File

@ -16,6 +16,7 @@ from syrupy.filters import paths
from homeassistant.components.media_player import ( from homeassistant.components.media_player import (
ATTR_GROUP_MEMBERS, ATTR_GROUP_MEMBERS,
ATTR_INPUT_SOURCE,
ATTR_MEDIA_ENQUEUE, ATTR_MEDIA_ENQUEUE,
ATTR_MEDIA_REPEAT, ATTR_MEDIA_REPEAT,
ATTR_MEDIA_SEEK_POSITION, ATTR_MEDIA_SEEK_POSITION,
@ -25,6 +26,7 @@ from homeassistant.components.media_player import (
DOMAIN as MEDIA_PLAYER_DOMAIN, DOMAIN as MEDIA_PLAYER_DOMAIN,
SERVICE_CLEAR_PLAYLIST, SERVICE_CLEAR_PLAYLIST,
SERVICE_JOIN, SERVICE_JOIN,
SERVICE_SELECT_SOURCE,
SERVICE_UNJOIN, SERVICE_UNJOIN,
MediaPlayerEntityFeature, MediaPlayerEntityFeature,
) )
@ -620,6 +622,31 @@ async def test_media_player_get_queue_action(
assert response == snapshot(exclude=paths(f"{entity_id}.elapsed_time")) assert response == snapshot(exclude=paths(f"{entity_id}.elapsed_time"))
async def test_media_player_select_source_action(
hass: HomeAssistant,
music_assistant_client: MagicMock,
) -> None:
"""Test media_player entity select source action."""
await setup_integration_from_fixtures(hass, music_assistant_client)
entity_id = "media_player.test_player_1"
mass_player_id = "00:00:00:00:00:01"
state = hass.states.get(entity_id)
assert state
await hass.services.async_call(
MEDIA_PLAYER_DOMAIN,
SERVICE_SELECT_SOURCE,
{
ATTR_ENTITY_ID: entity_id,
ATTR_INPUT_SOURCE: "linein",
},
blocking=True,
)
assert music_assistant_client.send_command.call_count == 1
assert music_assistant_client.send_command.call_args == call(
"players/cmd/select_source", player_id=mass_player_id, source="linein"
)
async def test_media_player_supported_features( async def test_media_player_supported_features(
hass: HomeAssistant, hass: HomeAssistant,
music_assistant_client: MagicMock, music_assistant_client: MagicMock,
@ -652,6 +679,7 @@ async def test_media_player_supported_features(
| MediaPlayerEntityFeature.TURN_ON | MediaPlayerEntityFeature.TURN_ON
| MediaPlayerEntityFeature.TURN_OFF | MediaPlayerEntityFeature.TURN_OFF
| MediaPlayerEntityFeature.SEARCH_MEDIA | MediaPlayerEntityFeature.SEARCH_MEDIA
| MediaPlayerEntityFeature.SELECT_SOURCE
) )
assert state.attributes["supported_features"] == expected_features assert state.attributes["supported_features"] == expected_features
# remove power control capability from player, trigger subscription callback # remove power control capability from player, trigger subscription callback