# How to Integrate Your Own Music Playback Devices with Music Assistant

> Integrate your own music playback devices with Music Assistant using its extensible provider architecture. Learn how to implement a PlayerProvider subclass and register custom players for seamless audio control.

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

---

**Yes, Music Assistant supports custom playback devices through its extensible provider architecture by implementing a PlayerProvider subclass that discovers and registers Player instances with the global player manager.**

Music Assistant’s server-side architecture is designed for hardware extensibility. If you want to integrate your own music playback devices with Music Assistant, you can build a custom Player Provider that bridges your hardware’s protocol to the application’s unified playback API.

## Understanding the Provider Architecture

The integration mechanism centers on two abstract base classes defined in the core models.

### The PlayerProvider Base Class

The `PlayerProvider` class in [`music_assistant/models/player_provider.py`](https://github.com/music-assistant/server/blob/main/music_assistant/models/player_provider.py) serves as the foundation for all device integrations. It defines mandatory hooks including `handle_async_init` for asynchronous setup and `discover_players` for device detection. When the provider loads, it interacts with `self.mass.players`—the global player manager exposed through the central `Mass` object—to register devices.

### The Player Model

The `Player` class in [`music_assistant/models/player.py`](https://github.com/music-assistant/server/blob/main/music_assistant/models/player.py) represents a single playback endpoint. Each instance stores the device’s unique ID, capabilities, and a reference to its parent provider. Concrete implementations override methods like `play()`, `pause()`, and `set_volume()` to translate generic commands into device-specific protocol calls.

## Implementing a Custom Player Provider

Building a new integration requires four components: a provider class, a player class, a manifest file, and discovery logic.

### Step 1: Create the Provider Structure

Create a new directory under `music_assistant/providers/` (e.g., `my_device`). This folder must contain:

- [`provider.py`](https://github.com/music-assistant/server/blob/main/provider.py): Subclassing `PlayerProvider`
- [`player.py`](https://github.com/music-assistant/server/blob/main/player.py): Subclassing `Player`  
- [`manifest.json`](https://github.com/music-assistant/server/blob/main/manifest.json): Declaring metadata and configuration schema
- Optional helper modules for device communication

### Step 2: Implement Discovery

The `discover_players` coroutine handles device detection. When Music Assistant starts or configuration changes, it invokes this method. The provider must instantiate concrete `Player` objects and register them using `await self.mass.players.register(player)`.

### Step 3: Handle Playback Commands

Concrete `Player` subclasses implement transport controls. When the UI or Home Assistant integration issues a command, the player manager routes it to the appropriate `Player` instance, which then communicates with the physical device via HTTP, Bluetooth, or proprietary SDK.

### Step 4: Register the Manifest

The [`manifest.json`](https://github.com/music-assistant/server/blob/main/manifest.json) file declares the provider type as `"player"` and defines configuration requirements. Music Assistant automatically loads providers by reading these manifests at startup.

## Complete Skeleton Implementation

Here's a minimal working example based on the demo provider at [`music_assistant/providers/_demo_player_provider/provider.py`](https://github.com/music-assistant/server/blob/main/music_assistant/providers/_demo_player_provider/provider.py):

```python

# my_device/provider.py

from __future__ import annotations
from typing import TYPE_CHECKING, cast

from music_assistant.models.player_provider import PlayerProvider
from .player import MyDevicePlayer   # your concrete Player implementation

from .constants import CONF_DEVICE_IP

if TYPE_CHECKING:
    from music_assistant.helpers.util import some_helper

class MyDeviceProvider(PlayerProvider):
    """Player provider for MyDevice hardware."""

    async def handle_async_init(self) -> None:
        # Load any provider-specific config (e.g. IP address)

        self.logger.info("MyDeviceProvider init with config %s", self.config)

    async def discover_players(self) -> None:
        """Discover MyDevice units on the network."""
        ip = cast(str, self.config.get_value(CONF_DEVICE_IP))
        # Example: single-device scenario

        player = MyDevicePlayer(
            provider=self,
            player_id=f"mydevice_{ip}",
        )
        await self.mass.players.register(player)

    async def remove_player(self, player_id: str) -> None:
        """Optional: called when the user removes a device."""
        await self.mass.players.unregister(player_id)

```

```python

# my_device/player.py

from __future__ import annotations
from music_assistant.models.player import Player

class MyDevicePlayer(Player):
    """Concrete Player for a single MyDevice unit."""

    async def play(self) -> None:
        # Send play command to the device (e.g. HTTP POST)

        await self.provider.api_call("play", self.player_id)

    async def pause(self) -> None:
        await self.provider.api_call("pause", self.player_id)

    # Implement other actions (stop, set_volume, seek, etc.) as needed.

```

```json
// my_device/manifest.json
{
  "name": "MyDevice",
  "domain": "mydevice",
  "description": "Support for MyDevice playback hardware",
  "type": "player",
  "required_features": [],
  "optional_features": ["REMOVE_PLAYER"],
  "config_schema": {
    "type": "object",
    "properties": {
      "device_ip": { "type": "string", "title": "Device IP address" }
    },
    "required": ["device_ip"]
  }
}

```

## How Commands Flow Through the System

Understanding the runtime behavior helps debug integrations:

1. **Discovery**: MA calls `discover_players()` on each loaded provider.
2. **Registration**: The provider creates `Player` instances and registers them with `await self.mass.players.register(player)`.
3. **Routing**: Playback commands from the UI reach the player manager, which forwards them to the correct `Player` object.
4. **State Updates**: Providers push changes via `player.update_state()` or unregister disconnected devices via `await self.mass.players.unregister(player_id)`.

Because the architecture operates asynchronously, providers can handle network I/O without blocking the core server.

## Summary

- Music Assistant uses a provider architecture to integrate custom playback devices.
- Extend `PlayerProvider` in [`music_assistant/models/player_provider.py`](https://github.com/music-assistant/server/blob/main/music_assistant/models/player_provider.py) to handle discovery and lifecycle management.
- Extend `Player` in [`music_assistant/models/player.py`](https://github.com/music-assistant/server/blob/main/music_assistant/models/player.py) to implement device-specific controls.
- Register devices using `self.mass.players.register()` and handle cleanup via `remove_player()`.
- Include a [`manifest.json`](https://github.com/music-assistant/server/blob/main/manifest.json) with type `"player"` for automatic loading at startup.

## Frequently Asked Questions

### Do I need to modify Music Assistant core code to add a new device?

No. You only need to create a new provider directory under `music_assistant/providers/` with your implementation. The server automatically discovers and loads your code based on the [`manifest.json`](https://github.com/music-assistant/server/blob/main/manifest.json) file, following the pattern shown in `music_assistant/providers/_demo_player_provider/`.

### What methods must I implement in the Player class?

You must implement the core transport controls required by your device, typically including `play()`, `pause()`, and `set_volume()`. The base class in [`music_assistant/models/player.py`](https://github.com/music-assistant/server/blob/main/music_assistant/models/player.py) defines the interface; implement only what your hardware supports and set capability flags accordingly.

### How does Music Assistant discover my devices?

The server periodically invokes the `discover_players()` coroutine defined in your `PlayerProvider` subclass. You implement detection logic there—whether through mDNS scanning, static IP configuration, or API polling—and register found devices with the player manager using `await self.mass.players.register(player)`.

### Can I support device grouping or multi-room audio?

Yes. The `Player` model supports grouping capabilities. When implementing your concrete player class, override the grouping methods and ensure the [`manifest.json`](https://github.com/music-assistant/server/blob/main/manifest.json) declares the appropriate optional features. The player manager handles synchronization across grouped devices.