How to Integrate Your Own Music Playback Devices with Music Assistant

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 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 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: Subclassing PlayerProvider
  • player.py: Subclassing Player
  • 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 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:


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

# 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.
// 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 to handle discovery and lifecycle management.
  • Extend Player in 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 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 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 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 declares the appropriate optional features. The player manager handles synchronization across grouped devices.

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 →