How Player Providers Discover and Register Players in Music Assistant

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. 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.


# 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 (lines 66-104), the discovery coroutine handles concurrent scanning and player setup:


# 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:


# 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), 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 (lines 15-39) defines the interface that all concrete players must implement. The constructor initializes the provider reference, configuration entry, and internal state snapshot:


# 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, lines 1630-1645) serves as the central registry. Its register_or_update method is the single entry point for all player providers:


# 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:


# 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.
  • 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.
  • 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →