From 9e3957f9310c2e958ba3c2d32ed223230d24875b Mon Sep 17 00:00:00 2001 From: Chris <53621277+smitec11@users.noreply.github.com> Date: Thu, 17 Sep 2026 21:18:17 +0200 Subject: [PATCH] Keep existing HEOS group members when joining a player (#182515) Co-authored-by: Claude Opus 5 --- homeassistant/components/heos/media_player.py | 7 +++++ tests/components/heos/test_media_player.py | 28 ++++++++++++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/heos/media_player.py b/homeassistant/components/heos/media_player.py index c9710c3825be..ed1d9f304c8e 100644 --- a/homeassistant/components/heos/media_player.py +++ b/homeassistant/components/heos/media_player.py @@ -452,6 +452,13 @@ class HeosMediaPlayer(CoordinatorEntity[HeosCoordinator], MediaPlayerEntity): async def async_join_players(self, group_members: list[str]) -> None: """Join `group_members` as a player group with the current player.""" player_ids: list[int] = [self._player.player_id] + # Keep the members of the group this player already leads. HEOS replaces + # the group with the players provided, so members that are not sent + # again are removed when another player is added to the group. + for group in self.coordinator.heos.groups.values(): + if group.lead_player_id == self._player.player_id: + player_ids.extend(group.member_player_ids) + break # Resolve entity_ids to player_ids entity_registry = er.async_get(self.hass) for entity_id in group_members: diff --git a/tests/components/heos/test_media_player.py b/tests/components/heos/test_media_player.py index 2cf8492646cb..50e411dc59b8 100644 --- a/tests/components/heos/test_media_player.py +++ b/tests/components/heos/test_media_player.py @@ -1,5 +1,6 @@ """Tests for the Heos Media Player platform.""" +from collections.abc import Callable from datetime import timedelta import re from typing import Any @@ -10,6 +11,7 @@ from pyheos import ( BrowseResult, CommandFailedError, HeosError, + HeosPlayer, MediaItem, MediaMusicSource, MediaType as HeosMediaType, @@ -1566,7 +1568,7 @@ async def test_browse_media_invalid_content_id( [ (["media_player.test_player_2"], [1, 2]), (["media_player.test_player_2", "media_player.test_player"], [1, 2]), - (["media_player.test_player"], [1]), + (["media_player.test_player"], [1, 2]), ], ) async def test_media_player_join_group( @@ -1591,6 +1593,30 @@ async def test_media_player_join_group( controller.set_group.assert_called_once_with(expected) +async def test_media_player_join_group_keeps_existing_members( + hass: HomeAssistant, + config_entry: MockConfigEntry, + controller: MockHeos, + players: dict[int, HeosPlayer], + player_factory: Callable[[int, str, str], HeosPlayer], +) -> None: + """Test joining a player to a group retains the current members.""" + players[3] = player_factory(3, "Test Player 3", "Speaker") + controller.mock_set_players(players) + config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.services.async_call( + MEDIA_PLAYER_DOMAIN, + SERVICE_JOIN, + { + ATTR_ENTITY_ID: "media_player.test_player", + ATTR_GROUP_MEMBERS: ["media_player.test_player_3"], + }, + blocking=True, + ) + controller.set_group.assert_called_once_with([1, 2, 3]) + + async def test_media_player_join_group_error( hass: HomeAssistant, config_entry: MockConfigEntry, controller: MockHeos ) -> None: