Skip to content

Commit

Permalink
Feat: Bump models to 1.1.2
Browse files Browse the repository at this point in the history
Feat: Bump models to 1.1.2
Feat: Adjust code to changes in the models
Feat: Refactor sync to group to make it more universal
  • Loading branch information
marcelveldt committed Nov 20, 2024
1 parent 5339405 commit a7eeb3b
Show file tree
Hide file tree
Showing 17 changed files with 213 additions and 196 deletions.
195 changes: 104 additions & 91 deletions music_assistant/controllers/players.py

Large diffs are not rendered by default.

16 changes: 8 additions & 8 deletions music_assistant/models/player_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,8 +169,8 @@ async def cmd_previous(self, player_id: str) -> None:
# will only be called for players with 'next_previous' feature set.
raise NotImplementedError

async def cmd_sync(self, player_id: str, target_player: str) -> None:
"""Handle SYNC command for given player.
async def cmd_group(self, player_id: str, target_player: str) -> None:
"""Handle GROUP command for given player.
Join/add the given player(id) to the given (master) player/sync group.
Expand All @@ -180,21 +180,21 @@ async def cmd_sync(self, player_id: str, target_player: str) -> None:
# will only be called for players with SYNC feature set.
raise NotImplementedError

async def cmd_unsync(self, player_id: str) -> None:
"""Handle UNSYNC command for given player.
async def cmd_ungroup(self, player_id: str) -> None:
"""Handle UNGROUP command for given player.
Remove the given player from any syncgroups it currently is synced to.
Remove the given player from any (sync)groups it currently is grouped to.
- player_id: player_id of the player to handle the command.
"""
# will only be called for players with SYNC feature set.
raise NotImplementedError

async def cmd_sync_many(self, target_player: str, child_player_ids: list[str]) -> None:
async def cmd_group_many(self, target_player: str, child_player_ids: list[str]) -> None:
"""Create temporary sync group by joining given players to target player."""
for child_id in child_player_ids:
# default implementation, simply call the cmd_sync for all child players
await self.cmd_sync(child_id, target_player)
# default implementation, simply call the cmd_group for all child players
await self.cmd_group(child_id, target_player)

async def poll_player(self, player_id: str) -> None:
"""Poll player for state updates.
Expand Down
10 changes: 5 additions & 5 deletions music_assistant/providers/_template_player_provider/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -339,8 +339,8 @@ async def enqueue_next_media(self, player_id: str, media: PlayerMedia) -> None:
"""
# this method should handle the enqueuing of the next queue item on the player.

async def cmd_sync(self, player_id: str, target_player: str) -> None:
"""Handle SYNC command for given player.
async def cmd_group(self, player_id: str, target_player: str) -> None:
"""Handle GROUP command for given player.
Join/add the given player(id) to the given (master) player/sync group.
Expand All @@ -351,10 +351,10 @@ async def cmd_sync(self, player_id: str, target_player: str) -> None:
# this method should handle the sync command for the given player.
# you should join the given player to the target_player/syncgroup.

async def cmd_unsync(self, player_id: str) -> None:
"""Handle UNSYNC command for given player.
async def cmd_ungroup(self, player_id: str) -> None:
"""Handle UNGROUP command for given player.
Remove the given player from any syncgroups it currently is synced to.
Remove the given player from any (sync)groups it currently is grouped to.
- player_id: player_id of the player to handle the command.
"""
Expand Down
13 changes: 7 additions & 6 deletions music_assistant/providers/airplay/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -347,8 +347,8 @@ async def cmd_volume_set(self, player_id: str, volume_level: int) -> None:
await self.mass.cache.set(player_id, volume_level, base_key=CACHE_KEY_PREV_VOLUME)

@lock
async def cmd_sync(self, player_id: str, target_player: str) -> None:
"""Handle SYNC command for given player.
async def cmd_group(self, player_id: str, target_player: str) -> None:
"""Handle GROUP command for given player.
Join/add the given player(id) to the given (master) player/sync group.
Expand Down Expand Up @@ -398,10 +398,10 @@ async def cmd_sync(self, player_id: str, target_player: str) -> None:
self.mass.players.update(parent_player.player_id, skip_forward=True)

@lock
async def cmd_unsync(self, player_id: str) -> None:
"""Handle UNSYNC command for given player.
async def cmd_ungroup(self, player_id: str) -> None:
"""Handle UNGROUP command for given player.
Remove the given player from any syncgroups it currently is synced to.
Remove the given player from any (sync)groups it currently is grouped to.
- player_id: player_id of the player to handle the command.
"""
Expand Down Expand Up @@ -518,10 +518,11 @@ async def _setup_player(
),
supported_features=(
PlayerFeature.PAUSE,
PlayerFeature.SYNC,
PlayerFeature.SET_MEMBERS,
PlayerFeature.VOLUME_SET,
),
volume_level=volume,
can_group_with={self.instance_id},
)
await self.mass.players.register_or_update(mass_player)

Expand Down
20 changes: 11 additions & 9 deletions music_assistant/providers/bluesound/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@


PLAYER_FEATURES_BASE = {
PlayerFeature.SYNC,
PlayerFeature.SET_MEMBERS,
PlayerFeature.VOLUME_MUTE,
PlayerFeature.PAUSE,
}
Expand Down Expand Up @@ -188,9 +188,10 @@ async def update_attributes(self) -> None:

if self.sync_status.leader is None:
if self.sync_status.followers:
self.mass_player.group_childs = (
self.sync_status.followers if len(self.sync_status.followers) > 1 else set()
)
if len(self.sync_status.followers) > 1:
self.mass_player.group_childs.set(self.sync_status.followers)
else:
self.mass_player.group_childs.clear()
self.mass_player.synced_to = None

if self.status.state == "stream":
Expand All @@ -205,7 +206,7 @@ async def update_attributes(self) -> None:
self.mass_player.current_media = None

else:
self.mass_player.group_childs = set()
self.mass_player.group_childs.clear()
self.mass_player.synced_to = self.sync_status.leader
self.mass_player.active_source = self.sync_status.leader

Expand Down Expand Up @@ -294,6 +295,7 @@ async def on_mdns_service_state_change(
),
needs_poll=True,
poll_interval=30,
can_group_with={self.instance_id},
)
await self.mass.players.register(mass_player)

Expand Down Expand Up @@ -392,12 +394,12 @@ async def poll_player(self, player_id: str) -> None:
if bluos_player := self.bluos_players[player_id]:
await bluos_player.update_attributes()

# TODO fix sync & unsync
# TODO fix sync & ungroup

async def cmd_sync(self, player_id: str, target_player: str) -> None:
async def cmd_group(self, player_id: str, target_player: str) -> None:
"""Handle SYNC command for BluOS player."""

async def cmd_unsync(self, player_id: str) -> None:
"""Handle UNSYNC command for BluOS player."""
async def cmd_ungroup(self, player_id: str) -> None:
"""Handle UNGROUP command for BluOS player."""
if bluos_player := self.bluos_players[player_id]:
await bluos_player.client.player.leave_group()
6 changes: 3 additions & 3 deletions music_assistant/providers/chromecast/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -437,13 +437,13 @@ def on_new_cast_status(self, castplayer: CastPlayer, status: CastStatus) -> None
# handle stereo pairs
if castplayer.cast_info.is_multichannel_group:
castplayer.player.type = PlayerType.STEREO_PAIR
castplayer.player.group_childs = set()
castplayer.player.group_childs.clear()
# handle cast groups
if castplayer.cast_info.is_audio_group and not castplayer.cast_info.is_multichannel_group:
castplayer.player.type = PlayerType.GROUP
castplayer.player.group_childs = {
castplayer.player.group_childs.set(
str(UUID(x)) for x in castplayer.mz_controller.members
}
)
castplayer.player.supported_features = (
PlayerFeature.POWER,
PlayerFeature.VOLUME_SET,
Expand Down
16 changes: 8 additions & 8 deletions music_assistant/providers/hass_players/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -334,8 +334,8 @@ async def cmd_volume_mute(self, player_id: str, muted: bool) -> None:
target={"entity_id": player_id},
)

async def cmd_sync(self, player_id: str, target_player: str) -> None:
"""Handle SYNC command for given player.
async def cmd_group(self, player_id: str, target_player: str) -> None:
"""Handle GROUP command for given player.
Join/add the given player(id) to the given (master) player/sync group.
Expand All @@ -350,10 +350,10 @@ async def cmd_sync(self, player_id: str, target_player: str) -> None:
target={"entity_id": target_player},
)

async def cmd_unsync(self, player_id: str) -> None:
"""Handle UNSYNC command for given player.
async def cmd_ungroup(self, player_id: str) -> None:
"""Handle UNGROUP command for given player.
Remove the given player from any syncgroups it currently is synced to.
Remove the given player from any (sync)groups it currently is grouped to.
- player_id: player_id of the player to handle the command.
"""
Expand Down Expand Up @@ -460,13 +460,13 @@ def _update_player_attributes(self, player: Player, attributes: dict[str, Any])
player.current_item_id = value
if key == "group_members":
if value and value[0] == player.player_id:
player.group_childs = value
player.group_childs.set(value)
player.synced_to = None
elif value and value[0] != player.player_id:
player.group_childs = set()
player.group_childs.clear()
player.synced_to = value[0]
else:
player.group_childs = set()
player.group_childs.clear()
player.synced_to = None

