# How Player Providers Discover and Register Players in Music Assistant

> Learn how player providers discover and register players in Music Assistant using network scanning like MDNS SSDP for seamless integration with Web API and Home Assistant.

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

---

**Player providers discover devices using network scanning protocols like MDNS or SSDP, instantiate concrete `Player` subclasses, and register them via `await self.mass.players.register_or_update(player)` to make them available to the Web API and Home Assistant integration.**

Music Assistant is a powerful open-source media server that unifies streaming services and local media libraries. Understanding how player providers discover and register players is essential for developers extending the platform or debugging device connectivity issues. The architecture follows a strict three-phase lifecycle that keeps protocol-specific discovery logic isolated from the core registration system.

## The Three-Phase Player Lifecycle

Every speaker or streaming endpoint in Music Assistant follows a standardized lifecycle:

- **Discovery** – The provider scans the network using protocol-specific methods (MDNS, SSDP, HTTP APIs, or proprietary libraries).
- **Instantiation** – For each discovered device, the provider creates a concrete subclass of `Player` (e.g., `SonosPlayer`, `MPDPlayer`).
- **Registration** – The provider hands the instance to the `PlayersController`, which stores it, links protocol parents, and broadcasts state changes to the Web API and UI.

## Provider Discovery Implementation

All player providers inherit from the `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). This class establishes the contract for device discovery while allowing providers to implement protocol-specific scanning logic.

### The PlayerProvider Base Class

The abstract base class stores a reference to the global `MusicAssistant` instance and defines the `discover_players()` method that concrete providers must implement.

```python

# music_assistant/models/player_provider.py

class PlayerProvider(ABC):
    """Base class for all player providers."""
    # concrete providers must implement `discover_players()`

```

### Concrete Discovery Example (Sonos)

The Sonos provider demonstrates a real-world implementation using the `aiosonos` library to browse the local network. Located in [`music_assistant/providers/sonos/provider.py`](https://github.com/music-assistant/server/blob/main/music_assistant/providers/sonos/provider.py) (lines 66-104), the discovery coroutine handles concurrent scanning and player setup:

```python

# music_assistant/providers/sonos/provider.py

async def discover_players(self) -> None:
    """Discover Sonos devices on the network."""
    if self._discovery_running:
        return
    self._discovery_running = True
    try:
        discovered_devices: set[SoCo] = await discover()   # aiosonos helper

        for soco in discovered_devices:
            await self._setup_player(soco)                # create a Player instance

    finally:
        self._discovery_running = False

```

The helper method `_setup_player` instantiates a `SonosPlayer` and immediately registers it with the core:

```python

# music_assistant/providers/sonos/provider.py – inside _setup_player

await self.mass.players.register_or_update(sonos_player)

```

Other providers follow identical patterns. The WiiM provider uses MDNS discovery ([`music_assistant/providers/wiim/provider.py`](https://github.com/music-assistant/server/blob/main/music_assistant/providers/wiim/provider.py)), while HTTP-based providers poll REST endpoints against known device IPs.

## The Player Object Model

The `Player` base class in [`music_assistant/models/player.py`](https://github.com/music-assistant/server/blob/main/music_assistant/models/player.py) (lines 15-39) defines the interface that all concrete players must implement. The constructor initializes the provider reference, configuration entry, and internal state snapshot:

```python

# music_assistant/models/player.py

def __init__(self, provider: PlayerProvider, player_id: str) -> None:
    self.mass = provider.mass                     # reference to the core

    self.logger = provider.logger
    self._attr_supported_features = set()
    # …

    self._state = PlayerState(                    # final API-ready snapshot

        player_id=self.player_id,
        provider=self.provider_id,
        type=self.type,
        name=self.display_name,
        available=self.available,
        device_info=self.device_info,
        supported_features=self.supported_features,
        playback_state=self.playback_state,
    )

```

This abstraction allows the core to interact with Sonos, MPD, or Chromecast devices through a uniform interface regardless of underlying protocol differences.

## Registration via the PlayersController

The `PlayersController` ([`music_assistant/controllers/players/controller.py`](https://github.com/music-assistant/server/blob/main/music_assistant/controllers/players/controller.py), lines 1630-1645) serves as the central registry. Its `register_or_update` method is the single entry point for all player providers:

```python

# music_assistant/controllers/players/controller.py

async def register_or_update(self, player: Player) -> None:
    """Add a new player or update an existing one."""
    # 1️⃣ Store it in the internal dict keyed by player_id

    self._players[player.player_id] = player
    # 2️⃣ Link protocol parent if this is a protocol player

    await self._link_protocol_player(player)
    # 3️⃣ Fire callbacks so UI & Home Assistant are notified

    await self._notify_player_registered(player)

```

The controller handles **protocol-parent linking** for players that exist as children of aggregate devices (like universal groups) and manages cleanup when players disappear from the network.

## Building a Custom Provider

To implement discovery in a custom provider, override `discover_players()` and follow the registration pattern:

```python

# my_custom_provider.py

from music_assistant.models import PlayerProvider, Player

class MyCustomPlayer(Player):
    # implement required abstract methods (play, pause, …)

    pass

class MyCustomProvider(PlayerProvider):
    async def discover_players(self) -> None:
        # 1️⃣ Find devices (example: HTTP poll)

        for info in await self._poll_my_api():
            player = MyCustomPlayer(self, player_id=info["id"])
            player._attr_name = info["name"]
            # 2️⃣ Hand the player to the core

            await self.mass.players.register_or_update(player)

```

## Summary

- Player providers inherit from `PlayerProvider` and implement `discover_players()` to scan for devices using protocol-specific methods defined in [`music_assistant/models/player_provider.py`](https://github.com/music-assistant/server/blob/main/music_assistant/models/player_provider.py).
- Discovered devices become concrete `Player` subclasses (e.g., `SonosPlayer`) that encapsulate device capabilities and state.
- Registration occurs through `await self.mass.players.register_or_update(player)` in [`music_assistant/controllers/players/controller.py`](https://github.com/music-assistant/server/blob/main/music_assistant/controllers/players/controller.py).
- The `PlayersController` stores players in an internal dictionary, links protocol hierarchies, and notifies the Web API and Home Assistant integration of state changes.

## Frequently Asked Questions

### What network protocols do player providers use for discovery?

Providers use various mechanisms depending on the device ecosystem. Sonos uses the `aiosonos` library for proprietary discovery, WiiM uses MDNS broadcasting, and other providers implement SSDP or HTTP polling against REST APIs.

### Can a provider register players without implementing discovery?

Yes. While most providers implement automatic discovery, you can manually instantiate `Player` subclasses and call `register_or_update()` directly. This is useful for virtual players or devices with static configurations that don't support network scanning.

### What happens when a player disconnects from the network?

The provider detects the absence during its next discovery scan or via connection callbacks. It updates the player's `available` property to `False`, and the `PlayersController` handles the state change propagation to connected clients without removing the player from the registry.

### How does the system handle duplicate player registrations?

The `register_or_update` method in `PlayersController` checks for existing entries by `player_id`. If the player already exists, it updates the existing instance rather than creating a duplicate, ensuring state continuity across reconnection events.