/
/
/
1# Sync Group Player Provider
2
3## Overview
4
5The Sync Group Player provider enables creating persistent groups of compatible speakers that play audio in perfect synchronization. Unlike temporary sync operations (manually syncing players together), sync groups are permanent player entities with their own queue and configuration.
6
7### Key Features
8
9- **Persistent Groups**: Created groups persist across restarts and appear as regular players
10- **Protocol Compatibility**: Automatically enforces that only compatible players (same sync protocol) can be grouped
11- **Dynamic Membership**: Optional support for adding/removing members during playback
12- **Sync Leader Selection**: Automatically selects and manages the sync leader
13- **Queue Ownership**: The sync group owns the playback queue, not individual members
14
15## How It Differs from Manual Sync
16
17| Manual Sync | Sync Group |
18|-------------|------------|
19| Temporary, dissolves when stopped | Permanent player entity |
20| Queue belongs to leader player | Queue belongs to the group |
21| Leader is explicitly chosen | Leader is auto-selected |
22| Direct player-to-player sync | Abstracted group layer |
23
24## Architecture
25
26### Component Overview
27
28```
29âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
30â SyncGroupProvider â
31â - Discovers/registers sync group players from config â
32â - Creates/removes sync groups via UI â
33âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
34 â
35 âââââââââââââââââ´ââââââââââââââââ
36 â â
37 âââââââââââ¼ââââââââââ âââââââââââ¼ââââââââââ
38 â SyncGroupPlayer â â SyncGroupPlayer â
39 â "Living Room" â â "Whole House" â
40 â â â â
41 â sync_leader ââââââ¼âââ â sync_leader ââââââ¼âââ
42 â group_members: â â â group_members: â â
43 â - AirPlay A â â â - Sonos 1 â â
44 â - AirPlay B â â â - Sonos 2 â â
45 â - AirPlay C â â â - Sonos 3 â â
46 âââââââââââââââââââââ â âââââââââââââââââââââ â
47 â â
48 ââââââââââââââ¼âââââââââââ ââââââââââââââ¼âââââââââââ
49 â Actual Player A â â Actual Sonos 1 â
50 â (sync leader) â â (sync leader) â
51 â âââsynced to itâââ â â âââsynced to itâââ â
52 â â Player B â â â â Sonos 2 â â
53 â â Player C â â â â Sonos 3 â â
54 â ââââââââââââââââââ â â ââââââââââââââââââ â
55 âââââââââââââââââââââââââ âââââââââââââââââââââââââ
56```
57
58### File Structure
59
60```
61sync_group/
62âââ __init__.py # Provider setup and config entries
63âââ provider.py # SyncGroupProvider - creates/removes groups
64âââ player.py # SyncGroupPlayer - group player implementation
65âââ constants.py # Constants and feature definitions
66âââ manifest.json # Provider manifest (builtin, non-disableable)
67âââ README.md # This file
68```
69
70## Sync Leader Concept
71
72The sync group doesn't directly play audio. Instead, it delegates to a **sync leader** - one of the member players that actually handles the playback and syncs the other members to itself.
73
74### Sync Leader Selection
75
76The sync leader is selected when the group is powered on (which forms the group). Selection is also re-evaluated when the current leader is removed from the group or becomes unavailable.
77
781. **Keep current leader**: If a leader exists and is still available, keep it
792. **Prefer protocol continuity**: When re-selecting after a leader change while playing, prefer a member that supports the currently active output protocol so the live session can continue without a teardown
803. **Prioritize static members**: For static groups, prefer members from the configured list
814. **First available**: Otherwise pick the first available member as leader
82
83### Leader Responsibilities
84
85The sync leader is the **source of truth** for the group's playback state:
86- Receives the actual `play_media` command
87- Syncs all other group members to itself
88- Reports playback state, elapsed time, current media, and active source to the group (via the group's `_update_attributes` reading the leader's raw values)
89- Contributes features (enqueue, gapless, volume, DSP) to the group
90
91> **State derivation note:** The leader's own `__final_playback_state` does **not** mirror its parent group â that would create a circular dependency (group derives from leader â leader derives from group â both stuck at last value). Members of an active group always report their own raw playback state; only manually-synced clients (`synced_to`) mirror their leader's state.
92
93## Group Types
94
95### Static Groups
96
97- **Fixed membership**: Members defined at creation, cannot be changed during playback
98- **Use case**: Permanent whole-home audio setup
99- **Behavior**: All static members rejoin automatically when playback starts
100
101### Dynamic Groups
102
103- **Flexible membership**: Members can be added/removed at any time
104- **Use case**: Ad-hoc grouping based on current needs
105- **Behavior**: Supports `SET_MEMBERS` feature for runtime changes
106- **Configuration**: Enable "Dynamic members" option when creating the group
107
108## Protocol Compatibility
109
110Players can only be grouped if they support the same sync protocol. This is enforced through the `can_group_with` mechanism:
111
1121. **First member added**: Its `can_group_with` set becomes the reference
1132. **Subsequent members**: Must be in the reference set to be added
1143. **Incompatible players**: Silently skipped during group formation
115
116### Compatible Protocol Examples
117
118- AirPlay players can group with other AirPlay players
119- Sonos players can group with other Sonos players
120- Squeezelite players can group with other Squeezelite players
121- **Cross-protocol grouping is NOT supported**
122
123## Protocol Linking Integration
124
125The sync group leverages the Player Controller's protocol linking system through its elected sync leader. This is important for devices that support multiple streaming protocols.
126
127### How It Works
128
129When a sync group starts playback:
130
1311. **Sync leader is elected** from the group members
1322. **Play command forwarded** to the sync leader via `_handle_play_media()`
1333. **Protocol selection happens** on the sync leader using `_select_best_output_protocol()`
1344. **Best protocol chosen** based on:
135 - Protocol already grouped/synced with other players (highest priority)
136 - User's preferred output protocol setting
137 - Native playback if available
138 - Best available protocol by priority
139
140### Example Scenario
141
142Consider a sync group mixing a Universal Player (Denon AVR with multiple protocols) and native AirPlay devices:
143
144```
145âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
146â Sync Group: "Living Room" â
147â â
148â Members: Denon AVR, HomePod, Apple TV â
149â Sync Leader: Denon AVR (Universal Player) â
150â Compatible via: AirPlay protocol â
151âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
152 â
153 â play_media() forwarded to leader
154 â¼
155âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
156â Denon AVR (Sync Leader - Universal Player) â
157â â
158â Linked Output Protocols: â
159â - AirPlay âââ selected (members are AirPlay-compatible) â
160â - Chromecast â
161â - DLNA â
162â â
163â â _select_best_output_protocol() chooses AirPlay â
164â â Syncs HomePod and Apple TV via AirPlay protocol â
165âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
166```
167
168In this scenario, the Denon AVR has three output protocols available. Since the other sync group members (HomePod, Apple TV) are AirPlay devices, the protocol selection logic picks AirPlay as the output protocol. All three devices then sync together via AirPlay.
169
170### Why This Matters
171
172- **Unified experience**: Users interact with one sync group player
173- **Automatic optimization**: The leader picks the best protocol for its device type
174- **Protocol-aware syncing**: Members sync using their native protocol (Sonos-to-Sonos, AirPlay-to-AirPlay)
175- **Fallback support**: If native protocol unavailable, linked protocols provide alternatives
176
177For detailed information on protocol linking, output protocol selection, and how devices with multiple protocols are handled, see the [Player Controller README](../../controllers/players/README.md#multi-protocol-player-system).
178
179## Group Lifecycle
180
181The group's lifecycle is driven by **power**:
182
183- `power(True)` **forms** the group: selects a sync leader and syncs all members to it
184- `power(False)` **dissolves** the group: ungroups all members from the leader and clears the sync leader
185- `stop()` only stops the leader â it does **not** dissolve the group; the group remains powered and ready to resume
186
187This mirrors how a typical AVR or stereo system behaves: turn it on, it's an active output; turn it off, it's gone.
188
189### Powering On
190
191```
1921. cmd_power(syncgroup, True) (also called implicitly by play_media / play)
193 â
1942. _form_syncgroup() runs
195 â
196 ââ⺠Ensure static members are included in _attr_group_members
197 ââ⺠Select sync leader (if not already set)
198 ââ⺠Move sync leader to the front of the member list
199 ââ⺠If leader is currently playing something else, stop it (and wait for IDLE)
200 ââ⺠cmd_set_members on the leader to sync the remaining members
201 â
2023. _attr_powered = True ; state event emitted
203```
204
205### Starting Playback
206
207```
2081. User starts playback on SyncGroupPlayer
209 â
2102. play_media(media)
211 â
212 ââ⺠Optimistically set _attr_current_media / _attr_active_source
213 ââ⺠_form_syncgroup() # idempotent - recovers if dissolved-but-powered
214 ââ⺠_handle_play_media(sync_leader, media) # leader actually plays
215 â
2163. Leader starts playback, synced members follow
217```
218
219### Stopping Playback
220
221```
2221. User stops playback on SyncGroupPlayer
223 â
2242. stop() forwarded to sync leader (group stays powered & formed)
225```
226
227### Powering Off
228
229```
2301. cmd_power(syncgroup, False)
231 â
2322. If currently playing/paused: stop() first
233 â
2343. _dissolve_syncgroup()
235 â
236 ââ⺠cmd_set_members on leader to remove all sync children (waits for state)
237 ââ⺠Clear leader's active_output_protocol (when leader is not still playing)
238 ââ⺠sync_leader = None
239 â
2404. _attr_powered = False ; state event emitted
241```
242
243### State Polling
244
245While the group is playing, `SyncGroupPlayer.poll()` is called every 1 second to refresh `elapsed_time` from the sync leader. When idle, the poll interval drops to 30 seconds. This avoids the per-second eventbus cascade that would happen if we forwarded every elapsed_time tick from the leader through the group's update chain.
246
247## Dynamic Member Management
248
249When `SET_MEMBERS` is called on a dynamic group:
250
251### Adding Members
252
2531. Validate the member exists, is available, and is not in the members filter
2542. If there is no sync leader yet (empty / unpowered group): just register the member; sync happens when the group is next formed
2553. Otherwise check compatibility with the current sync leader's `can_group_with` (which already includes all of the leader's linked output protocols, so e.g. an AirPlay-only player IS valid for a Sonos leader that has AirPlay as a linked protocol)
2564. Incompatible members are **not** registered (avoids stranding orphan entries in the group)
2575. Compatible members are appended to the internal member list and forwarded to `cmd_set_members` on the leader, which handles protocol selection (and possibly switching the leader to a different output protocol so the new member can be grouped via that protocol)
258
259### Removing Members
260
2611. Remove from the internal member list (static members cannot be removed)
2622. If removing the **sync leader** while playing:
263 - If the active protocol supports dynamic leader switching (provider domain is in `PROVIDERS_WITH_DYNAMIC_LEADER_SWITCH` â currently AirPlay, Snapcast, Sendspin), perform a **seamless handoff** at the protocol level: pick a new leader from the live session, then call `set_members(player_ids_to_remove=[old_leader_protocol])` on the old session player and `set_members(player_ids_to_add=[remaining_protocol_ids])` on the new leader's protocol player. Remaining members keep playing.
264 - If the chosen new leader is not part of the live session (e.g. a freshly-added player), or the protocol doesn't support handoff: fall back to **dissolve + re-form** (brief audio gap)
2653. If removing a non-leader member: forward to `cmd_set_members` on the leader
266
267### Removing Last Member
268
269If the last member is removed, the group is dissolved (leader stopped, sync_leader cleared).
270
271## Feature Inheritance
272
273The SyncGroupPlayer has limited base features but inherits additional capabilities from the sync leader:
274
275### Base Features
276- `PLAY_MEDIA` - Always supported
277- `POWER` - Always supported (powered state is the canonical "is this group active" signal)
278
279### Features from Sync Leader (when active)
280- `ENQUEUE` - Queue next track
281- `GAPLESS_PLAYBACK` - Seamless track transitions
282- `VOLUME_SET` - Volume control
283- `VOLUME_MUTE` - Mute control
284- `MULTI_DEVICE_DSP` - DSP processing
285
286### Dynamic Feature
287- `SET_MEMBERS` - Only if group is configured as dynamic
288
289## Configuration Options
290
291### Group Members
292
293Multi-select list of players to include in the group. Only non-group players are shown as options. For static groups, these are the permanent members. For dynamic groups, these are the initial members.
294
295### Enable Dynamic Members
296
297Boolean option to allow runtime member changes. When enabled:
298- Group supports `SET_MEMBERS` feature
299- Members can be added/removed via UI or API
300- Group can start with zero members
301
302## Provider Details
303
304### Player ID Format
305
306Sync group players use the format: `syncgroup_{random_8_chars}`
307
308Example: `syncgroup_ab12cd34`
309
310### Provider Features
311
312- `CREATE_GROUP_PLAYER` - Create new sync groups
313- `REMOVE_GROUP_PLAYER` - Delete sync groups
314
315### Builtin Provider
316
317The Sync Group provider is:
318- **Builtin**: Automatically available, no installation needed
319- **Single instance**: Only one provider instance exists
320- **Non-disableable**: Cannot be disabled by users
321
322## State Properties
323
324The SyncGroupPlayer reads most state from the sync leader's **raw** attributes (deliberately not `.state.*`, see the leader-responsibilities note above):
325
326| Property | Source |
327|----------|--------|
328| `powered` | `_attr_powered` â set by `power()`, the canonical "is this group active" signal |
329| `playback_state` | Sync leader's raw `state.playback_state` (or IDLE if no leader) |
330| `elapsed_time` | Sync leader's raw `state.elapsed_time` |
331| `elapsed_time_last_updated` | Sync leader's raw `state.elapsed_time_last_updated` |
332| `current_media` | Sync leader's raw `current_media` (set optimistically in `play_media`) |
333| `active_source` | Sync leader's raw `active_source` (set optimistically in `play_media`) |
334| `group_members` | Sync leader's reported `state.group_members` (preferred) or internal list |
335| `can_group_with` | Aggregated from all current members' `can_group_with` |
336| `supported_features` | Base features + features inherited from the active sync leader |
337
338## Related Documentation
339
340- [Player Controller README](../../controllers/players/README.md) - For understanding player management, protocol linking, and sync coordination
341- [Universal Player README](../universal_player/README.md) - For understanding how protocol players are merged
342