MusicBee Remote Protocol SDK & Implementers Guide
Protocol v4.0 Protocol v4.5
This document provides a complete technical specification for implementing clients compatible with the MusicBee Remote (MBRC) protocol versions 4 and 4.5. Use this guide to build remote control applications, integrations, or alternative clients.
- Open TCP socket to
host:3000(or discovered port) - Send:
{"context":"player","data":"android"}\r\n - Send:
{"context":"protocol","data":{"protocol_version":4}}\r\n - Send:
{"context":"init","data":null}\r\n - Receive initial state (track info, cover art, player status)
- Send commands, receive push events - you're connected!
Table of Contents
- 1. Protocol Fundamentals
- 2. Service Discovery (UDP)
- 3. Connection & Handshake
- 4. Version Negotiation
- 5. Connection Commands
- 6. Player Control Commands
- 7. Now Playing Commands
- 8. Queue/Playlist Commands
- 9. Library Browse Commands
- 10. Advanced Commands (v4.5)
- 11. Server Push Events
- 12. Serialization
- 13. Networking Layer
- 14. Reconnection Strategy
- 15. Error Handling
- 16. IP Filtering & Security
- 17. Implementation Checklist
- 18. Code Examples
- 19. Protocol Testing & Compatibility
- Appendix A: Data Types Reference
1. Protocol Fundamentals #
Message Format
All protocol communication uses JSON over TCP with CRLF (\r\n) line termination:
{
"context": "command_name",
"data": {
"field": "value"
}
}Key Characteristics
| Property | Value |
|---|---|
| Encoding | UTF-8 without BOM |
| Line Termination | \r\n (CRLF - Windows style) |
| Message Boundary | One JSON message per line |
| Serialization | JSON (Newtonsoft.Json compatible) |
| Default Port | TCP 3000 (configurable in plugin settings) |
| Discovery | UDP Multicast 239.1.5.10:45345 |
Recommended Timeouts
| Timeout | Value | Description |
|---|---|---|
| Connect Timeout | 5 seconds | TCP connection establishment |
| Handshake Timeout | 10 seconds | Complete player/protocol/init exchange |
| Command Response | 30 seconds | Waiting for command response |
| Ping Interval | 30-60 seconds | Heartbeat frequency during idle |
| Pong Timeout | 5 seconds | Time to wait for pong response |
| Dead Connection | 90 seconds | Declare dead after 3 missed pings |
| Position Update | ~20 seconds | Server broadcasts position periodically |
Socket Configuration
// Recommended socket options for clients socket.NoDelay = true; // Disable Nagle algorithm for low latency socket.ReceiveTimeout = 30000; // 30 second receive timeout socket.SendTimeout = 5000; // 5 second send timeout socket.KeepAlive = true; // Enable TCP keep-alive
2. Service Discovery (UDP) #
Before establishing a TCP connection, clients can discover available MBRC servers on the local network using UDP multicast. This enables zero-configuration setup where clients automatically find servers without manual IP entry.
Discovery Protocol Parameters
| Parameter | Value | Notes |
|---|---|---|
| Multicast Address | 239.1.5.10 | IPv4 multicast group |
| Port | 45345 | UDP port for discovery |
| Encoding | UTF-8 | No BOM, no line termination required |
| Format | JSON | Single JSON object per datagram |
Discovery Flow
Client Server(s)
| |
| 1. Join multicast group 239.1.5.10:45345 |
| |
| 2. Send discovery request |
|------ {"context":"discovery", -------------------->|
| "address":"192.168.1.50"} |
| |
| 3. Server receives|
| on all NICs |
| |
| 4. Server responds with endpoint info |
|<----- {"context":"notify", ------------------------|
| "address":"192.168.1.10", |
| "name":"DESKTOP-ABC", |
| "port":3000} |
| |
| 5. Client connects via TCP to 192.168.1.10:3000 |
| |
Discovery Request
Client sends a JSON message to the multicast group with its own IP address:
{
"context": "discovery",
"address": "192.168.1.50"
}| Field | Type | Required | Description |
|---|---|---|---|
context | string | Yes | Must be "discovery" |
address | string | Yes | Client's IP address (used for subnet matching) |
Discovery Response
Server responds directly to the client's UDP endpoint (not multicast):
{
"context": "notify",
"address": "192.168.1.10",
"name": "DESKTOP-MUSICBEE",
"port": 3000
}| Field | Type | Description |
|---|---|---|
context | string | Always "notify" for successful discovery |
address | string | Server's IP address on the same subnet as client |
name | string | Computer name (from COMPUTERNAME environment variable) |
port | integer | TCP port for MBRC protocol (from plugin settings) |
Discovery Error Response
If the request is malformed or missing required fields:
{
"context": "error",
"description": "missing address"
}| Error | Description |
|---|---|
missing address | Request did not include client's IP address |
unsupported action | Context was not "discovery" |
Subnet Matching Logic
The server selects which network interface IP to return based on subnet matching:
- Parse client's IP address from the request
- If client is loopback (127.x.x.x), return first available interface IP
- For each server network interface:
- Calculate network address using interface's subnet mask
- Compare with client's network address
- If match, use this interface's IP in response
- Fallback: return first available interface IP if no match
Client Implementation Example (C#)
public async Task<List<DiscoveredServer>> DiscoverServersAsync(int timeoutMs = 3000)
{
var servers = new List<DiscoveredServer>();
var multicast = IPAddress.Parse("239.1.5.10");
var port = 45345;
using var udp = new UdpClient();
udp.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
udp.JoinMulticastGroup(multicast);
// Get local IP for the request
var localIp = GetLocalIPAddress();
var request = JsonConvert.SerializeObject(new {
context = "discovery",
address = localIp
});
var bytes = Encoding.UTF8.GetBytes(request);
// Send discovery request
await udp.SendAsync(bytes, bytes.Length, new IPEndPoint(multicast, port));
// Collect responses until timeout
var deadline = DateTime.UtcNow.AddMilliseconds(timeoutMs);
while (DateTime.UtcNow < deadline)
{
udp.Client.ReceiveTimeout = (int)(deadline - DateTime.UtcNow).TotalMilliseconds;
try
{
var endpoint = new IPEndPoint(IPAddress.Any, 0);
var response = udp.Receive(ref endpoint);
var json = Encoding.UTF8.GetString(response);
var server = JsonConvert.DeserializeObject<DiscoveredServer>(json);
if (server?.Context == "notify")
servers.Add(server);
}
catch (SocketException) { break; } // Timeout
}
return servers;
}
public class DiscoveredServer
{
[JsonProperty("context")] public string Context { get; set; }
[JsonProperty("address")] public string Address { get; set; }
[JsonProperty("name")] public string Name { get; set; }
[JsonProperty("port")] public int Port { get; set; }
}Multiple Servers
In environments with multiple MusicBee instances (different computers), clients may receive multiple discovery responses. Present all discovered servers to the user for selection, displaying:
- Name: Computer name for identification
- Address:Port: Connection endpoint
3. Connection & Handshake #
TCP Connection Setup
Configure the TCP socket with these settings for optimal performance:
// Recommended socket configuration socket.NoDelay = true; // Disable Nagle algorithm (low latency) socket.ReceiveTimeout = 30000; // 30 second receive timeout socket.SendTimeout = 5000; // 5 second send timeout writer.NewLine = "\r\n"; // CRLF line termination
Three-Packet Handshake (Required Order)
The server validates packet order. Sending packets out of sequence will cause connection failure.
Client Server
| |
|---------------- TCP Connect ------------------>|
| |
| Packet 0: Player Identification |
|-- {"context":"player","data":"android"} ------>|
| |
| Packet 1: Protocol Negotiation |
|-- {"context":"protocol", |
| "data":{"protocol_version":4}} ------------>|
| |
| Packet 2: Init Request (optional) |
|-- {"context":"init","data":null} ------------->|
| |
|<---------- Initial State Messages -------------|
| (nowplayingtrack, playerstate, etc) |
Packet 0 - Player Identification (REQUIRED FIRST)
{
"context": "player",
"data": "android"
}Identifies the client type. Use "android" for compatibility.
Packet 1 - Protocol Negotiation (REQUIRED SECOND)
{
"context": "protocol",
"data": {
"protocol_version": 4,
"no_broadcast": false
}
}Declares protocol version. Set no_broadcast: true to disable UDP broadcast responses.
Packet 2 - Init Request (OPTIONAL but RECOMMENDED)
{
"context": "init",
"data": null
}Requests initial system status. Server responds with a burst of 6 messages containing complete current state:
Init Response Burst (6 Messages)
After receiving init, the server immediately sends these messages in order:
Server Response Burst (all sent immediately) ββββββββββββββββββββββββββββββββββββββββββββββ 1. nowplayingtrack β Current track metadata (artist, album, title, etc.) 2. nowplayingrating β Track rating (0-5 scale, or -1 if unrated) 3. nowplayinglovestatus β Love status (true/false/loved) 4. playerstatus β Complete player state (volume, shuffle, repeat, etc.) 5. nowplayingcover β Album art (Base64 encoded image) 6. nowplayinglyrics β Current track lyrics (if available)
| # | Context | Purpose | Notes |
|---|---|---|---|
| 1 | nowplayingtrack | Track metadata | See Now Playing Commands section |
| 2 | nowplayingrating | Track rating | -1 = unrated, 0-5 = rated |
| 3 | nowplayinglovestatus | Love/like status | true, false, or "loved" |
| 4 | playerstatus | Player state | Volume, shuffle, repeat, scrobble, state |
| 5 | nowplayingcover | Album art | Base64 encoded image data |
| 6 | nowplayinglyrics | Lyrics | May be empty if no lyrics available |
4. Version Negotiation #
Protocol Version History
| Protocol | MusicBee SDK | Features Added |
|---|---|---|
| 2.0 | 3.0 | Base functionality: player control, now playing, library search, queue operations |
| 2.1 | 3.0 | Added: ping/pong, init, separate playerplay/playerpause |
| 3.0 | 3.0 | Added: playlistplay, library browse (browsegenres, etc.), nowplayingqueue |
| 4.0 | 3.0 | Added: playeroutput, radiostations, nowplayingdetails, nowplayingtagchange, libraryalbumcover |
| 4.5 | 3.0 / 3.1 | ARiA extension + MusicBee 3.1 features when available |
Protocol 4.5 Feature Availability
Plugin v1.5.x negotiates the protocol version based on the client's request. Feature availability depends on the negotiated version, settings, and MusicBee version:
| Feature Set | Requirement | Commands |
|---|---|---|
| Extended Track Metadata | Client requests v4.5+ | year, rating, bitrate, format, playcount, skipcount, lastplayed, dateadded, loved in browsetracks |
| ARiA (Initiator Actions) | ARiA enabled in plugin settings | playerinitiatoraction, configureariahotkeys |
| Storage Path | ARiA enabled in plugin settings | pluginstoragepath |
| Visualizers | Experimental Features enabled in plugin settings | playervisualizer, playervisualizerlist |
| Album Navigation | MusicBee 3.1+ (API 50) | playernextalbum, playerpreviousalbum |
| Podcasts | MusicBee 3.1+ (API 51) | podcastsubscriptions, podcastepisodes, podcastplay |
| Playlist Notifications | MusicBee 3.1+ (API 49) | playlistcreated, playlistupdated, playlistdeleted |
| Two-Way Sync | Client requests v4.5+ | librarysetrating, librarysetlove |
| Real-time Library Sync | Plugin v1.5.x+ | librarysubscribe, libraryunsubscribe, librarytagchanged, libraryfileadded, libraryfiledeleted, libraryratingchanged, libraryplaycountchanged |
pluginversion to detect MusicBee SDK level for MB 3.1 features.
Version Negotiation Sequence
The client declares its supported protocol version in the handshake. The server negotiates the version and responds accordingly:
CLIENT (v4 legacy) SERVER (Plugin 1.5.x)
| |
|-- {"context":"protocol", |
| "data":{"protocol_version":4}} ------>|
| |
|<-- {"context":"protocol", |
| "data":4.0} -------------------------|
| |
| Result: Protocol 4.0, NO extended |
| metadata (backward compatible) |
CLIENT (v4.5+) SERVER (Plugin 1.5.x)
| |
|-- {"context":"protocol", |
| "data":{"protocol_version":4.5}} ---->|
| |
|<-- {"context":"protocol", |
| "data":4.5} -------------------------|
| |
| Result: Protocol 4.5, extended metadata |
| ARiA if enabled in settings |
Detecting Server Capabilities
Method 1: Protocol Response (Version Negotiation)
// Server responds with negotiated version based on client request { "context": "protocol", "data": 4.0 // Client requested v4 - no extended metadata } { "context": "protocol", "data": 4.5 // Client requested v4.5+ - extended metadata enabled }
Method 2: Plugin Version Request (MusicBee SDK Detection)
// Request {"context": "pluginversion", "data": null} // Response {"context": "pluginversion", "data": "1.5.26.3"} // Version mapping: // Plugin 1.4.x = MusicBee 3.0 SDK (no podcasts, no album nav) // Plugin 1.5.x = MusicBee 3.1 SDK (podcasts, album nav, playlist notifications) // // Protocol version is negotiated - request v4.5 to get extended metadata // ARiA availability depends on plugin settings (separate from protocol version)
Feature Detection Matrix
| Feature | Protocol 4.0 (legacy clients) | Protocol 4.5 (MB 3.0) | Protocol 4.5 (MB 3.1+) |
|---|---|---|---|
| Basic transport (play, pause, next, etc.) | Yes | Yes | Yes |
| Volume, shuffle, repeat, scrobble | Yes | Yes | Yes |
| Now playing info, cover, lyrics, position | Yes | Yes | Yes |
| Library browse & search | Yes | Yes | Yes |
| Queue operations | Yes | Yes | Yes |
| Playlists | Yes | Yes | Yes |
| Output devices, radio stations | Yes | Yes | Yes |
playerinitiatoraction (ARiA) | No | If ARiA enabled | If ARiA enabled |
pluginstoragepath | No | If ARiA enabled | If ARiA enabled |
playervisualizer / playervisualizerlist | No | If Experimental enabled | If Experimental enabled |
playernextalbum / playerpreviousalbum (API 50) | No | No | Yes |
podcastsubscriptions / podcastepisodes / podcastplay (API 51) | No | No | Yes |
| Playlist notifications (API 49+) | No | No | Yes |
Backward Compatibility Rules
- v4 client + v1.5.x plugin: Server responds with v4.0 - no extended metadata, no breaking changes for legacy clients
- v4.5 client + v1.5.x plugin: Server responds with v4.5 - extended metadata included
- v4.5 client + v1.4.x plugin: Client works, but ARiA commands are ignored (no error, no response)
- MusicBee 3.1 commands on 3.0: Commands are ignored silently
- Unknown commands: Server silently ignores commands it doesn't recognize
- Client responsibility: Request the protocol version you support (4.5 for extended features); check plugin version for MB 3.1 features
protocol_version: 4.5 in handshake to get extended metadata. Then request pluginversion to determine MusicBee SDK level (1.4.x = MB 3.0, 1.5.x = MB 3.1). ARiA (keyboard automation) and Experimental Features (visualizers) are separate plugin settings that users must enable.
5. Connection Commands #
Ping/Pong Heartbeat
Keep-alive mechanism. Send ping every 30 seconds; timeout after 90 seconds (3 missed pongs).
// Client sends ping {"context": "ping", "data": null} // Server responds with pong {"context": "pong", "data": null}
Verify Connection
{"context": "verifyconnection", "data": null}Plugin Version
// Request {"context": "pluginversion", "data": null} // Response {"context": "pluginversion", "data": "1.5.26.3"}
Plugin Instance ID v1.5.x+
Returns a unique identifier for this plugin installation. The GUID is generated on first run and persisted to settings. Use this to detect when connecting to a different MusicBee server (e.g., for library cache invalidation).
// Request {"context": "plugininstanceid", "data": null} // Response {"context": "plugininstanceid", "data": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"}
6. Player Control Commands #
Transport Controls
| Command | Context | Data |
|---|---|---|
| Play | playerplay | null |
| Pause | playerpause | null |
| Play/Pause Toggle | playerplaypause | null |
| Stop | playerstop | null |
| Next Track | playernext | null |
| Previous Track | playerprevious | null |
| Next Album | playernextalbum | null (API 50+) |
| Previous Album | playerpreviousalbum | null (API 50+) |
Volume Control
// Set absolute volume (0-100) {"context": "playervolume", "data": "75"} // Increase volume by 5 {"context": "playervolume", "data": "+5"} // Decrease volume by 5 {"context": "playervolume", "data": "-5"}
Mute Control
{"context": "playermute", "data": "toggle"} // or "on" / "off"Shuffle/Repeat
// Shuffle {"context": "playershuffle", "data": "toggle"} // or true/false // Repeat modes: "none", "all", "one", "toggle" {"context": "playerrepeat", "data": "all"}
Auto DJ
{"context": "playerautodj", "data": true} // or falsePlayer State Response
// Request {"context": "playerstatus", "data": null} // Response (Complete Player Status) { "context": "playerstatus", "data": { "playerrepeat": "None", // "None", "All", "One" "playermute": false, // boolean "playershuffle": "off", // See shuffle states below "playerscrobble": true, // Last.fm scrobbling enabled "playerstate": "Playing", // "Playing", "Paused", "Stopped" "playervolume": "75" // String "0"-"100" } }
Shuffle State Values
The playershuffle field can be a boolean (v4 legacy) or a string (v4.5):
| Value | Description |
|---|---|
"off" | Shuffle disabled - tracks play in order |
"shuffle" | Standard shuffle - randomized playback |
"autodj" | Auto DJ enabled - MusicBee selects tracks |
true / false | Legacy boolean (v4 protocol only) |
playershuffle is sent as a boolean. If v4.5+, it's sent as a string with the three-state value.
Repeat State Values
| Value | Description |
|---|---|
"None" | No repeat - stop after queue ends |
"All" | Repeat entire queue |
"One" | Repeat current track only |
7. Now Playing Commands #
Track Information
// Request {"context": "nowplayingtrack", "data": null} // Response { "context": "nowplayingtrack", "data": { "artist": "Artist Name", "album": "Album Name", "albumArtist": "Album Artist", "title": "Track Title", "year": "2024", "genre": "Rock", "path": "/path/to/file.mp3", "duration": 240000, // milliseconds "rating": 4.5, // 0-5 scale "playCount": 5, "bitrate": 320, // kbps "format": "MP3", "trackNo": 5, "discNo": 1 } }
Cover Art
// Request {"context": "nowplayingcover", "data": null} // Response (Base64 encoded JPEG/PNG) { "context": "nowplayingcover", "data": "iVBORw0KGgoAAAANSUhEUgAAAAUA..." }
Position/Seek
// Get current position {"context": "nowplayingposition", "data": null} // Response {"context": "nowplayingposition", "data": {"position": 45000}} // Seek to position (milliseconds) {"context": "nowplayingposition", "data": 60000}
Rating
// Request current rating (use "-1") {"context": "nowplayingrating", "data": "-1"} // Set rating (0-5, as string with decimal) {"context": "nowplayingrating", "data": "4.5"}
Last.fm Love/Ban
// Request current status (use "-1") {"context": "nowplayinglfmrating", "data": "-1"} // Set: "love", "ban", or "normal" {"context": "nowplayinglfmrating", "data": "love"}
Lyrics
Request lyrics for the currently playing track. Lyrics are retrieved from the file's embedded tags or MusicBee's lyrics database.
// Request {"context": "nowplayinglyrics", "data": null} // Response (lyrics found) { "context": "nowplayinglyrics", "data": { "status": 200, "lyrics": "First line of lyrics\nSecond line of lyrics\n\nVerse two..." } } // Response (no lyrics) { "context": "nowplayinglyrics", "data": { "status": 404, "lyrics": "" } }
| Field | Type | Description |
|---|---|---|
status | integer | 200 = lyrics found, 404 = no lyrics available |
lyrics | string | Lyrics text with newlines (\n). LRC timestamps stripped. XML-escaped. |
[0:30.123]) and XML-escapes special characters. Clients receive plain text lyrics suitable for display.
Extended Track Details
Request comprehensive metadata for the currently playing track, including technical file properties.
// Request {"context": "nowplayingdetails", "data": null} // Response (complete format) { "context": "nowplayingdetails", "data": { // Core metadata "albumArtist": "Pink Floyd", "genre": "Progressive Rock", "trackNo": "6", "trackCount": "13", "discNo": "2", "discCount": "2", "grouping": "", "publisher": "Columbia Records", "ratingAlbum": "5", "composer": "Roger Waters, David Gilmour", "comment": "", "encoder": "LAME 3.100", // File properties "kind": "FLAC Audio", "format": "FLAC", "size": "45.2 MB", "channels": "2", "sampleRate": "44100", "bitrate": "1411", "duration": "6:22", // Statistics "dateModified": "2024-06-15 14:30:00", "dateAdded": "2020-01-10 09:15:00", "lastPlayed": "2025-01-26 20:45:00", "playCount": "42", "skipCount": "3" } }
Tag Editing v4.0
Modify metadata tags on the currently playing track. Changes are written to the file immediately.
// Edit a tag { "context": "nowplayingtagchange", "data": { "tag": "Genre", "value": "Art Rock" } } // Response (returns updated details) { "context": "nowplayingdetails", "data": { /* updated track details */ } }
| Tag Name | Description | Example Value |
|---|---|---|
TrackTitle | Song title | "Comfortably Numb" |
Artist | Track artist | "Pink Floyd" |
Album | Album name | "The Wall" |
AlbumArtist | Album artist | "Pink Floyd" |
Year | Release year | "1979" |
Genre | Genre | "Progressive Rock" |
TrackNo | Track number | "6" |
TrackCount | Total tracks | "13" |
DiscNo | Disc number | "2" |
DiscCount | Total discs | "2" |
Grouping | Grouping tag | "Classic Albums" |
Publisher | Record label | "Columbia" |
Composer | Composer(s) | "Roger Waters" |
Comment | Comment field | "Remastered" |
Encoder | Encoder info | "LAME 3.100" |
Lyrics | Embedded lyrics | "Hello, is there..." |
RatingAlbum | Album rating | "5" |
Library_CommitTagsToFile. MusicBee panels are refreshed after changes. For rating changes, prefer nowplayingrating which doesn't modify the file.
8. Queue/Playlist Commands #
Get Now Playing List (with Pagination)
// Request { "context": "nowplayinglist", "data": { "offset": 0, "limit": 500 } } // Response { "context": "nowplayinglist", "data": { "playingIndex": 5, // Current track (0-based) "total": 150, "data": [ { "title": "Track Title", "artist": "Artist Name", "album": "Album Name", "path": "/path/to/file.mp3", "position": 1, // 1-based position "duration": 240000 } ] } }
Queue Operations
| Action | Context | Data |
|---|---|---|
| Play at index | nowplayinglistplay | 5 (0-based index) |
| Remove track | nowplayinglistremove | 5 (index) |
| Move track | nowplayinglistmove | {"from": 3, "to": 7} |
| Clear queue | nowplayinglistclear | null |
| Queue next | nowplayingqueuenext | "/path/to/file.mp3" |
| Queue last | nowplayingqueuelast | "/path/to/file.mp3" |
Playlists
// Get all playlists {"context": "playlistlist", "data": null} // Response { "context": "playlistlist", "data": [ { "name": "My Playlist", "url": "MyPlaylist", "trackCount": 25 } ] } // Play a playlist {"context": "playlistplay", "data": "MyPlaylist"}
9. Library Browse Commands #
offset and limit parameters for large libraries.
Browse Genres
// Request { "context": "browsegenres", "data": {"offset": 0, "limit": 100} } // Response item { "genre": "Rock", "count": 250, "artistCount": 35 }
Browse Artists
{
"context": "browseartists",
"data": {"offset": 0, "limit": 100}
}
// Response item
{
"artist": "Artist Name",
"albumCount": 5,
"count": 45
}Browse Albums
{
"context": "browsealbums",
"data": {"offset": 0, "limit": 100}
}
// Response item
{
"album": "Album Title",
"artist": "Artist Name",
"year": "2024",
"count": 12
}Browse Tracks
{
"context": "browsetracks",
"data": {"offset": 0, "limit": 100}
}
// Response item (base fields)
{
"title": "Track Title",
"artist": "Artist Name",
"album": "Album Name",
"genre": "Rock",
"trackno": 5,
"disc": 1,
"src": "/path/to/file.mp3"
}
// Response item (v4.5+ extended metadata)
{
"title": "Track Title",
"artist": "Artist Name",
"album": "Album Name",
"genre": "Rock",
"trackno": 5,
"disc": 1,
"src": "/path/to/file.mp3",
// Extended fields (v4.5+)
"year": "2024",
"rating": "4",
"bitrate": "320",
"format": "MP3",
"playcount": 42,
"skipcount": 3,
"lastplayed": "2025-12-20 14:30:00",
"dateadded": "2024-06-15 09:00:00",
"loved": "L" // L=Loved, B=Banned, ""=Neither
}Extended Metadata Fields (v4.5)
When protocol v4.5 is negotiated, track responses include these additional fields:
| Field | Type | Description |
|---|---|---|
playcount | int | Number of times track has been played |
skipcount | int | Number of times track was skipped |
lastplayed | string | Date/time track was last played (ISO format) |
dateadded | string | Date/time track was added to library |
loved | string | "L" = Loved, "B" = Banned, "" = Neither |
rating | string | Star rating (0-5) |
year | string | Release year |
bitrate | string | Audio bitrate in kbps |
format | string | Audio format (MP3, FLAC, etc.) |
Library Search
| Search Type | Context |
|---|---|
| Search Artists | librarysearchartist |
| Search Albums | librarysearchalbum |
| Search Genres | librarysearchgenre |
| Search Titles | librarysearchtitle |
Search Response Formats
Each search type returns a different data structure:
// Artist Search Response { "context": "librarysearchartist", "data": [ { "artist": "Pink Floyd", "count": 75 // Number of tracks }, { "artist": "Pink Martini", "count": 42 } ] } // Album Search Response { "context": "librarysearchalbum", "data": [ { "album": "The Wall", "artist": "Pink Floyd", "count": 26 // Tracks on album }, { "album": "The Dark Side of the Moon", "artist": "Pink Floyd", "count": 10 } ] } // Get Albums for Specific Artist { "context": "libraryartistalbums", "data": "Pink Floyd" // Exact artist name } // Response { "context": "libraryartistalbums", "data": [ { "album": "The Wall", "artist": "Pink Floyd", "count": 26 }, { "album": "Wish You Were Here", "artist": "Pink Floyd", "count": 5 } ] }
libraryartistalbums with the exact artist name to get albums.
Library Queue Operations
| Action | Context |
|---|---|
| Queue Genre | libraryqueuegenre |
| Queue Artist | libraryqueueartist |
| Queue Album | libraryqueuealbum |
| Queue Track | libraryqueuetrack |
| Play All | libraryplayall |
Common Workflow: Search and Queue Track
This workflow demonstrates searching for a track and adding it to the Now Playing queue:
// Step 1: Search for tracks by title { "context": "librarysearchtitle", "data": { "query": "comfortably numb", "offset": 0, "limit": 50 } } // Response: list of matching tracks with file paths (src field) { "context": "librarysearchtitle", "data": { "total": 3, "data": [ { "title": "Comfortably Numb", "artist": "Pink Floyd", "album": "The Wall", "src": "D:\\Music\\Pink Floyd\\The Wall\\Comfortably Numb.flac" } ] } }
// Step 2a: Add track to END of Now Playing queue { "context": "nowplayingqueuelast", "data": "D:\\Music\\Pink Floyd\\The Wall\\Comfortably Numb.flac" } // OR Step 2b: Add track as NEXT in queue (plays after current track) { "context": "nowplayingqueuenext", "data": "D:\\Music\\Pink Floyd\\The Wall\\Comfortably Numb.flac" } // OR Step 2c: Play NOW (replaces queue and starts playback) { "context": "libraryqueuetrack", "data": "D:\\Music\\Pink Floyd\\The Wall\\Comfortably Numb.flac" }
nowplayingqueuelast- Appends to end of queue, current playback continuesnowplayingqueuenext- Inserts after current track, plays nextlibraryqueuetrack- Replaces queue and starts playing immediately
Set Track Rating v4.5
Set the star rating (0-5) for any library track by file path. Changes are saved to MusicBee immediately.
// Set rating for a track { "context": "librarysetrating", "data": { "path": "D:\\Music\\Pink Floyd\\The Wall\\Comfortably Numb.flac", "rating": "4.0" } } // Response (success) { "context": "librarysetrating", "data": { "success": true, "path": "D:\\Music\\Pink Floyd\\The Wall\\Comfortably Numb.flac", "rating": 4.0 } }
Set Track Love Status v4.5
Set the love/ban status for any library track by file path. This syncs with Last.fm if enabled in MusicBee.
// Love a track { "context": "librarysetlove", "data": { "path": "D:\\Music\\Pink Floyd\\The Wall\\Comfortably Numb.flac", "status": "love" } } // Response (success) { "context": "librarysetlove", "data": { "success": true, "path": "D:\\Music\\Pink Floyd\\The Wall\\Comfortably Numb.flac", "status": "love" } }
| Status Value | MusicBee Effect |
|---|---|
love | Mark as loved (β₯) |
ban | Mark as banned (β) |
normal | Clear love/ban status |
Radio Stations v4.0
Retrieve the list of radio stations configured in MusicBee. Radio stations are internet streams added via File > Add Radio Station URL.
// Request with pagination { "context": "radiostations", "data": { "offset": 0, "limit": 100 } } // Or simple request (returns all) {"context": "radiostations", "data": null}
Response:
{
"context": "radiostations",
"data": {
"offset": 0,
"limit": 100,
"total": 5,
"data": [
{
"name": "BBC Radio 1",
"url": "http://bbcmedia.ic.llnwd.net/stream/bbcmedia_radio1_mf_p"
},
{
"name": "KEXP 90.3 FM",
"url": "http://live-mp3-128.kexp.org/kexp128.mp3"
}
]
}
}| Response Field | Type | Description |
|---|---|---|
offset | integer | Starting index in the full list |
limit | integer | Maximum items requested |
total | integer | Total number of radio stations |
data | array | Array of radio station objects |
data[].name | string | Station name (track title metadata) |
data[].url | string | Stream URL |
libraryqueuetrack with the station URL:
{"context": "libraryqueuetrack", "data": "http://stream.url"}
Album Cover Art v4.0
Retrieve album cover art for any album in the library (not just now playing).
// Request album cover { "context": "libraryalbumcover", "data": { "album": "The Wall", "artist": "Pink Floyd" } } // Response (Base64 JPEG) { "context": "libraryalbumcover", "data": "/9j/4AAQSkZJRgABAQEASABIAAD/..." }
Cover Cache Build Status v4.0
Check if the plugin is currently building the album cover cache. The cache build runs in the background when first started or when manually triggered from plugin settings.
// Request status {"context": "librarycovercachebuildstatus", "data": null} // Response (cache build in progress) {"context": "librarycovercachebuildstatus", "data": true} // Response (cache build complete or idle) {"context": "librarycovercachebuildstatus", "data": false}
libraryalbumcover requests may be slower. Monitor with this command to show a "building cache" indicator in your UI.
10. Advanced Commands (v4.5) #
ARiA - Initiator Actions v4.5
Execute automated action scripts on the server for complex UI operations.
{
"context": "playerinitiatoraction",
"data": "sndKeys(^a);delay(100);sndKeys(^c)"
}
// Supported functions:
// sndKeys(keys) - Send keyboard input (^ = Ctrl, + = Shift, % = Alt)
// delay(ms) - Wait for specified millisecondsTab Navigation (via ARiA)
Tab navigation is done via playerinitiatoraction by sending MusicBee's tab hotkeys:
// Navigate to Tab 1 (requires MusicBee hotkey configured for Ctrl+Alt+A) {"context": "playerinitiatoraction", "data": "sndKeys(^%a)"}
Focus Control Prefixes
ARiA supports focus control prefixes to determine where keystrokes are sent:
// Focus MusicBee before sending keys (default behavior) {"context": "playerinitiatoraction", "data": "sndKeys(!mb!CTRL ALT Q)"} // Send keys to currently focused window (no focus change) {"context": "playerinitiatoraction", "data": "sndKeys(!nofocus!^c)"}
| Prefix | Behavior |
|---|---|
!mb! | Focus MusicBee window before sending keys (default if no prefix) |
!nofocus! | Send keys to currently focused window without changing focus |
Storage Path v4.5
Get MusicBee storage paths for cache validation and finding iTunes XML export. Requires ARiA enabled.
// Request storage paths {"context": "pluginstoragepath", "data": null} // Response (ARiA enabled) { "context": "pluginstoragepath", "data": { "musicBeePath": "C:\\Users\\user\\AppData\\Roaming\\MusicBee", "pluginPath": "C:\\Users\\user\\AppData\\Roaming\\MusicBee\\mb_remote", "itunesXml": "C:\\Users\\user\\Music\\iTunes Music Library.xml", "itunesXmlExists": true } } // Response (ARiA disabled) { "context": "pluginstoragepath", "data": { "error": "Not Enabled", "message": "ARiA must be enabled to access storage paths" } }
Configure ARiA Hotkeys v4.5
Remotely configure MusicBee's tab navigation hotkeys for ARiA. Creates a backup before modifying settings. Requires ARiA enabled.
// Check current hotkey status (read-only) {"context": "configureariahotkeys", "data": {"action": "check"}} // Configure missing hotkeys (modifies MusicBee3Settings.ini) {"context": "configureariahotkeys", "data": {"action": "configure"}} // Response { "context": "configureariahotkeys", "data": { "success": true, "configured": 11, "total": 11, "added": 2, "hotkeys": [ {"name": "Tab1", "isConfigured": true, "hotkey": "Ctrl+Alt+A"}, {"name": "Tab2", "isConfigured": true, "hotkey": "Ctrl+Alt+S"}, // ... Tab3-Tab9 (D,F,G,H,J,K,L) {"name": "VisualiserToggle", "isConfigured": true, "hotkey": "Ctrl+Alt+V"}, {"name": "VisualiserFullscreen", "isConfigured": true, "hotkey": "Ctrl+Alt+Z"} ], "settingsPath": "C:\\...\\MusicBee3Settings.ini", "backupPath": "C:\\...\\MusicBee3Settings.ini.backup_20251227_120000", "message": "Added 2 ARiA hotkeys. Restart MusicBee to apply." } }
Note: MusicBee must be restarted for new hotkeys to take effect.
Visualizers
Control MusicBee's visualizer plugins. Requires Experimental Features enabled in plugin settings.
Get Visualizer List
// Request {"context": "playervisualizerlist", "data": null} // Response (success) { "context": "playervisualizerlist", "data": { "visualizers": ["Spectrum", "Milkdrop 2", "ProjectM"], "defaultVisualizer": "Milkdrop 2", "defaultState": "Normal", "currentState": "Off" } } // Response (experimental features disabled) { "context": "playervisualizerlist", "data": { "error": "Not Enabled", "message": "Experimental features are disabled in plugin settings" } }
Set Visualizer State
// Show visualizer in normal window { "context": "playervisualizer", "data": { "name": "Milkdrop 2", "state": "normal" } } // Show visualizer fullscreen { "context": "playervisualizer", "data": { "name": "Milkdrop 2", "state": "fullscreen" } } // Turn off visualizer { "context": "playervisualizer", "data": { "name": "", "state": "off" } } // Response (success) { "context": "playervisualizer", "data": { "success": true, "message": "Visualizer 'Milkdrop 2' shown in fullscreen state" } }
| State Value | Description |
|---|---|
normal, window | Show in normal window |
fullscreen, full | Show fullscreen |
desktop | Show on desktop (wallpaper mode) |
off, hide, close | Turn off visualizer |
Podcasts (MusicBee 3.1+)
Access podcast subscriptions and episodes. Requires MusicBee 3.1+ (API 51).
Get Podcast Subscriptions
// Request {"context": "podcastsubscriptions", "data": null} // Response (success) { "context": "podcastsubscriptions", "data": { "success": true, "error": null, "total": 3, "subscriptions": [ { "id": "podcast_12345", "title": "My Favorite Podcast", "grouping": "Technology", "genre": "Podcast", "description": "Weekly tech discussions...", "downloadedCount": 5 } ] } } // Response (error - requires MusicBee 3.1+) { "context": "podcastsubscriptions", "data": { "success": false, "error": "Requires MusicBee 3.1+", "subscriptions": [], "total": 0 } }
Get Podcast Episodes
// Request { "context": "podcastepisodes", "data": { "id": "podcast_12345" } } // Response (success) { "context": "podcastepisodes", "data": { "success": true, "error": null, "subscriptionId": "podcast_12345", "total": 25, "episodes": [ { "id": "episode_001", "subscriptionId": "podcast_12345", "title": "Episode 101: Latest News", "dateTime": "2025-01-15 10:00:00", "description": "In this episode we discuss...", "duration": "01:15:30", "isDownloaded": true, "hasBeenPlayed": false, "url": "https://example.com/podcast/episode101.mp3" } ] } }
Play Podcast Episode
// Request { "context": "podcastplay", "data": { "id": "https://example.com/podcast/episode101.mp3" } } // Response { "context": "podcastplay", "data": { "success": true, "message": "Playing episode", "url": "https://example.com/podcast/episode101.mp3" } }
Output Devices v4.0
Get available audio output devices and switch between them.
Get Output Device List
// Request {"context": "playeroutput", "data": null} // Response { "context": "playeroutput", "data": { "devices": [ { "name": "Speakers (Realtek Audio)", "active": true }, { "name": "Headphones (USB Audio)", "active": false }, { "name": "Digital Output (SPDIF)", "active": false } ] } }
Switch Output Device
// Request (switch to device by name) { "context": "playeroutputswitch", "data": "Headphones (USB Audio)" } // Response { "context": "playeroutputswitch", "data": { "success": true } }
Library Subscriptions v4.5
Subscribe to real-time library change notifications. The client registers for events and the plugin pushes updates as they happen - no polling required.
| Command | Direction | Description |
|---|---|---|
librarysubscribe | Client β Plugin | Subscribe to library change events |
libraryunsubscribe | Client β Plugin | Unsubscribe from library events |
librarytagchanged | Plugin β Client | Track metadata changed |
libraryfileadded | Plugin β Client | New file added to library |
libraryfiledeleted | Plugin β Client | File removed from library |
libraryratingchanged | Plugin β Client | Track rating changed |
libraryplaycountchanged | Plugin β Client | Play count or last played updated |
// Subscribe to library events {"context": "librarysubscribe", "data": null} // Example: Rating changed notification (Plugin β Client) { "context": "libraryratingchanged", "data": { "src": "C:\\Music\\track.mp3", "rating": "4" } }
11. Server Push Events #
The MBRC protocol is push-first - the server actively broadcasts state changes to all connected clients. Clients don't need to poll for updates; they receive notifications automatically when player state, track info, or other data changes.
Push Event Architecture
MusicBee Plugin Clients
| | |
|-- Track Changed --------> | |
| |-- nowplayingtrack -------> | (broadcast)
| |-- nowplayingcover -------> | (broadcast)
| |-- nowplayinglyrics ------> | (broadcast)
| | |
|-- Volume Changed -------> | |
| |-- playerstate -----------> | (broadcast)
| | |
|-- Play/Pause -----------> | |
| |-- playerstate -----------> | (broadcast)
| | |
|-- Queue Modified -------> | |
| |-- nowplayinglistchanged -> | (broadcast)
Player State Events
These events are broadcast when player state changes (play/pause/stop, volume, shuffle, repeat, etc.):
// Player state change (play/pause/stop, shuffle, repeat, scrobble) { "context": "playerstate", "data": { "state": "playing", // "playing", "paused", "stopped" "shuffle": false, "repeat": "none", // "none", "all", "one" "scrobble": true, "mute": false, "volume": 75 } } // Volume change (standalone event) { "context": "playervolume", "data": 75 } // Mute change (standalone event) { "context": "playermute", "data": true } // Repeat mode change { "context": "playerrepeat", "data": "all" } // Shuffle change { "context": "playershuffle", "data": true }
Track Change Events
These events are broadcast when the currently playing track changes:
// Now playing track changed { "context": "nowplayingtrack", "data": { "artist": "Pink Floyd", "album": "The Wall", "albumArtist": "Pink Floyd", "title": "Comfortably Numb", "year": "1979", "genre": "Progressive Rock", "path": "D:\\Music\\Pink Floyd\\The Wall\\Comfortably Numb.flac" } } // Cover art changed (Base64 JPEG) { "context": "nowplayingcover", "data": "/9j/4AAQSkZJRgABAQEASABIAAD/..." } // Lyrics changed { "context": "nowplayinglyrics", "data": { "status": 200, "lyrics": "Hello, is there anybody in there..." } } // Rating changed { "context": "nowplayingrating", "data": "4.5" } // Love status changed { "context": "nowplayinglfmrating", "data": "love" }
Playback Position Events
Position updates are sent periodically (every 20 seconds) and on seek:
// Position update { "context": "nowplayingposition", "data": { "current": 125000, // Current position in milliseconds "total": 382000 // Total duration in milliseconds } }
Queue Change Events
Broadcast when the Now Playing queue is modified:
// Now Playing list changed (tracks added, removed, or reordered) { "context": "nowplayinglistchanged", "data": true } // Recommended client action: re-fetch the now playing list {"context": "nowplayinglist", "data": {"offset": 0, "limit": 500}}
Playlist Change Events (MusicBee 3.1+)
These events are broadcast when playlists are created, modified, or deleted. Requires MusicBee 3.1+ (API 49).
// Playlist created { "context": "playlistcreated", "data": { "name": "New Playlist", "url": "NewPlaylist" } } // Playlist updated { "context": "playlistupdated", "data": { "name": "Updated Playlist", "url": "UpdatedPlaylist" } } // Playlist deleted { "context": "playlistdeleted", "data": { "name": "Deleted Playlist", "url": "DeletedPlaylist" } }
Library Change Events (v4.5 Subscriptions)
These events are broadcast to clients that have subscribed via librarysubscribe:
// Tag/metadata changed { "context": "librarytagchanged", "data": { "path": "D:\\Music\\track.mp3", "eventType": "tagchanged", "artist": "Artist Name", "title": "Track Title", // ... full metadata if includeMetadata=true } } // File added to library { "context": "libraryfileadded", "data": { "path": "D:\\Music\\new_track.mp3", "eventType": "fileadded" } } // File removed from library { "context": "libraryfiledeleted", "data": { "path": "D:\\Music\\removed_track.mp3", "eventType": "filedeleted" } } // Rating changed in library { "context": "libraryratingchanged", "data": { "path": "D:\\Music\\track.mp3", "eventType": "ratingchanged", "rating": "4" } } // Play count changed { "context": "libraryplaycountchanged", "data": { "path": "D:\\Music\\track.mp3", "eventType": "playcountchanged", "playCount": 43 } }
Push Event Summary Table
| Event Context | Trigger | Subscription Required |
|---|---|---|
playerstate | Play/pause/stop, shuffle, repeat changes | No (always broadcast) |
playervolume | Volume change | No |
playermute | Mute toggle | No |
playerrepeat | Repeat mode change | No |
playershuffle | Shuffle toggle | No |
nowplayingtrack | Track change | No |
nowplayingcover | Track change (cover art) | No |
nowplayinglyrics | Track change (lyrics) | No |
nowplayingrating | Current track rating change | No |
nowplayinglfmrating | Love/ban status change | No |
nowplayingposition | Periodic (20s) + seek | No |
nowplayinglistchanged | Queue modification | No |
playlistcreated | Playlist created (MB 3.1+) | No |
playlistupdated | Playlist modified (MB 3.1+) | No |
playlistdeleted | Playlist deleted (MB 3.1+) | No |
librarytagchanged | Track metadata changed | Yes (librarysubscribe) |
libraryfileadded | File added to library | Yes |
libraryfiledeleted | File removed from library | Yes |
libraryratingchanged | Track rating changed | Yes |
libraryplaycountchanged | Play count updated | Yes |
12. Serialization #
Message Class Structure
// C# example
public class SocketMessage
{
[JsonProperty("context")]
public string Context { get; set; }
[JsonProperty("data")]
public object? Data { get; set; }
public string ToJson() => JsonConvert.SerializeObject(this);
public static SocketMessage FromJson(string json)
=> JsonConvert.DeserializeObject<SocketMessage>(json);
}Field Name Flexibility
Handle multiple field name formats for compatibility with different plugin versions:
// The same field may appear as any of these: albumArtist | album_artist | AlbumArtist playCount | play_count | PlayCount trackNo | track_no | track // Defensive parsing example: var artist = item["artist"] ?? item["Artist"]; var trackNo = item["trackNo"] ?? item["track_no"] ?? item["track"];
13. Networking Layer #
Connection Architecture
+------------------+
| RemoteController | High-level command interface
+--------+---------+
|
+--------v---------+
| SocketClient | TCP connection, heartbeat, auto-reconnect
+--------+---------+
|
+--------v---------+
| Message Pipeline |
| +-------------+ |
| | Handler | | JSON parsing, event dispatch
| +-------------+ |
| | Correlator | | Request-response matching
| +-------------+ |
| | Chunked | | Large payload assembly
| +-------------+ |
+--------+---------+
|
+--------v---------+
| Network Layer | TCP Sockets, StreamReader/Writer
+------------------+
Connection State Machine
βββββββββββββββββββββββββββββββββββββββββββββββ
β β
βΌ β
ββββββββββββββββ connect() ββββββββββββββββββ β
β DISCONNECTED βββββββββββββββ>β CONNECTING β β
ββββββββββββββββ βββββββββ¬βββββββββ β
β² β β
β connected β
β β β
β βΌ β
β ββββββββββββββββββ handshake ok β
β β AUTHENTICATING ββββββββββββββββββ β
β βββββββββ¬βββββββββ β β
β β β β
β handshake β β
β failed β β
β β βΌ β
β β βββββββββββββββββββββββββββ
β β β CONNECTED ββ
β β β (fully operational) ββ
β β ββββββββββββββ¬βββββββββββββ
β β β β
β β disconnect/ β
β β error β
β β β β
β βΌ βΌ β
β max retries ββββββββββββββββββββββββββββββββββββββ β
β exceeded β RECONNECTING β β
β<ββββββββββββββββ (exponential backoff active) βββββββββ
β ββββββββββββββββββββββββββββββββββββββ retry
β
β user stop or
β fatal error
βΌ
ββββββββββββββββ
β ERROR β (connection permanently failed)
ββββββββββββββββ
| State | Description | Actions Available |
|---|---|---|
DISCONNECTED | No active connection | connect() |
CONNECTING | TCP connection establishing | Wait or cancel() |
AUTHENTICATING | Handshake in progress (playerβprotocolβinit) | Wait |
CONNECTED | Fully operational, commands accepted | All commands, disconnect() |
RECONNECTING | Auto-reconnection with backoff | cancel(), wait |
ERROR | Fatal error, manual intervention required | connect(), reset() |
State Transition Events
| From | Event | To |
|---|---|---|
| DISCONNECTED | User calls connect() | CONNECTING |
| CONNECTING | TCP connected | AUTHENTICATING |
| CONNECTING | TCP timeout/refused | RECONNECTING |
| AUTHENTICATING | Handshake complete | CONNECTED |
| AUTHENTICATING | Handshake failed/timeout | RECONNECTING |
| CONNECTED | Socket error/pong timeout | RECONNECTING |
| CONNECTED | User calls disconnect() | DISCONNECTED |
| RECONNECTING | Retry succeeds | AUTHENTICATING |
| RECONNECTING | Max retries exceeded | DISCONNECTED or ERROR |
Connection Health Metrics
Connection Quality Thresholds: Excellent: RTT < 50ms, Loss < 1% Good: RTT < 150ms, Loss < 5% Fair: RTT < 300ms, Loss < 10% Poor: RTT > 300ms or Loss > 10%
14. Reconnection Strategy #
Exponential Backoff
Attempt 1: 1 second Attempt 2: 2 seconds Attempt 3: 4 seconds Attempt 4: 8 seconds Attempt 5: 16 seconds Attempt 6+: 30 seconds (maximum) Maximum attempts: 10
Command Queueing During Disconnection
| Property | Value |
|---|---|
| Max Queue Size | 100 commands |
| Expiration | 5 minutes |
| Processing Delay | 100ms between commands |
Commands NOT Queued (Real-time only)
ping,ponginit,protocolverifyconnection
Commands Always Queued (Important)
playerplay,playerpause,playerstopplayernext,playerpreviousplayervolumeplaylistplay
15. Error Handling #
Error Response Format
{
"context": "error",
"data": "Error description message"
}Common Error Scenarios
| Scenario | Recovery |
|---|---|
| Connection timeout | Automatic reconnect with backoff |
| Invalid JSON | Log and skip message |
| Unknown command | Server ignores, no response |
| Handshake failure | Disconnect and retry |
| Heartbeat timeout | Trigger reconnection |
Error Response Types
| Context | Data | Meaning | Client Action |
|---|---|---|---|
error |
String message | Generic error occurred | Log and continue |
notallowed |
Empty string | Connection rejected (IP filtering) | Abort connection, notify user |
Command-Specific Error Responses
| Command | Error Condition | Response |
|---|---|---|
pluginstoragepath |
ARiA not enabled | {"error": "Not Enabled", "message": "ARiA must be enabled to access storage paths"} |
librarysetrating |
Track not found | {"success": false, "error": "Track not found"} |
librarysetlove |
Track not found | {"success": false, "error": "Track not found"} |
playerinitiatoraction |
ARiA not enabled | {"success": false, "error": "ARiA is not enabled"} |
playerinitiatoraction |
Unknown action | {"success": false, "error": "Unknown action: xyz"} |
configureariahotkeys |
ARiA not enabled | {"success": false, "error": "ARiA is not enabled"} |
UDP Discovery Errors
| Error | Context | Response |
|---|---|---|
| Missing client address | error |
{"context": "error", "description": "missing address"} |
| Unknown action | error |
{"context": "error", "description": "unsupported action"} |
Success Response Pattern
Commands that modify state typically return a success indicator:
// Success {"context": "librarysetrating", "data": {"success": true}} // Failure {"context": "librarysetrating", "data": {"success": false, "error": "Track not found"}}
16. IP Filtering & Security #
The plugin supports IP-based connection filtering to control which clients can connect. This is configured in the plugin settings.
Filter Modes
| Mode | Behavior |
|---|---|
All | Accept connections from any IP address (default) |
Specific | Only allow IPs in an explicit whitelist |
Range | Allow IPs within a defined range (same /24 subnet, last octet range) |
Loopback Always Allowed
Connections from 127.0.0.1 (localhost) are always permitted regardless of filter settings. This ensures local debugging and testing always works.
Specific IP Mode
When set to Specific, only IP addresses in the configured list are allowed:
Allowed IPs: ["192.168.1.50", "192.168.1.51", "192.168.1.100"] Connection from 192.168.1.50 β Allowed Connection from 192.168.1.99 β Rejected
Range Mode
When set to Range, IPs are validated against a base IP and maximum last octet:
Base IP: 192.168.1.10 Last Octet Max: 50 Connection from 192.168.1.25 β Allowed (25 is between 10 and 50) Connection from 192.168.1.5 β Rejected (5 < 10) Connection from 192.168.1.75 β Rejected (75 > 50) Connection from 192.168.2.25 β Rejected (different subnet)
Range Validation Rules
- First three octets must match the base IP (same /24 subnet)
- Last octet must be β₯ base IP's last octet
- Last octet must be β€ configured maximum
- Maximum last octet must be 1-254 (valid host range)
Connection Rejection
When a connection is rejected due to IP filtering:
- The TCP connection is accepted (for the rejection message)
- Server sends:
{"context":"notallowed","data":""} - Connection is immediately closed by server
// Rejection message (received immediately after connect, before handshake) {"context": "notallowed", "data": ""} // Client handling pseudocode on_message_received(msg): if msg.context == "notallowed": show_error("Connection rejected - your IP is not allowed") disconnect() // Do NOT attempt automatic reconnection // This requires user/admin action to resolve
Client Handling Recommendations
| Scenario | Recommended Action |
|---|---|
Receive notallowed | Show error, stop reconnection attempts |
Connection drops after notallowed | Normal - server closes immediately |
| Timeout after connect | Different issue - may be blocked by firewall |
17. Implementation Checklist #
- TCP socket with proper timeouts (receive: 30s, send: 5s)
- NoDelay enabled (disable Nagle algorithm)
- UTF-8 encoding without BOM
- CRLF (
\r\n) line termination - Ordered handshake: player -> protocol -> init
- Background receive thread with cleanup
- Thread-safe message dispatch (UI updates on main thread)
- Heartbeat mechanism (30s interval, 90s timeout)
- Auto-reconnection with exponential backoff
- Command queue for offline buffering
- Connection state machine
- Message handler/dispatcher by context
- Flexible field name parsing (camelCase/snake_case)
- Error handling and recovery
- Connection health monitoring
- Receive buffer β₯1MB for large payloads (cover art)
18. Code Examples #
Minimal C# Client
using System.Net.Sockets;
using System.Text;
using Newtonsoft.Json;
public class MbrcClient
{
private TcpClient _client;
private StreamWriter _writer;
private StreamReader _reader;
public async Task ConnectAsync(string host, int port = 5900)
{
_client = new TcpClient { NoDelay = true };
await _client.ConnectAsync(host, port);
var stream = _client.GetStream();
var utf8NoBom = new UTF8Encoding(false);
_writer = new StreamWriter(stream, utf8NoBom) { NewLine = "\r\n", AutoFlush = true };
_reader = new StreamReader(stream, utf8NoBom);
// Handshake
await SendAsync("player", "android");
await SendAsync("protocol", new { protocol_version = 4, no_broadcast = false });
await SendAsync("init", null);
// Start receive loop
_ = Task.Run(ReceiveLoop);
}
public async Task SendAsync(string context, object data)
{
var msg = JsonConvert.SerializeObject(new { context, data });
await _writer.WriteLineAsync(msg);
}
private async Task ReceiveLoop()
{
while (_client.Connected)
{
var line = await _reader.ReadLineAsync();
if (line != null)
{
ProcessMessage(line);
}
}
}
private void ProcessMessage(string json)
{
var msg = JsonConvert.DeserializeObject<dynamic>(json);
string context = msg.context;
switch (context)
{
case "nowplayingtrack":
Console.WriteLine($"Now Playing: {msg.data.artist} - {msg.data.title}");
break;
case "pong":
// Heartbeat response
break;
// Handle other messages...
}
}
}Python Client Example
import socket
import json
import threading
class MbrcClient:
def __init__(self, host, port=5900):
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
self.sock.connect((host, port))
def send(self, context, data=None):
msg = json.dumps({"context": context, "data": data})
self.sock.sendall((msg + "\r\n").encode('utf-8'))
def handshake(self):
self.send("player", "android")
self.send("protocol", {"protocol_version": 4, "no_broadcast": False})
self.send("init")
def receive_loop(self):
buffer = ""
while True:
data = self.sock.recv(4096).decode('utf-8')
buffer += data
while "\r\n" in buffer:
line, buffer = buffer.split("\r\n", 1)
msg = json.loads(line)
self.handle_message(msg)
def handle_message(self, msg):
context = msg.get("context")
data = msg.get("data")
if context == "nowplayingtrack":
print(f"Now Playing: {data['artist']} - {data['title']}")
elif context == "pong":
pass # Heartbeat
# Usage
client = MbrcClient("192.168.1.100")
client.handshake()
threading.Thread(target=client.receive_loop, daemon=True).start()
client.send("playerplaypause")Complete Command Reference (73 Commands)
Commands organized by protocol version. All commands from earlier versions remain available in later versions.
Protocol 2.0 - Base Commands (41)
| # | Context | Description | Data |
|---|---|---|---|
| Connection & System | |||
| 1 | error | Error response from server | Error message string |
| 2 | player | Client identification (handshake packet 0) | "android" |
| 3 | protocol | Protocol negotiation (handshake packet 1) | {"protocol_version": 4, "no_broadcast": false} |
| 4 | pluginversion | Request plugin version | null |
| 4b | plugininstanceid | Request unique plugin instance ID (v1.5.25+) | null |
| 5 | notallowed | Action not permitted response | Reason string |
| Player Controls | |||
| 6 | playerstatus | Request player state | null |
| 7 | playerstate | Player state response/notification | {"state": "playing", ...} |
| 8 | playerplaypause | Toggle play/pause | null |
| 9 | playerprevious | Previous track | null |
| 10 | playernext | Next track | null |
| 11 | playerstop | Stop playback | null |
| 12 | playervolume | Set/get volume | "75", "+5", "-5" (string) |
| 13 | playerrepeat | Set repeat mode | "none", "all", "one", "toggle" |
| 14 | playershuffle | Toggle/set shuffle | "toggle" or true/false |
| 15 | playermute | Toggle/set mute | "toggle", "on", "off" |
| 16 | scrobbler | Toggle/set scrobbling | "toggle" or true/false |
| 17 | playerautodj | Enable/disable Auto DJ | true or false |
| Now Playing | |||
| 18 | nowplayingtrack | Get current track info | null |
| 19 | nowplayingcover | Get album art (Base64) | null |
| 20 | nowplayingposition | Get/set playback position | null or milliseconds |
| 21 | nowplayinglyrics | Get track lyrics | null |
| 22 | nowplayingrating | Get/set track rating | "-1" (get) or "4.5" (set) |
| 23 | nowplayinglfmrating | Get/set Last.fm love/ban | "-1", "love", "ban", "normal" |
| 24 | nowplayinglist | Get queue with pagination | {"offset": 0, "limit": 500} |
| 25 | nowplayinglistchanged | Queue changed notification | Server push notification |
| 26 | nowplayinglistplay | Play track at index | 5 (0-based index) |
| 27 | nowplayinglistremove | Remove track from queue | 5 (index) |
| 28 | nowplayinglistmove | Move track in queue | {"from": 3, "to": 7} |
| 29 | nowplayinglistsearch | Search within queue | {"query": "search term"} |
| Library Search | |||
| 30 | librarysearchartist | Search artists | {"query": "beatles", "offset": 0, "limit": 100} |
| 31 | librarysearchalbum | Search albums | {"query": "abbey", "offset": 0, "limit": 100} |
| 32 | librarysearchgenre | Search genres | {"query": "rock", "offset": 0, "limit": 100} |
| 33 | librarysearchtitle | Search track titles | {"query": "yesterday", "offset": 0, "limit": 100} |
| Library Navigation | |||
| 34 | libraryartistalbums | Get albums by artist | {"artist": "The Beatles"} |
| 35 | librarygenreartists | Get artists in genre | {"genre": "Rock"} |
| 36 | libraryalbumtracks | Get tracks in album | {"album": "Abbey Road", "artist": "The Beatles"} |
| Library Queue | |||
| 37 | libraryqueuegenre | Queue all in genre | "Rock" |
| 38 | libraryqueueartist | Queue all by artist | "The Beatles" |
| 39 | libraryqueuealbum | Queue all in album | {"album": "Abbey Road", "artist": "The Beatles"} |
| 40 | libraryqueuetrack | Queue single track | "/path/to/file.mp3" |
| Playlists | |||
| 41 | playlistlist | Get all playlists | null |
Protocol 2.1 - Additions (5)
| # | Context | Description | Data |
|---|---|---|---|
| 42 | ping | Heartbeat request | null |
| 43 | pong | Heartbeat response | null |
| 44 | init | Request initial state (handshake packet 2) | null |
| 45 | playerplay | Start playback | null |
| 46 | playerpause | Pause playback | null |
Protocol 3.0 - Additions (7)
| # | Context | Description | Data |
|---|---|---|---|
| 47 | playlistplay | Play a playlist (replaces now playing) | "MyPlaylist" |
| 47b | playlistqueue | Queue a playlist (appends to now playing) | "MyPlaylist" |
| 48 | nobroadcast | Disable UDP broadcast for this client | true |
| 49 | browsegenres | Browse all genres | {"offset": 0, "limit": 100} |
| 50 | browseartists | Browse all artists | {"offset": 0, "limit": 100} |
| 51 | browsealbums | Browse all albums | {"offset": 0, "limit": 100} |
| 52 | browsetracks | Browse all tracks | {"offset": 0, "limit": 100} |
| 53 | nowplayingqueue | Queue track (next/last) | {"path": "/path/to/file.mp3", "type": "next"} |
Protocol 4.0 - MusicBee 3.0 SDK (9)
| # | Context | Description | Data |
|---|---|---|---|
| 54 | playeroutput | Get output devices list | null |
| 55 | playeroutputswitch | Switch output device | {"deviceId": "..."} |
| 56 | verifyconnection | Verify connection is alive | null |
| 57 | radiostations | Get radio stations | null |
| 58 | nowplayingdetails | Get extended track details | null |
| 59 | nowplayingtagchange | Modify track tags | {"tag": "rating", "value": "5"} |
| 60 | libraryplayall | Play entire library | null |
| 61 | libraryalbumcover | Get album cover art | {"album": "Abbey Road", "artist": "The Beatles"} |
| 62 | librarycovercachebuildstatus | Get cover cache build progress | null |
Protocol 4.5 - ARiA Extension (3) - Requires ARiA Enabled
| # | Context | Description | Data |
|---|---|---|---|
| 63 | playerinitiatoraction | Execute ARiA action script | "sndKeys(^a);delay(100);sndKeys(^c)" |
| 64 | pluginstoragepath | Get MusicBee storage paths | null |
| 65 | configureariahotkeys | Configure ARiA tab hotkeys in MusicBee settings | {"action": "check"} or {"action": "configure"} |
Protocol 4.5 - Visualizers (2) - Requires Experimental Features Enabled
| # | Context | Description | Data |
|---|---|---|---|
| 64 | playervisualizer | Set visualizer state | {"name": "Milkdrop", "state": "fullscreen"} |
| 65 | playervisualizerlist | Get available visualizers | null |
Protocol 4.5 - MusicBee 3.1 SDK Features (8) - Requires MB 3.1+
| # | Context | Description | Data |
|---|---|---|---|
| Album Navigation (API 50) | |||
| 66 | playerpreviousalbum | Skip to previous album | null |
| 67 | playernextalbum | Skip to next album | null |
| Podcasts (API 51) | |||
| 68 | podcastsubscriptions | Get podcast subscriptions | null |
| 69 | podcastepisodes | Get episodes for podcast | {"id": "podcast_id"} |
| 70 | podcastplay | Play podcast episode | {"id": "episode_id"} |
| Playlist Notifications (API 49+) | |||
| 71 | playlistcreated | Playlist created notification | Server push notification |
| 72 | playlistupdated | Playlist updated notification | Server push notification |
| 73 | playlistdeleted | Playlist deleted notification | Server push notification |
Protocol 4.5 - Two-Way Sync (2) - Library Metadata Write-Back
| # | Context | Description | Data |
|---|---|---|---|
| Rating & Love Status Sync | |||
| 74 | librarysetrating | Set track star rating (0-5) | {"path": "/path/to/file.mp3", "rating": "4.0"} |
| 75 | librarysetlove | Set track love/ban status | {"path": "/path/to/file.mp3", "status": "love"} |
librarysetrating- Rating values: 0 (no rating) to 5 (5 stars), decimals supportedlibrarysetlove- Status values:love,ban, ornormal
Protocol 4.5 - Real-time Library Subscriptions (7)
β Plugin Ready (v1.5.25+): Server-side implementation complete. Subscribe to receive push notifications when library data changes in MusicBee.
| # | Context | Direction | Description | Data |
|---|---|---|---|---|
| Subscription Management | ||||
| 76 | librarysubscribe | Client β Plugin | Subscribe to library events | {"eventTypes": ["tagchanged", "fileadded", ...], "includeMetadata": true} |
| 77 | libraryunsubscribe | Client β Plugin | Unsubscribe from library events | {} (empty object) |
| Event Notifications (Push from Server) | ||||
| 78 | librarytagchanged | Plugin β Client | Track metadata changed | See event payload below |
| 79 | libraryfileadded | Plugin β Client | New file added to library | See event payload below |
| 80 | libraryfiledeleted | Plugin β Client | File removed from library | See event payload below |
| 81 | libraryratingchanged | Plugin β Client | Track rating changed | See event payload below |
| 82 | libraryplaycountchanged | Plugin β Client | Play count updated | See event payload below |
Subscribe Request
{
"context": "librarysubscribe",
"data": {
"eventTypes": ["tagchanged", "fileadded", "filedeleted", "ratingchanged", "playcountchanged"],
"includeMetadata": true
}
}Parameters:
eventTypes(optional): Array of event types to subscribe to. Omit or passnullto subscribe to all events.includeMetadata(optional, default:true): Iftrue, event payloads include full track metadata. Iffalse, only file path and event type are sent.
Subscribe Response
{
"context": "librarysubscribe",
"data": {
"success": true,
"subscribedTo": ["tagchanged", "fileadded", "filedeleted", "ratingchanged", "playcountchanged"],
"includeMetadata": true
}
}Event Payload (with metadata)
{
"context": "librarytagchanged", // or libraryfileadded, libraryratingchanged, etc.
"data": {
"path": "D:\\Music\\Artist\\Album\\Track.mp3",
"eventType": "tagchanged",
"artist": "Artist Name",
"album": "Album Name",
"albumArtist": "Album Artist",
"title": "Track Title",
"genre": "Rock",
"year": "2024",
"rating": "4.0",
"trackNo": "5",
"discNo": "1",
"loved": "L", // "L" = Loved, "" = Not loved
"additional": null
}
}Event Payload (without metadata)
{
"context": "librarytagchanged",
"data": {
"path": "D:\\Music\\Artist\\Album\\Track.mp3",
"eventType": "tagchanged",
"additional": null
}
}- Client sends
librarysubscribeafter connection to opt-in - Plugin broadcasts events only to subscribed clients
- Events include file path for SQL cache lookup/update
- Set
includeMetadata: trueto receive full track data (for cache inserts) - Subscriptions are automatically cleaned up when client disconnects
- Available event types:
tagchanged,fileadded,filedeleted,ratingchanged,playcountchanged
19. Protocol Testing & Compatibility #
MBXRemote Protocol Tester (Developer Tool)
MBXRemote includes a hidden protocol compatibility tester for developers. This tool validates plugin responses, detects new fields, and generates compatibility reports.
Ctrl+Shift+Alt+T in MBXRemote to open the Protocol Tester dialog.
Test Coverage
- Handshake and protocol version negotiation
- Browse endpoints:
browseartists,browsegenres,browsealbums,browsetracks - Player status and state
- Now playing info and list
- Playlist and podcast endpoints
- Field validation (expected vs actual)
- Detection of additive fields in v1.5.x responses
Test Report Output
=== MBRC PROTOCOL COMPATIBILITY TEST REPORT === Plugin Version: 1.5.25.1226 Negotiated Protocol: 4.5 (client requested 4.5) Results: 10/10 passed, 0 failed ADDITIVE FIELDS (v1.5.x): - AlbumCount (browseartists) - always sent - ArtistCount (browsegenres) - always sent - year, rating, bitrate, format, playcount, skipcount, lastplayed, dateadded, loved (browsetracks) - v4.5 only Backward Compatibility: OK (protocol negotiation active)
Additive Fields (v1.5.x)
Plugin v1.5.x adds new fields to some responses. Protocol negotiation ensures backward compatibility:
| Endpoint | New Field | Notes |
|---|---|---|
browseartists | AlbumCount | Always sent - additive field, ignored by most parsers |
browsegenres | ArtistCount | Always sent - additive field, ignored by most parsers |
browsetracks | year, rating, bitrate, format, playcount, skipcount, lastplayed, dateadded, loved | Only sent when client requests v4.5+ (MBX-SQL Bridge metadata) |
AlbumCount and ArtistCount fields are safe.
Testing with Original Android App (kelsos/mbrc)
To verify backwards compatibility with the original MusicBee Remote Android app:
- Install: Download "MusicBee Remote" from mbrc.kelsos.net (no longer on Play Store)
- Configure: Open app Settings > Manual connection > Enter MusicBee host IP and port (default: 3000)
- Connect: Tap Connect and verify connection succeeds
- Test Library Browse:
- Navigate to Library tab
- Tap Artists - verify list loads without crash
- Tap Genres - verify list loads without crash
- These endpoints have new fields (
AlbumCount,ArtistCount)
- Test Playback: Play/pause, next/previous, volume - verify all work
- Test Now Playing: Verify track info, cover art, and queue display correctly
Expected Results
| Feature | Expected Behavior |
|---|---|
| Artist list | Shows artist names (AlbumCount field ignored) |
| Genre list | Shows genre names (ArtistCount field ignored) |
| Track list | Normal display (legacy clients request v4, no extended fields) |
| Playback controls | Full functionality |
| Queue management | Full functionality |
Testing with iOS App
iOS MusicBee Remote clients exist but additional testing is needed for full compatibility verification.
Codable with strict decoding may fail on unknown fields (AlbumCount, ArtistCount). Protocol negotiation protects against extended track metadata fields since legacy clients request v4 and receive v4.0 responses without them.
Raw Protocol Testing
For manual testing without a client application:
PowerShell Test Script
# Connect to MusicBee Remote plugin $client = New-Object System.Net.Sockets.TcpClient("localhost", 3000) $stream = $client.GetStream() $writer = New-Object System.IO.StreamWriter($stream) $reader = New-Object System.IO.StreamReader($stream) $writer.AutoFlush = $true # Handshake $writer.WriteLine('{"context":"player","data":"android"}') Start-Sleep -Milliseconds 100 $writer.WriteLine('{"context":"protocol","data":{"protocol_version":4}}') Start-Sleep -Milliseconds 100 # Read protocol response $response = $reader.ReadLine() Write-Host "Protocol response: $response" # Test browse artists $writer.WriteLine('{"context":"browseartists","data":{"offset":0,"limit":5}}') Start-Sleep -Milliseconds 500 $response = $reader.ReadLine() Write-Host "Artists response: $($response.Substring(0, [Math]::Min(500, $response.Length)))..." # Cleanup $client.Close()
netcat (Linux/macOS)
# Connect and send handshake
(echo '{"context":"player","data":"android"}'; \
echo '{"context":"protocol","data":{"protocol_version":4}}'; \
echo '{"context":"browseartists","data":{"offset":0,"limit":5}}'; \
sleep 2) | nc localhost 3000Compatibility Matrix
This applies to the additive fields (AlbumCount, ArtistCount) which are always sent. Extended track metadata fields are protected by protocol negotiation.
| Client Type | JSON Parser | Unknown Field Handling | Compatibility |
|---|---|---|---|
| Android (Kotlin) | Gson | Ignored by default | Compatible |
| Android (Kotlin) | Moshi (strict) | May fail | Check config |
| iOS (Swift) | JSONDecoder | Fails without CodingKeys | Check impl |
| Python | json module | Ignored | Compatible |
| JavaScript | JSON.parse() | Ignored | Compatible |
| C# (.NET) | Newtonsoft.Json | Ignored by default | Compatible |
| C# (.NET) | System.Text.Json | Configurable | Check config |
Appendix A: Data Types Reference #
This appendix provides detailed reference for all data types used in the protocol.
Track Object
Represents a music track. Field availability varies by context (now playing vs library browse).
| Field | Type | Context | Description |
|---|---|---|---|
artist | string | All | Track artist |
album | string | All | Album name |
albumArtist / album_artist | string | All | Album artist (may differ from track artist) |
title | string | All | Track title |
path | string | All | File path (Windows format) |
year | string | v4.5 | Release year |
genre | string | v4.5 | Genre |
trackNo / track_no | string | v4.5 | Track number |
discNo / disc_no | string | v4.5 | Disc number |
rating | string | v4.5 | Star rating (0-5, decimals allowed) |
bitrate | string | v4.5 | Bitrate in kbps |
format | string | v4.5 | Audio format (MP3, FLAC, etc.) |
sampleRate | string | Details | Sample rate in Hz |
duration | integer | Details | Duration in milliseconds |
playcount | string | v4.5 | Number of plays |
skipcount | string | v4.5 | Number of skips |
lastplayed | string | v4.5 | Last played datetime |
dateadded | string | v4.5 | Date added to library |
loved | string | v4.5 | "L" = loved, "" = not loved |
position | integer | Queue | Position in now playing queue (0-based) |
Now Playing List Item
{
"artist": "Pink Floyd",
"album": "The Wall",
"album_artist": "Pink Floyd",
"title": "Comfortably Numb",
"path": "D:\\Music\\Pink Floyd\\The Wall\\06 - Comfortably Numb.flac",
"position": 5
}Page Wrapper
All paginated responses use this structure:
{
"context": "browseartists",
"data": {
"data": [/* array of items */],
"offset": 0, // Starting position
"limit": 100, // Items per page
"total": 1500 // Total items available
}
}Artist Object
{
"artist": "Pink Floyd",
"AlbumCount": 15 // v1.5.x additive field
}Album Object
{
"album": "The Wall",
"artist": "Pink Floyd"
}Genre Object
{
"genre": "Progressive Rock",
"ArtistCount": 42 // v1.5.x additive field
}Playlist Object
{
"name": "My Favorites",
"url": "MyFavorites"
}Radio Station Object
{
"name": "BBC Radio 1",
"url": "http://stream.bbc.co.uk/radio1"
}Player State Values
| Field | Values | Description |
|---|---|---|
state | "playing", "paused", "stopped" | Playback state |
repeat | "none", "all", "one" | Repeat mode |
shuffle | true, false | Shuffle state |
mute | true, false | Mute state |
scrobble | true, false | Last.fm scrobbling enabled |
volume | 0-100 | Volume percentage |
Queue Type Values
Used with nowplayingqueue command:
| Value | Description |
|---|---|
"next" | Play immediately after current track |
"last" | Add to end of queue |
"now" | Play immediately (replaces current track) |
"add-and-play" | Add and start playing |
Love/Ban Status Values
| Value | Description |
|---|---|
"love" | Mark as loved (Last.fm) |
"ban" | Mark as banned (Last.fm) |
"normal" | Clear love/ban status |
"-1" | Request current status (response only) |
ARiA Action Script Syntax
Commands for the playerinitiatoraction feature:
| Command | Description | Example |
|---|---|---|
sndKeys(keys) | Send keystrokes | sndKeys(^a) (Ctrl+A) |
delay(ms) | Wait milliseconds | delay(100) |
click(x,y) | Mouse click at coords | click(100,200) |
run(path) | Execute program | run(notepad.exe) |
Cover Art Format
- Format: Base64-encoded JPEG
- Max size: 600x600 pixels (auto-resized by plugin)
- Encoding: Standard Base64 (no data: URI prefix)
- Empty: Empty string if no cover available
- Typical size: 50KB-500KB Base64 encoded per image
// Decoding cover art (C#) byte[] imageBytes = Convert.FromBase64String(coverData); using var ms = new MemoryStream(imageBytes); var image = Image.FromStream(ms); // Decoding cover art (JavaScript) const img = new Image(); img.src = 'data:image/jpeg;base64,' + coverData;
Date/Time Formats
| Field | Format | Example |
|---|---|---|
lastplayed | ISO 8601 | 2025-01-15T14:30:00 |
dateadded | ISO 8601 | 2024-06-20T09:15:00 |
Podcast dateTime | Full datetime | 2025-01-15 10:00:00 |
Podcast duration | HH:MM:SS | 01:15:30 |