MBRC Server Plugin Protocol v4/4.5 SDK

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.

MBRCPVAL - Protocol Validator: Use MBRCPVAL to validate your protocol implementation. This command-line tool provides mock server mode, proxy debugging, REPL testing, and automated test suites to verify client/server compatibility.
πŸš€ Quick Start: To connect and control MusicBee:
  1. Open TCP socket to host:3000 (or discovered port)
  2. Send: {"context":"player","data":"android"}\r\n
  3. Send: {"context":"protocol","data":{"protocol_version":4}}\r\n
  4. Send: {"context":"init","data":null}\r\n
  5. Receive initial state (track info, cover art, player status)
  6. Send commands, receive push events - you're connected!

Table of Contents

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

PropertyValue
EncodingUTF-8 without BOM
Line Termination\r\n (CRLF - Windows style)
Message BoundaryOne JSON message per line
SerializationJSON (Newtonsoft.Json compatible)
Default PortTCP 3000 (configurable in plugin settings)
DiscoveryUDP Multicast 239.1.5.10:45345
Critical: UTF-8 BOM (Byte Order Mark) must NOT be included. A BOM would add invisible characters that break JSON parsing.

Recommended Timeouts

TimeoutValueDescription
Connect Timeout5 secondsTCP connection establishment
Handshake Timeout10 secondsComplete player/protocol/init exchange
Command Response30 secondsWaiting for command response
Ping Interval30-60 secondsHeartbeat frequency during idle
Pong Timeout5 secondsTime to wait for pong response
Dead Connection90 secondsDeclare dead after 3 missed pings
Position Update~20 secondsServer 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

ParameterValueNotes
Multicast Address239.1.5.10IPv4 multicast group
Port45345UDP port for discovery
EncodingUTF-8No BOM, no line termination required
FormatJSONSingle 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"
}
FieldTypeRequiredDescription
contextstringYesMust be "discovery"
addressstringYesClient'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
}
FieldTypeDescription
contextstringAlways "notify" for successful discovery
addressstringServer's IP address on the same subnet as client
namestringComputer name (from COMPUTERNAME environment variable)
portintegerTCP port for MBRC protocol (from plugin settings)

Discovery Error Response

If the request is malformed or missing required fields:

{
  "context": "error",
  "description": "missing address"
}
ErrorDescription
missing addressRequest did not include client's IP address
unsupported actionContext was not "discovery"

Subnet Matching Logic

The server selects which network interface IP to return based on subnet matching:

  1. Parse client's IP address from the request
  2. If client is loopback (127.x.x.x), return first available interface IP
  3. 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
  4. Fallback: return first available interface IP if no match
Implementation Note: Servers bind a UDP listener to each network interface separately, joining the multicast group on each. This ensures discovery works across multiple NICs (e.g., Ethernet + WiFi).

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:

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)
#ContextPurposeNotes
1nowplayingtrackTrack metadataSee Now Playing Commands section
2nowplayingratingTrack rating-1 = unrated, 0-5 = rated
3nowplayinglovestatusLove/like statustrue, false, or "loved"
4playerstatusPlayer stateVolume, shuffle, repeat, scrobble, state
5nowplayingcoverAlbum artBase64 encoded image data
6nowplayinglyricsLyricsMay be empty if no lyrics available
Important: Process all 6 messages to fully populate your UI state. If no track is playing, some fields may be empty/null but messages are still sent.

4. Version Negotiation #

Protocol Version History

ProtocolMusicBee SDKFeatures Added
2.03.0Base functionality: player control, now playing, library search, queue operations
2.13.0Added: ping/pong, init, separate playerplay/playerpause
3.03.0Added: playlistplay, library browse (browsegenres, etc.), nowplayingqueue
4.03.0Added: playeroutput, radiostations, nowplayingdetails, nowplayingtagchange, libraryalbumcover
4.53.0 / 3.1ARiA 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 SetRequirementCommands
Extended Track MetadataClient requests v4.5+year, rating, bitrate, format, playcount, skipcount, lastplayed, dateadded, loved in browsetracks
ARiA (Initiator Actions)ARiA enabled in plugin settingsplayerinitiatoraction, configureariahotkeys
Storage PathARiA enabled in plugin settingspluginstoragepath
VisualizersExperimental Features enabled in plugin settingsplayervisualizer, playervisualizerlist
Album NavigationMusicBee 3.1+ (API 50)playernextalbum, playerpreviousalbum
PodcastsMusicBee 3.1+ (API 51)podcastsubscriptions, podcastepisodes, podcastplay
Playlist NotificationsMusicBee 3.1+ (API 49)playlistcreated, playlistupdated, playlistdeleted
Two-Way SyncClient requests v4.5+librarysetrating, librarysetlove
Real-time Library SyncPlugin v1.5.x+librarysubscribe, libraryunsubscribe, librarytagchanged, libraryfileadded, libraryfiledeleted, libraryratingchanged, libraryplaycountchanged
Important: Plugin v1.5.x negotiates the protocol version. If the client requests v4, the server responds with v4.0 (backward compatible, no extended metadata). If the client requests v4.5+, the server responds with v4.5 (extended metadata enabled). ARiA (keyboard automation) and Experimental Features (visualizers) are separate security settings in the plugin. Use 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

