# How Music Assistant Implements AirPlay, Chromecast, and DLNA Player Protocols

> Discover how Music Assistant integrates AirPlay Chromecast and DLNA. Learn about its uniform playback API and automatic merging of multi-protocol devices for a seamless audio experience.

- Repository: [Music Assistant/server](https://github.com/music-assistant/server)
- Tags: deep-dive
- Published: 2026-06-21

---

**Music Assistant treats AirPlay, Chromecast, and DLNA as lightweight protocol wrappers that expose a uniform playback API while delegating actual streaming to protocol-specific binaries and libraries, automatically merging multi-protocol devices into a single UniversalPlayer.**

Music Assistant uses a sophisticated abstraction layer to handle heterogeneous streaming hardware through a unified interface. The server implements dedicated protocol providers for AirPlay 2, Chromecast, and DLNA, each wrapping vendor-specific libraries while exposing a consistent player API to the rest of the application.

## Protocol Architecture Overview

The architecture separates discovery from playback control. Each protocol provider inherits from the common `PlayerProvider` base class defined in [`music_assistant/models/player_provider.py`](https://github.com/music-assistant/server/blob/main/music_assistant/models/player_provider.py). These providers register discovered hardware with the central **PlayerController** (`self.mass.players`), which then aggregates multi-protocol devices into virtual universal players that survive server restarts.

## Protocol-Specific Implementations

### AirPlay 2 Streaming via cliap2

The AirPlay 2 implementation in [`music_assistant/providers/airplay/protocols/airplay2.py`](https://github.com/music-assistant/server/blob/main/music_assistant/providers/airplay/protocols/airplay2.py) uses the `AirPlay2Stream` class to manage the `cliap2` binary. This class handles session establishment by feeding audio via stdin and forwarding commands through a named pipe. It manages **NTP-based start-time synchronization**, volume control, and optional Apple pairing credentials for authenticated streams.

### Chromecast Discovery and Control

The Chromecast provider in [`music_assistant/providers/chromecast/provider.py`](https://github.com/music-assistant/server/blob/main/music_assistant/providers/chromecast/provider.py) leverages the `pychromecast` library for device discovery. The `ChromecastProvider` creates a `CastBrowser` instance via `start_discovery()` to listen for network announcements, then instantiates `ChromecastPlayer` objects (defined in [`music_assistant/providers/chromecast/player.py`](https://github.com/music-assistant/server/blob/main/music_assistant/providers/chromecast/player.py)) to maintain device state and metadata. For legacy control scenarios, the provider manages a *Sendspin* bridge.

### DLNA MediaRenderer Handling

The DLNA provider in [`music_assistant/providers/dlna/provider.py`](https://github.com/music-assistant/server/blob/main/music_assistant/providers/dlna/provider.py) listens for SSDP announcements, filtering specifically for "MediaRenderer" device types. When `DLNAPlayerProvider.on_upnp_service_discovered` detects a compatible device, it extracts the Unique Device Name (UDN) and description URL, then constructs an `async_upnp_client` factory. The resulting `DLNAPlayer` streams content via HTTP-based GET requests to the device.

## Universal Player Aggregation

When hardware supports multiple protocols—such as a HomePod supporting both AirPlay 2 and DLNA—Music Assistant creates a **UniversalPlayer** to aggregate the protocol-specific instances. This logic resides in [`music_assistant/providers/universal_player/provider.py`](https://github.com/music-assistant/server/blob/main/music_assistant/providers/universal_player/provider.py).

### Device Key Generation and Locking

The `_get_device_key_from_players` method generates stable identifiers preferring **MAC addresses** (normalized via `normalize_mac_for_matching` in the utility helpers), falling back to UUIDs, and finally to protocol player IDs. To prevent race conditions during simultaneous protocol discovery, the provider maintains per-device `asyncio.Lock` instances in `_universal_player_locks`.

### Player Creation and Linking

The `create_universal_player` method constructs new virtual players when none exists, while existing universal players receive additional protocol capabilities via `add_protocol_player`. The provider persists protocol IDs, identifiers, and device information to the Music Assistant configuration database, ensuring the virtual player survives server restarts.

### Intelligent Name Selection

The `_get_clean_player_name` method selects the most user-friendly display name from available protocol players, preferring descriptive names from Chromecast and DLNA over technical MAC-style identifiers.

## Practical Implementation Examples

### Starting an AirPlay 2 Stream

```python

# From music_assistant/providers/airplay/protocols/airplay2.py

stream = AirPlay2Stream(
    player_id="kitchen_homepod",
    audio_source=audio_buffer,
    credentials=apple_credentials
)
await stream.start(start_ntp=target_ntp_time)

```

### Chromecast Discovery

```python

# From music_assistant/providers/chromecast/provider.py

self.browser = CastBrowser(
    self.chromecast_listener,
    self.zeroconf_instance
)
await self.browser.start_discovery()

```

### DLNA SSDP Handling

```python

# From music_assistant/providers/dlna/provider.py

async def on_upnp_service_discovered(self, discovery_info):
    if "MediaRenderer" in discovery_info.get("st", ""):
        await self._device_discovered(
            udn=discovery_info["usn"],
            description_url=discovery_info["location"]
        )

```

### Accessing the Universal Player

```python

# PlayerController automatically handles protocol aggregation

player = mass.players.get_player_by_name("Living Room")
await mass.players.cmd_play(
    player.player_id,
    "https://example.org/stream.mp3"
)

```

## Summary

- Music Assistant implements protocol-specific providers for AirPlay 2, Chromecast, and DLNA that inherit from `PlayerProvider` in [`music_assistant/models/player_provider.py`](https://github.com/music-assistant/server/blob/main/music_assistant/models/player_provider.py).
- AirPlay 2 uses the `cliap2` binary with named pipes for command control and NTP timing for synchronization.
- Chromecast relies on `pychromecast` for discovery and maintains device state through `ChromecastPlayer` instances.
- DLNA devices are discovered via SSDP and controlled using `async_upnp_client` with HTTP streaming.
- The `UniversalPlayerProvider` in [`music_assistant/providers/universal_player/provider.py`](https://github.com/music-assistant/server/blob/main/music_assistant/providers/universal_player/provider.py) merges multi-protocol devices using MAC-based keys and per-device locking.
- All protocols expose a unified API to the PlayerController, allowing identical playback commands regardless of underlying technology.

## Frequently Asked Questions

### How does Music Assistant handle devices that support multiple protocols?

Music Assistant creates a UniversalPlayer that aggregates all protocol-specific player instances for a single physical device. The system uses MAC address-based device keys to identify hardware uniquely across different discovery mechanisms, then links additional protocol capabilities via `add_protocol_player` while persisting the configuration to survive restarts.

### What binary does Music Assistant use for AirPlay 2 streaming?

The AirPlay 2 provider uses the `cliap2` binary, which receives audio via stdin and accepts commands through a named pipe. The `AirPlay2Stream` class in [`music_assistant/providers/airplay/protocols/airplay2.py`](https://github.com/music-assistant/server/blob/main/music_assistant/providers/airplay/protocols/airplay2.py) manages this process, handling NTP-based start times and optional Apple pairing credentials for authenticated sessions.

### How does the DLNA provider discover devices on the network?

The DLNA provider listens for SSDP multicast announcements and filters for devices advertising "MediaRenderer" service types. When `on_upnp_service_discovered` detects a compatible device, it extracts the device description URL and creates a `DLNAPlayer` instance using `async_upnp_client` to handle SOAP control and HTTP streaming.

### Why does Music Assistant prefer MAC addresses for device identification?

MAC addresses provide stable hardware identifiers that persist across network changes and protocol variations. The `_get_device_key_from_players` method normalizes MAC addresses using `normalize_mac_for_matching` to create consistent device keys, falling back to UUIDs or player IDs only when MAC information is unavailable.