A compact, touch-friendly MagicMirror² module for controlling Music Assistant directly. It displays the active queue, provides large playback controls, switches between players, and launches configured playlists.
It does not require Home Assistant, embed the Music Assistant web app, or depend on another MagicMirror module.
Status: Active and maintained.
- MagicMirror²
- Music Assistant 2.9 or newer
- A Music Assistant long-lived access token
- A browser that can reach the Music Assistant server
From the MagicMirror modules directory:
git clone https://github.com/bwente/MMM-MusicAssistant-Controller.git
cd MMM-MusicAssistant-Controller
npm testThis module has no production npm dependencies.
Keep the token outside the public module and MagicMirror configuration. Copy
token.example.js to a private location:
"use strict";
module.exports = {
token: "replace-with-a-music-assistant-long-lived-token",
};Restrict the file so only the account running MagicMirror can read it:
chmod 600 /absolute/private/path/music-assistant-token.jstokenFile must be an absolute path. The token is loaded by the node helper, passed only to
this module instance, and used in the WebSocket auth command. The module never writes it to
logs, the DOM, local storage, or a URL. Do not place a real token in this repository.
Add the module to config/config.js:
{
module: "MMM-MusicAssistant-Controller",
position: "fullscreen_above",
config: {
serverUrl: "http://music-assistant.local:8095",
tokenFile: "/absolute/private/path/music-assistant-token.js",
compact: false,
volumeStep: 5,
playlists: [
{
label: "Focus",
uri: "library://playlist/1",
icon: "bullseye",
},
{
label: "Relax",
uri: "library://playlist/2",
icon: "couch",
},
{
label: "News",
uri: "library://playlist/3",
icon: "newspaper",
},
{
label: "Party",
uri: "library://playlist/4",
icon: "music",
},
{
label: "Radio",
uri: "library://playlist/5",
icon: "radio",
},
],
},
},The address and playlist IDs above are examples only. With no playerId configured, users can
select from the players reported by Music Assistant.
| Option | Type | Default | Description |
|---|---|---|---|
serverUrl |
string | http://music-assistant.local:8095 |
Music Assistant HTTP(S) base URL. |
tokenFile |
string | empty | Required absolute path to the private CommonJS token file. |
playerId |
string | empty | Hardcode an exact player ID and hide the player selector. |
compact |
boolean | false |
Use a shorter, denser layout while retaining large touch targets. |
volumeStep |
number | 5 |
Percentage-point change for volume up/down. |
playlists |
array | [] |
Launcher tiles; each accepts label, uri, icon, or cover. |
reconnectBaseDelay |
number | 1000 |
Initial reconnect delay in milliseconds. |
reconnectMaxDelay |
number | 30000 |
Maximum reconnect delay in milliseconds. |
A configured playerId requires that exact player and removes the selector. When playerId is
empty, the module restores the last locally selected player and then falls back to the first
available player. The selector is also hidden automatically whenever Music Assistant reports only
one available player. Player names are display-only and are never used for resolution.
Configuration keys are case-sensitive: use playerId with a capital I, not playerid.
The standard touchscreen layout remains the default.
Set compact: true when the controller is a supplementary module in a normal MagicMirror region.
Compact mode is capped at 420px wide, uses smaller now-playing artwork and controls, and arranges
playlist launchers in a two-column grid. It works in any standard MagicMirror position and does
not require a fullscreen layout.
{
module: "MMM-MusicAssistant-Controller",
position: "top_right",
config: {
serverUrl: "http://music-assistant.local:8095",
tokenFile: "/absolute/private/path/music-assistant-token.js",
compact: true,
playlists: [
{ label: "Focus", uri: "library://playlist/1", icon: "bullseye" },
{ label: "Radio", uri: "library://playlist/2", icon: "radio" },
],
},
},Compact mode also works in top_left, bottom_left, bottom_right, and center regions. The
module respects the width supplied by its MagicMirror region and does not claim fullscreen space.
Use Music Assistant's players/all API command and copy the player_id belonging to the desired
player. On a computer that can reach Music Assistant:
read -rsp "Music Assistant token: " MA_TOKEN
echo
curl --silent --show-error \
--header "Authorization: Bearer ${MA_TOKEN}" \
--header "Content-Type: application/json" \
--data '{"message_id":"1","command":"players/all","args":{}}' \
"http://music-assistant.local:8095/api" |
jq -r '.result[] | "\(.name)\t\(.player_id)"'
unset MA_TOKENThis prints each player name followed by its exact ID. If jq is unavailable, omit the final
pipe and inspect the player_id fields in the JSON response. Use your real Music Assistant URL,
but do not paste the token directly into the command or save the output in public documentation.
The same command and response schema can be inspected in Music Assistant's generated API
documentation at http://YOUR_MA_SERVER:8095/api-docs.
icon is a Font Awesome icon name without the fa- prefix. cover can be an image URL or a
MagicMirror-served image path. A tile uses player_queues/play_media with the selected player ID
as the queue and replaces the current queue.
All controls support touch, mouse, Tab, Enter, Space, arrow keys, and Escape. Focus is only claimed after this interface receives navigation input, so normal page navigation and other modules retain focus otherwise.
External modules can send:
| Notification | Action |
|---|---|
MUSIC_PLAY |
Start or resume playback |
MUSIC_PAUSE |
Pause playback |
MUSIC_STOP |
Stop playback |
MUSIC_PLAY_URI |
Replace the queue and play a Music Assistant URI |
MUSIC_SET_VOLUME |
Set an absolute volume from 0–100 |
MUSIC_SELECT_PLAYER |
Select an available player when playerId is not configured |
MUSIC_CONTROL_UP, MUSIC_CONTROL_LEFT |
Move focus backward |
MUSIC_CONTROL_DOWN, MUSIC_CONTROL_RIGHT |
Move focus forward |
MUSIC_CONTROL_SELECT |
Activate the focused control |
MUSIC_CONTROL_BACK |
Close player selection or release encoder focus |
MUSIC_PLAY_PAUSE |
Toggle playback |
MUSIC_NEXT |
Next item |
MUSIC_PREVIOUS |
Previous item |
MUSIC_VOLUME_UP |
Raise volume by volumeStep |
MUSIC_VOLUME_DOWN |
Lower volume by volumeStep |
For example:
this.sendNotification("MUSIC_CONTROL_RIGHT");
this.sendNotification("MUSIC_CONTROL_SELECT");No GPIO or device-specific code is included.
Commands that accept payloads use these forms:
this.sendNotification("MUSIC_PLAY_URI", {
uri: "library://playlist/1",
playerId: "optional-player-id",
});
this.sendNotification("MUSIC_SET_VOLUME", {
volume: 35,
playerId: "optional-player-id",
});
this.sendNotification("MUSIC_SELECT_PLAYER", { playerId: "player-id" });MUSIC_PLAY_URI also accepts a URI string, MUSIC_SET_VOLUME accepts a number, and
MUSIC_SELECT_PLAYER accepts a player-ID string. When this module has a configured playerId,
that fixed player always wins and MUSIC_SELECT_PLAYER is ignored. This prevents an automation
from accidentally controlling a different room.
Explicit MUSIC_PLAY, MUSIC_PAUSE, and MUSIC_STOP notifications are preferable to toggles in
scheduled automations. For example, a scheduler can send MUSIC_PLAY_URI at 14:00 and
MUSIC_STOP at 15:00 without depending on the player's previous state. Scheduling and quiet-hour
policy remain the responsibility of the sending module or Home Assistant.
The controller broadcasts:
| Notification | Payload |
|---|---|
MUSIC_STATE_CHANGED |
playerId, state, title, artist, volume, elapsed, duration |
MUSIC_CONNECTION_CHANGED |
state, error |
Broadcasts are deduplicated and contain no authentication information.
The module follows MagicMirror's global language setting and includes Bulgarian (bg), Danish
(da), German (de), English (en), Spanish (es), French (fr), Hungarian (hu), Dutch
(nl), Russian (ru), and Thai (th). Player names, track metadata, and configured playlist
labels remain exactly as supplied by Music Assistant or your configuration.
The module connects to ws://…/ws or wss://…/ws, waits for server information, authenticates,
and retrieves players/all and player_queues/all. Music Assistant events update the existing
DOM rather than rebuilding it. Connection failures use capped exponential backoff with jitter.
The status line distinguishes connecting, reconnecting, disconnected, and error states.
If MagicMirror is served over HTTPS, Music Assistant should also be served over HTTPS so the browser permits the secure WebSocket connection. Ensure Music Assistant permits the MagicMirror origin and that both the Electron renderer and browser clients can resolve the configured host.
Artwork uses Music Assistant's image proxy. Browsers cache image responses normally; no additional disk cache is maintained by default.
The default layout fits a 1024×600 fullscreen region without scrolling with five playlist tiles. At narrower widths, tiles wrap to three columns. The module uses no backdrop filters, masks, blur, canvas, iframe, animation loop, or GPU-heavy effect. A one-second timer changes only the progress elements.
If many playlist tiles are configured, available vertical space may be exceeded. Five tiles are recommended for a 1024×600 display.
npm install
npm run lint
npm testThe dependency-free Node test suite covers command construction, player resolution, state normalization, server events, progress events, reconnection delay, WebSocket authentication, pending-command failure, and notification mapping.
For a live smoke test:
- Start MagicMirror and confirm the status becomes connected.
- Select each player and reload MagicMirror to verify persistence.
- Start playback outside MagicMirror and confirm metadata and controls synchronize.
- Disconnect the Music Assistant host temporarily; confirm the reconnecting status and recovery.
- Exercise touch, mouse, keyboard, and any external notification controller.
- Authentication error: create a new long-lived token and verify the private file exports
either
{ token: "…" }or the token string itself. - No players: verify players are enabled and available in Music Assistant. Prefer an exact
playerId. - Artwork missing: verify the browser can load the Music Assistant HTTP URL directly.
- Connection repeatedly restarts: check hostname resolution, firewall rules, HTTPS mixed content, and Music Assistant logs. The token is deliberately omitted from module logs.
Music Assistant exposes generated API documentation at http://YOUR_MA_SERVER:8095/api-docs.
Its public API overview and official frontend client are
the protocol references used by this module.
From the installed module directory:
cd ~/MagicMirror/modules/MMM-MusicAssistant-Controller
git pull --ff-only
npm install --omit=devRestart MagicMirror after updating. The authentication token and MagicMirror configuration are stored outside this repository and are not changed by an update.
See CHANGELOG.md for release details.

