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: SubclassingPlayerProviderplayer.py: SubclassingPlayermanifest.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:
- Discovery: MA calls
discover_players()on each loaded provider. - Registration: The provider creates
Playerinstances and registers them withawait self.mass.players.register(player). - Routing: Playback commands from the UI reach the player manager, which forwards them to the correct
Playerobject. - State Updates: Providers push changes via
player.update_state()or unregister disconnected devices viaawait 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
PlayerProviderinmusic_assistant/models/player_provider.pyto handle discovery and lifecycle management. - Extend
Playerinmusic_assistant/models/player.pyto implement device-specific controls. - Register devices using
self.mass.players.register()and handle cleanup viaremove_player(). - Include a
manifest.jsonwith 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →