# Music Assistant Discovery Controller: mDNS and Zeroconf Player Detection Explained

> Discover how the Music Assistant controller uses mDNS and Zeroconf for player detection. Learn about service record browsing and automatic state dispatching.

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

---

**The Music Assistant DiscoveryController uses a shared AsyncZeroconf instance to browse mDNS service records and broadcasts the server as a `_mass._tcp.local.` service, automatically dispatching state changes to providers that declare supported service types in their manifests.**

The Music Assistant server repository implements a sophisticated network discovery mechanism that enables automatic detection of media players. The DiscoveryController, located in [`music_assistant/controllers/discovery/controller.py`](https://github.com/music-assistant/server/blob/main/music_assistant/controllers/discovery/controller.py), orchestrates both mDNS/Zeroconf and SSDP/UPnP protocols to discover devices like Chromecast, AirPlay, and Sonos players without manual configuration.

## How the Discovery Controller Works

The DiscoveryController combines two complementary protocols to discover network devices. **mDNS/Zeroconf** discovers service records advertised by devices (such as `_raop._tcp.local.` for AirPlay or `_googlecast._tcp.local.` for Chromecast) using the `zeroconf` Python package. **SSDP/UPnP** handles broadcast-based discovery of DLNA devices using `async_upnp_client`.

The controller is instantiated once at server start and performs five core responsibilities: creating a shared AsyncZeroconf instance, registering the Music Assistant server on the network, running a global AsyncServiceBrowser, dispatching mDNS state changes to providers, and maintaining cache-aware lookups for reliable player resolution.

### Zeroconf Service Creation and Interface Selection

The `_create_aiozc` method initializes the AsyncZeroconf instance based on network adapter configuration. The helper function `get_zeroconf_args` in [`music_assistant/helpers/util.py`](https://github.com/music-assistant/server/blob/main/music_assistant/helpers/util.py) inspects host network adapters via `ifaddr.get_adapters()` to determine whether Zeroconf operates in IPv4-only, IPv6-only, or dual-stack mode.

On macOS and FreeBSD, the controller forces IPv4-only mode because the `IPVersion.All` socket cannot join IPv4 multicast groups on these platforms. When the configuration entry `CONF_ZEROCONF_INTERFACES` is set to `"all"`, the function returns an explicit list of concrete IP addresses rather than using `InterfaceChoice.Default`, ensuring devices on secondary network adapters are discovered.

### Global mDNS Service Browser

The `_setup_mdns_browser` method creates a single AsyncServiceBrowser that listens for all service types required by loaded providers. When a provider declares `mdns_discovery` entries in its manifest (such as `["_googlecast._tcp.local."]` for Chromecast), the browser automatically includes these service types.

State changes trigger the `_on_mdns_service_state_change` callback, which processes events at a verbose log level. The callback creates an `AsyncServiceInfo` object for added or updated services, requests details with a 3000ms timeout, and dispatches the event to all registered providers via their `on_mdns_service_state_change` coroutine.

## Provider Integration and Service Registration

Providers declare their discovery requirements in their manifest files using the `mdns_discovery` key. For example, the Chromecast provider includes `"mdns_discovery": ["_googlecast._tcp.local."]` in its manifest configuration.

When a provider loads, the controller executes `_replay_mdns_discovery` to ensure cached entries for already-seen devices are immediately reported. This guarantees that devices discovered before the provider initialized are not missed. The controller also manages the `_mass._tcp.local.` service registration via `_register_mass_service`, announcing the Music Assistant server to the network.

## Manual Service Discovery and Caching

The `async_find_mdns_service` method enables synchronous queries against the Zeroconf cache with optional waiting for new events. This supports reliable "player-by-name" lookups, such as finding a specific AirPlay device by its service name.

The method accepts a `service_type`, optional `name_filter`, and `timeout` parameter. When querying the cache, it extracts IP addresses using `get_primary_ip_address_from_zeroconf` and ports via helper functions from [`music_assistant/helpers/util.py`](https://github.com/music-assistant/server/blob/main/music_assistant/helpers/util.py).

## Code Examples

### Querying a specific player by name

```python

# Assume `mass` is the MusicAssistant instance

service_type = "_raop._tcp.local."
name = "LivingRoomTV"

info = await mass.discovery.async_find_mdns_service(
    service_type=service_type,
    name_filter=name,
    timeout=5.0,
)

if info:
    from music_assistant.helpers.util import get_primary_ip_address_from_zeroconf, get_port_from_zeroconf
    ip = get_primary_ip_address_from_zeroconf(info)
    port = get_port_from_zeroconf(info)
    print(f"Found {name} at {ip}:{port}")
else:
    print("Device not found")

```

### Creating a custom provider with mDNS discovery

```json
// myprovider/manifest.json
{
  "name": "MyDevice",
  "mdns_discovery": ["_mydevice._tcp.local."]
}

```

```python

# myprovider/__init__.py

from zeroconf import ServiceStateChange

async def on_mdns_service_state_change(self, name, state, info):
    if state is ServiceStateChange.Added:
        from music_assistant.helpers.util import get_primary_ip_address_from_zeroconf
        ip = get_primary_ip_address_from_zeroconf(info)
        self.log.info("MyDevice discovered at %s", ip)
    elif state is ServiceStateChange.Removed:
        self.log.info("MyDevice %s left the network", name)

```

### Configuring all interfaces for discovery

```yaml

# In Music Assistant configuration

zeroconf_interfaces: "all"

```

Setting `CONF_ZEROCONF_INTERFACES` to `"all"` forces `get_zeroconf_args(use_all_interfaces=True)` to return an explicit list of IP addresses, avoiding the default `InterfaceChoice.Default` that might miss devices on secondary adapters.

## Summary

- The DiscoveryController in [`music_assistant/controllers/discovery/controller.py`](https://github.com/music-assistant/server/blob/main/music_assistant/controllers/discovery/controller.py) manages both mDNS/Zeroconf and SSDP discovery protocols.
- **Interface selection** automatically adapts to IPv4, IPv6, or dual-stack based on system adapters, with macOS/FreeBSD forced to IPv4-only mode.
- Providers declare discovery needs via `mdns_discovery` entries in their manifests, receiving callbacks via `on_mdns_service_state_change`.
- The `async_find_mdns_service` method enables cache-aware lookups by service name with configurable timeouts.
- The controller replays cached mDNS entries when providers load to ensure no devices are missed during startup.

## Frequently Asked Questions

### How does Music Assistant handle mDNS discovery on multi-homed systems?

On systems with multiple network interfaces, the `get_zeroconf_args` function in [`music_assistant/helpers/util.py`](https://github.com/music-assistant/server/blob/main/music_assistant/helpers/util.py) inspects adapters via `ifaddr.get_adapters()`. When `CONF_ZEROCONF_INTERFACES` is set to `"all"`, it returns an explicit list of IP addresses for each interface rather than using `InterfaceChoice.Default`, ensuring Zeroconf listens on all available networks.

### What happens when a provider loads after devices have already been discovered?

The controller executes `_replay_mdns_discovery` when a provider initializes, which scans the Zeroconf cache and dispatches cached service entries to the provider's `on_mdns_service_state_change` method. This guarantees that devices discovered before the provider loaded are immediately reported without waiting for new mDNS broadcasts.

### How can I manually look up a specific player's IP address using the DiscoveryController?

Use the `async_find_mdns_service` method with the appropriate service type and name filter. The method queries the Zeroconf cache synchronously and waits up to the specified timeout for new entries if the device isn't cached. Extract the IP using `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).

### Why does Music Assistant force IPv4-only mode on macOS and FreeBSD?

According to the source code in [`music_assistant/helpers/util.py`](https://github.com/music-assistant/server/blob/main/music_assistant/helpers/util.py), the `IPVersion.All` socket option cannot properly join IPv4 multicast groups on macOS and FreeBSD systems. To ensure reliable player detection on these platforms, `get_zeroconf_args` forces IPv4-only mode when it detects these operating systems.