async def _late_add_player(self, entity_id: str) -> None:
Expand Down
50 changes: 23 additions & 27 deletions music_assistant/providers/player_group/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -279,22 +279,18 @@ async def on_player_config_change(self, config: PlayerConfig, changed_keys: set[
# ensure we filter invalid members
members = self._filter_members(config.get_value(CONF_GROUP_TYPE), members)
if group_player := self.mass.players.get(config.player_id):
group_player.group_childs = members
group_player.group_childs.set(members)
if group_player.powered:
# power on group player (which will also resync) if needed
await self.cmd_power(group_player.player_id, True)
if f"values/{CONFIG_ENTRY_DYNAMIC_MEMBERS.key}" in changed_keys:
# dynamic members feature changed
if group_player := self.mass.players.get(config.player_id):
if PlayerFeature.SYNC in group_player.supported_features:
group_player.supported_features = tuple(
x for x in group_player.supported_features if x != PlayerFeature.SYNC
)
if PlayerFeature.SET_MEMBERS in group_player.supported_features:
group_player.supported_features.remove(PlayerFeature.SET_MEMBERS)
group_player.can_group_with.clear()
else:
group_player.supported_features = (
*group_player.supported_features,
PlayerFeature.SYNC,
)
group_player.supported_features.add(PlayerFeature.SET_MEMBERS)
await super().on_player_config_change(config, changed_keys)

async def cmd_stop(self, player_id: str) -> None:
Expand Down Expand Up @@ -352,13 +348,13 @@ async def cmd_power(self, player_id: str, powered: bool) -> None:
group_member_ids = self.mass.config.get_raw_player_config_value(
player_id, CONF_GROUP_MEMBERS, []
)
group_player.group_childs = {
group_player.group_childs.set(
x
for x in group_member_ids
if (child_player := self.mass.players.get(x))
and child_player.available
and child_player.enabled
}
)

if powered:
# handle TURN_ON of the group player by turning on all members
Expand All @@ -382,7 +378,7 @@ async def cmd_power(self, player_id: str, powered: bool) -> None:
else:
# handle TURN_OFF of the group player by turning off all members
# optimistically set the group state to prevent race conditions
# with the unsync command
# with the ungroup command
group_player.powered = False
for member in self.mass.players.iter_group_members(
group_player, only_powered=True, active_only=True
Expand All @@ -401,7 +397,7 @@ async def cmd_power(self, player_id: str, powered: bool) -> None:
self.mass.players.update(group_player.player_id)
if not powered:
# reset the group members when powered off
group_player.group_childs = set(
group_player.group_childs.set(
self.mass.config.get_raw_player_config_value(player_id, CONF_GROUP_MEMBERS, [])
)

Expand Down Expand Up @@ -549,11 +545,11 @@ async def remove_player(self, player_id: str) -> None:
return
if group_player.powered:
# edge case: the group player is powered and being removed
# make sure to turn it off first (which will also unsync a syncgroup)
# make sure to turn it off first (which will also ungroup a syncgroup)
await self.cmd_power(player_id, False)

async def cmd_sync(self, player_id: str, target_player: str) -> None:
"""Handle SYNC command for given player.
async def cmd_group(self, player_id: str, target_player: str) -> None:
"""Handle GROUP command for given player.
Join/add the given player(id) to the given (master) player/sync group.
Expand All @@ -576,17 +572,17 @@ async def cmd_sync(self, player_id: str, target_player: str) -> None:
f"Adjusting group members is not allowed for group {group_player.display_name}"
)
new_members = self._filter_members(group_type, [*group_player.group_childs, player_id])
group_player.group_childs = new_members
group_player.group_childs.set(new_members)
if group_player.powered:
# power on group player (which will also resync) if needed
await self.cmd_power(target_player, True)

async def cmd_unsync_member(self, player_id: str, target_player: str) -> None:
"""Handle UNSYNC command for given player.
async def cmd_ungroup_member(self, player_id: str, target_player: str) -> None:
"""Handle UNGROUP command for given player.
Remove the given player(id) from the given (master) player/sync group.
- player_id: player_id of the (child) player to unsync from the group.
- player_id: player_id of the (child) player to ungroup from the group.
- target_player: player_id of the group player.
"""
group_player = self.mass.players.get(target_player, raise_unavailable=True)
Expand All @@ -607,12 +603,12 @@ async def cmd_unsync_member(self, player_id: str, target_player: str) -> None:
was_playing = child_player.state == PlayerState.PLAYING
# forward command to the player provider
if player_provider := self.mass.players.get_player_provider(child_player.player_id):
await player_provider.cmd_unsync(child_player.player_id)
await player_provider.cmd_ungroup(child_player.player_id)
child_player.active_group = None
child_player.active_source = None
group_player.group_childs = {x for x in group_player.group_childs if x != player_id}
group_player.group_childs.set({x for x in group_player.group_childs if x != player_id})
if is_sync_leader and was_playing:
# unsyncing the sync leader will stop the group so we need to resume
# ungrouping the sync leader will stop the group so we need to resume
self.mass.call_later(2, self.mass.players.cmd_play, group_player.player_id)
elif group_player.powered:
# power on group player (which will also resync) if needed
Expand Down Expand Up @@ -670,7 +666,7 @@ async def _register_group_player(
CONFIG_ENTRY_DYNAMIC_MEMBERS.key,
CONFIG_ENTRY_DYNAMIC_MEMBERS.default_value,
):
player_features.add(PlayerFeature.SYNC)
player_features.add(PlayerFeature.SET_MEMBERS)

player = Player(
player_id=group_player_id,
Expand Down Expand Up @@ -741,8 +737,8 @@ async def _sync_syncgroup(self, group_player: Player) -> None:
members_to_sync: list[str] = []
for member in self.mass.players.iter_group_members(group_player, active_only=False):
if member.synced_to and member.synced_to != sync_leader.player_id:
# unsync first
await self.mass.players.cmd_unsync(member.player_id)
# ungroup first
await self.mass.players.cmd_ungroup(member.player_id)
if sync_leader.player_id == member.player_id:
# skip sync leader
continue
Expand All @@ -754,7 +750,7 @@ async def _sync_syncgroup(self, group_player: Player) -> None:
continue
members_to_sync.append(member.player_id)
if members_to_sync:
await self.mass.players.cmd_sync_many(sync_leader.player_id, members_to_sync)
await self.mass.players.cmd_group_many(sync_leader.player_id, members_to_sync)

async def _on_mass_player_added_event(self, event: MassEvent) -> None:
"""Handle player added event from player controller."""
Expand Down
Loading

0 comments on commit a7eeb3b

Please sign in to comment.