FeatureProtocol 4.0
(legacy clients)
Protocol 4.5
(MB 3.0)
Protocol 4.5
(MB 3.1+)
Basic transport (play, pause, next, etc.)YesYesYes
Volume, shuffle, repeat, scrobbleYesYesYes
Now playing info, cover, lyrics, positionYesYesYes
Library browse & searchYesYesYes
Queue operationsYesYesYes
PlaylistsYesYesYes
Output devices, radio stationsYesYesYes
playerinitiatoraction (ARiA)NoIf ARiA enabledIf ARiA enabled
pluginstoragepathNoIf ARiA enabledIf ARiA enabled
playervisualizer / playervisualizerlistNoIf Experimental enabledIf Experimental enabled
playernextalbum / playerpreviousalbum (API 50)NoNoYes
podcastsubscriptions / podcastepisodes / podcastplay (API 51)NoNoYes
Playlist notifications (API 49+)NoNoYes

Backward Compatibility Rules

Best Practice: Request 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"}
Use Case: Clients that cache library data locally should request the instance ID on each connection. If the ID differs from the previously stored value, the cache may be stale and should be cleared or refreshed.

6. Player Control Commands #

Transport Controls

CommandContextData
Playplayerplaynull
Pauseplayerpausenull
Play/Pause Toggleplayerplaypausenull
Stopplayerstopnull
Next Trackplayernextnull
Previous Trackplayerpreviousnull
Next Albumplayernextalbumnull (API 50+)
Previous Albumplayerpreviousalbumnull (API 50+)

Volume Control

Important: Volume data must be sent as a string, not a number.
// 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 false

Player 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):

ValueDescription
"off"Shuffle disabled - tracks play in order
"shuffle"Standard shuffle - randomized playback
"autodj"Auto DJ enabled - MusicBee selects tracks
true / falseLegacy boolean (v4 protocol only)
Protocol Compatibility: If client negotiated protocol v4 (legacy), playershuffle is sent as a boolean. If v4.5+, it's sent as a string with the three-state value.

Repeat State Values

ValueDescription
"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": ""
  }
}
FieldTypeDescription
statusinteger200 = lyrics found, 404 = no lyrics available
lyricsstringLyrics text with newlines (\n). LRC timestamps stripped. XML-escaped.
Lyrics Processing: MusicBee strips LRC timing tags (e.g., [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 NameDescriptionExample Value
TrackTitleSong title"Comfortably Numb"
ArtistTrack artist"Pink Floyd"
AlbumAlbum name"The Wall"
AlbumArtistAlbum artist"Pink Floyd"
YearRelease year"1979"
GenreGenre"Progressive Rock"
TrackNoTrack number"6"
TrackCountTotal tracks"13"
DiscNoDisc number"2"
DiscCountTotal discs"2"
GroupingGrouping tag"Classic Albums"
PublisherRecord label"Columbia"
ComposerComposer(s)"Roger Waters"
CommentComment field"Remastered"
EncoderEncoder info"LAME 3.100"
LyricsEmbedded lyrics"Hello, is there..."
RatingAlbumAlbum rating"5"
Important: Tag changes are committed to the file immediately using 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

ActionContextData
Play at indexnowplayinglistplay5 (0-based index)
Remove tracknowplayinglistremove5 (index)
Move tracknowplayinglistmove{"from": 3, "to": 7}
Clear queuenowplayinglistclearnull
Queue nextnowplayingqueuenext"/path/to/file.mp3"
Queue lastnowplayingqueuelast"/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 #

Pagination Required: All library browse commands require 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:

FieldTypeDescription
playcountintNumber of times track has been played
skipcountintNumber of times track was skipped
lastplayedstringDate/time track was last played (ISO format)
dateaddedstringDate/time track was added to library
lovedstring"L" = Loved, "B" = Banned, "" = Neither
ratingstringStar rating (0-5)
yearstringRelease year
bitratestringAudio bitrate in kbps
formatstringAudio format (MP3, FLAC, etc.)

Library Search

Search TypeContext
Search Artistslibrarysearchartist
Search Albumslibrarysearchalbum
Search Genreslibrarysearchgenre
Search Titleslibrarysearchtitle

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 }
  ]
}
Search Matching: Searches use contains matching (partial match), not exact. Searching for "pink" will match "Pink Floyd" and "Pink Martini". Use libraryartistalbums with the exact artist name to get albums.

Library Queue Operations

ActionContext
Queue Genrelibraryqueuegenre
Queue Artistlibraryqueueartist
Queue Albumlibraryqueuealbum
Queue Tracklibraryqueuetrack
Play Alllibraryplayall

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"
}
Queue vs Play:

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
  }
}
Rating Scale: 0 = no rating, 1-5 = star rating. Decimals supported (e.g., 3.5 for 3.5 stars).

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 ValueMusicBee Effect
loveMark as loved (β™₯)
banMark as banned (⊘)
normalClear 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 FieldTypeDescription
offsetintegerStarting index in the full list
limitintegerMaximum items requested
totalintegerTotal number of radio stations
dataarrayArray of radio station objects
data[].namestringStation name (track title metadata)
data[].urlstringStream URL
Playing Radio: To play a radio station, use 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}
Cover Cache: The plugin caches album covers for faster response times. During the initial cache build, 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 milliseconds

Tab 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)"}
PrefixBehavior
!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 ValueDescription
normal, windowShow in normal window
fullscreen, fullShow fullscreen
desktopShow on desktop (wallpaper mode)
off, hide, closeTurn 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.

CommandDirectionDescription
librarysubscribeClient β†’ PluginSubscribe to library change events
libraryunsubscribeClient β†’ PluginUnsubscribe from library events
librarytagchangedPlugin β†’ ClientTrack metadata changed
libraryfileaddedPlugin β†’ ClientNew file added to library
libraryfiledeletedPlugin β†’ ClientFile removed from library
libraryratingchangedPlugin β†’ ClientTrack rating changed
libraryplaycountchangedPlugin β†’ ClientPlay 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 ContextTriggerSubscription Required
playerstatePlay/pause/stop, shuffle, repeat changesNo (always broadcast)
playervolumeVolume changeNo
playermuteMute toggleNo
playerrepeatRepeat mode changeNo
playershuffleShuffle toggleNo
nowplayingtrackTrack changeNo
nowplayingcoverTrack change (cover art)No
nowplayinglyricsTrack change (lyrics)No
nowplayingratingCurrent track rating changeNo
nowplayinglfmratingLove/ban status changeNo
nowplayingpositionPeriodic (20s) + seekNo
nowplayinglistchangedQueue modificationNo
playlistcreatedPlaylist created (MB 3.1+)No
playlistupdatedPlaylist modified (MB 3.1+)No
playlistdeletedPlaylist deleted (MB 3.1+)No
librarytagchangedTrack metadata changedYes (librarysubscribe)
libraryfileaddedFile added to libraryYes
libraryfiledeletedFile removed from libraryYes
libraryratingchangedTrack rating changedYes
libraryplaycountchangedPlay count updatedYes
Implementation Note: Clients should maintain a message handler that processes incoming events in a loop. Events can arrive at any time, not just in response to commands. Use state management to update UI when push events arrive.

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)
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
StateDescriptionActions Available
DISCONNECTEDNo active connectionconnect()
CONNECTINGTCP connection establishingWait or cancel()
AUTHENTICATINGHandshake in progress (player→protocol→init)Wait
CONNECTEDFully operational, commands acceptedAll commands, disconnect()
RECONNECTINGAuto-reconnection with backoffcancel(), wait
ERRORFatal error, manual intervention requiredconnect(), reset()

State Transition Events

FromEventTo
DISCONNECTEDUser calls connect()CONNECTING
CONNECTINGTCP connectedAUTHENTICATING
CONNECTINGTCP timeout/refusedRECONNECTING
AUTHENTICATINGHandshake completeCONNECTED
AUTHENTICATINGHandshake failed/timeoutRECONNECTING
CONNECTEDSocket error/pong timeoutRECONNECTING
CONNECTEDUser calls disconnect()DISCONNECTED
RECONNECTINGRetry succeedsAUTHENTICATING
RECONNECTINGMax retries exceededDISCONNECTED 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

