From 6f9a39ab89313ffd5601b4fe8ef5389419bbfdf8 Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Mon, 26 May 2025 16:28:18 +0200 Subject: [PATCH] Add select source action to Music Assistant (#145619) --- .../music_assistant/media_player.py | 11 +++++++ .../music_assistant/fixtures/players.json | 30 +++++++++++++++++-- .../snapshots/test_media_player.ambr | 14 +++++++-- .../music_assistant/test_media_player.py | 28 +++++++++++++++++ 4 files changed, 79 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/music_assistant/media_player.py b/homeassistant/components/music_assistant/media_player.py index 5dc8ab2ec00..91c9d5ffd90 100644 --- a/homeassistant/components/music_assistant/media_player.py +++ b/homeassistant/components/music_assistant/media_player.py @@ -292,6 +292,10 @@ class MusicAssistantPlayer(MusicAssistantEntity, MediaPlayerEntity): self._attr_state = MediaPlayerState(player.state.value) else: 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] = [] if player.group_childs: @@ -459,6 +463,11 @@ class MusicAssistantPlayer(MusicAssistantEntity, MediaPlayerEntity): """Remove this player from any group.""" 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 async def _async_handle_play_media( self, @@ -735,4 +744,6 @@ class MusicAssistantPlayer(MusicAssistantEntity, MediaPlayerEntity): if self.player.power_control != PLAYER_CONTROL_NONE: supported_features |= MediaPlayerEntityFeature.TURN_ON 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 diff --git a/tests/components/music_assistant/fixtures/players.json b/tests/components/music_assistant/fixtures/players.json index e8978f17f86..58ce20da824 100644 --- a/tests/components/music_assistant/fixtures/players.json +++ b/tests/components/music_assistant/fixtures/players.json @@ -18,7 +18,8 @@ "pause", "set_members", "power", - "enqueue" + "enqueue", + "select_source" ], "elapsed_time": null, "elapsed_time_last_updated": 0, @@ -43,7 +44,32 @@ "hide_player_in_ui": ["when_unavailable"], "expose_to_ha": true, "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", diff --git a/tests/components/music_assistant/snapshots/test_media_player.ambr b/tests/components/music_assistant/snapshots/test_media_player.ambr index f561a5c3afb..5782156e722 100644 --- a/tests/components/music_assistant/snapshots/test_media_player.ambr +++ b/tests/components/music_assistant/snapshots/test_media_player.ambr @@ -54,6 +54,7 @@ 'media_duration': 300, 'media_position': 0, 'media_title': 'Test Track', + 'source': 'spotify', 'supported_features': , 'volume_level': 0.2, }), @@ -125,6 +126,7 @@ 'media_title': 'November Rain', 'repeat': 'all', 'shuffle': True, + 'source': 'test_group_player_1', 'supported_features': , 'volume_level': 0.06, }), @@ -142,6 +144,10 @@ }), 'area_id': None, 'capabilities': dict({ + 'source_list': list([ + 'Music Assistant Queue', + 'Line-In', + ]), }), 'config_entry_id': , 'config_subentry_id': , @@ -165,7 +171,7 @@ 'original_name': None, 'platform': 'music_assistant', 'previous_unique_id': None, - 'supported_features': , + 'supported_features': , 'translation_key': None, 'unique_id': '00:00:00:00:00:01', 'unit_of_measurement': None, @@ -181,7 +187,11 @@ ]), 'icon': 'mdi:speaker', 'mass_player_type': 'player', - 'supported_features': , + 'source_list': list([ + 'Music Assistant Queue', + 'Line-In', + ]), + 'supported_features': , }), 'context': , 'entity_id': 'media_player.test_player_1', diff --git a/tests/components/music_assistant/test_media_player.py b/tests/components/music_assistant/test_media_player.py index 288d49092e5..e2b45db45e4 100644 --- a/tests/components/music_assistant/test_media_player.py +++ b/tests/components/music_assistant/test_media_player.py @@ -16,6 +16,7 @@ from syrupy.filters import paths from homeassistant.components.media_player import ( ATTR_GROUP_MEMBERS, + ATTR_INPUT_SOURCE, ATTR_MEDIA_ENQUEUE, ATTR_MEDIA_REPEAT, ATTR_MEDIA_SEEK_POSITION, @@ -25,6 +26,7 @@ from homeassistant.components.media_player import ( DOMAIN as MEDIA_PLAYER_DOMAIN, SERVICE_CLEAR_PLAYLIST, SERVICE_JOIN, + SERVICE_SELECT_SOURCE, SERVICE_UNJOIN, MediaPlayerEntityFeature, ) @@ -620,6 +622,31 @@ async def test_media_player_get_queue_action( 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( hass: HomeAssistant, music_assistant_client: MagicMock, @@ -652,6 +679,7 @@ async def test_media_player_supported_features( | MediaPlayerEntityFeature.TURN_ON | MediaPlayerEntityFeature.TURN_OFF | MediaPlayerEntityFeature.SEARCH_MEDIA + | MediaPlayerEntityFeature.SELECT_SOURCE ) assert state.attributes["supported_features"] == expected_features # remove power control capability from player, trigger subscription callback