/
/
/
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 session continuity**: When re-selecting after a leader change while playing, prefer a member that the live session already feeds, since only such a member can inherit the session without a teardown
803. **Prefer protocol continuity**: Otherwise prefer a member that supports the currently active output protocol, so the group at least stays on that protocol
814. **Prioritize static members**: For static groups, prefer members from the configured list
825. **First available**: Otherwise pick the first available member as leader
83
84### Leader Responsibilities
85
86The sync leader is the **source of truth** for the group's playback state:
87- Receives the actual `play_media` command
88- Syncs all other group members to itself
89- 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)
90- Contributes features (enqueue, gapless, volume, DSP) to the group
91
92> **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.
93
94## Group Types
95
96### Static Groups
97
98- **Fixed membership**: Members defined at creation, cannot be changed during playback
99- **Use case**: Permanent whole-home audio setup
100- **Behavior**: All static members rejoin automatically when playback starts
101
102### Dynamic Groups
103
104- **Flexible membership**: Members can be added/removed at any time
105- **Use case**: Ad-hoc grouping based on current needs
106- **Behavior**: Supports `SET_MEMBERS` feature for runtime changes
107- **Configuration**: Enable "Dynamic members" option when creating the group
108
109## Protocol Compatibility
110
111Players can only be grouped if they support the same sync protocol. This is enforced through the `can_group_with` mechanism:
112
1131. **First member added**: Its `can_group_with` set becomes the reference
1142. **Subsequent members**: Must be in the reference set to be added
1153. **Incompatible players**: Silently skipped during group formation
116
117### Compatible Protocol Examples
118
119- AirPlay players can group with other AirPlay players
120- Sonos players can group with other Sonos players
121- Squeezelite players can group with other Squeezelite players
122- **Cross-protocol grouping is NOT supported**
123
124## Protocol Linking Integration
125
126The 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.
127
128### How It Works
129
130When a sync group starts playback:
131
1321. **Sync leader is elected** from the group members
1332. **Play command forwarded** to the sync leader via `_handle_play_media()`
1343. **Protocol selection happens** on the sync leader using `_select_best_output_protocol()`
1354. **Best protocol chosen** based on:
136 - Protocol already grouped/synced with other players (highest priority)
137 - User's preferred output protocol setting
138 - Native playback if available
139 - Best available protocol by priority
140
141### Example Scenario
142
143Consider a sync group mixing a Universal Player (Denon AVR with multiple protocols) and native AirPlay devices:
144
145```
146âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
147â Sync Group: "Living Room" â
148â â
149â Members: Denon AVR, HomePod, Apple TV â
150â Sync Leader: Denon AVR (Universal Player) â
151â Compatible via: AirPlay protocol â
152âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
153 â
154 â play_media() forwarded to leader
155 â¼
156âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
157â Denon AVR (Sync Leader - Universal Player) â
158â â
159â Linked Output Protocols: â
160â - AirPlay âââ selected (members are AirPlay-compatible) â
161â - Chromecast â
162â - DLNA â
163â â
164â â _select_best_output_protocol() chooses AirPlay â
165â â Syncs HomePod and Apple TV via AirPlay protocol â
166âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
167```
168
169In 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.
170
171### Why This Matters
172
173- **Unified experience**: Users interact with one sync group player
174- **Automatic optimization**: The leader picks the best protocol for its device type
175- **Protocol-aware syncing**: Members sync using their native protocol (Sonos-to-Sonos, AirPlay-to-AirPlay)
176- **Fallback support**: If native protocol unavailable, linked protocols provide alternatives
177
178For 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).
179
180## Group Lifecycle
181
182The group's lifecycle is driven by **power**:
183
184- `power(True)` **forms** the group: selects a sync leader and syncs all members to it
185- `power(False)` **dissolves** the group: ungroups all members from the leader and clears the sync leader
186- `stop()` only stops the leader â it does **not** dissolve the group; the group remains powered and ready to resume
187
188This mirrors how a typical AVR or stereo system behaves: turn it on, it's an active output; turn it off, it's gone.
189
190### Powering On
191
192```
1931. cmd_power(syncgroup, True) (also called implicitly by play_media / play)
194 â
1952. _form_syncgroup() runs
196 â
197 ââ⺠Ensure static members are included in _attr_group_members
198 ââ⺠Select sync leader (if not already set)
199 ââ⺠Move sync leader to the front of the member list
200 ââ⺠If leader is currently playing something else, stop it (and wait for IDLE)
201 ââ⺠cmd_set_members on the leader to sync the remaining members
202 â
2033. _attr_powered = True ; state event emitted
204```
205
206### Starting Playback
207
208```
2091. User starts playback on SyncGroupPlayer
210 â
2112. play_media(media)
212 â
213 ââ⺠Optimistically set _attr_current_media / _attr_active_source
214 ââ⺠_form_syncgroup() # idempotent - recovers if dissolved-but-powered
215 ââ⺠_handle_play_media(sync_leader, media) # leader actually plays
216 â
2173. Leader starts playback, synced members follow
218```
219
220### Stopping Playback
221
222```
2231. User stops playback on SyncGroupPlayer
224 â
2252. stop() forwarded to sync leader (group stays powered & formed)
226```
227
228### Powering Off
229
230```
2311. cmd_power(syncgroup, False)
232 â
2332. If currently playing/paused: stop() first
234 â
2353. _dissolve_syncgroup()
236 â
237 ââ⺠cmd_set_members on leader to remove all sync children (waits for state)
238 ââ⺠Clear leader's active_output_protocol (when leader is not still playing)
239 ââ⺠sync_leader = None
240 â
2414. _attr_powered = False ; state event emitted
242```
243
244### State Polling
245
246While 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.
247
248## Dynamic Member Management
249
250When `SET_MEMBERS` is called on a dynamic group:
251
252### Adding Members
253
2541. Validate the member exists, is available, and is not in the members filter
2552. If there is no sync leader yet (empty / unpowered group): just register the member; sync happens when the group is next formed
2563. 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)
2574. Incompatible members are **not** registered (avoids stranding orphan entries in the group)
2585. 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)
259
260### Removing Members
261
2621. Remove from the internal member list (static members cannot be removed)
2632. If removing the **sync leader** while playing:
264 - 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.
265 - If no remaining member is part of the live session (e.g. only freshly-added players are left), or the protocol doesn't support handoff: fall back to **dissolve + re-form** (brief audio gap)
2663. If removing a non-leader member: forward to `cmd_set_members` on the leader
267
268### Removing Last Member
269
270If the last member is removed, the group is dissolved (leader stopped, sync_leader cleared).
271
272## Feature Inheritance
273
274The SyncGroupPlayer has limited base features but inherits additional capabilities from the sync leader:
275
276### Base Features
277- `PLAY_MEDIA` - Always supported
278- `POWER` - Always supported (powered state is the canonical "is this group active" signal)
279
280### Features from Sync Leader (when active)
281- `ENQUEUE` - Queue next track
282- `GAPLESS_PLAYBACK` - Seamless track transitions
283- `VOLUME_SET` - Volume control
284- `VOLUME_MUTE` - Mute control
285- `MULTI_DEVICE_DSP` - DSP processing
286
287### Dynamic Feature
288- `SET_MEMBERS` - Only if group is configured as dynamic
289
290## Configuration Options
291
292### Group Members
293
294Multi-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.
295
296### Enable Dynamic Members
297
298Boolean option to allow runtime member changes. When enabled:
299- Group supports `SET_MEMBERS` feature
300- Members can be added/removed via UI or API
301- Group can start with zero members
302
303## Provider Details
304
305### Player ID Format
306
307Sync group players use the format: `syncgroup_{random_8_chars}`
308
309Example: `syncgroup_ab12cd34`
310
311### Provider Features
312
313- `CREATE_GROUP_PLAYER` - Create new sync groups
314- `REMOVE_GROUP_PLAYER` - Delete sync groups
315
316### Builtin Provider
317
318The Sync Group provider is:
319- **Builtin**: Automatically available, no installation needed
320- **Single instance**: Only one provider instance exists
321- **Non-disableable**: Cannot be disabled by users
322
323## State Properties
324
325The SyncGroupPlayer reads most state from the sync leader's **raw** attributes (deliberately not `.state.*`, see the leader-responsibilities note above):
326
327| Property | Source |
328|----------|--------|
329| `powered` | `_attr_powered` â set by `power()`, the canonical "is this group active" signal |
330| `playback_state` | Sync leader's raw `state.playback_state` (or IDLE if no leader) |
331| `elapsed_time` | Sync leader's raw `state.elapsed_time` |
332| `elapsed_time_last_updated` | Sync leader's raw `state.elapsed_time_last_updated` |
333| `current_media` | Sync leader's raw `current_media` (set optimistically in `play_media`) |
334| `active_source` | Sync leader's raw `active_source` (set optimistically in `play_media`) |
335| `group_members` | Sync leader's reported `state.group_members` (preferred) or internal list |
336| `can_group_with` | Aggregated from all current members' `can_group_with` |
337| `supported_features` | Base features + features inherited from the active sync leader |
338
339## Related Documentation
340
341- [Player Controller README](../../controllers/players/README.md) - For understanding player management, protocol linking, and sync coordination
342- [Universal Player README](../universal_player/README.md) - For understanding how protocol players are merged
343