PropertyValue
Max Queue Size100 commands
Expiration5 minutes
Processing Delay100ms between commands

Commands NOT Queued (Real-time only)

Commands Always Queued (Important)

15. Error Handling #

Error Response Format

{
  "context": "error",
  "data": "Error description message"
}

Common Error Scenarios

ScenarioRecovery
Connection timeoutAutomatic reconnect with backoff
Invalid JSONLog and skip message
Unknown commandServer ignores, no response
Handshake failureDisconnect and retry
Heartbeat timeoutTrigger reconnection

Error Response Types

ContextDataMeaningClient 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

CommandError ConditionResponse
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

ErrorContextResponse
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

ModeBehavior
AllAccept connections from any IP address (default)
SpecificOnly allow IPs in an explicit whitelist
RangeAllow 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

Connection Rejection

When a connection is rejected due to IP filtering:

// 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

ScenarioRecommended Action
Receive notallowedShow error, stop reconnection attempts
Connection drops after notallowedNormal - server closes immediately
Timeout after connectDifferent issue - may be blocked by firewall
Security Note: IP filtering provides basic access control but is not a security boundary. IPs can be spoofed on compromised networks. For sensitive deployments, consider additional measures like VPN or firewall rules.

17. Implementation Checklist #

Threading Model: Messages arrive asynchronously on a background thread. If updating UI, dispatch to the main/UI thread. Use a lock or concurrent queue if accessing shared state from both send and receive paths.

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)

#ContextDescriptionData
Connection & System
1errorError response from serverError message string
2playerClient identification (handshake packet 0)"android"
3protocolProtocol negotiation (handshake packet 1){"protocol_version": 4, "no_broadcast": false}
4pluginversionRequest plugin versionnull
4bplugininstanceidRequest unique plugin instance ID (v1.5.25+)null
5notallowedAction not permitted responseReason string
Player Controls
6playerstatusRequest player statenull
7playerstatePlayer state response/notification{"state": "playing", ...}
8playerplaypauseToggle play/pausenull
9playerpreviousPrevious tracknull
10playernextNext tracknull
11playerstopStop playbacknull
12playervolumeSet/get volume"75", "+5", "-5" (string)
13playerrepeatSet repeat mode"none", "all", "one", "toggle"
14playershuffleToggle/set shuffle"toggle" or true/false
15playermuteToggle/set mute"toggle", "on", "off"
16scrobblerToggle/set scrobbling"toggle" or true/false
17playerautodjEnable/disable Auto DJtrue or false
Now Playing
18nowplayingtrackGet current track infonull
19nowplayingcoverGet album art (Base64)null
20nowplayingpositionGet/set playback positionnull or milliseconds
21nowplayinglyricsGet track lyricsnull
22nowplayingratingGet/set track rating"-1" (get) or "4.5" (set)
23nowplayinglfmratingGet/set Last.fm love/ban"-1", "love", "ban", "normal"
24nowplayinglistGet queue with pagination{"offset": 0, "limit": 500}
25nowplayinglistchangedQueue changed notificationServer push notification
26nowplayinglistplayPlay track at index5 (0-based index)
27nowplayinglistremoveRemove track from queue5 (index)
28nowplayinglistmoveMove track in queue{"from": 3, "to": 7}
29nowplayinglistsearchSearch within queue{"query": "search term"}
Library Search
30librarysearchartistSearch artists{"query": "beatles", "offset": 0, "limit": 100}
31librarysearchalbumSearch albums{"query": "abbey", "offset": 0, "limit": 100}
32librarysearchgenreSearch genres{"query": "rock", "offset": 0, "limit": 100}
33librarysearchtitleSearch track titles{"query": "yesterday", "offset": 0, "limit": 100}
Library Navigation
34libraryartistalbumsGet albums by artist{"artist": "The Beatles"}
35librarygenreartistsGet artists in genre{"genre": "Rock"}
36libraryalbumtracksGet tracks in album{"album": "Abbey Road", "artist": "The Beatles"}
Library Queue
37libraryqueuegenreQueue all in genre"Rock"
38libraryqueueartistQueue all by artist"The Beatles"
39libraryqueuealbumQueue all in album{"album": "Abbey Road", "artist": "The Beatles"}
40libraryqueuetrackQueue single track"/path/to/file.mp3"
Playlists
41playlistlistGet all playlistsnull

Protocol 2.1 - Additions (5)

