Music Assistant Player Groups and Sync Groups Management: Architecture and Implementation

Music Assistant server implements two distinct group player architectures—SyncGroupPlayer for synchronized multi-room playback and UniversalGroupPlayer for virtual multicast streaming—both inheriting from the core Player model and managed through a centralized controller with support for dynamic or static membership, idle-grace lifecycle handling, and configurable power controls.

The Music Assistant server repository provides sophisticated group player capabilities that allow multiple physical audio devices to function as unified logical targets. Understanding the architecture behind Music Assistant player groups and sync groups management is essential for developers integrating with the API or configuring complex multi-room audio setups. The implementation distinguishes between perfectly synchronized playback groups that maintain sample-accurate alignment and universal groups that stream a single encoded audio flow to heterogeneous devices.

Understanding Group Player Types

Music Assistant defines two primary group implementations, each serving different synchronization and protocol requirements.

SyncGroupPlayer for Multi-Room Synchronization

The SyncGroupPlayer class, defined in music_assistant/providers/sync_group/player.py, enables perfectly synchronized playback across multiple physical devices. This implementation ensures that audio output remains sample-accurate across all group members, making it ideal for multi-room scenarios where timing alignment is critical.

Key properties include:

  • sync_leader – Designates the current leader player that drives playback for the entire group
  • supported_features – Dynamically aggregates capabilities from the leader, ensuring volume, mute, and DSP controls appear in the UI
  • requires_flow_mode – Mirrors the leader's flow-mode setting, forcing the group to use identical streaming parameters

UniversalGroupPlayer for Virtual Streaming

The UniversalGroupPlayer class, located in music_assistant/providers/universal_group/player.py, creates a virtual streaming endpoint that serves a single encoded audio flow (FLAC or MP3) to any number of members. This approach benefits protocols that only accept a single source connection but need to distribute audio to multiple physical endpoints.

Key characteristics include:

  • Virtual streaming endpoint – Exposes URLs at /ugp/<player_id>.flac or /ugp/<player_id>.mp3 for client connections
  • UGPStream instance – Tracks multicast stream state through the stream attribute, with stream.done indicating active status
  • supported_sample_rates – Derived from CONF_UGP_OUTPUT_FORMAT configuration, limiting the group to a single encoded bitrate

Group Lifecycle and Session Management

Both group types share a common lifecycle implemented in the base Player model (music_assistant/models/player.py).

Formation and Activation

Groups follow a three-phase lifecycle:

  1. Formation – The group automatically forms when playback begins via cmd_play_media, establishing the initial member set and selecting a sync leader (for sync groups) or initializing the virtual stream (for universal groups)
  2. Active session – The is_active_session property returns True while the leader remains active or the virtual multicast stream is alive
  3. Idle-grace handling – When playback stops, a timer (IDLE_GRACE_SECONDS) maintains the group state to absorb rapid resume actions

Dissolution

If the idle-grace timer expires, the group dissolves and members revert to their prior independent states. For sync groups, dissolution also clears the sync_leader attribute.

Configuring Sync Group Players

Dynamic vs Static Members

Sync groups support two membership modes controlled by the CONF_DYNAMIC_GROUP_MEMBERS configuration entry:

  • Dynamic groups – When enabled, configured members act as a preset list; the actual member set is built only when the group powers on via on_config_updated
  • Static groups – Member lists from CONF_GROUP_MEMBERS are stored in _attr_static_group_members and used directly without runtime modification

Power Control Features

By default, groups lack power control capabilities. Users can enable a virtual power toggle by setting CONF_POWER_CONTROL to PLAYER_CONTROL_FAKE. When enabled, supported_features adds PlayerFeature.POWER, allowing UI-based power management even when underlying hardware lacks explicit power commands.

Configuring Universal Group Players

Output Format and Sample Rates

Universal groups derive their audio capabilities from the CONF_UGP_OUTPUT_FORMAT configuration entry. Unlike sync groups that pass through raw audio, universal groups transcode to a specific format, limiting supported_sample_rates to the configured output parameters.

Dynamic Member Handling

The is_dynamic property controls runtime membership changes. When CONF_DYNAMIC_GROUP_MEMBERS is enabled, the group advertises PlayerFeature.SET_MEMBERS, allowing clients to add or remove members via cmd_set_group_members while the stream is active.

Centralized Group Control Architecture

All group operations are orchestrated by the players controller in music_assistant/controllers/players/controller.py. This controller handles configuration updates, persists changes to member lists, and triggers formation or dissolution events.

Key controller responsibilities include:

  • Watching for configuration changes via on_config_updated callbacks
  • Processing group volume commands through cmd_group_volume
  • Managing member notifications via iter_group_members

Practical API Usage Examples

The following examples demonstrate how to interact with Music Assistant player groups programmatically:


# Retrieve a group player by its ID

group = mass.players.get_player("syncgroup_myroom")
assert group is not None

# Inspect aggregated supported features

print("Features:", group.supported_features)

# Dynamically modify group members (requires dynamic configuration)

await mass.players.cmd_set_group_members(
    group.player_id, 
    ["player1", "player2"]
)

# Initiate playback - triggers automatic group formation

await mass.players.cmd_play_media(
    player_id=group.player_id,
    media=PlayerMedia(url="https://example.com/song.mp3"),
)

# Stop playback - group remains alive for IDLE_GRACE_SECONDS

await mass.players.cmd_stop(group.player_id)

Summary

  • Two distinct architectures – SyncGroupPlayer provides sample-accurate synchronization via a leader-follower model, while UniversalGroupPlayer creates virtual multicast streams for protocol-constrained devices
  • Shared lifecycle – Both types use formation on playback start, active session tracking via is_active_session, and idle-grace dissolution controlled by IDLE_GRACE_SECONDS
  • Flexible membership – Support for both static member lists (CONF_GROUP_MEMBERS) and dynamic runtime configuration (CONF_DYNAMIC_GROUP_MEMBERS)
  • Centralized control – The players controller in music_assistant/controllers/players/controller.py manages all group state transitions and configuration persistence
  • Optional power control – Virtual power toggles can be exposed via CONF_POWER_CONTROL when hardware lacks native power management

Frequently Asked Questions

What is the difference between SyncGroupPlayer and UniversalGroupPlayer?

SyncGroupPlayer maintains perfect synchronization across members by designating a sync_leader that drives playback timing, making it ideal for multi-room audio where alignment matters. UniversalGroupPlayer creates a virtual streaming endpoint that serves a single encoded audio stream (FLAC/MP3) to multiple clients, which is necessary when target devices only accept individual connections or require specific encoded formats.

How does dynamic member configuration work in Music Assistant groups?

When CONF_DYNAMIC_GROUP_MEMBERS is enabled in the player configuration, the group exposes PlayerFeature.SET_MEMBERS and allows runtime modification of the member list via cmd_set_group_members. For static configurations, members are defined in CONF_GROUP_MEMBERS and stored in _attr_static_group_members, remaining fixed during the group lifecycle.

What triggers the dissolution of a group player?

A group dissolves when the IDLE_GRACE_SECONDS timer expires after playback stops, or when the sync leader disappears (for sync groups). During the idle-grace period, the group remains active to accommodate rapid resume actions without requiring re-formation overhead.

How does the idle-grace period affect group playback?

The idle-grace period maintains the group structure temporarily after cmd_stop is called, preventing unnecessary formation overhead during brief pauses. If playback resumes within this window, the group continues with existing members; if the timer expires, the group dissolves and members revert to independent states, requiring full re-formation on the next play command.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →