music-assistant-server

14.5 KBMD
README.md
14.5 KB303 lines • markdown
1# Player Controller Architecture
2
3This document provides an overview of the Music Assistant Player Controller architecture, including the Player/PlayerState model, multi-protocol player system, and universal player concept.
4
5## Table of Contents
6
7- [Overview](#overview)
8- [Player vs PlayerState](#player-vs-playerstate)
9- [Core Components](#core-components)
10- [Player Types](#player-types)
11- [Multi-Protocol Player System](#multi-protocol-player-system)
12- [Universal Player](#universal-player)
13- [Protocol Linking](#protocol-linking)
14- [Development Guide](#development-guide)
15
16## Overview
17
18The Player Controller is a core controller that manages all connected audio players from various providers. It provides:
19- Unified control interface for all players (play, pause, volume, etc.)
20- Multi-protocol player linking (combining AirPlay, Chromecast, DLNA for the same device)
21- Universal Player wrapping for devices without native vendor support
22- Sync group management for synchronized playback
23- Player state management and event broadcasting
24- User access control and permissions
25
26## Player vs PlayerState
27
28The Player Controller distinguishes between two key concepts:
29
30### Player (Internal Model)
31
32The `Player` class is the actual object provided by a Player Provider. It:
33- Incorporates the actual state of the player (volume, playback state, etc.)
34- Contains methods for controlling the player (play, pause, volume, etc.)
35- Is used internally by providers and the controller
36- May contain provider-specific implementation details
37
38### PlayerState (API Model)
39
40The `PlayerState` is a dataclass representing the final state of the player. It:
41- Includes any user customizations (custom name, hidden status, etc.)
42- Applies transformations (e.g., fake power/volume controls)
43- Is the object exposed to the outside world via the API
44- Is a snapshot created when `player.update_state()` is called
45- Contains only serializable data suitable for API consumers
46
47```
48┌─────────────────────────────────────────────────────────────────┐
49│                     Player (Internal)                            │
50│  - Provider-specific implementation                             │
51│  - Control methods (play, pause, volume_set, etc.)              │
52│  - Raw state (_attr_volume_level, _attr_playback_state, etc.)   │
53│  - Device info and identifiers                                  │
54└─────────────────────────────────┬───────────────────────────────┘
55                                  │
56                                  │ update_state()
57                                  ▼
58┌─────────────────────────────────────────────────────────────────┐
59│                   PlayerState (API)                             │
60│  - Final display name (with user customizations)                │
61│  - Transformed state (fake controls applied)                    │
62│  - Player controls configuration                                │
63│  - Serializable for API/WebSocket                               │
64└─────────────────────────────────────────────────────────────────┘
65```
66
67## Core Components
68
69### 1. PlayerController ([controller.py](controller.py))
70
71The main orchestrator that manages:
72- Player registration and lifecycle
73- Player commands (play, pause, stop, volume, etc.)
74- Protocol linking and evaluation
75- Universal player creation
76- Sync group coordination
77
78**Key responsibilities:**
79- Routes commands to appropriate players or protocol players
80- Manages player availability and state
81- Handles announcements and TTS playback
82- Coordinates sync groups and grouped playback
83
84### 2. ProtocolLinkingMixin ([protocol_linking.py](protocol_linking.py))
85
86Mixin class containing all protocol linking logic:
87- Matching protocol players to native players via device identifiers
88- Creating and managing Universal Players
89- Protocol link lifecycle (add, remove, cleanup)
90- Output protocol selection for playback
91
92### 3. Helper Utilities ([helpers.py](helpers.py))
93
94Contains standalone helper functions and decorators:
95- `handle_player_command` decorator for command validation
96- `AnnounceData` type definition
97
98## Player Types
99
100Players in Music Assistant have different types based on their capabilities:
101
102### PlayerType.PLAYER
103
104A regular player with native (vendor-specific) support. Examples:
105- Sonos speakers via the Sonos provider
106- Apple devices via the AirPlay provider (HomePod, Apple TV)
107- Google devices via the Chromecast provider (Nest Audio, Google Home)
108
109### PlayerType.PROTOCOL
110
111A generic protocol player without native vendor support. These are streaming endpoints discovered via generic protocols but manufactured by third parties. Examples:
112- Samsung TV discovered via AirPlay (not an Apple device)
113- Sony speaker discovered via Chromecast (not a Google device)
114- Any DLNA/UPnP device (always PROTOCOL type)
115
116**Important:** Protocol players with `PlayerType.PROTOCOL` are hidden from the UI and wrapped in a Universal Player or attached to an existing native player.
117
118### PlayerType.GROUP
119
120A group player that represents (synchronized) playback across multiple physical speakers.
121
122### PlayerType.STEREO_PAIR
123
124A dedicated stereo pair of two speakers acting as one player.
125
126## Multi-Protocol Player System
127
128Modern audio devices often support multiple streaming protocols (AirPlay, Chromecast, DLNA). The Player Controller automatically detects and links these protocols to provide a unified experience.
129
130### How It Works
131
132```
133┌─────────────────────────────────────────────────────────────────────┐
134│                     Physical Device                                 │
135│                  (e.g., Samsung Soundbar)                           │
136├─────────────────────────────────────────────────────────────────────┤
137│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐               │
138│  │   AirPlay    │  │  Chromecast  │  │    DLNA      │               │
139│  │   Protocol   │  │   Protocol   │  │   Protocol   │               │
140│  │   Player     │  │   Player     │  │   Player     │               │
141│  │  (hidden)    │  │  (hidden)    │  │  (hidden)    │               │
142│  └──────┬───────┘  └──────┬───────┘  └──────┬───────┘               │
143│         │                 │                 │                       │
144│         └─────────────────┼─────────────────┘                       │
145│                           │                                         │
146│                           ▼                                         │
147│              ┌─────────────────────────┐                            │
148│              │    Universal Player     │                            │
149│              │  (visible in UI)        │                            │
150│              │  - Aggregates protocols │                            │
151│              │  - Selects best output  │                            │
152│              │  - Unified control      │                            │
153│              └─────────────────────────┘                            │
154└─────────────────────────────────────────────────────────────────────┘
155```
156
157### Device Identifier Matching
158
159Protocol players are matched to the same physical device using identifiers in order of reliability:
160
1611. **MAC_ADDRESS** - Most reliable, unique to the network interface
1622. **SERIAL_NUMBER** - Unique device serial number
1633. **UUID** - Universally unique identifier
1644. **player_id** - Fallback for players without identifiers (e.g., Sendspin)
165
166**Note:** IP_ADDRESS is intentionally NOT used for matching as it can change with DHCP and cause incorrect matches between different devices.
167
168**Fallback behavior:** Protocol players that don't expose any identifiers (like Sendspin clients) will still get wrapped in a Universal Player using their player_id as the device key. This ensures all protocol players get a consistent user-facing interface.
169
170### Output Protocol Selection
171
172When playing media, the controller selects the best output protocol:
173
1741. **Grouped protocol** - If a protocol is actively grouped/synced, use it
1752. **User preference** - Honor user's configured preferred protocol
1763. **Native playback** - Use native PLAY_MEDIA if available
1774. **Best available** - Select by protocol priority (AirPlay > Chromecast > DLNA)
178
179## Universal Player
180
181The Universal Player is a virtual player that wraps one or more protocol players when no native vendor support exists.
182
183### When Created
184
185A Universal Player is created when:
1861. A device is discovered via a protocol but has no native provider
1872. The device's protocol player has `PlayerType.PROTOCOL`
1883. There is no existing native player that matches the device identifiers
189
190### Features
191
192- **Aggregates Features** - Combines capabilities from all linked protocols
193- **No PLAY_MEDIA** - Delegates playback to protocol players
194- **Unified Control** - Single point of control for volume, power, etc.
195- **Protocol Selection** - Automatically selects best protocol for playback
196
197### Lifecycle
198
199```
2001. Protocol player registered with PlayerType.PROTOCOL
2012. Controller checks for cached parent_id from previous session:
202   - If found, restores link immediately (skips evaluation)
203   - If parent not yet registered, waits without creating universal player
2043. If no cached parent, checks for matching native player (links immediately if found)
2054. If no native player, schedules delayed evaluation:
206   - 10 seconds standard delay (allows other protocols to register)
207   - 30 seconds if previously linked to a native player (allows native provider to start)
2085. After delay, finds all matching protocol players by identifiers
2096. Creates UniversalPlayer and links all protocols
2107. Protocol players become hidden, Universal Player visible
211```
212
213## Protocol Linking
214
215### Native Player Linking
216
217When a native player (e.g., Sonos) is registered, the controller:
2181. Searches for protocol players with matching identifiers
2192. Links matching protocols to the native player
2203. Protocol players become hidden, native player gains `output_protocols`
221
222### Protocol to Universal
223
224When protocol players are registered without a native match:
2251. Each protocol player schedules a delayed evaluation
2262. After the delay, matching protocols are grouped
2273. A Universal Player is created to wrap them all
2284. All protocol players link to the Universal Player
229
230### Universal to Native Promotion
231
232When a native player appears for a device that has a Universal Player:
2331. Native player is registered
2342. Controller finds matching Universal Player
2353. All protocol links transfer to the native player
2364. Universal Player is removed
2375. Native player becomes the visible entity
238
239## Development Guide
240
241### Adding Protocol Support
242
243When implementing a new protocol provider:
244
2451. Set `_attr_type = PlayerType.PROTOCOL` for generic devices (non-vendor devices)
2462. Set `_attr_type = PlayerType.PLAYER` for devices with native support (vendor's own devices)
2473. Populate `device_info.identifiers` with MAC, UUID, etc. (see below)
2484. Filter out devices that should only be handled by native providers (e.g., passive satellites)
2495. The Player Controller handles linking automatically
250
251### Adding Native Provider Support
252
253When implementing a native provider (e.g., Sonos, Bluesound) that should link to protocol players:
254
2551. Set `_attr_type = PlayerType.PLAYER` (or the property 'type') for all devices
2562. **Populate device identifiers** - This is critical for protocol linking:
257   ```python
258   self._attr_device_info = DeviceInfo(
259       model="Device Model",
260       manufacturer="Manufacturer Name",
261   )
262   # Add identifiers in order of preference (MAC is most reliable)
263   self._attr_device_info.add_identifier(IdentifierType.MAC_ADDRESS, "AA:BB:CC:DD:EE:FF")
264   self._attr_device_info.add_identifier(IdentifierType.UUID, "device-uuid-here")
265   ```
2663. The controller will automatically:
267   - Find protocol players (AirPlay, Chromecast, DLNA) with matching identifiers
268   - Link them to your native player as `output_protocols`
269   - Replace any existing Universal Player for that device
270
271**Identifier Priority:**
272- `MAC_ADDRESS` - Most reliable, unique to network interface
273- `SERIAL_NUMBER` - Unique device serial number
274- `UUID` - Universally unique identifier
275- `player_id` - Fallback when no identifiers available
276
277**Note:** `IP_ADDRESS` is NOT used for matching as it can change with DHCP.
278
279### Testing Protocol Linking
280
281Key scenarios to test:
282
2831. **Single protocol device** - Should create Universal Player
2842. **Multi-protocol device** - All protocols linked to one Universal Player
2853. **Late protocol discovery** - New protocol added to existing Universal Player
2864. **Native player appears** - Universal Player replaced by native
2875. **Protocol disappears** - Handle graceful degradation
288
289### Configuration Storage
290
291Protocol links are persisted in player configuration:
292- `linked_protocol_player_ids` - List of protocol player IDs
293- Restored on restart for fast reconnection
294
295### Key Methods (in protocol_linking.py)
296
297- `_evaluate_protocol_links()` - Entry point for link evaluation
298- `_try_link_protocol_to_native()` - Link protocol to existing native
299- `_schedule_protocol_evaluation()` - Delay evaluation for batching
300- `_create_or_update_universal_player()` - Create/update Universal Player
301- `_check_replace_universal_player()` - Replace Universal with native
302- `_select_best_output_protocol()` - Choose protocol for playback
303