#ContextDescriptionData
42pingHeartbeat requestnull
43pongHeartbeat responsenull
44initRequest initial state (handshake packet 2)null
45playerplayStart playbacknull
46playerpausePause playbacknull

Protocol 3.0 - Additions (7)

#ContextDescriptionData
47playlistplayPlay a playlist (replaces now playing)"MyPlaylist"
47bplaylistqueueQueue a playlist (appends to now playing)"MyPlaylist"
48nobroadcastDisable UDP broadcast for this clienttrue
49browsegenresBrowse all genres{"offset": 0, "limit": 100}
50browseartistsBrowse all artists{"offset": 0, "limit": 100}
51browsealbumsBrowse all albums{"offset": 0, "limit": 100}
52browsetracksBrowse all tracks{"offset": 0, "limit": 100}
53nowplayingqueueQueue track (next/last){"path": "/path/to/file.mp3", "type": "next"}

Protocol 4.0 - MusicBee 3.0 SDK (9)

#ContextDescriptionData
54playeroutputGet output devices listnull
55playeroutputswitchSwitch output device{"deviceId": "..."}
56verifyconnectionVerify connection is alivenull
57radiostationsGet radio stationsnull
58nowplayingdetailsGet extended track detailsnull
59nowplayingtagchangeModify track tags{"tag": "rating", "value": "5"}
60libraryplayallPlay entire librarynull
61libraryalbumcoverGet album cover art{"album": "Abbey Road", "artist": "The Beatles"}
62librarycovercachebuildstatusGet cover cache build progressnull

Protocol 4.5 - ARiA Extension (3) - Requires ARiA Enabled

#ContextDescriptionData
63playerinitiatoractionExecute ARiA action script"sndKeys(^a);delay(100);sndKeys(^c)"
64pluginstoragepathGet MusicBee storage pathsnull
65configureariahotkeysConfigure ARiA tab hotkeys in MusicBee settings{"action": "check"} or {"action": "configure"}

Protocol 4.5 - Visualizers (2) - Requires Experimental Features Enabled

#ContextDescriptionData
64playervisualizerSet visualizer state{"name": "Milkdrop", "state": "fullscreen"}
65playervisualizerlistGet available visualizersnull

Protocol 4.5 - MusicBee 3.1 SDK Features (8) - Requires MB 3.1+

#ContextDescriptionData
Album Navigation (API 50)
66playerpreviousalbumSkip to previous albumnull
67playernextalbumSkip to next albumnull
Podcasts (API 51)
68podcastsubscriptionsGet podcast subscriptionsnull
69podcastepisodesGet episodes for podcast{"id": "podcast_id"}
70podcastplayPlay podcast episode{"id": "episode_id"}
Playlist Notifications (API 49+)
71playlistcreatedPlaylist created notificationServer push notification
72playlistupdatedPlaylist updated notificationServer push notification
73playlistdeletedPlaylist deleted notificationServer push notification

Protocol 4.5 - Two-Way Sync (2) - Library Metadata Write-Back

#ContextDescriptionData
Rating & Love Status Sync
74librarysetratingSet track star rating (0-5){"path": "/path/to/file.mp3", "rating": "4.0"}
75librarysetloveSet track love/ban status{"path": "/path/to/file.mp3", "status": "love"}
Two-Way Sync: These commands modify MusicBee's library metadata. Changes are persisted immediately.

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.

#ContextDirectionDescriptionData
Subscription Management
76librarysubscribeClient β†’ PluginSubscribe to library events{"eventTypes": ["tagchanged", "fileadded", ...], "includeMetadata": true}
77libraryunsubscribeClient β†’ PluginUnsubscribe from library events{} (empty object)
Event Notifications (Push from Server)
78librarytagchangedPlugin β†’ ClientTrack metadata changedSee event payload below
79libraryfileaddedPlugin β†’ ClientNew file added to librarySee event payload below
80libraryfiledeletedPlugin β†’ ClientFile removed from librarySee event payload below
81libraryratingchangedPlugin β†’ ClientTrack rating changedSee event payload below
82libraryplaycountchangedPlugin β†’ ClientPlay count updatedSee event payload below
Subscribe Request
{
  "context": "librarysubscribe",
  "data": {
    "eventTypes": ["tagchanged", "fileadded", "filedeleted", "ratingchanged", "playcountchanged"],
    "includeMetadata": true
  }
}

Parameters:

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
  }
}
Real-time Library Sync: Subscribe to receive push notifications when library data changes in MusicBee.

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.

Access: Press Ctrl+Shift+Alt+T in MBXRemote to open the Protocol Tester dialog.

Test Coverage

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:

EndpointNew FieldNotes
browseartistsAlbumCountAlways sent - additive field, ignored by most parsers
browsegenresArtistCountAlways sent - additive field, ignored by most parsers
browsetracksyear, rating, bitrate, format, playcount, skipcount, lastplayed, dateadded, lovedOnly sent when client requests v4.5+ (MBX-SQL Bridge metadata)
Backwards Compatibility: Legacy clients requesting protocol v4 receive v4.0 responses without extended track metadata. Most JSON parsers (Gson, Jackson, Newtonsoft.Json) ignore unknown fields by default, so the additive AlbumCount and ArtistCount fields are safe.

Testing with Original Android App (kelsos/mbrc)

To verify backwards compatibility with the original MusicBee Remote Android app:

  1. Install: Download "MusicBee Remote" from mbrc.kelsos.net (no longer on Play Store)
  2. Configure: Open app Settings > Manual connection > Enter MusicBee host IP and port (default: 3000)
  3. Connect: Tap Connect and verify connection succeeds
  4. 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)
  5. Test Playback: Play/pause, next/previous, volume - verify all work
  6. Test Now Playing: Verify track info, cover art, and queue display correctly

Expected Results

FeatureExpected Behavior
Artist listShows artist names (AlbumCount field ignored)
Genre listShows genre names (ArtistCount field ignored)
Track listNormal display (legacy clients request v4, no extended fields)
Playback controlsFull functionality
Queue managementFull functionality

Testing with iOS App

iOS MusicBee Remote clients exist but additional testing is needed for full compatibility verification.

iOS Note: Swift 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 3000

Compatibility Matrix

This applies to the additive fields (AlbumCount, ArtistCount) which are always sent. Extended track metadata fields are protected by protocol negotiation.

Client TypeJSON ParserUnknown Field HandlingCompatibility
Android (Kotlin)GsonIgnored by defaultCompatible
Android (Kotlin)Moshi (strict)May failCheck config
iOS (Swift)JSONDecoderFails without CodingKeysCheck impl
Pythonjson moduleIgnoredCompatible
JavaScriptJSON.parse()IgnoredCompatible
C# (.NET)Newtonsoft.JsonIgnored by defaultCompatible
C# (.NET)System.Text.JsonConfigurableCheck 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).

FieldTypeContextDescription
artiststringAllTrack artist
albumstringAllAlbum name
albumArtist / album_artiststringAllAlbum artist (may differ from track artist)
titlestringAllTrack title
pathstringAllFile path (Windows format)
yearstringv4.5Release year
genrestringv4.5Genre
trackNo / track_nostringv4.5Track number
discNo / disc_nostringv4.5Disc number
ratingstringv4.5Star rating (0-5, decimals allowed)
bitratestringv4.5Bitrate in kbps
formatstringv4.5Audio format (MP3, FLAC, etc.)
sampleRatestringDetailsSample rate in Hz
durationintegerDetailsDuration in milliseconds
playcountstringv4.5Number of plays
skipcountstringv4.5Number of skips
lastplayedstringv4.5Last played datetime
dateaddedstringv4.5Date added to library
lovedstringv4.5"L" = loved, "" = not loved
positionintegerQueuePosition 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

FieldValuesDescription
state"playing", "paused", "stopped"Playback state
repeat"none", "all", "one"Repeat mode
shuffletrue, falseShuffle state
mutetrue, falseMute state
scrobbletrue, falseLast.fm scrobbling enabled
volume0-100Volume percentage

Queue Type Values

Used with nowplayingqueue command:

ValueDescription
"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

ValueDescription
"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:

CommandDescriptionExample
sndKeys(keys)Send keystrokessndKeys(^a) (Ctrl+A)
delay(ms)Wait millisecondsdelay(100)
click(x,y)Mouse click at coordsclick(100,200)
run(path)Execute programrun(notepad.exe)

Cover Art Format

Buffer Size: Cover art messages can be 500KB+ of Base64 text. Ensure your receive buffer is at least 1MB and use streaming JSON parsing or accumulate data until CRLF delimiter is found.
// 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

FieldFormatExample
lastplayedISO 86012025-01-15T14:30:00
dateaddedISO 86012024-06-20T09:15:00
Podcast dateTimeFull datetime2025-01-15 10:00:00
Podcast durationHH:MM:SS01:15:30