# How the Discovery Controller Uses mDNS/Zeroconf for Player Discovery in Music Assistant

> Learn how the Discovery Controller uses mDNS Zeroconf in Music Assistant to broadcast your server, find player services, and automatically update caches for seamless player discovery.

- Repository: [Music Assistant/server](https://github.com/music-assistant/server)
- Tags: how-to-guide
- Published: 2026-06-17

---

**The DiscoveryController manages a shared AsyncZeroconf instance to broadcast the Music Assistant server via mDNS, browse for player services, and dispatch state changes to providers with serialized handling and automatic cache replay.**

The **DiscoveryController** in the [music-assistant/server](https://github.com/music-assistant/server) repository orchestrates all mDNS/Zeroconf-based player discovery. It centralizes Zeroconf lifecycle management into a single async-aware component that handles both outgoing server announcements and incoming device detection across the local network.

## Initializing the Shared AsyncZeroconf Instance

When the controller starts, it constructs a single **AsyncZeroconf** object that the entire application shares. In [`music_assistant/controllers/discovery/controller.py`](https://github.com/music-assistant/server/blob/main/music_assistant/controllers/discovery/controller.py), the `_create_aiozc` method builds this instance using interface configuration from the core config (`CONF_ZEROCONF_INTERFACES`).

The helper `get_zeroconf_args` (located in [`music_assistant/helpers/util.py`](https://github.com/music-assistant/server/blob/main/music_assistant/helpers/util.py)) generates the correct `ip_version` and `interfaces` arguments based on user preferences. This allows administrators to restrict discovery to specific network interfaces, IPv4-only, or IPv6-only environments. The resulting object is stored as `self._aiozc` and exposed through the `aiozc` property for all consumers, preventing duplicate network sockets and keeping the mDNS cache centralized.

## Broadcasting the Server via mDNS

The controller announces the Music Assistant instance itself to the network. The `_register_mass_service` method registers a `_mass._tcp.local.` service, enabling other players and Home Assistant to discover the MA server via standard mDNS browsing. This self-announcement ensures the server is visible as a first-class citizen on the local network.

## Browsing for Player Services

All provider manifests declaring `mdns_discovery` are collected during startup. The `_setup_mdns_browser` method creates a single **AsyncServiceBrowser** that listens for the union of service types defined across all providers. This global browser routes every mDNS state change—whether a service appears, updates, or disappears—to the controller's centralized handler.

## Dispatching Events to Providers

When the browser detects a change, `_on_mdns_service_state_change` iterates over loaded providers and matches the service type to the appropriate handler. The controller maintains per-provider `asyncio.Lock` instances (`self._mdns_locks`) to serialize concurrent state changes, preventing race conditions when multiple mDNS events arrive rapidly.

If the state is `ServiceStateChange.Added`, the provider receives the `AsyncServiceInfo` to register the player. If `ServiceStateChange.Removed`, the provider unregisters it. This architecture ensures that network fluctuations do not corrupt the internal player registry.

## Replaying Cached Discovery Results

Providers loaded after startup (or reloaded during runtime) must still discover devices already present on the network. The `_replay_mdns_discovery` method addresses this by iterating through the Zeroconf cache (`self.aiozc.zeroconf.cache.cache`) and replaying any cached entries matching the provider's declared service types. This guarantees that a provider never misses a device simply because it loaded after the initial mDNS announcement.

## Looking Up Services by Name

For providers that need to resolve a specific device by name, the `async_find_mdns_service` method offers a targeted query API. This method first checks the Zeroconf cache for an exact match, then optionally waits for a new mDNS event if the device is not yet cached. It uses the shared `aiozc` instance and returns the full `AsyncServiceInfo` record, allowing providers to extract IP addresses and other metadata.

## Implementation Example for Providers

Providers interact with the discovery controller through standardized callbacks and helper methods. Below are typical implementation patterns from the codebase.

Requesting a player by service name:

```python
async def get_player_by_name(self, name: str) -> Player | None:
    # Ask the discovery controller for an mDNS service matching the device name

    info = await self.mass.discovery.async_find_mdns_service(
        service_type="_raop._tcp.local.",  # RAOP (AirPlay) service

        name_filter=name,
        timeout=5.0,
    )
    if info is None:
        return None
    # Extract the IP address from the Zeroconf info

    ip = get_primary_ip_address_from_zeroconf(info)
    return Player(name=name, ip_address=ip)

```

Handling state change callbacks:

```python
async def on_mdns_service_state_change(
    self, name: str, state: ServiceStateChange, info: AsyncServiceInfo | None
) -> None:
    if state is ServiceStateChange.Added and info:
        ip = get_primary_ip_address_from_zeroconf(info)
        self.register_player(name, ip)
    elif state is ServiceStateChange.Removed:
        self.unregister_player(name)

```

Both examples rely on `get_primary_ip_address_from_zeroconf` from [`music_assistant/helpers/util.py`](https://github.com/music-assistant/server/blob/main/music_assistant/helpers/util.py) to parse the raw Zeroconf data into usable IP addresses.

## Summary

- **Single Shared Instance**: The controller creates one `AsyncZeroconf` object (`self._aiozc`) that powers all discovery, server registration, and DNS resolution to avoid socket duplication.
- **Service Type**: Music Assistant broadcasts itself as `_mass._tcp.local.` and browses for service types declared in provider manifests.
- **Serialized Dispatch**: Per-provider `asyncio.Lock` instances ensure thread-safe handling of concurrent mDNS events through `_on_mdns_service_state_change`.
- **Cache Replay**: The `_replay_mdns_discovery` mechanism ensures late-loading providers receive notifications for devices already present on the network.
- **Targeted Lookup**: `async_find_mdns_service` provides synchronous-style lookups with cache-first resolution and optional waiting for new announcements.

## Frequently Asked Questions

### What mDNS service type does Music Assistant broadcast?

Music Assistant registers itself as a `_mass._tcp.local.` service via the `_register_mass_service` method in [`music_assistant/controllers/discovery/controller.py`](https://github.com/music-assistant/server/blob/main/music_assistant/controllers/discovery/controller.py). This allows other devices and Home Assistant to discover the MA server on the local network without manual configuration.

### How does the controller handle providers that load after devices are already discovered?

The `_replay_mdns_discovery` method iterates through the Zeroconf cache and replays cached entries matching the provider's service types. This ensures providers immediately learn about existing network devices regardless of when they were loaded relative to the initial mDNS announcements.

### Is the Zeroconf instance shared across the entire application?

Yes. The DiscoveryController creates a single `AsyncZeroconf` instance stored in `self._aiozc` and exposed through the `aiozc` property. All providers, the server registration logic, and helper utilities like `async_find_mdns_service` consume this same instance to maintain a centralized cache and prevent duplicate network sockets.

### How does the controller ensure thread-safe handling of mDNS events?

The controller maintains a dictionary of per-provider `asyncio.Lock` instances (`self._mdns_locks`). When `_on_mdns_service_state_change` dispatches an event to a provider, it acquires that provider's specific lock, guaranteeing serialized access even when multiple mDNS state changes occur simultaneously.