# MBXHub API > MusicBee REST API - v0.5.3.1 > > llms.txt specification: https://llmstxt.org/ > Requires MusicBee 3.1+ (API rev 53). Supports up to API rev 58 (MusicBee 3.6+ APIs available when present). ## Endpoints - Base URL: `http://localhost:8080` - WebSocket: `ws://localhost:8080/ws` - API Docs: `http://localhost:8080/docs` ## Player Control - `POST /player/play` - Start playback - `POST /player/pause` - Pause playback - `POST /player/playpause` - Toggle play/pause (alias: `/player/play-pause`) - `POST /player/stop` - Stop playback - `POST /player/next` - Next track - `POST /player/previous` - Previous track - `GET /player/volume` - Get volume (0-100) - `PUT /player/volume` - Set volume (body: {"volume":50}) - `GET /player/shuffle` - Get shuffle state - `PUT /player/shuffle` - Set shuffle (body: {"shuffle":true/false}) - `GET /player/autodj` - AutoDJ state - `POST /player/autodj/start` - Start AutoDJ - `POST /player/autodj/stop` - Stop AutoDJ - `GET /player/repeat` - Get repeat mode - `PUT /player/repeat` - Set repeat (off/all/one) - `GET /player/status` - Full player state - `GET /player/mute` - Mute state - `PUT /player/mute` - Set mute (body: {"mute":true}) - `GET /player/position` - Playback position in ms - `PUT /player/position` - Seek (body: {"position":ms}) - `POST /player/next-album` - Skip to next album - `POST /player/previous-album` - Skip to previous album - `POST /player/queue-random` - Queue a random track (alias: `/player/queuerandom`) ### Audio Processing - `GET /player/equaliser` - Get equalizer state (also `/player/equalizer`) - `PUT /player/equaliser` - Set equalizer - `GET /player/dsp` - Get DSP enabled state - `PUT /player/dsp` - Set DSP enabled - `GET /player/crossfade` - Get crossfade state - `PUT /player/crossfade` - Set crossfade - `GET /player/replaygain` - Get replay gain mode - `PUT /player/replaygain` - Set replay gain mode - `GET /player/scrobble` - Get scrobble (Last.fm) state - `PUT /player/scrobble` - Set scrobble ### Display Settings - `GET /player/stopaftercurrent` - Get stop-after-current state - `POST /player/stopaftercurrent` - Toggle stop-after-current - `GET /player/show-time-remaining` - Show time remaining preference - `GET /player/show-rating-track` - Show rating on track preference - `GET /player/show-rating-love` - Show love rating preference - `POST /player/show-equaliser` - Open the equaliser window in MusicBee (also `/player/show-equalizer`) - `GET /player/button-enabled?button=N` - Check if a UI button is enabled - `GET /player/output-devices` - List audio output devices - `PUT|POST /player/output-device` - Switch output device (body: {"device":"name"}) ## Device & Mixer Control ### Windows Audio Device - `GET /devices/audio/outputs` - List all active Windows audio render devices (name, id, isDefault) - `GET /devices/audio/volume` - Windows audio device volume and mute state - `PUT /devices/audio/volume` - Set Windows audio device volume (body: {"volume":50}) - `PUT /devices/audio/mute` - Set Windows audio device mute (body: {"mute":true}) ### Network Endpoints - `GET /devices/endpoints` - List configured network endpoints - `POST /devices/endpoints` - Add a network endpoint (body: {"ip":"192.168.1.50","type":"devialet","name":"Living Room"}) - `POST /devices/endpoints/scan` - mDNS scan for LAN speakers; bodyless (Content-Length: 0); ~4s blocking; returns [{name, ip, port, hostName, serviceType, alreadyConfigured, existingId}] - `DELETE /devices/endpoint/{id}` - Remove a saved endpoint - `GET /devices/endpoint/{id}/volume` - Get endpoint volume and mute state - `PUT /devices/endpoint/{id}/volume` - Set endpoint volume (body: {"volume":50}) - `PUT /devices/endpoint/{id}/mute` - Set endpoint mute (body: {"mute":true}) - `GET /devices/endpoint/{id}/sources` - List endpoint input sources - `PUT /devices/endpoint/{id}/source` - Select endpoint input source (body: {"sourceId":"upnp"}) ### Mixer Settings - `GET /mixer/settings` - Get mixer settings (default fader) - `PUT /mixer/settings` - Set mixer settings (body: {"defaultFader":"device"}) - `GET /mixer/volume` - Get volume from default fader (player or device) - `PUT /mixer/volume` - Set volume on default fader (body: {"volume":50}) ### Streaming - `POST /player/update-play-statistics` - Update play count and statistics ## Now Playing - `GET /nowplaying` - Current track metadata - `GET /nowplaying/artwork` - Album art (binary image) - `GET /nowplaying/lyrics` - Track lyrics. Response: `{ hasLyrics, lyrics, source?, label? }`. `source` is `"lyrics"` (MusicBee field) or `"comment"` (fallback to track Comment tag when no real lyrics exist). When `source: "comment"`, `label` is the chip text shown above the body (default "from comment"). Truncated to `LyricsFallback.MaxDisplayChars` (default 4000). Configurable via `LyricsFallback.{Enabled, MaxDisplayChars, Label}`; hard-killable via `ApiDisableLyricsFallback`. - `GET /nowplaying/position` - Playback position in ms - `GET /nowplaying/property?type=X` - File property (Size, Bitrate, DateAdded, etc.) - `GET /nowplaying/tag?field=X` - File tag (Artist, Album, Genre, Rating, etc.) - `GET /nowplaying/artwork-url` - Artwork as data URL (base64) - `GET /nowplaying/downloaded-artwork` - Downloaded artwork (binary) - `GET /nowplaying/downloaded-artwork-url` - Downloaded artwork as data URL - `GET /nowplaying/downloaded-lyrics` - Downloaded lyrics from provider - `GET /nowplaying/artist-picture` - Artist picture (binary, ?fadingPercent=0) - `GET /nowplaying/artist-thumbnail` - Artist thumbnail (smaller) - `GET /nowplaying/artist-picture-urls` - Artist picture URLs (?localOnly=false). Response includes a `pictures` array with `src` URLs (serveable via `/nowplaying/artist-pictures/{index}`) in addition to raw file paths. - `GET /nowplaying/artist-pictures/{index}` - Serve current artist's Nth picture as binary image (0-based). Query: ?localOnly=true - `GET /nowplaying/is-soundtrack` - Whether current track is a soundtrack - `GET /nowplaying/soundtrack-pictures` - Soundtrack picture URLs - `GET /nowplaying/spectrum` - Real-time spectrum data (frequency analysis) - `GET /nowplaying/sound-graph` - Waveform graph data - `GET /nowplaying/sound-graph-ex` - Extended waveform graph data - `GET /nowplaying/peak` - Current stereo peak and RMS levels (0.0-1.0). Returns {peak: [L, R], rms: [L, R]}. Requires MusicBee 3.6+ (API rev 58+) ## Queue - `GET /queue` - List queue (?offset=0&limit=50) - `GET /queue/current` - Current track index - `POST /queue/add` - Add tracks (body: {"url":"path","position":"next|last"} or batch: {"urls":["path1","path2"],"position":"last"}) - `POST /queue/playnow` - Play track immediately (body: {"url":"path","cueTrack":3}) - `POST /queue/clear` - Clear queue - `POST /queue/move` - Reorder (body: {"from":0,"to":5}) - `DELETE /queue/{index}` - Remove track by index - `POST /queue/play` - Play track at index (body: {"index":3,"cueTrack":1}) - `GET /queue/next-index?offset=1` - Get index N tracks ahead/behind - `GET /queue/has-prior` - Whether queue has prior tracks - `GET /queue/has-following` - Whether queue has following tracks - `POST /queue/play-library-shuffled` - Clear queue and play library shuffled - `GET /queue/{index}/url` - Get file URL for track at index - `GET /queue/{index}/tag?field=X` - Get tag for track at index - `GET /queue/{index}/property?type=X` - Get property for track at index ### Virtual Tracks (CUE) Virtual Tracks share the same audio file URL. The queue response includes Virtual Track metadata: - `cueTrack` - Track number when detected - `cueStartMs` - Start offset in milliseconds within the physical file - Track titles from CUE sheet (not embedded audio metadata) - Include `cueTrack` in add/playnow to preserve track identity ### CUE Track Resolution MBXHub detects CUE-backed audio files and resolves per-track metadata automatically across all surfaces: - `GET /player/status` - Returns CUE track title, artist, trackNo, cueTrack field - `GET /nowplaying/position` - Includes `cueTrack` and `cueTitle` when active - `GET /nowplaying/tag?field=TrackTitle` - Returns CUE track title (not base file) - WebSocket `TrackChanged` events include `cueTrack` and `cueStartMs` fields when CUE active - Dashboard shows resolved CUE track metadata - Listen Here streaming: seeks to `cueStartMs`, shows track-relative progress, auto-advances at track end CUE parsing uses a built-in regex parser with encoding detection (BOM, UTF-8 validation, Windows-1252 fallback). No external CUE library is loaded or required. ## Library - `GET /library/files` - Query files (?query=&artist=&albumArtist=&album=&genre=&offset=&limit=&sort=) - `GET /library/artists` - List artists (?offset=&limit=&sort=) - `GET /library/albums` - List albums (?artist=&offset=&limit=&sort=) - `GET /library/albums/detailed` - Albums with firstTrackUrl, year, dateAdded for artwork lookups and sorting (?offset=&limit=) — eliminates per-album /library/files round-trips - `GET /library/album-artists` - List album artists (?offset=&limit=) - `GET /library/albums/by-artist` - Albums for an artist (?albumArtist= or ?artist=, &sort=alpha|year|year-asc). ?artist= queries ArtistPeople (broader), ?albumArtist= queries AlbumArtist (exact credit) - `GET /library/albums/unheard` - Albums where all tracks have playCount=0 (?offset=&limit=) - `GET /library/albums/with-pdf` - Albums that contain PDF booklets (?offset=&limit=) - `GET /library/genres` - List genres (?sort=) - `GET /library/inbox` - Files in MusicBee Inbox (Source Type 4). Progressive: browse tab hidden if empty - `GET /library/audiobooks` - Audiobook files (Source Type 32). Progressive: browse tab hidden if empty - `GET /library/videos` - Video files (Source Type 64). Progressive: browse tab hidden if empty - `GET /library/search?q=term` - Full-text search over title/artist/album/genre. Two modes: **strict** (default) requires the typed phrase to appear as a contiguous run of whole words in a single field — "st anger" matches the album "St. Anger" (and from v0.5.3.0 also matches mid-typing partials like "st an") but NOT "Stranger" or "Strange Anger"; **substring** is loose — any query word appearing as a substring passes ("anger" matches "Stranger"). Default mode from `Library.DisableStrictSearch` setting (false=strict, shipped default). Override per-call with `?substring=true|false`. Diacritic and punctuation normalized. Response includes `mode: "strict"|"substring"`. Also supports `?sort=`. **v0.5.3.0:** add `?dsl=true` to route the query through `SearchDslParser` (qualifier syntax — `artist:`, `album:`, `genre:`, `year:`, `rating:`, `fmt:`, range / boolean / grouping); cheat sheet at `GET /library/search/syntax`. **v0.5.3.0 backstop:** queries shorter than `search.live.minQueryLength` (default 2) return `400 QUERY_TOO_SHORT` with `{error: "Query must be at least N characters (got M)."}` — single-char walks stall every other MB-API consumer for the duration of the cursor lock, so this protects fleet-wide responsiveness from runaway non-conforming clients (curl, scripts). - `GET /library/search/syntax` - **v0.5.3.0.** Returns the DSL grammar as JSON (qualifiers, operators, worked examples). Response shape: `{version, qualifiers:[{name, type, allowsRange, allowsOperators, mapping, enumValues}], operators:[{symbol, description}], examples:[{query, description}]}`. Authoritative source — the reference below is derived from the same data. Used by Cmd+K's cheatsheet and any other client surfacing DSL syntax to users. #### Query DSL Reference The DSL layers on top of free-text: every plain word still matches normally, qualifiers narrow the result, operators compose. Auto-detected on `/library/search` and `/search` when `library.search.dsl.enabled = true` (default); force per-call with `?dsl=true`. **Qualifiers** (19 total): | Qualifier | Type | Range/Ops | Maps to | Notes | |---|---|---|---|---| | `artist:` | string | — | ArtistPeople | Includes featured / album artists | | `album:` | string | — | Album | | | `albumartist:` | string | — | AlbumArtist | Compilation-aware | | `genre:` | string | — | Genre | Multi-value tags split client-side | | `year:` | int | range + ops | Year | `year:1985`, `year:1985..1990`, `year:>=2000` | | `decade:` | enum | — | derived from Year | Values: `60s`, `70s`, `80s`, `90s`, `00s`, `10s`, `20s` | | `rating:` | int | range + ops | Rating (0–5) | `rating:>=4`, `rating:3..5` | | `loved:` | bool | — | Loved tag | `loved:true` / `loved:false` | | `bpm:` | int | range + ops | Tempo (Truedat/Essentia) | Requires fingerprint or mood-cache data | | `mood:` | string | — | AutoQ mood channel | Channel name (e.g. `mood:chill`). **Post-filter** | | `vibe:` | float | range + ops | AutoQ vibe score | 0–1. `vibe:>=0.7` | | `source:` | enum | — | MB Source Type | Values: `library`, `inbox`, `audiobooks`, `videos`, `podcasts`. Default from `library.search.dsl.defaultSource` | | `playlist:` | string | — | playlist filter | **Post-process** — intersects with named playlist membership | | `added:` | date | range + ops | DateAdded | ISO (`2025-01-01`), relative (`now-7d`), or named alias | | `played:` | date | range + ops | DateLastPlayed | Same date format as `added:` | | `playcount:` | int | range + ops | PlayCount | `playcount:>10`, `playcount:0` (never played) | | `duration:` | int (seconds) | range + ops | Duration | `duration:>3600` (over an hour) | | `path:` | string | — | FilePath substring | Case-insensitive substring match on full path | | `lyric:` | string | — | lyrics body | **Post-process**, expensive. Gated on `library.search.dsl.allowLyricSearch = true` (default off) | **Operators**: | Symbol | Meaning | Where it applies | |---|---|---| | `>` | greater than | Qualifiers with `allowsOperators` | | `>=` | greater than or equal | Same | | `<` | less than | Same | | `<=` | less than or equal | Same | | `..` | inclusive range (low..high) | Qualifiers with `allowsRange` | | `-` (prefix) | exclude | Free-text term or qualifier (e.g. `-genre:metal`, `-live`) | | `OR` / `or` | boolean OR | Between sibling expressions. Default between terms is AND | | `( ... )` | grouping | Forces precedence inside a larger expression | **Examples**: | Query | Result | |---|---| | `artist:radiohead` | Tracks by Radiohead | | `year:1985..1990` | Tracks released between 1985 and 1990 inclusive | | `year:1965 rating:>4` | 1965 tracks rated above 4 — two range/op qualifiers (implicit AND) | | `mood:chill rating:>=4` | Chill-mood tracks rated 4 or higher — mood is post-filter | | `rock -genre:metal` | Rock tracks excluding metal — free-text plus exclusion | | `(rock or metal) -live` | Rock or metal, but not live recordings | | `source:audiobooks duration:>3600` | Audiobooks longer than one hour | | `played:5` | Favorites you haven't played in the last 30 days | | `decade:80s -genre:disco` | 80s, no disco — decade enum plus genre exclusion | **Composition rules**: - Default between terms is AND. Use `OR` (or lowercase `or`) for disjunction; parentheses for grouping. - Free-text terms work alongside qualifiers — `radiohead year:>=2000` filters Radiohead from 2000 onward. - Post-filter qualifiers (`mood`, `playlist`, `lyric`) run after candidate selection, capped by `library.search.dsl.maxPostFilterCandidates` (default 5000). - Unknown qualifiers return `422 INVALID_DSL` with parse position and Levenshtein-based "did you mean" suggestions. - Date format: ISO (`2025-01-01`), relative (`now-7d`, `now-30d`), or named alias from `library.search.dsl.dateAliases` (defaults: `lastweek`, `lastmonth`, `thisyear`). - `GET /search?q=&buckets=&limit=&cursor=` - **v0.5.3.0 federated search.** Runs typed buckets (tracks / albums / artists / playlists / saved) in parallel and returns a unified response with cursor pagination on the tracks bucket, facet counts, and a top-hit. Cursors are HMAC-stamped + TTL-checked (stale → 410, malformed → 400, query-mismatch → 400). Backs the Cmd+K palette and (v0.5.3.0+) the dashboard search bar, player.html, browse.html, and explore.html. **DSL routing (v0.5.3.0 post-launch):** auto-detects DSL queries the same way `/library/search` does (qualifier colons, `..` ranges, `>`/`<`, ` OR `, leading `-`). When detected and `Search.Dsl.Enabled=true` (default), routes through DSL pipeline and response carries `mode: "dsl"`. Per-call `?dsl=true` explicit override still works. Plain free-text queries continue to use strict/substring mode. **Tracks bucket relevance-sorted** by match strength (title > album > artist, with prefix-match bonus) regardless of mode. **v0.5.3.0 backstop:** same `search.live.minQueryLength` (default 2) check as `/library/search` — short queries return `400 QUERY_TOO_SHORT`. **v0.5.3.0 cap bump:** per-bucket `?limit=` raised from 200 to 500 so browse.html's client-side aggregation gets the full intended slice. - `GET /system/search-index` - **v0.5.3.0.** Live engine + index state snapshot. Currently reports `engine:"mb"` (FTS5 substrate dropped during the post-decouple harvest; seam preserved for future engines). - `GET /library/file/{url}` - Get file metadata - `PUT /library/file/{url}` - Update tags (body: {"Artist":"name","Title":"name"}) - `GET /library/files/raw` - Raw file path list (no metadata, faster) - `GET /library/cuetest?query=` - Test CUE track resolution for a query - `POST /library/add` - Add file to library (body: {"url":"path"}) - `POST /library/artwork/batch` - Batch artwork fetch (body: {"urls":["path1","path2",...]}, max 50). Returns base64 data URIs keyed by URL, null for missing artwork - `POST /library/find-device-ids` - Find device persistent IDs (body: {"urls":[...]}) - `POST /library/sync-delta` - Get sync delta (body: {"deviceId":"...","since":"ISO date"}) - `POST /library/commit` - Commit pending tag changes to file (body: {"file":"path"}). Use after batching Library_SetFileTag RPC calls - `GET /library/evaluate?expression=&fileUrl=` - Evaluate MusicBee expression (template syntax: , $If(), virtual tags). fileUrl optional, defaults to now-playing. Requires MusicBee 3.4.1+ (API rev 55+) - `GET /library/no-artwork` - MusicBee's placeholder image for tracks with no artwork (binary). Cache-Control: 24h. Requires MusicBee 3.5+ (API rev 57+) - `GET /library/file/{url}/lyrics` - Lyrics for specific file - `GET /library/file/{url}/artwork` - Artwork for specific file (binary) - `GET /library/file/{url}/artwork-url` - Artwork as data URL for specific file - `GET /library/file/{url}/artwork-count` - Returns `0` (no artwork) or `1` (artwork present). Probes up to 20 embedded-artwork LOCATIONS and picks the largest byte-size as the canonical image; `/artwork?index=0` then serves that variant. Cached per fileUrl. - `GET /library/file/{url}/pdf` - PDF booklet from track's album folder (binary, application/pdf) - `GET /library/file/{url}/fan-art` - List all images in the track's album folder and one level of subfolders. Returns {folder, images:[], count}. Excludes: canonical primary-cover filenames (folder/cover/front/album × .jpg/.jpeg/.png) — duplicates of the primary artwork served by `/artwork`; ALL Windows Media Player cache files (AlbumArtSmall*, AlbumArt_*); thumbnails (<5KB); Thumbs.db; desktop.ini. Security: track must be in MusicBee library. - `GET /library/fan-art/{path}` - Serve a single fan art image by absolute path (binary). Security: image must be in a directory containing a MusicBee library file. Cache-Control: 1 hour. - `GET /library/file/{url}/has-pdf` - Check if PDF booklet exists (returns {hasPdf: bool, url: string}) - `GET /library/file/{url}/device-id` - Device persistent ID - `PUT /library/file/{url}/device-id` - Set device persistent ID - `GET /library/artist/{name}/similar` - Similar artists (?limit=10) - `GET /library/artist/{name}/picture` - Artist picture (binary) - `GET /library/artist/{name}/thumbnail` - Artist thumbnail (smaller) - `GET /library/artist/{name}/pictures` - Artist picture URLs (?localOnly=false). Response includes a `pictures` array with `src` URLs (serveable via `/library/artist/{name}/pictures/{index}`) in addition to raw file paths. - `GET /library/artist/{name}/pictures/{index}` - Serve artist picture by index as binary image (0-based, from /pictures list). Query: ?localOnly=true - `GET /library/recent` - Recently played tracks sorted by last played descending (?limit=50&offset=0&days=30) - `GET /library/videos` - Video files from MusicBee's Video library node. Returns `{total, videos: [{url, title, artist, album, kind, duration}]}`. Title falls back to filename when tag is empty. Uses Source Type 64 query ## Saved Search & History (v0.5.3.0) ### Saved searches Persisted query under a name, evaluated on a schedule. Backed by `mbxhub-search.json` next to `mbxhub.json`. Enabled by default; toggle via `library.search.savedSearch.enabled`. Disabled endpoints return 404 NOT_FOUND. **All write/mutation routes return 403 FORBIDDEN when `ApiReadOnlyMode` is set.** - `GET /search/saved` - List all saved searches `{searches:[...], total}` - `POST /search/saved` - Create. Body `{name, query, delivery?, schedule?, mbPlaylist?}`. Server generates id. 422 INVALID_DSL on parse error (with parse position); 409 on duplicate name (case-insensitive, per host); 422 INVALID_NAME if name missing or > 80 chars - `GET /search/saved/{id}` - Fetch one - `PUT /search/saved/{id}` - Update name / query / delivery / schedule / mbPlaylist - `DELETE /search/saved/{id}` - 204 on success, 404 if unknown - `GET /search/saved/{id}/results` - Evaluate now; returns Track B response shape (matched URLs + count) - `POST /search/saved/{id}/run` - Force re-evaluation; returns diff vs `LastMatchUrls`. Drives the `SearchMatched` WS broadcast (D2) ### Search history Server-side recent-search log shared by Cmd+K and any other search bar. Falls through to client localStorage if 404. Mutations gated on `ApiReadOnlyMode`. - `GET /search/history?limit=N` - Newest-first. `limit` defaults 20, caps at 200 - `POST /search/history` - Body `{query, source}`. Empty body clears (returns `{cleared:true}`) - `DELETE /search/history` - Wipe all entries ## Radio - `GET /radio/stations` - List radio stations (returns `{total, stations: [{url, name}]}`) ## Playlists - `GET /playlists` - List all playlists - `POST /playlists` - Create playlist (body: {"name":"My Playlist","files":["path1","path2"]}) - `GET /playlists/{url}/files` - Get playlist tracks - `POST /playlists/{url}/files` - Add tracks (body: {"urls":["path1","path2"]}) - `POST /playlists/{url}/play` - Play playlist - `GET /playlists/{url}` - Get playlist details - `PUT /playlists/{url}` - Update playlist name - `DELETE /playlists/{url}` - Delete playlist ## WebSocket Connect to `ws://localhost:8080/ws` for real-time events: ```javascript const ws = new WebSocket('ws://localhost:8080/ws'); ws.onmessage = (e) => { const msg = JSON.parse(e.data); // msg.event: TrackChanged, PlayStateChanged, VolumeChanged, etc. // msg.data: event-specific payload // msg.timestamp: ISO 8601 timestamp }; // Subscribe to specific events: ws.send(JSON.stringify({ subscribe: ['TrackChanged', 'PlayStateChanged'] })); ``` Event types: - `TrackChanged` - {"fileUrl":"...","title":"...","artist":"...","album":"...","duration":ms,"artworkUrl":"/nowplaying/artwork","cueTrack":N,"cueStartMs":ms} (cueTrack + cueStartMs present only when CUE active) - `PlayStateChanged` - {"state":"playing|paused|stopped"} - `PositionChanged` - {"position":ms,"duration":ms} - `VolumeChanged` - {"volume":0-100,"muted":bool} - `ShuffleChanged` - {"enabled":bool} - `RepeatChanged` - {"mode":"none|all|one"} - `QueueChanged` - {"action":"changed","index":-1,"totalTracks":-1} - `MetadataChanged` - {"fileUrl":"...","rating":-1 to 5,"love":"L|B|"} - `Reaction` - {"emoji":"fire","type":"fire","nickname":"Guest","trackTitle":"...","trackArtist":"..."} - `TasteChanged` - {"topGenres":[...],"topArtists":[...],"bpmRange":[lo,hi],"mood":"...","moodConfidence":0.0-1.0,"influenceCount":N,"reactionCount":N} - `ThemeChanged` - {"activeMode":1|2,"accentHue":0-360,"accentSaturation":0-100,"accentLightness":0-100,"bgHue":0-360,"bgSaturation":0-100,"bgLightness":0-100,"surfaceHue":0-360,"surfaceSaturation":0-100,"surfaceLightness":0-100,"textHue":0-360,"textSaturation":0-100,"textLightness":0-100,"intensity":0-100} - `SearchMatched` - **v0.5.3.0.** Periodic-evaluator broadcast when a saved search's match set changes. `{"id":"...","name":"...","added":[urls],"removed":[urls],"total":N,"evaluatedAt":"ISO8601"}`. Driven by `SavedSearchScheduler` ticking each saved-search interval; only fires when the diff is non-empty ## Static Pages (/pages/) MBXHub serves HTML, CSS, and JS from the `/pages/` directory. ### How It Works - Files location: `%APPDATA%\MusicBee\MBXHub\pages\` - Access via: `http://{host}/pages/yourfile.html` - Supports: .html, .css, .js, .json, images - No build step - just save and refresh ### Included Pages Read these for working reference code: - `GET /pages/index.html` - Landing page listing available views - `GET /pages/player.html` - Full 3-column player (browse iframe, now playing, queue) with drag-drop queue reorder and touch support. Browse panel embeds browse.html via iframe with theme sync via postMessage - `GET /pages/nowplaying.html` - Focused standalone now-playing view. Drag-drop queue reorder on upcoming tracks. Auto-stacks below 800px into three collapsible blades (Now Playing, Queue, Lyrics). - **Now Playing has two states**, never fully collapses to header-only: **Expanded** (~50% viewport, full artwork + reactions) and **Compact** (~52px header bar with mini artwork thumb + title + artist + transport buttons: prev / play-pause / next, all hitting `/player/previous` `/player/playpause` `/player/next`). The play/pause icon mirrors `isPlaying` driven by the `PlayStateChanged` WebSocket event. - **Queue and Lyrics use last-touch-wins**: at most one is Expanded at a time. Tapping a collapsed Queue or Lyrics header expands it and auto-collapses the peer to its header bar. - Floating 6px progress bar fixed at viewport bottom drives seek via click -> `PUT /player/position`. Position fill driven by the `PositionChanged` WebSocket event with a 30s `/nowplaying/position` poll as a tab-visibility-gated safety net. - All blade-collapse / transport markup is gated by the stacked layout (typical at tablet width or fullscreen browser). Charm-tab / iframe-mode rendering is unchanged - gated by `body.iframe-mode` and `html.embedded` rules. - `GET /pages/browse.html` - Library browser: 8 tabs (Albums/Artists/Genres/Playlists/Tracks/Podcasts/Radio/Moods) with responsive tab sizing, drilldown and breadcrumbs, diacritic-normalized fuzzy search with 8-category grouped results, album art grid with lazy loading, batch queue via long-press, playlist picker, search-driven Tracks tab via /library/search, video items with direct play (no drilldown), auto-search fallback (when inline name filtering yields no results, auto-triggers full-text server search), light/dark theme - `GET /pages/config.html` - Settings and configuration interface for dashboard and AutoQ - `GET /pages/autoq.html` - AutoQ Tuning Console: mixer-style sliders for all scoring weights, reaction scores, and normalization ranges - `GET /pages/mixer.html` - Unified fader mixing surface: three independent faders for Player (MusicBee), Device (Windows audio), and Endpoint (network speaker) volume. Configurable default fader, mute controls, endpoint source selection. Charm bar integration via postMessage - `GET /pages/explore.html` - Album art explorer: browse albums as artwork with source filters (All/1mo/3mo/12mo/Unheard), search, sort, image gallery with embedded artwork probing, PDF booklet viewing, play/queue controls, theme sync. Accepts `?album=&artist=` query params for seeding from search or dashboard. Artist discography grid in expanded view shows all albums by the current artist. Expanded-view hero has paired close (×) and dashboard home (⌂) buttons — close dismisses the overlay and stays on explore; home navigates to `/dashboard`. Home is hidden in iframe-mode - `GET /pages/play.html` - Use MusicBee from a browser. Laid out like the MusicBee AMOLED skin: artist picker (left) with infinite-scroll + jump-on-letter typeahead, pluggable middle pane (Albums / Library / Now-Playing iframe registry), upcoming-queue + now-playing card (right), full-width transport footer. - Transport: play/pause, prev/next, shuffle, repeat, AutoDJ, volume + mute, scrubber, love, 5-star rating, AutoQ mood select + refresh, reaction buttons (fire/heart/like/dislike/ban) gated on AutoQ availability - "+ Add To Playlist" dropdown beside love/stars: quick-curate the playing track to any playlist; "+ New Playlist…" creates one with the current track as seed; last-used playlist bubbles to the top - ARiA presets dropdown in the overflow flyout (auto-hides if ARiA isn't enabled) - Influence thumbs (artist + genre) on the right-column now-playing card, visible only when mbxq reports `/influences/current` - Global media-key shortcuts: Space = play/pause, ← = previous, → = next (skipped while typing in inputs) - Cmd+K command palette (Ctrl+K) — same component as the dashboard via shared `/pages/components/cmdk-bootstrap.js`; action rows show the global hotkey alongside (Space / ← / → / ↑ / ↓) - **Listen Here** (browser-side audio): output toggle (🔊 speakers / 🎧 browser) in the transport flyout. Speakers is the default (MB plays through the host machine, controlled via `/player/*`). Browser plays the same server queue via a hidden `