Rockbox open source high quality audio player as a Music Player Daemon
mpris rockbox mpd libadwaita audio rust zig deno
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add Mintlify documentation and update OpenAPI

+6772 -2743
+20
README.md
··· 610 610 <p style="margin-top: 20px; margin-bottom: 20px;"> 611 611 <img src="./docs/grpc.png" width="100%" /> 612 612 </p> 613 + 614 + --- 615 + 616 + ## 📖 Documentation 617 + 618 + Full guides, configuration reference, audio-output setup, API reference, and 619 + SDK docs are published at: 620 + 621 + **[👉 View full documentation](https://rockboxzig.mintlify.app)** 622 + 623 + The Mintlify source lives in [`mintlify/`](./mintlify/). Topics covered: 624 + 625 + - **Getting started** — install, quickstart, configuration 626 + - **Audio output** — built-in SDL, Snapcast, AirPlay, Squeezelite, Chromecast, UPnP 627 + - **Audio settings** — parametric EQ, DSP, ReplayGain, crossfade 628 + - **Clients** — web UI, desktop apps, MPD, MPRIS 629 + - **API reference** — HTTP REST (auto-generated from OpenAPI), GraphQL, gRPC, MPD 630 + - **SDKs** — TypeScript, Python, Ruby, Elixir, Clojure, Gleam 631 + - **Architecture** — build system, PCM sinks, cross-cutting concerns 632 + - **Reference** — `rockbox` and `rockboxd` CLI, ports, `settings.toml`, troubleshooting, FAQ
+1021 -2743
crates/server/openapi.json
··· 1 1 { 2 2 "openapi": "3.1.0", 3 - "x-stoplight": { 4 - "id": "jz8crifyfa3cs" 5 - }, 6 3 "info": { 7 - "title": "Rockbox API v1", 8 - "version": "1.0", 4 + "title": "Rockbox HTTP API", 5 + "version": "1.0.0", 6 + "summary": "HTTP REST API for rockboxd — playback, library, playlists, devices, settings.", 7 + "description": "Rockbox Zig exposes its full feature surface over HTTP REST on port 6063 (configurable via `ROCKBOX_TCP_PORT`). It complements the GraphQL API on :6062 and the gRPC API on :6061 — all three speak to the same in-process state.\n\nAll handlers are defined in `crates/server/src/handlers/` and registered in `crates/server/src/lib.rs:run_http_server()`.", 9 8 "contact": { 10 - "name": "Tsiry Sandratraina", 11 - "email": "tsiry.sndr@fluentci.io" 9 + "name": "Rockbox Zig", 10 + "url": "https://github.com/tsirysndr/rockbox-zig" 12 11 }, 13 - "summary": "Retrieve metadata from Rockbox Library, control playback.", 14 - "description": "", 15 - "x-logo": { 16 - "url": "https://avatars.githubusercontent.com/u/1327269?s=200&v=4" 12 + "license": { 13 + "name": "GPL-2.0", 14 + "url": "https://www.gnu.org/licenses/old-licenses/gpl-2.0.html" 17 15 } 18 16 }, 19 17 "servers": [ 20 18 { 21 - "url": "http://localhost:6063" 19 + "url": "http://localhost:6063", 20 + "description": "Default local rockboxd" 22 21 } 23 22 ], 23 + "tags": [ 24 + { "name": "Albums" }, 25 + { "name": "Artists" }, 26 + { "name": "Tracks" }, 27 + { "name": "Search" }, 28 + { "name": "Browse" }, 29 + { "name": "Player" }, 30 + { "name": "Playlist (queue)" }, 31 + { "name": "Saved playlists" }, 32 + { "name": "Smart playlists" }, 33 + { "name": "Track stats" }, 34 + { "name": "Devices" }, 35 + { "name": "Settings" }, 36 + { "name": "System" }, 37 + { "name": "Bluetooth" } 38 + ], 24 39 "paths": { 25 40 "/albums": { 26 41 "get": { 27 - "summary": "Get Several Albums", 28 - "tags": [ 29 - "Albums" 30 - ], 42 + "operationId": "getAlbums", 43 + "tags": ["Albums"], 44 + "summary": "List all albums", 31 45 "responses": { 32 46 "200": { 33 - "description": "OK", 47 + "description": "Array of albums", 34 48 "content": { 35 49 "application/json": { 36 - "schema": { 37 - "type": "array", 38 - "items": { 39 - "$ref": "#/components/schemas/Album.v1", 40 - "x-stoplight": { 41 - "id": "dhzrs9eun7c4r" 42 - } 43 - } 44 - } 50 + "schema": { "type": "array", "items": { "$ref": "#/components/schemas/Album" } } 45 51 } 46 52 } 47 53 } 48 - }, 49 - "operationId": "get-albums", 50 - "parameters": [ 51 - { 52 - "schema": { 53 - "type": "integer" 54 - }, 55 - "in": "query", 56 - "name": "limit", 57 - "description": "The maximum number of items to return. Default: 20. Minimum: 1. Maximum: 50." 58 - }, 59 - { 60 - "schema": { 61 - "type": "integer" 62 - }, 63 - "in": "query", 64 - "name": "offset", 65 - "description": "The index of the first item to return. Default: 0 (the first item). Use with limit to get the next set of items." 66 - } 67 - ] 54 + } 68 55 } 69 56 }, 70 57 "/albums/{id}": { 71 - "parameters": [ 72 - { 73 - "schema": { 74 - "type": "string" 75 - }, 76 - "name": "id", 77 - "in": "path", 78 - "required": true 79 - } 80 - ], 81 58 "get": { 82 - "summary": "Get Album", 83 - "tags": [ 84 - "Albums" 85 - ], 59 + "operationId": "getAlbum", 60 + "tags": ["Albums"], 61 + "summary": "Get an album by id", 62 + "parameters": [{ "$ref": "#/components/parameters/IdPath" }], 86 63 "responses": { 87 - "200": { 88 - "description": "OK", 89 - "content": { 90 - "application/json": { 91 - "schema": { 92 - "$ref": "#/components/schemas/Album.v1" 93 - } 94 - } 95 - } 96 - }, 97 - "404": { 98 - "description": "Not Found" 99 - } 100 - }, 101 - "operationId": "get-albums-id" 64 + "200": { "description": "Album", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Album" } } } }, 65 + "404": { "$ref": "#/components/responses/NotFound" } 66 + } 102 67 } 103 68 }, 104 69 "/albums/{id}/tracks": { 105 - "parameters": [ 106 - { 107 - "schema": { 108 - "type": "string" 109 - }, 110 - "name": "id", 111 - "in": "path", 112 - "required": true 113 - } 114 - ], 115 70 "get": { 116 - "summary": "Get Album Tracks", 71 + "operationId": "getAlbumTracks", 72 + "tags": ["Albums"], 73 + "summary": "List tracks in an album", 74 + "parameters": [{ "$ref": "#/components/parameters/IdPath" }], 117 75 "responses": { 118 76 "200": { 119 - "description": "OK", 120 - "content": { 121 - "application/json": { 122 - "schema": { 123 - "type": "array", 124 - "items": { 125 - "$ref": "#/components/schemas/Track.v1", 126 - "x-stoplight": { 127 - "id": "lesjtxruzxhwd" 128 - } 129 - } 130 - } 131 - } 132 - } 77 + "description": "Tracks belonging to the album", 78 + "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/Track" } } } } 133 79 } 134 - }, 135 - "operationId": "get-albums-id-tracks", 136 - "parameters": [ 137 - { 138 - "schema": { 139 - "type": "integer" 140 - }, 141 - "in": "query", 142 - "name": "limit", 143 - "description": "The maximum number of items to return. Default: 20. Minimum: 1. Maximum: 50." 144 - }, 145 - { 146 - "schema": { 147 - "type": "integer" 148 - }, 149 - "in": "query", 150 - "name": "offset", 151 - "description": "The index of the first item to return. Default: 0 (the first item). Use with limit to get the next set of items." 152 - } 153 - ], 154 - "tags": [ 155 - "Albums" 156 - ] 80 + } 157 81 } 158 82 }, 159 83 "/artists": { 160 84 "get": { 161 - "summary": "Get Several Artists", 162 - "tags": [ 163 - "Artists" 164 - ], 85 + "operationId": "getArtists", 86 + "tags": ["Artists"], 87 + "summary": "List all artists", 165 88 "responses": { 166 - "200": { 167 - "description": "OK", 168 - "content": { 169 - "application/json": { 170 - "schema": { 171 - "type": "array", 172 - "items": { 173 - "$ref": "#/components/schemas/Artist.v1", 174 - "x-stoplight": { 175 - "id": "fjrt0wk35qlt5" 176 - } 177 - } 178 - } 179 - } 180 - } 181 - } 182 - }, 183 - "operationId": "get-artists", 184 - "parameters": [ 185 - { 186 - "schema": { 187 - "type": "integer" 188 - }, 189 - "in": "query", 190 - "name": "limit", 191 - "description": "The maximum number of items to return. Default: 20. Minimum: 1. Maximum: 50." 192 - }, 193 - { 194 - "schema": { 195 - "type": "integer" 196 - }, 197 - "in": "query", 198 - "name": "offset", 199 - "description": "The index of the first item to return. Default: 0 (the first item). Use with limit to get the next set of items." 200 - } 201 - ] 89 + "200": { "description": "Array of artists", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/Artist" } } } } } 90 + } 202 91 } 203 92 }, 204 93 "/artists/{id}": { 205 - "parameters": [ 206 - { 207 - "schema": { 208 - "type": "string" 209 - }, 210 - "name": "id", 211 - "in": "path", 212 - "required": true 94 + "get": { 95 + "operationId": "getArtist", 96 + "tags": ["Artists"], 97 + "summary": "Get an artist by id", 98 + "parameters": [{ "$ref": "#/components/parameters/IdPath" }], 99 + "responses": { 100 + "200": { "description": "Artist", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Artist" } } } }, 101 + "404": { "$ref": "#/components/responses/NotFound" } 102 + } 103 + } 104 + }, 105 + "/artists/{id}/albums": { 106 + "get": { 107 + "operationId": "getArtistAlbums", 108 + "tags": ["Artists"], 109 + "summary": "List albums by an artist", 110 + "parameters": [{ "$ref": "#/components/parameters/IdPath" }], 111 + "responses": { 112 + "200": { "description": "Albums by the artist", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/Album" } } } } } 213 113 } 214 - ], 114 + } 115 + }, 116 + "/artists/{id}/tracks": { 215 117 "get": { 216 - "summary": "Get Artist", 217 - "tags": [ 218 - "Artists" 219 - ], 118 + "operationId": "getArtistTracks", 119 + "tags": ["Artists"], 120 + "summary": "List tracks by an artist", 121 + "parameters": [{ "$ref": "#/components/parameters/IdPath" }], 220 122 "responses": { 221 - "200": { 222 - "description": "OK", 223 - "content": { 224 - "application/json": { 225 - "schema": { 226 - "type": "array", 227 - "items": { 228 - "$ref": "#/components/schemas/Artist.v1", 229 - "x-stoplight": { 230 - "id": "m69bd1mn62bxr" 231 - } 232 - } 233 - } 234 - } 235 - } 236 - }, 237 - "404": { 238 - "description": "Not Found" 239 - } 240 - }, 241 - "operationId": "get-artists-id" 123 + "200": { "description": "Tracks by the artist", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/Track" } } } } } 124 + } 242 125 } 243 126 }, 244 127 "/tracks": { 245 128 "get": { 246 - "summary": "Get Several Tracks", 129 + "operationId": "getTracks", 130 + "tags": ["Tracks"], 131 + "summary": "List all tracks in the library", 247 132 "responses": { 248 - "200": { 249 - "description": "OK", 250 - "headers": {}, 251 - "content": { 252 - "application/json": { 253 - "schema": { 254 - "type": "array", 255 - "items": { 256 - "$ref": "#/components/schemas/Track.v1", 257 - "x-stoplight": { 258 - "id": "ara2m1lvcmddb" 259 - } 260 - } 133 + "200": { "description": "All tracks", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/Track" } } } } } 134 + } 135 + } 136 + }, 137 + "/tracks/{id}": { 138 + "get": { 139 + "operationId": "getTrack", 140 + "tags": ["Tracks"], 141 + "summary": "Get a track by id", 142 + "parameters": [{ "$ref": "#/components/parameters/IdPath" }], 143 + "responses": { 144 + "200": { "description": "Track", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Track" } } } }, 145 + "404": { "$ref": "#/components/responses/NotFound" } 146 + } 147 + } 148 + }, 149 + "/tracks/stream-metadata": { 150 + "put": { 151 + "operationId": "saveStreamTrackMetadata", 152 + "tags": ["Tracks"], 153 + "summary": "Persist metadata for an HTTP stream URL", 154 + "description": "Used by the player when a remote URL is loaded — saves title/artist/album/duration so the URL can be resolved from the DB later.", 155 + "requestBody": { 156 + "required": true, 157 + "content": { 158 + "application/json": { 159 + "schema": { 160 + "type": "object", 161 + "required": ["url", "title", "artist", "album", "duration_ms"], 162 + "properties": { 163 + "url": { "type": "string", "format": "uri" }, 164 + "title": { "type": "string" }, 165 + "artist": { "type": "string" }, 166 + "album": { "type": "string" }, 167 + "duration_ms": { "type": "integer", "format": "int32", "minimum": 0 } 261 168 } 262 169 } 263 170 } 264 171 } 265 172 }, 266 - "operationId": "get-tracks", 267 - "requestBody": { 268 - "content": {}, 269 - "description": "" 270 - }, 271 - "x-internal": false, 272 - "parameters": [ 273 - { 274 - "schema": { 275 - "type": "integer" 276 - }, 277 - "in": "query", 278 - "name": "limit", 279 - "description": "The maximum number of items to return. Default: 20. Minimum: 1. Maximum: 50." 280 - }, 281 - { 282 - "schema": { 283 - "type": "integer" 284 - }, 285 - "in": "query", 286 - "name": "offset", 287 - "description": "The index of the first item to return. Default: 0 (the first item). Use with limit to get the next set of items." 288 - } 289 - ], 290 - "tags": [ 291 - "Tracks" 292 - ] 173 + "responses": { "204": { "description": "Stored" } } 293 174 } 294 175 }, 295 - "/tracks/{id}": { 296 - "parameters": [ 297 - { 298 - "schema": { 299 - "type": "string" 300 - }, 301 - "name": "id", 302 - "in": "path", 303 - "required": true 304 - } 305 - ], 176 + "/search": { 306 177 "get": { 307 - "summary": "Get Track", 178 + "operationId": "search", 179 + "tags": ["Search"], 180 + "summary": "Full-text search powered by Typesense", 181 + "parameters": [ 182 + { "name": "q", "in": "query", "schema": { "type": "string" }, "description": "Search term" } 183 + ], 308 184 "responses": { 309 185 "200": { 310 - "description": "OK", 186 + "description": "Matching tracks, albums and artists", 311 187 "content": { 312 188 "application/json": { 313 189 "schema": { 314 - "$ref": "#/components/schemas/Track.v1" 315 - }, 316 - "examples": { 317 - "Track": { 318 - "value": { 319 - "album": "2014 Forest Hills Drive", 320 - "album_artist": "J. Cole", 321 - "album_id": "cm1ibibnn00043fj7ppakq9w9", 322 - "artist": "J. Cole", 323 - "artist_id": "cm1ibibnn00033fj7hy019na5", 324 - "bitrate": 96, 325 - "composer": "Roy Hammond, Jermaine Cole, CHARLES SIMMONS", 326 - "created_at": 1727296114, 327 - "disc_number": 1, 328 - "filesize": 2872773, 329 - "frequency": 44100, 330 - "genre_id": "", 331 - "id": "cm1ibibnn00053fj79ghe6b2d", 332 - "length": 239381, 333 - "md5": "b5a0d86e156e6d02c90f647bf5cc6fc2", 334 - "path": "/home/coder/Music/03 - J. Cole - Wet Dreamz(Explicit).m4a", 335 - "title": "Wet Dreamz", 336 - "track_number": 3, 337 - "updated_at": 1727296114, 338 - "year": 2014, 339 - "year_string": "2014-12-09" 340 - } 190 + "type": "object", 191 + "properties": { 192 + "tracks": { "type": "array", "items": { "$ref": "#/components/schemas/Track" } }, 193 + "albums": { "type": "array", "items": { "$ref": "#/components/schemas/Album" } }, 194 + "artists": { "type": "array", "items": { "$ref": "#/components/schemas/Artist" } } 341 195 } 342 196 } 343 197 } 344 - }, 345 - "headers": {} 346 - }, 347 - "404": { 348 - "description": "Not Found" 198 + } 349 199 } 350 - }, 351 - "operationId": "get-tracks-trackid", 352 - "tags": [ 353 - "Tracks" 354 - ] 200 + } 355 201 } 356 202 }, 357 - "/artists/{id}/albums": { 358 - "parameters": [ 359 - { 360 - "schema": { 361 - "type": "string" 362 - }, 363 - "name": "id", 364 - "in": "path", 365 - "required": true 366 - } 367 - ], 203 + "/browse/tree-entries": { 368 204 "get": { 369 - "summary": "Get Artist's Albums", 370 - "tags": [ 371 - "artists" 372 - ], 373 - "responses": {}, 374 - "operationId": "get-artists-id-albums", 205 + "operationId": "getTreeEntries", 206 + "tags": ["Browse"], 207 + "summary": "Browse the filesystem under music_dir", 375 208 "parameters": [ 376 - { 377 - "schema": { 378 - "type": "integer" 379 - }, 380 - "in": "query", 381 - "name": "limit", 382 - "description": "The maximum number of items to return. Default: 20. Minimum: 1. Maximum: 50." 383 - }, 384 - { 385 - "schema": { 386 - "type": "integer" 387 - }, 388 - "in": "query", 389 - "name": "offset", 390 - "description": "The index of the first item to return. Default: 0 (the first item). Use with limit to get the next set of items." 391 - } 392 - ] 209 + { "name": "q", "in": "query", "schema": { "type": "string" }, "description": "Path to list (absolute). Defaults to $ROCKBOX_LIBRARY or $HOME/Music." }, 210 + { "name": "show_hidden", "in": "query", "schema": { "type": "string", "enum": ["true", "false"] }, "description": "Show dotfiles (default false)" } 211 + ], 212 + "responses": { 213 + "200": { "description": "Directory entries", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/TreeEntry" } } } } }, 214 + "500": { "description": "Path is not a directory or other I/O error" } 215 + } 393 216 } 394 217 }, 395 - "/browse/tree-entries": { 218 + "/player": { 396 219 "get": { 397 - "summary": "Get Entries", 398 - "tags": [ 399 - "Browse" 400 - ], 401 - "responses": {}, 402 - "operationId": "get-tree_entries", 403 - "parameters": [ 404 - { 405 - "schema": { 406 - "type": "string" 407 - }, 408 - "in": "query", 409 - "name": "q", 410 - "description": "path" 411 - }, 412 - { 413 - "schema": { 414 - "type": "boolean" 415 - }, 416 - "in": "query", 417 - "name": "show_hidden" 418 - } 419 - ] 420 - }, 421 - "parameters": [] 220 + "operationId": "getCurrentPlayer", 221 + "tags": ["Player"], 222 + "summary": "Get the current output device", 223 + "responses": { 224 + "200": { "description": "Current device", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Device" } } } } 225 + } 226 + } 227 + }, 228 + "/player/load": { 229 + "put": { 230 + "operationId": "loadTracks", 231 + "tags": ["Player"], 232 + "summary": "Load tracks into an external player (Cast/AirPlay)", 233 + "requestBody": { 234 + "required": true, 235 + "content": { "application/json": { "schema": { "$ref": "#/components/schemas/LoadTracks" } } } 236 + }, 237 + "responses": { 238 + "200": { "description": "Loaded" }, 239 + "404": { "description": "No external player connected or no playable tracks" } 240 + } 241 + } 422 242 }, 423 243 "/player/play": { 424 - "parameters": [], 425 244 "put": { 426 - "summary": "Play", 427 - "operationId": "put-player-play", 428 - "responses": { 429 - "200": { 430 - "description": "OK" 431 - } 432 - }, 433 - "tags": [ 434 - "Player" 435 - ], 245 + "operationId": "play", 246 + "tags": ["Player"], 247 + "summary": "Start playback at an offset", 436 248 "parameters": [ 437 - { 438 - "schema": { 439 - "type": "integer" 440 - }, 441 - "in": "query", 442 - "name": "elapsed" 443 - }, 444 - { 445 - "schema": { 446 - "type": "integer" 447 - }, 448 - "in": "query", 449 - "name": "offset" 450 - } 451 - ] 249 + { "name": "elapsed", "in": "query", "schema": { "type": "integer", "format": "int64" } }, 250 + { "name": "offset", "in": "query", "schema": { "type": "integer", "format": "int64" } } 251 + ], 252 + "responses": { "200": { "description": "Playback started" } } 452 253 } 453 254 }, 255 + "/player/pause": { 256 + "put": { "operationId": "pause", "tags": ["Player"], "summary": "Pause playback", "responses": { "200": { "description": "Paused" } } } 257 + }, 258 + "/player/resume": { 259 + "put": { "operationId": "resume", "tags": ["Player"], "summary": "Resume playback", "responses": { "200": { "description": "Resumed" } } } 260 + }, 454 261 "/player/ff-rewind": { 455 262 "put": { 456 - "summary": "Fast Forward Rewind", 457 - "operationId": "put-player-ff_rewind", 458 - "responses": { 459 - "200": { 460 - "description": "OK" 461 - } 462 - }, 463 - "tags": [ 464 - "Player" 465 - ] 466 - }, 467 - "parameters": [] 263 + "operationId": "ffRewind", 264 + "tags": ["Player"], 265 + "summary": "Seek to an absolute position (ms)", 266 + "parameters": [{ "name": "newtime", "in": "query", "required": true, "schema": { "type": "integer", "format": "int32" } }], 267 + "responses": { "200": { "description": "Seeked" } } 268 + } 468 269 }, 469 270 "/player/status": { 470 271 "get": { 471 - "summary": "Get Playback Status", 472 - "tags": [ 473 - "Player" 474 - ], 475 - "responses": {}, 476 - "operationId": "get-player-status" 272 + "operationId": "getStatus", 273 + "tags": ["Player"], 274 + "summary": "Get the playback status", 275 + "responses": { 276 + "200": { "description": "Audio status", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AudioStatus" } } } } 277 + } 477 278 } 478 279 }, 479 280 "/player/current-track": { 480 281 "get": { 481 - "summary": "Get Currently Playing Track", 482 - "tags": [ 483 - "Player" 484 - ], 282 + "operationId": "getCurrentTrack", 283 + "tags": ["Player"], 284 + "summary": "Get the currently playing track", 485 285 "responses": { 486 286 "200": { 487 - "description": "OK", 488 - "content": { 489 - "application/json": { 490 - "schema": { 491 - "$ref": "#/components/schemas/Track.v1" 492 - } 493 - } 494 - } 287 + "description": "Currently playing Mp3Entry, or null if nothing is playing", 288 + "content": { "application/json": { "schema": { "oneOf": [ { "$ref": "#/components/schemas/Mp3Entry" }, { "type": "null" } ] } } } 495 289 } 496 - }, 497 - "operationId": "get-player-current-track", 498 - "description": "Get the object currently being played " 290 + } 499 291 } 500 292 }, 501 293 "/player/next-track": { 502 294 "get": { 503 - "summary": "Get Next Track", 504 - "tags": [ 505 - "Player" 506 - ], 295 + "operationId": "getNextTrack", 296 + "tags": ["Player"], 297 + "summary": "Get the next queued track", 507 298 "responses": { 508 299 "200": { 509 - "description": "OK", 510 - "content": { 511 - "application/json": { 512 - "schema": { 513 - "$ref": "#/components/schemas/Track.v1" 514 - } 515 - } 516 - } 300 + "description": "Next Mp3Entry, or null if at end of queue", 301 + "content": { "application/json": { "schema": { "oneOf": [ { "$ref": "#/components/schemas/Mp3Entry" }, { "type": "null" } ] } } } 517 302 } 518 - }, 519 - "operationId": "get-player-next-track", 520 - "description": "Get the next track" 303 + } 521 304 } 522 305 }, 523 306 "/player/flush-and-reload-tracks": { 524 - "put": { 525 - "summary": "Flush And Reload Tracks", 526 - "operationId": "put-player-flush-and-reload-tracks", 307 + "put": { "operationId": "flushAndReloadTracks", "tags": ["Player"], "summary": "Flush PCM buffers and reload the current queue", "responses": { "200": { "description": "Reloaded" } } } 308 + }, 309 + "/player/next": { 310 + "put": { "operationId": "next", "tags": ["Player"], "summary": "Skip to the next track", "responses": { "200": { "description": "Skipped" } } } 311 + }, 312 + "/player/previous": { 313 + "put": { "operationId": "previous", "tags": ["Player"], "summary": "Skip to the previous track", "responses": { "200": { "description": "Skipped" } } } 314 + }, 315 + "/player/stop": { 316 + "put": { "operationId": "stop", "tags": ["Player"], "summary": "Hard-stop playback", "responses": { "200": { "description": "Stopped" } } } 317 + }, 318 + "/player/file-position": { 319 + "get": { 320 + "operationId": "getFilePosition", 321 + "tags": ["Player"], 322 + "summary": "Get the current byte offset in the playing file", 527 323 "responses": { 528 - "200": { 529 - "description": "OK" 530 - } 531 - }, 532 - "tags": [ 533 - "Player" 534 - ] 324 + "200": { "description": "Byte offset", "content": { "application/json": { "schema": { "type": "integer", "format": "int64" } } } } 325 + } 535 326 } 536 327 }, 537 - "/settings": { 328 + "/player/volume": { 538 329 "get": { 539 - "summary": "Get Global Settings", 540 - "tags": [ 541 - "Settings" 542 - ], 330 + "operationId": "getVolume", 331 + "tags": ["Player"], 332 + "summary": "Get current volume range and value", 543 333 "responses": { 544 334 "200": { 545 - "description": "OK", 546 - "content": { 547 - "application/json": { 548 - "schema": { 549 - "$ref": "#/components/schemas/Settings.v1" 550 - } 335 + "description": "Volume info", 336 + "content": { "application/json": { "schema": { 337 + "type": "object", 338 + "properties": { 339 + "volume": { "type": "integer", "format": "int32" }, 340 + "min": { "type": "integer", "format": "int32" }, 341 + "max": { "type": "integer", "format": "int32" } 551 342 } 552 - } 343 + } } } 553 344 } 554 - }, 555 - "operationId": "get-settings" 345 + } 556 346 }, 557 347 "put": { 558 - "summary": "Update Settings", 559 - "operationId": "put-settings", 560 - "responses": { 561 - "200": { 562 - "description": "OK" 563 - } 564 - }, 565 - "tags": [ 566 - "Settings" 567 - ], 348 + "operationId": "adjustVolume", 349 + "tags": ["Player"], 350 + "summary": "Adjust volume by N firmware-defined steps", 568 351 "requestBody": { 569 - "content": { 570 - "application/json": { 571 - "schema": { 572 - "type": "object", 573 - "properties": { 574 - "music_dir": { 575 - "type": "string", 576 - "x-stoplight": { 577 - "id": "s5w686bbel2ow" 578 - } 579 - }, 580 - "playlist_shuffle": { 581 - "type": "boolean", 582 - "x-stoplight": { 583 - "id": "74lbit2keyuc1" 584 - } 585 - }, 586 - "repeat_mode": { 587 - "type": "integer", 588 - "x-stoplight": { 589 - "id": "tukzj0gpepqxd" 590 - } 591 - }, 592 - "bass": { 593 - "type": "integer", 594 - "x-stoplight": { 595 - "id": "6t0mo3j9ii8le" 596 - } 597 - }, 598 - "treble": { 599 - "type": "integer", 600 - "x-stoplight": { 601 - "id": "m6ps5g53ql35f" 602 - } 603 - }, 604 - "bass_cutoff": { 605 - "type": "integer", 606 - "x-stoplight": { 607 - "id": "btltz18zg0wh3" 608 - } 609 - }, 610 - "treble_cutoff": { 611 - "type": "integer", 612 - "x-stoplight": { 613 - "id": "ctcf9ue3n09oj" 614 - } 615 - }, 616 - "crossfade": { 617 - "type": "integer", 618 - "x-stoplight": { 619 - "id": "1wmv1tkgdlb33" 620 - } 621 - }, 622 - "fade_on_stop": { 623 - "type": "boolean", 624 - "x-stoplight": { 625 - "id": "y2gm9cth8mkgn" 626 - } 627 - }, 628 - "fade_in_delay": { 629 - "type": "integer", 630 - "x-stoplight": { 631 - "id": "gdikoz2jfg82y" 632 - } 633 - }, 634 - "fade_in_duration": { 635 - "type": "integer", 636 - "x-stoplight": { 637 - "id": "w32xyzy8n607i" 638 - } 639 - }, 640 - "fade_out_delay": { 641 - "type": "integer", 642 - "x-stoplight": { 643 - "id": "sr257acz1kkbl" 644 - } 645 - }, 646 - "fade_out_duration": { 647 - "type": "integer", 648 - "x-stoplight": { 649 - "id": "lbrm6uqfk0wix" 650 - } 651 - }, 652 - "fade_out_mixmode": { 653 - "type": "integer", 654 - "x-stoplight": { 655 - "id": "m8b0tu8h0f5nq" 656 - } 657 - }, 658 - "balance": { 659 - "type": "integer", 660 - "x-stoplight": { 661 - "id": "ilnhsvl03we54" 662 - } 663 - }, 664 - "stereo_width": { 665 - "type": "integer", 666 - "x-stoplight": { 667 - "id": "sk1xaig7kukxo" 668 - } 669 - }, 670 - "stereosw_mode": { 671 - "type": "integer", 672 - "x-stoplight": { 673 - "id": "vlyej7vc8e4um" 674 - } 675 - }, 676 - "surround_enabled": { 677 - "type": "integer", 678 - "x-stoplight": { 679 - "id": "yfvcl8ps2obmp" 680 - } 681 - }, 682 - "surround_balance": { 683 - "type": "integer", 684 - "x-stoplight": { 685 - "id": "66mf6d8w8f2q7" 686 - } 687 - }, 688 - "surround_fx1": { 689 - "type": "integer", 690 - "x-stoplight": { 691 - "id": "3hug8gn2kiymj" 692 - } 693 - }, 694 - "surround_fx2": { 695 - "type": "integer", 696 - "x-stoplight": { 697 - "id": "uxjs9bez3bhen" 698 - } 699 - }, 700 - "party_mode": { 701 - "type": "boolean", 702 - "x-stoplight": { 703 - "id": "4w5jl41gjhbaq" 704 - } 705 - }, 706 - "channel_config": { 707 - "type": "integer", 708 - "x-stoplight": { 709 - "id": "76h4qwztxv3jg" 710 - } 711 - }, 712 - "player_name": { 713 - "type": "string", 714 - "x-stoplight": { 715 - "id": "kx3laxpui8wn3" 716 - } 717 - }, 718 - "eq_enabled": { 719 - "type": "boolean", 720 - "x-stoplight": { 721 - "id": "8n2wj0eul68hv" 722 - } 723 - }, 724 - "eq_band_settings": { 725 - "type": "array", 726 - "x-stoplight": { 727 - "id": "tvp7sid0vwyc4" 728 - }, 729 - "items": { 730 - "x-stoplight": { 731 - "id": "o30uc4yx8gac5" 732 - }, 733 - "type": "object", 734 - "properties": { 735 - "cutoff": { 736 - "type": "integer", 737 - "x-stoplight": { 738 - "id": "xbwoyf2cghtse" 739 - } 740 - }, 741 - "q": { 742 - "type": "integer", 743 - "x-stoplight": { 744 - "id": "ix73oc8eroz08" 745 - } 746 - }, 747 - "gain": { 748 - "type": "integer", 749 - "x-stoplight": { 750 - "id": "b941ccqlswavi" 751 - } 752 - } 753 - } 754 - } 755 - }, 756 - "replaygain_settings": { 757 - "type": "object", 758 - "x-stoplight": { 759 - "id": "p31bpzizz4hbf" 760 - }, 761 - "properties": { 762 - "noclip": { 763 - "type": "boolean", 764 - "x-stoplight": { 765 - "id": "2uu5oesku4vq1" 766 - } 767 - }, 768 - "type": { 769 - "type": "integer", 770 - "x-stoplight": { 771 - "id": "evpt8uo1ak8rb" 772 - } 773 - }, 774 - "preamp": { 775 - "type": "integer", 776 - "x-stoplight": { 777 - "id": "99letqgynncba" 778 - } 779 - } 780 - } 781 - } 782 - } 783 - } 784 - } 785 - } 352 + "required": true, 353 + "content": { "application/json": { "schema": { "$ref": "#/components/schemas/NewVolume" } } } 354 + }, 355 + "responses": { 356 + "200": { "description": "Adjusted", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/NewVolume" } } } } 786 357 } 787 358 } 788 359 }, 789 - "/status": { 790 - "get": { 791 - "summary": "Get System Status", 792 - "tags": [ 793 - "System" 794 - ], 795 - "responses": { 796 - "200": { 797 - "description": "OK", 798 - "content": { 799 - "application/json": { 800 - "schema": { 801 - "type": "object", 802 - "properties": { 803 - "dircache_size": { 804 - "type": "integer" 805 - }, 806 - "last_screen": { 807 - "type": "integer" 808 - }, 809 - "last_volume_change": { 810 - "type": "integer" 811 - }, 812 - "resume_crc32": { 813 - "type": "integer" 814 - }, 815 - "resume_elapsed": { 816 - "type": "integer" 817 - }, 818 - "resume_index": { 819 - "type": "integer" 820 - }, 821 - "resume_offset": { 822 - "type": "integer" 823 - }, 824 - "runtime": { 825 - "type": "integer" 826 - }, 827 - "topruntime": { 828 - "type": "integer" 829 - }, 830 - "viewer_icon_count": { 831 - "type": "integer" 832 - } 833 - }, 834 - "x-examples": { 835 - "Example 1": { 836 - "dircache_size": -1, 837 - "last_screen": 0, 838 - "last_volume_change": -1, 839 - "resume_crc32": 4294967295, 840 - "resume_elapsed": 4294967295, 841 - "resume_index": -1, 842 - "resume_offset": 4294967295, 843 - "runtime": 40599, 844 - "topruntime": 40599, 845 - "viewer_icon_count": 0 846 - } 847 - } 848 - }, 849 - "examples": { 850 - "Example 1": { 851 - "value": { 852 - "dircache_size": -1, 853 - "last_screen": 0, 854 - "last_volume_change": -1, 855 - "resume_crc32": 4294967295, 856 - "resume_elapsed": 4294967295, 857 - "resume_index": -1, 858 - "resume_offset": 4294967295, 859 - "runtime": 40599, 860 - "topruntime": 40599, 861 - "viewer_icon_count": 0 862 - } 863 - } 864 - } 865 - } 866 - } 867 - } 360 + "/playlists": { 361 + "post": { 362 + "operationId": "createPlaylist", 363 + "tags": ["Playlist (queue)"], 364 + "summary": "Replace the live queue with a new playlist", 365 + "requestBody": { 366 + "required": true, 367 + "content": { "application/json": { "schema": { "$ref": "#/components/schemas/NewPlaylist" } } } 868 368 }, 869 - "operationId": "get-status" 369 + "responses": { 370 + "200": { "description": "Start index of the new playlist (as plain text)", "content": { "text/plain": { "schema": { "type": "string" } } } } 371 + } 870 372 } 871 373 }, 872 - "/version": { 873 - "get": { 874 - "summary": "Get Rockbox Version", 875 - "tags": [ 876 - "System" 374 + "/playlists/start": { 375 + "put": { 376 + "operationId": "startPlaylist", 377 + "tags": ["Playlist (queue)"], 378 + "summary": "Start playback at a queue index", 379 + "parameters": [ 380 + { "name": "start_index", "in": "query", "schema": { "type": "integer", "format": "int32", "default": 0 } }, 381 + { "name": "elapsed", "in": "query", "schema": { "type": "integer", "format": "int64", "default": 0 } }, 382 + { "name": "offset", "in": "query", "schema": { "type": "integer", "format": "int64", "default": 0 } } 877 383 ], 878 - "responses": { 879 - "200": { 880 - "description": "OK", 881 - "content": { 882 - "application/json": { 883 - "schema": { 884 - "type": "object", 885 - "properties": { 886 - "version": { 887 - "type": "string" 888 - } 889 - }, 890 - "x-examples": { 891 - "Example 1": { 892 - "version": "7ca0a9895d-240918" 893 - } 894 - } 895 - }, 896 - "examples": { 897 - "Example 1": { 898 - "value": { 899 - "version": "7ca0a9895d-240918" 900 - } 901 - } 902 - } 903 - } 904 - } 905 - } 906 - }, 907 - "operationId": "get-version" 384 + "responses": { "200": { "description": "Started" } } 908 385 } 909 386 }, 910 - "/player/resume": { 387 + "/playlists/shuffle": { 911 388 "put": { 912 - "summary": "Resume Playback", 913 - "operationId": "put-player-resume", 389 + "operationId": "shufflePlaylist", 390 + "tags": ["Playlist (queue)"], 391 + "summary": "Shuffle the live queue", 392 + "parameters": [ 393 + { "name": "start_index", "in": "query", "schema": { "type": "integer", "format": "int32", "default": 0 } } 394 + ], 395 + "responses": { "200": { "description": "Shuffle return code (text)", "content": { "text/plain": { "schema": { "type": "string" } } } } } 396 + } 397 + }, 398 + "/playlists/amount": { 399 + "get": { 400 + "operationId": "getPlaylistAmount", 401 + "tags": ["Playlist (queue)"], 402 + "summary": "Number of tracks in the live queue", 914 403 "responses": { 915 - "200": { 916 - "description": "OK" 917 - } 918 - }, 919 - "tags": [ 920 - "Player" 921 - ] 404 + "200": { "description": "Amount", "content": { "application/json": { "schema": { "type": "object", "properties": { "amount": { "type": "integer", "format": "int32" } } } } } } 405 + } 922 406 } 923 407 }, 924 - "/player/pause": { 408 + "/playlists/resume": { 925 409 "put": { 926 - "summary": "Pause Playback", 927 - "operationId": "put-player-pause", 928 - "responses": { 929 - "200": { 930 - "description": "OK" 931 - } 932 - }, 933 - "tags": [ 934 - "Player" 935 - ] 410 + "operationId": "resumePlaylist", 411 + "tags": ["Playlist (queue)"], 412 + "summary": "Resume the saved control file playlist", 413 + "responses": { "200": { "description": "Status code", "content": { "application/json": { "schema": { "type": "object", "properties": { "code": { "type": "integer", "format": "int32" } } } } } } } 936 414 } 937 415 }, 938 - "/player/next": { 416 + "/playlists/resume-track": { 939 417 "put": { 940 - "summary": "Play Next Track", 941 - "operationId": "put-player-next", 418 + "operationId": "resumeTrack", 419 + "tags": ["Playlist (queue)"], 420 + "summary": "Resume the saved track at its previous offset", 421 + "responses": { "200": { "description": "Resumed" } } 422 + } 423 + }, 424 + "/playlists/{id}/tracks": { 425 + "parameters": [{ "$ref": "#/components/parameters/IdPath" }], 426 + "get": { 427 + "operationId": "getPlaylistTracks", 428 + "tags": ["Playlist (queue)"], 429 + "summary": "List tracks currently in the queue", 942 430 "responses": { 943 - "200": { 944 - "description": "OK" 945 - } 431 + "200": { "description": "Mp3Entry list", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/Mp3Entry" } } } } } 432 + } 433 + }, 434 + "post": { 435 + "operationId": "insertTracks", 436 + "tags": ["Playlist (queue)"], 437 + "summary": "Insert tracks into the live queue", 438 + "requestBody": { 439 + "required": true, 440 + "content": { "application/json": { "schema": { "$ref": "#/components/schemas/InsertTracks" } } } 946 441 }, 947 - "tags": [ 948 - "Player" 949 - ] 442 + "responses": { 443 + "200": { "description": "Insertion position or 0 (text)", "content": { "text/plain": { "schema": { "type": "string" } } } } 444 + } 445 + }, 446 + "delete": { 447 + "operationId": "removeTracks", 448 + "tags": ["Playlist (queue)"], 449 + "summary": "Remove tracks from the live queue", 450 + "requestBody": { 451 + "required": true, 452 + "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DeleteTracks" } } } 453 + }, 454 + "responses": { 455 + "200": { "description": "Result code (text)", "content": { "text/plain": { "schema": { "type": "string" } } } } 456 + } 950 457 } 951 458 }, 952 - "/player/previous": { 953 - "parameters": [], 954 - "put": { 955 - "summary": "Play Previous Track", 956 - "operationId": "put-player-prev", 459 + "/playlists/{id}": { 460 + "get": { 461 + "operationId": "getPlaylist", 462 + "tags": ["Playlist (queue)"], 463 + "summary": "Get the live queue and its metadata", 464 + "parameters": [{ "$ref": "#/components/parameters/IdPath" }], 957 465 "responses": { 958 - "200": { 959 - "description": "OK" 960 - } 961 - }, 962 - "tags": [ 963 - "Player" 964 - ] 466 + "200": { "description": "PlaylistInfo", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PlaylistInfo" } } } } 467 + } 965 468 } 966 469 }, 967 - "/player/stop": { 968 - "put": { 969 - "summary": "Stop Playback", 970 - "operationId": "put-player-stop", 470 + "/saved-playlists": { 471 + "get": { 472 + "operationId": "listSavedPlaylists", 473 + "tags": ["Saved playlists"], 474 + "summary": "List saved playlists, optionally filtered by folder", 475 + "parameters": [ 476 + { "name": "folder_id", "in": "query", "schema": { "type": "string" } } 477 + ], 971 478 "responses": { 972 - "200": { 973 - "description": "OK" 974 - } 479 + "200": { "description": "Saved playlists", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/SavedPlaylist" } } } } } 480 + } 481 + }, 482 + "post": { 483 + "operationId": "createSavedPlaylist", 484 + "tags": ["Saved playlists"], 485 + "summary": "Create a saved playlist", 486 + "requestBody": { 487 + "required": true, 488 + "content": { "application/json": { "schema": { 489 + "type": "object", 490 + "required": ["name"], 491 + "properties": { 492 + "name": { "type": "string" }, 493 + "description": { "type": "string" }, 494 + "image": { "type": "string" }, 495 + "folder_id": { "type": "string" }, 496 + "track_ids": { "type": "array", "items": { "type": "string" } } 497 + } 498 + } } } 975 499 }, 976 - "tags": [ 977 - "Player" 978 - ] 500 + "responses": { 501 + "201": { "description": "Created", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SavedPlaylist" } } } }, 502 + "400": { "description": "Missing required fields" } 503 + } 979 504 } 980 505 }, 981 - "/playlists": { 982 - "post": { 983 - "summary": "Create New Playlist", 984 - "operationId": "post-playlists", 506 + "/saved-playlists/{id}": { 507 + "parameters": [{ "$ref": "#/components/parameters/IdPath" }], 508 + "get": { 509 + "operationId": "getSavedPlaylist", 510 + "tags": ["Saved playlists"], 511 + "summary": "Get a saved playlist", 985 512 "responses": { 986 - "200": { 987 - "description": "OK", 988 - "content": {} 989 - } 990 - }, 513 + "200": { "description": "Saved playlist", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SavedPlaylist" } } } }, 514 + "404": { "$ref": "#/components/responses/NotFound" } 515 + } 516 + }, 517 + "put": { 518 + "operationId": "updateSavedPlaylist", 519 + "tags": ["Saved playlists"], 520 + "summary": "Update a saved playlist's metadata", 991 521 "requestBody": { 992 - "content": { 993 - "application/json": { 994 - "schema": { 995 - "type": "object", 996 - "properties": { 997 - "name": { 998 - "type": "string", 999 - "x-stoplight": { 1000 - "id": "xicf18cyr9u6y" 1001 - } 1002 - }, 1003 - "tracks": { 1004 - "type": "array", 1005 - "x-stoplight": { 1006 - "id": "jawv3jvtfvlik" 1007 - }, 1008 - "items": { 1009 - "x-stoplight": { 1010 - "id": "zgptcbpd8bpep" 1011 - }, 1012 - "type": "string" 1013 - } 1014 - } 1015 - } 1016 - }, 1017 - "examples": { 1018 - "Example 1": { 1019 - "value": { 1020 - "name": "demo", 1021 - "tracks": [ 1022 - "/home/coder/Music/03 - J. Cole - Wet Dreamz(Explicit).m4a" 1023 - ] 1024 - } 1025 - } 1026 - } 522 + "required": true, 523 + "content": { "application/json": { "schema": { 524 + "type": "object", 525 + "required": ["name"], 526 + "properties": { 527 + "name": { "type": "string" }, 528 + "description": { "type": "string" }, 529 + "image": { "type": "string" }, 530 + "folder_id": { "type": "string" } 1027 531 } 1028 - } 532 + } } } 1029 533 }, 1030 - "tags": [ 1031 - "Playlists" 1032 - ] 534 + "responses": { "204": { "description": "Updated" } } 535 + }, 536 + "delete": { 537 + "operationId": "deleteSavedPlaylist", 538 + "tags": ["Saved playlists"], 539 + "summary": "Delete a saved playlist", 540 + "responses": { "204": { "description": "Deleted" } } 1033 541 } 1034 542 }, 1035 - "/playlists/start": { 1036 - "put": { 1037 - "summary": "Start Playlist", 1038 - "operationId": "put-playlists-start", 543 + "/saved-playlists/{id}/tracks": { 544 + "parameters": [{ "$ref": "#/components/parameters/IdPath" }], 545 + "get": { 546 + "operationId": "getSavedPlaylistTracks", 547 + "tags": ["Saved playlists"], 548 + "summary": "List tracks in a saved playlist", 1039 549 "responses": { 1040 - "200": { 1041 - "description": "OK" 1042 - } 550 + "200": { "description": "Tracks", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/Track" } } } } } 551 + } 552 + }, 553 + "post": { 554 + "operationId": "addTracksToSavedPlaylist", 555 + "tags": ["Saved playlists"], 556 + "summary": "Add tracks to a saved playlist", 557 + "requestBody": { 558 + "required": true, 559 + "content": { "application/json": { "schema": { 560 + "type": "object", 561 + "required": ["track_ids"], 562 + "properties": { "track_ids": { "type": "array", "items": { "type": "string" } } } 563 + } } } 1043 564 }, 1044 - "tags": [ 1045 - "Playlists" 1046 - ], 1047 - "parameters": [ 1048 - { 1049 - "schema": { 1050 - "type": "integer" 1051 - }, 1052 - "in": "query", 1053 - "name": "start_index" 1054 - }, 1055 - { 1056 - "schema": { 1057 - "type": "integer" 1058 - }, 1059 - "in": "query", 1060 - "name": "elapsed" 1061 - }, 1062 - { 1063 - "schema": { 1064 - "type": "integer" 1065 - }, 1066 - "in": "query", 1067 - "name": "offset" 1068 - } 1069 - ] 565 + "responses": { "204": { "description": "Added" } } 1070 566 } 1071 567 }, 1072 - "/playlists/shuffle": { 1073 - "put": { 1074 - "summary": "Shuffle Playlist", 1075 - "operationId": "put-playlists-shuffle", 568 + "/saved-playlists/{id}/track-ids": { 569 + "get": { 570 + "operationId": "getSavedPlaylistTrackIds", 571 + "tags": ["Saved playlists"], 572 + "summary": "List track ids in a saved playlist", 573 + "parameters": [{ "$ref": "#/components/parameters/IdPath" }], 1076 574 "responses": { 1077 - "200": { 1078 - "description": "OK" 1079 - } 1080 - }, 1081 - "tags": [ 1082 - "Playlists" 575 + "200": { "description": "Track ids", "content": { "application/json": { "schema": { "type": "array", "items": { "type": "string" } } } } } 576 + } 577 + } 578 + }, 579 + "/saved-playlists/{id}/tracks/{track_id}": { 580 + "delete": { 581 + "operationId": "removeTrackFromSavedPlaylist", 582 + "tags": ["Saved playlists"], 583 + "summary": "Remove a track from a saved playlist", 584 + "parameters": [ 585 + { "$ref": "#/components/parameters/IdPath" }, 586 + { "name": "track_id", "in": "path", "required": true, "schema": { "type": "string" } } 1083 587 ], 1084 - "parameters": [ 1085 - { 1086 - "schema": { 1087 - "type": "number" 1088 - }, 1089 - "in": "query", 1090 - "name": "start_index" 1091 - } 1092 - ] 588 + "responses": { "204": { "description": "Removed" } } 1093 589 } 1094 590 }, 1095 - "/playlists/amount": { 1096 - "get": { 1097 - "summary": "Get Playlist Size", 1098 - "operationId": "get-playlists-amount", 591 + "/saved-playlists/{id}/play": { 592 + "post": { 593 + "operationId": "playSavedPlaylist", 594 + "tags": ["Saved playlists"], 595 + "summary": "Load a saved playlist into the queue and start playing", 596 + "parameters": [{ "$ref": "#/components/parameters/IdPath" }], 1099 597 "responses": { 1100 - "200": { 1101 - "description": "OK" 1102 - } 1103 - }, 1104 - "tags": [ 1105 - "Playlists" 1106 - ] 598 + "204": { "description": "Started" }, 599 + "422": { "description": "Empty playlist or unresolved tracks" } 600 + } 1107 601 } 1108 602 }, 1109 - "/playlists/resume": { 1110 - "put": { 1111 - "summary": "Resume Playlist", 1112 - "operationId": "put-playlists-resume", 603 + "/saved-playlists/folders": { 604 + "get": { 605 + "operationId": "listPlaylistFolders", 606 + "tags": ["Saved playlists"], 607 + "summary": "List playlist folders", 1113 608 "responses": { 1114 - "200": { 1115 - "description": "OK" 1116 - } 609 + "200": { "description": "Folders", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/PlaylistFolder" } } } } } 610 + } 611 + }, 612 + "post": { 613 + "operationId": "createPlaylistFolder", 614 + "tags": ["Saved playlists"], 615 + "summary": "Create a playlist folder", 616 + "requestBody": { 617 + "required": true, 618 + "content": { "application/json": { "schema": { 619 + "type": "object", "required": ["name"], "properties": { "name": { "type": "string" } } 620 + } } } 1117 621 }, 1118 - "tags": [ 1119 - "Playlists" 1120 - ] 622 + "responses": { 623 + "201": { "description": "Created", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PlaylistFolder" } } } } 624 + } 1121 625 } 1122 626 }, 1123 - "/playlists/resume-track": { 1124 - "put": { 1125 - "summary": "Resume Playlist Track", 1126 - "operationId": "put-playlists-resume-track", 1127 - "responses": { 1128 - "200": { 1129 - "description": "OK" 1130 - } 1131 - }, 1132 - "tags": [ 1133 - "Playlists" 1134 - ] 627 + "/saved-playlists/folders/{id}": { 628 + "delete": { 629 + "operationId": "deletePlaylistFolder", 630 + "tags": ["Saved playlists"], 631 + "summary": "Delete a playlist folder", 632 + "parameters": [{ "$ref": "#/components/parameters/IdPath" }], 633 + "responses": { "204": { "description": "Deleted" } } 1135 634 } 1136 635 }, 1137 - "/playlists/current": { 636 + "/smart-playlists": { 1138 637 "get": { 1139 - "summary": "Get Playlist Details", 1140 - "tags": [ 1141 - "Playlists" 1142 - ], 638 + "operationId": "listSmartPlaylists", 639 + "tags": ["Smart playlists"], 640 + "summary": "List smart playlists", 1143 641 "responses": { 1144 - "200": { 1145 - "description": "OK", 1146 - "content": { 1147 - "application/json": { 1148 - "schema": { 1149 - "type": "object", 1150 - "properties": { 1151 - "amount": { 1152 - "type": "integer" 1153 - }, 1154 - "index": { 1155 - "type": "integer" 1156 - }, 1157 - "max_playlist_size": { 1158 - "type": "integer" 1159 - }, 1160 - "first_index": { 1161 - "type": "integer" 1162 - }, 1163 - "last_insert_pos": { 1164 - "type": "integer" 1165 - }, 1166 - "seed": { 1167 - "type": "integer" 1168 - }, 1169 - "last_shuffled_start": { 1170 - "type": "integer" 1171 - }, 1172 - "tracks": { 1173 - "type": "array", 1174 - "items": { 1175 - "$ref": "#/components/schemas/Track.v1", 1176 - "x-stoplight": { 1177 - "id": "nv4fqov9gf4t4" 1178 - } 1179 - } 1180 - } 1181 - } 1182 - } 1183 - } 642 + "200": { "description": "Smart playlists", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/SmartPlaylist" } } } } } 643 + } 644 + }, 645 + "post": { 646 + "operationId": "createSmartPlaylist", 647 + "tags": ["Smart playlists"], 648 + "summary": "Create a smart playlist with a rule criteria", 649 + "requestBody": { 650 + "required": true, 651 + "content": { "application/json": { "schema": { 652 + "type": "object", 653 + "required": ["name", "rules"], 654 + "properties": { 655 + "name": { "type": "string" }, 656 + "description": { "type": "string" }, 657 + "image": { "type": "string" }, 658 + "folder_id": { "type": "string" }, 659 + "rules": { "$ref": "#/components/schemas/RuleCriteria" } 1184 660 } 1185 - } 661 + } } } 662 + }, 663 + "responses": { 664 + "201": { "description": "Created", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SmartPlaylist" } } } }, 665 + "400": { "description": "Missing required fields" } 1186 666 } 1187 - }, 1188 - "operationId": "get-current-playlist", 1189 - "description": "", 1190 - "requestBody": { 1191 - "content": {} 1192 667 } 1193 668 }, 1194 - "/playlists/current/tracks": { 669 + "/smart-playlists/{id}": { 670 + "parameters": [{ "$ref": "#/components/parameters/IdPath" }], 1195 671 "get": { 1196 - "summary": "Get Playlist Tracks", 1197 - "tags": [ 1198 - "Playlists" 1199 - ], 672 + "operationId": "getSmartPlaylist", 673 + "tags": ["Smart playlists"], 674 + "summary": "Get a smart playlist", 1200 675 "responses": { 1201 - "200": { 1202 - "description": "OK", 1203 - "content": { 1204 - "application/json": { 1205 - "schema": { 1206 - "type": "array", 1207 - "items": { 1208 - "$ref": "#/components/schemas/Track.v1", 1209 - "x-stoplight": { 1210 - "id": "ls4kga6ga92bp" 1211 - } 1212 - } 1213 - } 1214 - } 1215 - } 1216 - } 1217 - }, 1218 - "operationId": "get-playlists-current-tracks", 1219 - "description": "", 1220 - "requestBody": { 1221 - "content": {} 676 + "200": { "description": "Smart playlist", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SmartPlaylist" } } } }, 677 + "404": { "$ref": "#/components/responses/NotFound" } 1222 678 } 1223 679 }, 1224 - "post": { 1225 - "summary": "Insert Tracks", 1226 - "operationId": "post-playlists-current-tracks", 1227 - "responses": { 1228 - "200": { 1229 - "description": "OK" 1230 - } 1231 - }, 680 + "put": { 681 + "operationId": "updateSmartPlaylist", 682 + "tags": ["Smart playlists"], 683 + "summary": "Update a smart playlist", 1232 684 "requestBody": { 1233 - "content": { 1234 - "application/json": { 1235 - "schema": { 1236 - "type": "object", 1237 - "properties": { 1238 - "position": { 1239 - "type": "integer", 1240 - "x-stoplight": { 1241 - "id": "03yo7z5xfx9zg" 1242 - } 1243 - }, 1244 - "tracks": { 1245 - "type": "array", 1246 - "x-stoplight": { 1247 - "id": "lifh2pzcb1nmj" 1248 - }, 1249 - "items": { 1250 - "x-stoplight": { 1251 - "id": "pqnsp7w5ujb0i" 1252 - }, 1253 - "type": "string" 1254 - } 1255 - }, 1256 - "directory": { 1257 - "type": "string", 1258 - "x-stoplight": { 1259 - "id": "u2vi3f2cfouo0" 1260 - } 1261 - } 1262 - } 1263 - } 685 + "required": true, 686 + "content": { "application/json": { "schema": { 687 + "type": "object", 688 + "required": ["name", "rules"], 689 + "properties": { 690 + "name": { "type": "string" }, 691 + "description": { "type": "string" }, 692 + "image": { "type": "string" }, 693 + "folder_id": { "type": "string" }, 694 + "rules": { "$ref": "#/components/schemas/RuleCriteria" } 1264 695 } 1265 - } 696 + } } } 1266 697 }, 1267 - "tags": [ 1268 - "Playlists" 1269 - ] 698 + "responses": { 699 + "204": { "description": "Updated" }, 700 + "404": { "$ref": "#/components/responses/NotFound" } 701 + } 1270 702 }, 1271 703 "delete": { 1272 - "summary": "Remove Tracks from Playlist", 1273 - "operationId": "delete-playlists-current-tracks", 704 + "operationId": "deleteSmartPlaylist", 705 + "tags": ["Smart playlists"], 706 + "summary": "Delete a smart playlist", 1274 707 "responses": { 1275 - "200": { 1276 - "description": "OK" 1277 - } 1278 - }, 1279 - "requestBody": { 1280 - "content": { 1281 - "application/json": { 1282 - "schema": { 1283 - "type": "object", 1284 - "properties": { 1285 - "positions": { 1286 - "type": "array", 1287 - "x-stoplight": { 1288 - "id": "7sodedhj2syc7" 1289 - }, 1290 - "items": { 1291 - "x-stoplight": { 1292 - "id": "fblagbx73vo6x" 1293 - }, 1294 - "type": "integer" 1295 - } 1296 - } 1297 - } 1298 - } 1299 - } 1300 - } 1301 - }, 1302 - "tags": [ 1303 - "Playlists" 1304 - ] 708 + "204": { "description": "Deleted" }, 709 + "404": { "$ref": "#/components/responses/NotFound" } 710 + } 1305 711 } 1306 712 }, 1307 - "/artists/{id}/tracks": { 1308 - "parameters": [ 1309 - { 1310 - "schema": { 1311 - "type": "string" 1312 - }, 1313 - "name": "id", 1314 - "in": "path", 1315 - "required": true 713 + "/smart-playlists/{id}/tracks": { 714 + "get": { 715 + "operationId": "getSmartPlaylistTracks", 716 + "tags": ["Smart playlists"], 717 + "summary": "Resolve a smart playlist to its current matching tracks", 718 + "parameters": [{ "$ref": "#/components/parameters/IdPath" }], 719 + "responses": { 720 + "200": { "description": "Resolved tracks", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/Track" } } } } }, 721 + "404": { "$ref": "#/components/responses/NotFound" } 722 + } 723 + } 724 + }, 725 + "/smart-playlists/{id}/play": { 726 + "post": { 727 + "operationId": "playSmartPlaylist", 728 + "tags": ["Smart playlists"], 729 + "summary": "Resolve a smart playlist and start playing its tracks", 730 + "parameters": [{ "$ref": "#/components/parameters/IdPath" }], 731 + "responses": { 732 + "204": { "description": "Started" }, 733 + "404": { "$ref": "#/components/responses/NotFound" }, 734 + "422": { "description": "Resolves to zero tracks" } 1316 735 } 1317 - ], 736 + } 737 + }, 738 + "/track-stats/{id}": { 1318 739 "get": { 1319 - "summary": "Get Artist's Tracks", 1320 - "tags": [ 1321 - "Artists" 1322 - ], 740 + "operationId": "getTrackStats", 741 + "tags": ["Track stats"], 742 + "summary": "Get listening stats for a track", 743 + "parameters": [{ "$ref": "#/components/parameters/IdPath" }], 1323 744 "responses": { 1324 - "200": { 1325 - "description": "OK", 1326 - "content": { 1327 - "application/json": { 1328 - "schema": { 1329 - "type": "array", 1330 - "items": { 1331 - "$ref": "#/components/schemas/Track.v1", 1332 - "x-stoplight": { 1333 - "id": "an0l7e2vpmx6x" 1334 - } 1335 - } 1336 - } 1337 - } 1338 - } 1339 - } 1340 - }, 1341 - "operationId": "get-artists-id-tracks" 745 + "200": { "description": "Track stats", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TrackStats" } } } }, 746 + "404": { "$ref": "#/components/responses/NotFound" } 747 + } 748 + } 749 + }, 750 + "/track-stats/{id}/played": { 751 + "post": { 752 + "operationId": "recordTrackPlayed", 753 + "tags": ["Track stats"], 754 + "summary": "Record a 'played' event for a track", 755 + "parameters": [{ "$ref": "#/components/parameters/IdPath" }], 756 + "responses": { "204": { "description": "Recorded" } } 757 + } 758 + }, 759 + "/track-stats/{id}/skipped": { 760 + "post": { 761 + "operationId": "recordTrackSkipped", 762 + "tags": ["Track stats"], 763 + "summary": "Record a 'skipped' event for a track", 764 + "parameters": [{ "$ref": "#/components/parameters/IdPath" }], 765 + "responses": { "204": { "description": "Recorded" } } 766 + } 767 + }, 768 + "/devices": { 769 + "get": { 770 + "operationId": "getDevices", 771 + "tags": ["Devices"], 772 + "summary": "List all known output devices (discovered + virtual)", 773 + "responses": { 774 + "200": { "description": "Devices", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/Device" } } } } } 775 + } 776 + } 777 + }, 778 + "/devices/{id}": { 779 + "get": { 780 + "operationId": "getDevice", 781 + "tags": ["Devices"], 782 + "summary": "Get a device by id (use 'current' for the active sink)", 783 + "parameters": [{ "$ref": "#/components/parameters/IdPath" }], 784 + "responses": { 785 + "200": { "description": "Device", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Device" } } } }, 786 + "404": { "$ref": "#/components/responses/NotFound" } 787 + } 1342 788 } 1343 789 }, 1344 - "/player/volume": { 790 + "/devices/{id}/connect": { 1345 791 "put": { 1346 - "summary": "Adjust Volume", 1347 - "operationId": "put-player-volume", 792 + "operationId": "connectDevice", 793 + "tags": ["Devices"], 794 + "summary": "Switch the active sink to this device", 795 + "parameters": [{ "$ref": "#/components/parameters/IdPath" }], 1348 796 "responses": { 1349 - "200": { 1350 - "description": "OK" 1351 - } 1352 - }, 1353 - "tags": [ 1354 - "Player" 1355 - ], 1356 - "requestBody": { 1357 - "content": { 1358 - "application/json": { 1359 - "schema": { 1360 - "type": "object", 1361 - "properties": { 1362 - "steps": { 1363 - "type": "integer", 1364 - "x-stoplight": { 1365 - "id": "t7dk1iz5f0eol" 1366 - } 1367 - } 1368 - } 1369 - } 1370 - } 1371 - } 797 + "200": { "description": "Connected" }, 798 + "400": { "description": "Unknown device service" }, 799 + "404": { "$ref": "#/components/responses/NotFound" } 1372 800 } 1373 801 } 1374 802 }, 1375 - "/scan-library": { 803 + "/devices/{id}/disconnect": { 1376 804 "put": { 1377 - "summary": "Scan Library", 1378 - "operationId": "put-scan-library", 805 + "operationId": "disconnectDevice", 806 + "tags": ["Devices"], 807 + "summary": "Disconnect the active device and revert to builtin", 808 + "parameters": [{ "$ref": "#/components/parameters/IdPath" }], 809 + "responses": { "200": { "description": "Disconnected" } } 810 + } 811 + }, 812 + "/settings": { 813 + "get": { 814 + "operationId": "getSettings", 815 + "tags": ["Settings"], 816 + "summary": "Get the global settings (in-memory snapshot)", 1379 817 "responses": { 1380 - "200": { 1381 - "description": "OK" 1382 - } 818 + "200": { "description": "Settings", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GlobalSettings" } } } } 819 + } 820 + }, 821 + "put": { 822 + "operationId": "updateSettings", 823 + "tags": ["Settings"], 824 + "summary": "Apply a partial settings update and persist to settings.toml", 825 + "requestBody": { 826 + "required": true, 827 + "content": { "application/json": { "schema": { "$ref": "#/components/schemas/NewGlobalSettings" } } } 1383 828 }, 1384 - "tags": [ 1385 - "Library" 1386 - ] 829 + "responses": { "204": { "description": "Saved" } } 830 + } 831 + }, 832 + "/version": { 833 + "get": { 834 + "operationId": "getVersion", 835 + "tags": ["System"], 836 + "summary": "Get the running rockboxd version", 837 + "responses": { 838 + "200": { "description": "Version", "content": { "application/json": { "schema": { "type": "string" } } } } 839 + } 1387 840 } 1388 841 }, 1389 - "/search": { 842 + "/status": { 1390 843 "get": { 1391 - "summary": "Search", 1392 - "tags": [ 1393 - "Library" 844 + "operationId": "getGlobalStatus", 845 + "tags": ["System"], 846 + "summary": "Get global runtime status", 847 + "responses": { 848 + "200": { "description": "Global status", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GlobalStatus" } } } } 849 + } 850 + } 851 + }, 852 + "/scan-library": { 853 + "put": { 854 + "operationId": "scanLibrary", 855 + "tags": ["System"], 856 + "summary": "Trigger a library scan and (optionally) rebuild the search index", 857 + "parameters": [ 858 + { "name": "path", "in": "query", "schema": { "type": "string" }, "description": "Path to scan; defaults to $HOME/Music" }, 859 + { "name": "rebuild_index", "in": "query", "schema": { "type": "string", "enum": ["true", "false", "1", "0"] }, "description": "Re-build the Typesense collections" } 1394 860 ], 1395 861 "responses": { 1396 - "200": { 1397 - "description": "OK", 1398 - "headers": {}, 1399 - "content": { 1400 - "application/json": { 1401 - "schema": { 1402 - "type": "object", 1403 - "properties": { 1404 - "albums": { 1405 - "type": "array", 1406 - "x-stoplight": { 1407 - "id": "019f0i09xzdu8" 1408 - }, 1409 - "items": { 1410 - "$ref": "#/components/schemas/Album.v1", 1411 - "x-stoplight": { 1412 - "id": "m5ctl94xkgv4i" 1413 - } 1414 - } 1415 - }, 1416 - "artists": { 1417 - "type": "array", 1418 - "x-stoplight": { 1419 - "id": "q4j72lavmdcoc" 1420 - }, 1421 - "items": { 1422 - "$ref": "#/components/schemas/Artist.v1", 1423 - "x-stoplight": { 1424 - "id": "z0k5m35wf3fuh" 1425 - } 1426 - } 1427 - }, 1428 - "tracks": { 1429 - "type": "array", 1430 - "x-stoplight": { 1431 - "id": "zgycozcr6tror" 1432 - }, 1433 - "items": { 1434 - "$ref": "#/components/schemas/Track.v1", 1435 - "x-stoplight": { 1436 - "id": "iadt803zpcu5p" 1437 - } 1438 - } 1439 - }, 1440 - "files": { 1441 - "type": "array", 1442 - "x-stoplight": { 1443 - "id": "wuydvte7y6xsw" 1444 - }, 1445 - "items": { 1446 - "x-stoplight": { 1447 - "id": "699qj4o9kba5g" 1448 - }, 1449 - "type": "object", 1450 - "properties": { 1451 - "name": { 1452 - "type": "string", 1453 - "x-stoplight": { 1454 - "id": "viqtl758xypm1" 1455 - } 1456 - }, 1457 - "time_write": { 1458 - "type": "integer", 1459 - "x-stoplight": { 1460 - "id": "bv5om5xxc8vag" 1461 - } 1462 - }, 1463 - "is_directory": { 1464 - "type": "boolean", 1465 - "x-stoplight": { 1466 - "id": "psu25s55d12o1" 1467 - } 1468 - } 1469 - } 1470 - } 1471 - }, 1472 - "liked_tracks": { 1473 - "type": "array", 1474 - "x-stoplight": { 1475 - "id": "53rj1yd8k5h4s" 1476 - }, 1477 - "items": { 1478 - "$ref": "#/components/schemas/Track.v1", 1479 - "x-stoplight": { 1480 - "id": "9xob3497ldmtt" 1481 - } 1482 - } 1483 - }, 1484 - "liked_albums": { 1485 - "type": "array", 1486 - "x-stoplight": { 1487 - "id": "dfq6obcjknhjs" 1488 - }, 1489 - "items": { 1490 - "$ref": "#/components/schemas/Album.v1", 1491 - "x-stoplight": { 1492 - "id": "gicyfb787tg0w" 1493 - } 1494 - } 1495 - } 1496 - } 1497 - } 1498 - } 1499 - } 1500 - } 1501 - }, 1502 - "operationId": "get-search", 862 + "200": { "description": "Scan started/complete (returns '0' on success)", "content": { "text/plain": { "schema": { "type": "string" } } } } 863 + } 864 + } 865 + }, 866 + "/openapi.json": { 867 + "get": { 868 + "operationId": "getOpenApi", 869 + "tags": ["System"], 870 + "summary": "Get this OpenAPI document", 871 + "responses": { 872 + "200": { "description": "OpenAPI spec", "content": { "application/json": {} } } 873 + } 874 + } 875 + }, 876 + "/bluetooth/scan": { 877 + "post": { 878 + "operationId": "scanBluetooth", 879 + "tags": ["Bluetooth"], 880 + "summary": "Scan for Bluetooth devices (Linux only)", 1503 881 "parameters": [ 1504 - { 1505 - "schema": { 1506 - "type": "string" 1507 - }, 1508 - "in": "query", 1509 - "name": "q" 1510 - } 1511 - ] 882 + { "name": "timeout_secs", "in": "query", "schema": { "type": "integer", "format": "int64", "default": 10 } } 883 + ], 884 + "responses": { 885 + "200": { "description": "Discovered devices", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/BluetoothDevice" } } } } } 886 + } 887 + } 888 + }, 889 + "/bluetooth/devices": { 890 + "get": { 891 + "operationId": "getBluetoothDevices", 892 + "tags": ["Bluetooth"], 893 + "summary": "List paired Bluetooth devices (Linux only)", 894 + "responses": { 895 + "200": { "description": "Devices", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/BluetoothDevice" } } } } } 896 + } 897 + } 898 + }, 899 + "/bluetooth/devices/{addr}/connect": { 900 + "put": { 901 + "operationId": "connectBluetoothDevice", 902 + "tags": ["Bluetooth"], 903 + "summary": "Connect to a paired Bluetooth device (Linux only)", 904 + "parameters": [{ "name": "addr", "in": "path", "required": true, "schema": { "type": "string" } }], 905 + "responses": { 906 + "200": { "description": "Connected" }, 907 + "500": { "description": "Bluez error" } 908 + } 909 + } 910 + }, 911 + "/bluetooth/devices/{addr}/disconnect": { 912 + "put": { 913 + "operationId": "disconnectBluetoothDevice", 914 + "tags": ["Bluetooth"], 915 + "summary": "Disconnect a Bluetooth device (Linux only)", 916 + "parameters": [{ "name": "addr", "in": "path", "required": true, "schema": { "type": "string" } }], 917 + "responses": { 918 + "200": { "description": "Disconnected" }, 919 + "500": { "description": "Bluez error" } 920 + } 1512 921 } 1513 922 } 1514 923 }, 1515 924 "components": { 925 + "parameters": { 926 + "IdPath": { 927 + "name": "id", 928 + "in": "path", 929 + "required": true, 930 + "schema": { "type": "string" } 931 + } 932 + }, 933 + "responses": { 934 + "NotFound": { 935 + "description": "Not found" 936 + } 937 + }, 1516 938 "schemas": { 1517 - "Album.v1": { 939 + "Album": { 940 + "type": "object", 941 + "properties": { 942 + "id": { "type": "string" }, 943 + "title": { "type": "string" }, 944 + "artist": { "type": "string" }, 945 + "year": { "type": "integer", "format": "int32", "nullable": true }, 946 + "year_string": { "type": "string", "nullable": true }, 947 + "artist_id": { "type": "string" }, 948 + "md5": { "type": "string" }, 949 + "album_art": { "type": "string", "nullable": true } 950 + } 951 + }, 952 + "Artist": { 953 + "type": "object", 954 + "properties": { 955 + "id": { "type": "string" }, 956 + "name": { "type": "string" }, 957 + "image":{ "type": "string", "nullable": true }, 958 + "bio": { "type": "string", "nullable": true } 959 + } 960 + }, 961 + "Track": { 962 + "type": "object", 963 + "properties": { 964 + "id": { "type": "string" }, 965 + "path": { "type": "string" }, 966 + "title": { "type": "string" }, 967 + "artist": { "type": "string" }, 968 + "album": { "type": "string" }, 969 + "album_artist": { "type": "string" }, 970 + "composer": { "type": "string" }, 971 + "genre": { "type": "string", "nullable": true }, 972 + "year": { "type": "integer", "format": "int64", "nullable": true }, 973 + "year_string": { "type": "string", "nullable": true }, 974 + "track_number": { "type": "integer", "format": "int64", "nullable": true }, 975 + "disc_number": { "type": "integer", "format": "int64" }, 976 + "length": { "type": "integer", "format": "int64", "description": "Duration in milliseconds" }, 977 + "filesize": { "type": "integer", "format": "int64" }, 978 + "bitrate": { "type": "integer", "format": "int64" }, 979 + "frequency": { "type": "integer", "format": "int64" }, 980 + "album_id": { "type": "string" }, 981 + "artist_id": { "type": "string" }, 982 + "genre_id": { "type": "string" }, 983 + "album_art": { "type": "string", "nullable": true }, 984 + "md5": { "type": "string" }, 985 + "created_at": { "type": "string", "format": "date-time" }, 986 + "updated_at": { "type": "string", "format": "date-time" } 987 + } 988 + }, 989 + "TreeEntry": { 990 + "type": "object", 991 + "properties": { 992 + "name": { "type": "string", "description": "Absolute path" }, 993 + "time_write": { "type": "integer", "format": "int64", "description": "Unix timestamp of last modification" }, 994 + "attr": { "type": "integer", "format": "int32", "description": "0x10 = directory" } 995 + } 996 + }, 997 + "Mp3Entry": { 998 + "type": "object", 999 + "description": "Rockbox firmware-side representation of a playing or queued track", 1000 + "properties": { 1001 + "path": { "type": "string" }, 1002 + "title": { "type": "string" }, 1003 + "artist": { "type": "string" }, 1004 + "album": { "type": "string" }, 1005 + "albumartist": { "type": "string" }, 1006 + "composer": { "type": "string" }, 1007 + "genre_string": { "type": "string" }, 1008 + "tracknum": { "type": "integer", "format": "int32" }, 1009 + "discnum": { "type": "integer", "format": "int32" }, 1010 + "year": { "type": "integer", "format": "int32" }, 1011 + "year_string": { "type": "string" }, 1012 + "length": { "type": "integer", "format": "int64", "description": "Duration in milliseconds" }, 1013 + "elapsed": { "type": "integer", "format": "int64", "description": "Current position in milliseconds" }, 1014 + "filesize": { "type": "integer", "format": "int64" }, 1015 + "bitrate": { "type": "integer", "format": "int64" }, 1016 + "frequency": { "type": "integer", "format": "int64" }, 1017 + "id": { "type": "string", "nullable": true }, 1018 + "album_id": { "type": "string", "nullable": true }, 1019 + "artist_id": { "type": "string", "nullable": true }, 1020 + "genre_id": { "type": "string", "nullable": true }, 1021 + "album_art": { "type": "string", "nullable": true } 1022 + } 1023 + }, 1024 + "AudioStatus": { 1518 1025 "type": "object", 1519 - "x-examples": { 1520 - "Example 1": { 1521 - "album_art": "216ccc791352fbbffc11268b984db19a.jpg", 1522 - "artist": "J. Cole", 1523 - "id": "cm1ibibn600013fj7d96nsaeh", 1524 - "md5": "d55c5a429200828bc798d9f10253c1af", 1525 - "title": "2014 Forest Hills Drive", 1526 - "year": 2014, 1527 - "year_string": "2014-12-09" 1026 + "properties": { 1027 + "status": { 1028 + "type": "integer", "format": "int32", 1029 + "description": "0 = stopped, 1 = playing, 2 = paused (bitfield)" 1528 1030 } 1529 - }, 1530 - "examples": [ 1531 - { 1532 - "album_art": "216ccc791352fbbffc11268b984db19a.jpg", 1533 - "artist": "J. Cole", 1534 - "id": "cm1ibibn600013fj7d96nsaeh", 1535 - "md5": "d55c5a429200828bc798d9f10253c1af", 1536 - "title": "2014 Forest Hills Drive", 1537 - "year": 2014, 1538 - "year_string": "2014-12-09" 1539 - } 1540 - ], 1541 - "title": "Album", 1031 + } 1032 + }, 1033 + "Device": { 1034 + "type": "object", 1035 + "properties": { 1036 + "id": { "type": "string" }, 1037 + "name": { "type": "string" }, 1038 + "host": { "type": "string" }, 1039 + "ip": { "type": "string" }, 1040 + "port": { "type": "integer", "format": "int32" }, 1041 + "service": { "type": "string", "description": "builtin | fifo | airplay | squeezelite | upnp | chromecast | snapcast" }, 1042 + "app": { "type": "string" }, 1043 + "is_connected": { "type": "boolean" }, 1044 + "is_cast_device": { "type": "boolean" }, 1045 + "is_source_device": { "type": "boolean" }, 1046 + "is_current_device": { "type": "boolean" }, 1047 + "base_url": { "type": "string", "nullable": true } 1048 + } 1049 + }, 1050 + "LoadTracks": { 1051 + "type": "object", 1052 + "required": ["tracks"], 1053 + "properties": { 1054 + "tracks": { "type": "array", "items": { "type": "string" }, "description": "Track paths or HTTP URLs" }, 1055 + "shuffle": { "type": "boolean" } 1056 + } 1057 + }, 1058 + "InsertTracks": { 1059 + "type": "object", 1060 + "required": ["tracks", "position"], 1061 + "properties": { 1062 + "tracks": { "type": "array", "items": { "type": "string" } }, 1063 + "directory": { "type": "string", "description": "Insert all audio files under this directory instead of an explicit list" }, 1064 + "position": { "type": "integer", "format": "int32", "description": "0=Next, 1=After current, 2=Last, 3=First/replace, 7=LastShuffled" } 1065 + } 1066 + }, 1067 + "DeleteTracks": { 1068 + "type": "object", 1069 + "required": ["positions"], 1070 + "properties": { 1071 + "positions": { "type": "array", "items": { "type": "integer", "format": "int32" }, "description": "Empty array clears the entire queue" } 1072 + } 1073 + }, 1074 + "NewPlaylist": { 1075 + "type": "object", 1076 + "required": ["tracks"], 1077 + "properties": { 1078 + "name": { "type": "string" }, 1079 + "tracks": { "type": "array", "items": { "type": "string" } } 1080 + } 1081 + }, 1082 + "PlaylistInfo": { 1083 + "type": "object", 1084 + "properties": { 1085 + "amount": { "type": "integer", "format": "int32" }, 1086 + "index": { "type": "integer", "format": "int32" }, 1087 + "max_playlist_size": { "type": "integer", "format": "int32" }, 1088 + "first_index": { "type": "integer", "format": "int32" }, 1089 + "last_insert_pos": { "type": "integer", "format": "int32" }, 1090 + "seed": { "type": "integer", "format": "int32" }, 1091 + "last_shuffled_start": { "type": "integer", "format": "int32" }, 1092 + "entries": { "type": "array", "items": { "$ref": "#/components/schemas/Mp3Entry" } } 1093 + } 1094 + }, 1095 + "NewVolume": { 1096 + "type": "object", 1097 + "required": ["steps"], 1098 + "properties": { 1099 + "steps": { "type": "integer", "format": "int32", "description": "Positive = louder, negative = quieter" } 1100 + } 1101 + }, 1102 + "SavedPlaylist": { 1103 + "type": "object", 1104 + "properties": { 1105 + "id": { "type": "string" }, 1106 + "name": { "type": "string" }, 1107 + "description": { "type": "string", "nullable": true }, 1108 + "image": { "type": "string", "nullable": true }, 1109 + "folder_id": { "type": "string", "nullable": true }, 1110 + "track_count": { "type": "integer", "format": "int32" }, 1111 + "created_at": { "type": "string", "format": "date-time" }, 1112 + "updated_at": { "type": "string", "format": "date-time" } 1113 + } 1114 + }, 1115 + "PlaylistFolder": { 1116 + "type": "object", 1542 1117 "properties": { 1543 - "album_art": { 1544 - "type": "string" 1545 - }, 1546 - "artist": { 1547 - "type": "string" 1548 - }, 1549 - "id": { 1550 - "type": "string" 1551 - }, 1552 - "md5": { 1553 - "type": "string" 1554 - }, 1555 - "title": { 1556 - "type": "string" 1557 - }, 1558 - "year": { 1559 - "type": "integer" 1560 - }, 1561 - "year_string": { 1562 - "type": "string" 1563 - }, 1564 - "tracks": { 1565 - "type": "array", 1566 - "x-stoplight": { 1567 - "id": "cexr5aqti95au" 1568 - }, 1569 - "items": { 1570 - "$ref": "#/components/schemas/Track.v1", 1571 - "x-stoplight": { 1572 - "id": "8sb8bq9omx42b" 1573 - } 1574 - } 1575 - } 1118 + "id": { "type": "string" }, 1119 + "name": { "type": "string" }, 1120 + "created_at": { "type": "string", "format": "date-time" } 1576 1121 } 1577 1122 }, 1578 - "Artist.v1": { 1579 - "x-stoplight": { 1580 - "id": "0wktkyg4gevd3" 1581 - }, 1123 + "SmartPlaylist": { 1582 1124 "type": "object", 1583 - "x-examples": { 1584 - "Example 1": { 1585 - "id": "cm1ibibn600003fj70egpmcbc", 1586 - "name": "J. Cole" 1587 - } 1588 - }, 1589 - "examples": [ 1590 - { 1591 - "id": "cm1ibibn600003fj70egpmcbc", 1592 - "name": "J. Cole" 1593 - } 1594 - ], 1595 - "title": "Artist", 1596 1125 "properties": { 1597 - "id": { 1598 - "type": "string" 1599 - }, 1600 - "name": { 1601 - "type": "string" 1602 - }, 1603 - "bio": { 1604 - "type": "string", 1605 - "x-stoplight": { 1606 - "id": "bv1vmm5or30d9" 1607 - } 1608 - }, 1609 - "image": { 1610 - "type": "string", 1611 - "x-stoplight": { 1612 - "id": "zziizes11l6yp" 1613 - } 1614 - }, 1615 - "albums": { 1616 - "type": "array", 1617 - "x-stoplight": { 1618 - "id": "gt6wtmfwwqno0" 1619 - }, 1620 - "items": { 1621 - "$ref": "#/components/schemas/Album.v1", 1622 - "x-stoplight": { 1623 - "id": "s0p904ajhds7m" 1624 - } 1625 - } 1626 - }, 1627 - "tracks": { 1628 - "type": "array", 1629 - "x-stoplight": { 1630 - "id": "fdb8ajuw7tq2s" 1631 - }, 1632 - "items": { 1633 - "$ref": "#/components/schemas/Track.v1", 1634 - "x-stoplight": { 1635 - "id": "7xmpqe7kfh8uy" 1636 - } 1637 - } 1638 - } 1126 + "id": { "type": "string" }, 1127 + "name": { "type": "string" }, 1128 + "description": { "type": "string", "nullable": true }, 1129 + "image": { "type": "string", "nullable": true }, 1130 + "folder_id": { "type": "string", "nullable": true }, 1131 + "rules": { "$ref": "#/components/schemas/RuleCriteria" }, 1132 + "created_at": { "type": "string", "format": "date-time" }, 1133 + "updated_at": { "type": "string", "format": "date-time" } 1639 1134 } 1640 1135 }, 1641 - "Settings.v1": { 1136 + "RuleCriteria": { 1642 1137 "type": "object", 1138 + "description": "Smart-playlist rule set. See `crates/playlists/src/rules.rs` for the canonical definition.", 1643 1139 "properties": { 1644 - "afr_enabled": { 1645 - "type": "integer" 1646 - }, 1647 - "album_art": { 1648 - "type": "integer" 1649 - }, 1650 - "autocreatebookmark": { 1651 - "type": "integer" 1652 - }, 1653 - "autoloadbookmark": { 1654 - "type": "integer" 1655 - }, 1656 - "autoresume_automatic": { 1657 - "type": "integer" 1658 - }, 1659 - "autoresume_enable": { 1660 - "type": "boolean" 1661 - }, 1662 - "autoresume_paths": { 1663 - "type": "string" 1664 - }, 1665 - "autoupdatebookmark": { 1666 - "type": "boolean" 1667 - }, 1668 - "backdrop_file": { 1669 - "type": "string" 1670 - }, 1671 - "backlight_on_button_hold": { 1672 - "type": "integer" 1673 - }, 1674 - "backlight_timeout": { 1675 - "type": "integer" 1676 - }, 1677 - "backlight_timeout_plugged": { 1678 - "type": "integer" 1679 - }, 1680 - "balance": { 1681 - "type": "integer" 1682 - }, 1683 - "bass": { 1684 - "type": "integer" 1685 - }, 1686 - "bass_cutoff": { 1687 - "type": "integer" 1688 - }, 1689 - "battery_capacity": { 1690 - "type": "integer" 1691 - }, 1692 - "battery_display": { 1693 - "type": "integer" 1694 - }, 1695 - "battery_type": { 1696 - "type": "integer" 1697 - }, 1698 - "beep": { 1699 - "type": "integer" 1700 - }, 1701 - "bg_color": { 1702 - "type": "integer" 1703 - }, 1704 - "bidir_limit": { 1705 - "type": "integer" 1706 - }, 1707 - "bl_filter_first_keypress": { 1708 - "type": "boolean" 1709 - }, 1710 - "bl_selective_actions": { 1711 - "type": "boolean" 1712 - }, 1713 - "bl_selective_actions_mask": { 1714 - "type": "integer" 1715 - }, 1716 - "brightness": { 1717 - "type": "integer" 1718 - }, 1719 - "browse_current": { 1720 - "type": "boolean" 1721 - }, 1722 - "browser_default": { 1723 - "type": "integer" 1724 - }, 1725 - "bt_selective_softlock_actions": { 1726 - "type": "boolean" 1727 - }, 1728 - "bt_selective_softlock_actions_mask": { 1729 - "type": "integer" 1730 - }, 1731 - "buffer_margin": { 1732 - "type": "integer" 1733 - }, 1734 - "caption_backlight": { 1735 - "type": "boolean" 1736 - }, 1737 - "car_adapter_mode": { 1738 - "type": "boolean" 1739 - }, 1740 - "car_adapter_mode_delay": { 1741 - "type": "integer" 1742 - }, 1743 - "channel_config": { 1744 - "type": "integer" 1745 - }, 1746 - "colors_file": { 1747 - "type": "string" 1748 - }, 1749 - "compressor_settings": { 1750 - "type": "object", 1751 - "properties": { 1752 - "attack_time": { 1753 - "type": "integer" 1754 - }, 1755 - "knee": { 1756 - "type": "integer" 1757 - }, 1758 - "makeup_gain": { 1759 - "type": "integer" 1760 - }, 1761 - "ratio": { 1762 - "type": "integer" 1763 - }, 1764 - "release_time": { 1765 - "type": "integer" 1766 - }, 1767 - "threshold": { 1768 - "type": "integer" 1769 - } 1770 - } 1771 - }, 1772 - "constrain_next_folder": { 1773 - "type": "boolean" 1774 - }, 1775 - "contrast": { 1776 - "type": "integer" 1777 - }, 1778 - "crossfade": { 1779 - "type": "integer" 1780 - }, 1781 - "crossfade_fade_in_delay": { 1782 - "type": "integer" 1783 - }, 1784 - "crossfade_fade_in_duration": { 1785 - "type": "integer" 1786 - }, 1787 - "crossfade_fade_out_delay": { 1788 - "type": "integer" 1789 - }, 1790 - "crossfade_fade_out_duration": { 1791 - "type": "integer" 1792 - }, 1793 - "crossfade_fade_out_mixmode": { 1794 - "type": "integer" 1795 - }, 1796 - "crossfeed": { 1797 - "type": "integer" 1798 - }, 1799 - "crossfeed_cross_gain": { 1800 - "type": "integer" 1801 - }, 1802 - "crossfeed_direct_gain": { 1803 - "type": "integer" 1804 - }, 1805 - "crossfeed_hf_attenuation": { 1806 - "type": "integer" 1807 - }, 1808 - "crossfeed_hf_cutoff": { 1809 - "type": "integer" 1810 - }, 1811 - "cuesheet": { 1812 - "type": "boolean" 1813 - }, 1814 - "cursor_style": { 1815 - "type": "integer" 1816 - }, 1817 - "default_codepage": { 1818 - "type": "integer" 1819 - }, 1820 - "depth_3d": { 1821 - "type": "integer" 1822 - }, 1823 - "dircache": { 1824 - "type": "boolean" 1825 - }, 1826 - "dirfilter": { 1827 - "type": "integer" 1828 - }, 1829 - "disable_mainmenu_scrolling": { 1830 - "type": "boolean" 1831 - }, 1832 - "disk_spindown": { 1833 - "type": "integer" 1834 - }, 1835 - "dithering_enabled": { 1836 - "type": "boolean" 1837 - }, 1838 - "eq_band_settings": { 1140 + "operator": { "type": "string", "enum": ["AND", "OR"], "default": "AND" }, 1141 + "rules": { 1839 1142 "type": "array", 1840 1143 "items": { 1841 1144 "type": "object", 1842 1145 "properties": { 1843 - "cutoff": { 1844 - "type": "integer" 1845 - }, 1846 - "gain": { 1847 - "type": "integer" 1848 - }, 1849 - "q": { 1850 - "type": "integer" 1851 - } 1852 - } 1853 - } 1854 - }, 1855 - "eq_enabled": { 1856 - "type": "boolean" 1857 - }, 1858 - "eq_precut": { 1859 - "type": "integer" 1860 - }, 1861 - "fade_on_stop": { 1862 - "type": "boolean" 1863 - }, 1864 - "ff_rewind_accel": { 1865 - "type": "integer" 1866 - }, 1867 - "ff_rewind_min_step": { 1868 - "type": "integer" 1869 - }, 1870 - "fg_color": { 1871 - "type": "integer" 1872 - }, 1873 - "flip_display": { 1874 - "type": "boolean" 1875 - }, 1876 - "font_file": { 1877 - "type": "string" 1878 - }, 1879 - "glyphs_to_cache": { 1880 - "type": "integer" 1881 - }, 1882 - "governor": { 1883 - "type": "integer" 1884 - }, 1885 - "hold_lr_for_scroll_in_list": { 1886 - "type": "boolean" 1887 - }, 1888 - "hotkey_tree": { 1889 - "type": "integer" 1890 - }, 1891 - "hotkey_wps": { 1892 - "type": "integer" 1893 - }, 1894 - "icon_file": { 1895 - "type": "string" 1896 - }, 1897 - "interpret_numbers": { 1898 - "type": "integer" 1899 - }, 1900 - "invert": { 1901 - "type": "boolean" 1902 - }, 1903 - "kbd_file": { 1904 - "type": "string" 1905 - }, 1906 - "keep_current_track_on_replace_playlist": { 1907 - "type": "boolean" 1908 - }, 1909 - "keyclick": { 1910 - "type": "integer" 1911 - }, 1912 - "keyclick_hardware": { 1913 - "type": "boolean" 1914 - }, 1915 - "keyclick_repeats": { 1916 - "type": "integer" 1917 - }, 1918 - "keypress_restarts_sleeptimer": { 1919 - "type": "boolean" 1920 - }, 1921 - "lang_file": { 1922 - "type": "string" 1923 - }, 1924 - "lcd_sleep_after_backlight_off": { 1925 - "type": "integer" 1926 - }, 1927 - "list_accel_start_delay": { 1928 - "type": "integer" 1929 - }, 1930 - "list_accel_wait": { 1931 - "type": "integer" 1932 - }, 1933 - "list_line_padding": { 1934 - "type": "integer" 1935 - }, 1936 - "list_order": { 1937 - "type": "integer" 1938 - }, 1939 - "list_separator_color": { 1940 - "type": "integer" 1941 - }, 1942 - "list_separator_height": { 1943 - "type": "integer" 1944 - }, 1945 - "list_wraparound": { 1946 - "type": "boolean" 1947 - }, 1948 - "lse_color": { 1949 - "type": "integer" 1950 - }, 1951 - "lss_color": { 1952 - "type": "integer" 1953 - }, 1954 - "lst_color": { 1955 - "type": "integer" 1956 - }, 1957 - "max_files_in_dir": { 1958 - "type": "integer" 1959 - }, 1960 - "max_files_in_playlist": { 1961 - "type": "integer" 1962 - }, 1963 - "next_folder": { 1964 - "type": "integer" 1965 - }, 1966 - "offset_out_of_view": { 1967 - "type": "boolean" 1968 - }, 1969 - "party_mode": { 1970 - "type": "boolean" 1971 - }, 1972 - "pause_rewind": { 1973 - "type": "integer" 1974 - }, 1975 - "pbe": { 1976 - "type": "integer" 1977 - }, 1978 - "pbe_precut": { 1979 - "type": "integer" 1980 - }, 1981 - "peak_meter_clip_hold": { 1982 - "type": "integer" 1983 - }, 1984 - "peak_meter_dbfs": { 1985 - "type": "boolean" 1986 - }, 1987 - "peak_meter_hold": { 1988 - "type": "integer" 1989 - }, 1990 - "peak_meter_max": { 1991 - "type": "integer" 1992 - }, 1993 - "peak_meter_min": { 1994 - "type": "integer" 1995 - }, 1996 - "peak_meter_release": { 1997 - "type": "integer" 1998 - }, 1999 - "pitch_mode_semitone": { 2000 - "type": "boolean" 2001 - }, 2002 - "pitch_mode_timestretch": { 2003 - "type": "boolean" 2004 - }, 2005 - "play_frequency": { 2006 - "type": "integer" 2007 - }, 2008 - "play_selected": { 2009 - "type": "boolean" 2010 - }, 2011 - "player_name": { 2012 - "type": "string" 2013 - }, 2014 - "playlist_catalog_dir": { 2015 - "type": "string" 2016 - }, 2017 - "playlist_shuffle": { 2018 - "type": "boolean" 2019 - }, 2020 - "playlist_viewer_icons": { 2021 - "type": "boolean" 2022 - }, 2023 - "playlist_viewer_indices": { 2024 - "type": "boolean" 2025 - }, 2026 - "playlist_viewer_track_display": { 2027 - "type": "integer" 2028 - }, 2029 - "power_mode": { 2030 - "type": "integer" 2031 - }, 2032 - "poweroff": { 2033 - "type": "integer" 2034 - }, 2035 - "prevent_skip": { 2036 - "type": "boolean" 2037 - }, 2038 - "recursive_dir_insert": { 2039 - "type": "integer" 2040 - }, 2041 - "repeat_mode": { 2042 - "type": "integer" 2043 - }, 2044 - "replaygain_settings": { 2045 - "type": "object", 2046 - "properties": { 2047 - "noclip": { 2048 - "type": "boolean" 2049 - }, 2050 - "preamp": { 2051 - "type": "integer" 2052 - }, 2053 - "type": { 2054 - "type": "integer" 1146 + "field": { "type": "string", "description": "play_count | skip_count | last_played | year | duration_ms | genre | artist | …" }, 1147 + "op": { "type": "string", "description": "eq | ne | gt | gte | lt | lte | contains | within | not_within" }, 1148 + "value": {} 2055 1149 } 2056 1150 } 2057 1151 }, 2058 - "resume_rewind": { 2059 - "type": "integer" 2060 - }, 2061 - "rewind_across_tracks": { 2062 - "type": "boolean" 2063 - }, 2064 - "roll_off": { 2065 - "type": "integer" 2066 - }, 2067 - "root_menu_customized": { 2068 - "type": "boolean" 2069 - }, 2070 - "runtimedb": { 2071 - "type": "boolean" 2072 - }, 2073 - "sbs_file": { 2074 - "type": "string" 2075 - }, 2076 - "screen_scroll_step": { 2077 - "type": "integer" 2078 - }, 2079 - "scroll_delay": { 2080 - "type": "integer" 2081 - }, 2082 - "scroll_paginated": { 2083 - "type": "boolean" 2084 - }, 2085 - "scroll_speed": { 2086 - "type": "integer" 2087 - }, 2088 - "scroll_step": { 2089 - "type": "integer" 2090 - }, 2091 - "scrollbar": { 2092 - "type": "integer" 2093 - }, 2094 - "scrollbar_width": { 2095 - "type": "integer" 2096 - }, 2097 - "shortcuts_replaces_qs": { 2098 - "type": "boolean" 2099 - }, 2100 - "show_filename_ext": { 2101 - "type": "integer" 2102 - }, 2103 - "show_icons": { 2104 - "type": "boolean" 2105 - }, 2106 - "show_path_in_browser": { 2107 - "type": "integer" 2108 - }, 2109 - "show_queue_options": { 2110 - "type": "integer" 2111 - }, 2112 - "show_shuffled_adding_options": { 2113 - "type": "boolean" 2114 - }, 2115 - "show_shutdown_message": { 2116 - "type": "boolean" 2117 - }, 2118 - "single_mode": { 2119 - "type": "integer" 2120 - }, 2121 - "skip_length": { 2122 - "type": "integer" 2123 - }, 2124 - "sleeptimer_duration": { 2125 - "type": "integer" 2126 - }, 2127 - "sleeptimer_on_startup": { 2128 - "type": "boolean" 2129 - }, 2130 - "sort_case": { 2131 - "type": "boolean" 2132 - }, 2133 - "sort_dir": { 2134 - "type": "integer" 2135 - }, 2136 - "sort_file": { 2137 - "type": "integer" 2138 - }, 2139 - "spdif_enable": { 2140 - "type": "boolean" 2141 - }, 2142 - "speaker_mode": { 2143 - "type": "integer" 2144 - }, 2145 - "start_directory": { 2146 - "type": "string" 2147 - }, 2148 - "start_in_screen": { 2149 - "type": "integer" 2150 - }, 2151 - "statusbar": { 2152 - "type": "integer" 2153 - }, 2154 - "stereo_width": { 2155 - "type": "integer" 2156 - }, 2157 - "stereosw_mode": { 2158 - "type": "integer" 2159 - }, 2160 - "surround_balance": { 2161 - "type": "integer" 2162 - }, 2163 - "surround_enabled": { 2164 - "type": "integer" 2165 - }, 2166 - "surround_fx1": { 2167 - "type": "integer" 2168 - }, 2169 - "surround_fx2": { 2170 - "type": "boolean" 2171 - }, 2172 - "surround_method2": { 2173 - "type": "boolean" 2174 - }, 2175 - "surround_mix": { 2176 - "type": "integer" 2177 - }, 2178 - "tagcache_autoupdate": { 2179 - "type": "boolean" 2180 - }, 2181 - "tagcache_db_path": { 2182 - "type": "string" 2183 - }, 2184 - "tagcache_ram": { 2185 - "type": "integer" 2186 - }, 2187 - "tagcache_scan_paths": { 2188 - "type": "string" 2189 - }, 2190 - "talk_battery_level": { 2191 - "type": "boolean" 2192 - }, 2193 - "talk_dir": { 2194 - "type": "integer" 2195 - }, 2196 - "talk_dir_clip": { 2197 - "type": "boolean" 2198 - }, 2199 - "talk_file": { 2200 - "type": "integer" 2201 - }, 2202 - "talk_file_clip": { 2203 - "type": "boolean" 2204 - }, 2205 - "talk_filetype": { 2206 - "type": "boolean" 2207 - }, 2208 - "talk_menu": { 2209 - "type": "boolean" 2210 - }, 2211 - "talk_mixer_amp": { 2212 - "type": "integer" 2213 - }, 2214 - "timeformat": { 2215 - "type": "integer" 2216 - }, 2217 - "timestretch_enabled": { 2218 - "type": "boolean" 2219 - }, 2220 - "touch_mode": { 2221 - "type": "integer" 2222 - }, 2223 - "touchpad_deadzone": { 2224 - "type": "integer" 2225 - }, 2226 - "touchpad_sensitivity": { 2227 - "type": "integer" 2228 - }, 2229 - "treble": { 2230 - "type": "integer" 2231 - }, 2232 - "treble_cutoff": { 2233 - "type": "integer" 2234 - }, 2235 - "ts_calibration_data": { 1152 + "sort": { 2236 1153 "type": "object", 1154 + "nullable": true, 2237 1155 "properties": { 2238 - "A": { 2239 - "type": "integer" 2240 - }, 2241 - "B": { 2242 - "type": "integer" 2243 - }, 2244 - "C": { 2245 - "type": "integer" 2246 - }, 2247 - "D": { 2248 - "type": "integer" 2249 - }, 2250 - "E": { 2251 - "type": "integer" 2252 - }, 2253 - "F": { 2254 - "type": "integer" 2255 - }, 2256 - "divider": { 2257 - "type": "integer" 2258 - } 1156 + "field": { "type": "string" }, 1157 + "dir": { "type": "string", "enum": ["asc", "desc"] } 2259 1158 } 2260 1159 }, 2261 - "unplug_autoresume": { 2262 - "type": "boolean" 2263 - }, 2264 - "unplug_mode": { 2265 - "type": "integer" 2266 - }, 2267 - "usb_charging": { 2268 - "type": "integer" 2269 - }, 2270 - "usb_hid": { 2271 - "type": "boolean" 2272 - }, 2273 - "usb_keypad_mode": { 2274 - "type": "integer" 2275 - }, 2276 - "usb_skip_first_drive": { 2277 - "type": "boolean" 2278 - }, 2279 - "usemrb": { 2280 - "type": "integer" 2281 - }, 2282 - "viewers_icon_file": { 2283 - "type": "string" 2284 - }, 2285 - "volume": { 2286 - "type": "integer" 2287 - }, 2288 - "volume_adjust_mode": { 2289 - "type": "integer" 2290 - }, 2291 - "volume_adjust_norm_steps": { 2292 - "type": "integer" 2293 - }, 2294 - "volume_limit": { 2295 - "type": "integer" 2296 - }, 2297 - "volume_type": { 2298 - "type": "integer" 2299 - }, 2300 - "warnon_erase_dynplaylist": { 2301 - "type": "boolean" 2302 - }, 2303 - "wps_file": { 2304 - "type": "string" 2305 - } 2306 - }, 2307 - "x-examples": { 2308 - "Example 1": { 2309 - "afr_enabled": 0, 2310 - "album_art": 0, 2311 - "autocreatebookmark": 1935762276, 2312 - "autoloadbookmark": 1869623098, 2313 - "autoresume_automatic": 0, 2314 - "autoresume_enable": false, 2315 - "autoresume_paths": "", 2316 - "autoupdatebookmark": true, 2317 - "backdrop_file": "", 2318 - "backlight_on_button_hold": 0, 2319 - "backlight_timeout": 0, 2320 - "backlight_timeout_plugged": 0, 2321 - "balance": 0, 2322 - "bass": 0, 2323 - "bass_cutoff": 0, 2324 - "battery_capacity": 0, 2325 - "battery_display": 65536, 2326 - "battery_type": 1946157056, 2327 - "beep": 0, 2328 - "bg_color": 0, 2329 - "bidir_limit": 0, 2330 - "bl_filter_first_keypress": false, 2331 - "bl_selective_actions": false, 2332 - "bl_selective_actions_mask": 0, 2333 - "brightness": 0, 2334 - "browse_current": false, 2335 - "browser_default": 0, 2336 - "bt_selective_softlock_actions": false, 2337 - "bt_selective_softlock_actions_mask": 0, 2338 - "buffer_margin": 1, 2339 - "caption_backlight": false, 2340 - "car_adapter_mode": false, 2341 - "car_adapter_mode_delay": 60, 2342 - "channel_config": 0, 2343 - "colors_file": "", 2344 - "compressor_settings": { 2345 - "attack_time": 0, 2346 - "knee": 0, 2347 - "makeup_gain": 0, 2348 - "ratio": 0, 2349 - "release_time": 0, 2350 - "threshold": 0 2351 - }, 2352 - "constrain_next_folder": false, 2353 - "contrast": 1985966958, 2354 - "crossfade": 0, 2355 - "crossfade_fade_in_delay": 2, 2356 - "crossfade_fade_in_duration": 0, 2357 - "crossfade_fade_out_delay": 2, 2358 - "crossfade_fade_out_duration": 0, 2359 - "crossfade_fade_out_mixmode": 2, 2360 - "crossfeed": -60, 2361 - "crossfeed_cross_gain": 700, 2362 - "crossfeed_direct_gain": 4294967136, 2363 - "crossfeed_hf_attenuation": 0, 2364 - "crossfeed_hf_cutoff": 0, 2365 - "cuesheet": false, 2366 - "cursor_style": 825127794, 2367 - "default_codepage": 8, 2368 - "depth_3d": 3400, 2369 - "dircache": false, 2370 - "dirfilter": 1, 2371 - "disable_mainmenu_scrolling": false, 2372 - "disk_spindown": 0, 2373 - "dithering_enabled": true, 2374 - "eq_band_settings": [ 2375 - { 2376 - "cutoff": 0, 2377 - "gain": 10, 2378 - "q": 64 2379 - }, 2380 - { 2381 - "cutoff": 0, 2382 - "gain": 10, 2383 - "q": 125 2384 - }, 2385 - { 2386 - "cutoff": 0, 2387 - "gain": 10, 2388 - "q": 250 2389 - }, 2390 - { 2391 - "cutoff": 0, 2392 - "gain": 10, 2393 - "q": 500 2394 - }, 2395 - { 2396 - "cutoff": 0, 2397 - "gain": 10, 2398 - "q": 1000 2399 - }, 2400 - { 2401 - "cutoff": 0, 2402 - "gain": 10, 2403 - "q": 2000 2404 - }, 2405 - { 2406 - "cutoff": 0, 2407 - "gain": 10, 2408 - "q": 4000 2409 - }, 2410 - { 2411 - "cutoff": 0, 2412 - "gain": 10, 2413 - "q": 8000 2414 - }, 2415 - { 2416 - "cutoff": 0, 2417 - "gain": 7, 2418 - "q": 16000 2419 - }, 2420 - { 2421 - "cutoff": 0, 2422 - "gain": 0, 2423 - "q": 0 2424 - } 2425 - ], 2426 - "eq_enabled": true, 2427 - "eq_precut": 7, 2428 - "fade_on_stop": false, 2429 - "ff_rewind_accel": 846620009, 2430 - "ff_rewind_min_step": 1650614627, 2431 - "fg_color": 0, 2432 - "flip_display": true, 2433 - "font_file": "", 2434 - "glyphs_to_cache": 0, 2435 - "governor": 0, 2436 - "hold_lr_for_scroll_in_list": true, 2437 - "hotkey_tree": 0, 2438 - "hotkey_wps": 50, 2439 - "icon_file": "", 2440 - "interpret_numbers": 0, 2441 - "invert": true, 2442 - "kbd_file": "", 2443 - "keep_current_track_on_replace_playlist": false, 2444 - "keyclick": 0, 2445 - "keyclick_hardware": true, 2446 - "keyclick_repeats": 2, 2447 - "keypress_restarts_sleeptimer": false, 2448 - "lang_file": "", 2449 - "lcd_sleep_after_backlight_off": 0, 2450 - "list_accel_start_delay": 0, 2451 - "list_accel_wait": 0, 2452 - "list_line_padding": 6, 2453 - "list_order": 0, 2454 - "list_separator_color": 0, 2455 - "list_separator_height": 0, 2456 - "list_wraparound": false, 2457 - "lse_color": 0, 2458 - "lss_color": 0, 2459 - "lst_color": 0, 2460 - "max_files_in_dir": -1, 2461 - "max_files_in_playlist": 0, 2462 - "next_folder": 0, 2463 - "offset_out_of_view": false, 2464 - "party_mode": true, 2465 - "pause_rewind": 54710920, 2466 - "pbe": 0, 2467 - "pbe_precut": 0, 2468 - "peak_meter_clip_hold": 0, 2469 - "peak_meter_dbfs": false, 2470 - "peak_meter_hold": 0, 2471 - "peak_meter_max": 0, 2472 - "peak_meter_min": 0, 2473 - "peak_meter_release": 0, 2474 - "pitch_mode_semitone": false, 2475 - "pitch_mode_timestretch": false, 2476 - "play_frequency": 0, 2477 - "play_selected": true, 2478 - "player_name": "", 2479 - "playlist_catalog_dir": "", 2480 - "playlist_shuffle": false, 2481 - "playlist_viewer_icons": false, 2482 - "playlist_viewer_indices": false, 2483 - "playlist_viewer_track_display": 1, 2484 - "power_mode": 0, 2485 - "poweroff": 0, 2486 - "prevent_skip": false, 2487 - "recursive_dir_insert": 0, 2488 - "repeat_mode": 1, 2489 - "replaygain_settings": { 2490 - "noclip": false, 2491 - "preamp": -15, 2492 - "type": 0 2493 - }, 2494 - "resume_rewind": 35, 2495 - "rewind_across_tracks": false, 2496 - "roll_off": 320, 2497 - "root_menu_customized": false, 2498 - "runtimedb": false, 2499 - "sbs_file": "", 2500 - "screen_scroll_step": 909211702, 2501 - "scroll_delay": 1685024815, 2502 - "scroll_paginated": false, 2503 - "scroll_speed": 0, 2504 - "scroll_step": 1953718627, 2505 - "scrollbar": 50, 2506 - "scrollbar_width": 1000, 2507 - "shortcuts_replaces_qs": false, 2508 - "show_filename_ext": 2, 2509 - "show_icons": true, 2510 - "show_path_in_browser": 0, 2511 - "show_queue_options": 0, 2512 - "show_shuffled_adding_options": true, 2513 - "show_shutdown_message": false, 2514 - "single_mode": 60, 2515 - "skip_length": 6, 2516 - "sleeptimer_duration": 0, 2517 - "sleeptimer_on_startup": false, 2518 - "sort_case": true, 2519 - "sort_dir": 909192819, 2520 - "sort_file": 3551608, 2521 - "spdif_enable": true, 2522 - "speaker_mode": 0, 2523 - "start_directory": "", 2524 - "start_in_screen": 0, 2525 - "statusbar": 9, 2526 - "stereo_width": 100, 2527 - "stereosw_mode": 0, 2528 - "surround_balance": 0, 2529 - "surround_enabled": 0, 2530 - "surround_fx1": 0, 2531 - "surround_fx2": false, 2532 - "surround_method2": false, 2533 - "surround_mix": 1, 2534 - "tagcache_autoupdate": false, 2535 - "tagcache_db_path": "", 2536 - "tagcache_ram": 0, 2537 - "tagcache_scan_paths": "", 2538 - "talk_battery_level": true, 2539 - "talk_dir": 3, 2540 - "talk_dir_clip": true, 2541 - "talk_file": 1, 2542 - "talk_file_clip": false, 2543 - "talk_filetype": false, 2544 - "talk_menu": true, 2545 - "talk_mixer_amp": 1601136494, 2546 - "timeformat": 0, 2547 - "timestretch_enabled": false, 2548 - "touch_mode": 0, 2549 - "touchpad_deadzone": 0, 2550 - "touchpad_sensitivity": 0, 2551 - "treble": 0, 2552 - "treble_cutoff": 0, 2553 - "ts_calibration_data": { 2554 - "A": 0, 2555 - "B": 0, 2556 - "C": 0, 2557 - "D": 0, 2558 - "E": 0, 2559 - "F": 0, 2560 - "divider": 0 2561 - }, 2562 - "unplug_autoresume": true, 2563 - "unplug_mode": 0, 2564 - "usb_charging": 1868786015, 2565 - "usb_hid": false, 2566 - "usb_keypad_mode": 0, 2567 - "usb_skip_first_drive": false, 2568 - "usemrb": 0, 2569 - "viewers_icon_file": "", 2570 - "volume": 0, 2571 - "volume_adjust_mode": 0, 2572 - "volume_adjust_norm_steps": 0, 2573 - "volume_limit": 32, 2574 - "volume_type": 8421504, 2575 - "warnon_erase_dynplaylist": false, 2576 - "wps_file": "" 2577 - } 2578 - }, 2579 - "examples": [ 2580 - { 2581 - "afr_enabled": 0, 2582 - "album_art": 0, 2583 - "autocreatebookmark": 1935762276, 2584 - "autoloadbookmark": 1869623098, 2585 - "autoresume_automatic": 0, 2586 - "autoresume_enable": false, 2587 - "autoresume_paths": "", 2588 - "autoupdatebookmark": true, 2589 - "backdrop_file": "", 2590 - "backlight_on_button_hold": 0, 2591 - "backlight_timeout": 0, 2592 - "backlight_timeout_plugged": 0, 2593 - "balance": 0, 2594 - "bass": 0, 2595 - "bass_cutoff": 0, 2596 - "battery_capacity": 0, 2597 - "battery_display": 65536, 2598 - "battery_type": 1946157056, 2599 - "beep": 0, 2600 - "bg_color": 0, 2601 - "bidir_limit": 0, 2602 - "bl_filter_first_keypress": false, 2603 - "bl_selective_actions": false, 2604 - "bl_selective_actions_mask": 0, 2605 - "brightness": 0, 2606 - "browse_current": false, 2607 - "browser_default": 0, 2608 - "bt_selective_softlock_actions": false, 2609 - "bt_selective_softlock_actions_mask": 0, 2610 - "buffer_margin": 1, 2611 - "caption_backlight": false, 2612 - "car_adapter_mode": false, 2613 - "car_adapter_mode_delay": 60, 2614 - "channel_config": 0, 2615 - "colors_file": "", 2616 - "compressor_settings": { 2617 - "attack_time": 0, 2618 - "knee": 0, 2619 - "makeup_gain": 0, 2620 - "ratio": 0, 2621 - "release_time": 0, 2622 - "threshold": 0 2623 - }, 2624 - "constrain_next_folder": false, 2625 - "contrast": 1985966958, 2626 - "crossfade": 0, 2627 - "crossfade_fade_in_delay": 2, 2628 - "crossfade_fade_in_duration": 0, 2629 - "crossfade_fade_out_delay": 2, 2630 - "crossfade_fade_out_duration": 0, 2631 - "crossfade_fade_out_mixmode": 2, 2632 - "crossfeed": -60, 2633 - "crossfeed_cross_gain": 700, 2634 - "crossfeed_direct_gain": 4294967136, 2635 - "crossfeed_hf_attenuation": 0, 2636 - "crossfeed_hf_cutoff": 0, 2637 - "cuesheet": false, 2638 - "cursor_style": 825127794, 2639 - "default_codepage": 8, 2640 - "depth_3d": 3400, 2641 - "dircache": false, 2642 - "dirfilter": 1, 2643 - "disable_mainmenu_scrolling": false, 2644 - "disk_spindown": 0, 2645 - "dithering_enabled": true, 2646 - "eq_band_settings": [ 2647 - { 2648 - "cutoff": 0, 2649 - "gain": 10, 2650 - "q": 64 2651 - }, 2652 - { 2653 - "cutoff": 0, 2654 - "gain": 10, 2655 - "q": 125 2656 - }, 2657 - { 2658 - "cutoff": 0, 2659 - "gain": 10, 2660 - "q": 250 2661 - }, 2662 - { 2663 - "cutoff": 0, 2664 - "gain": 10, 2665 - "q": 500 2666 - }, 2667 - { 2668 - "cutoff": 0, 2669 - "gain": 10, 2670 - "q": 1000 2671 - }, 2672 - { 2673 - "cutoff": 0, 2674 - "gain": 10, 2675 - "q": 2000 2676 - }, 2677 - { 2678 - "cutoff": 0, 2679 - "gain": 10, 2680 - "q": 4000 2681 - }, 2682 - { 2683 - "cutoff": 0, 2684 - "gain": 10, 2685 - "q": 8000 2686 - }, 2687 - { 2688 - "cutoff": 0, 2689 - "gain": 7, 2690 - "q": 16000 2691 - }, 2692 - { 2693 - "cutoff": 0, 2694 - "gain": 0, 2695 - "q": 0 2696 - } 2697 - ], 2698 - "eq_enabled": true, 2699 - "eq_precut": 7, 2700 - "fade_on_stop": false, 2701 - "ff_rewind_accel": 846620009, 2702 - "ff_rewind_min_step": 1650614627, 2703 - "fg_color": 0, 2704 - "flip_display": true, 2705 - "font_file": "", 2706 - "glyphs_to_cache": 0, 2707 - "governor": 0, 2708 - "hold_lr_for_scroll_in_list": true, 2709 - "hotkey_tree": 0, 2710 - "hotkey_wps": 50, 2711 - "icon_file": "", 2712 - "interpret_numbers": 0, 2713 - "invert": true, 2714 - "kbd_file": "", 2715 - "keep_current_track_on_replace_playlist": false, 2716 - "keyclick": 0, 2717 - "keyclick_hardware": true, 2718 - "keyclick_repeats": 2, 2719 - "keypress_restarts_sleeptimer": false, 2720 - "lang_file": "", 2721 - "lcd_sleep_after_backlight_off": 0, 2722 - "list_accel_start_delay": 0, 2723 - "list_accel_wait": 0, 2724 - "list_line_padding": 6, 2725 - "list_order": 0, 2726 - "list_separator_color": 0, 2727 - "list_separator_height": 0, 2728 - "list_wraparound": false, 2729 - "lse_color": 0, 2730 - "lss_color": 0, 2731 - "lst_color": 0, 2732 - "max_files_in_dir": -1, 2733 - "max_files_in_playlist": 0, 2734 - "next_folder": 0, 2735 - "offset_out_of_view": false, 2736 - "party_mode": true, 2737 - "pause_rewind": 54710920, 2738 - "pbe": 0, 2739 - "pbe_precut": 0, 2740 - "peak_meter_clip_hold": 0, 2741 - "peak_meter_dbfs": false, 2742 - "peak_meter_hold": 0, 2743 - "peak_meter_max": 0, 2744 - "peak_meter_min": 0, 2745 - "peak_meter_release": 0, 2746 - "pitch_mode_semitone": false, 2747 - "pitch_mode_timestretch": false, 2748 - "play_frequency": 0, 2749 - "play_selected": true, 2750 - "player_name": "", 2751 - "playlist_catalog_dir": "", 2752 - "playlist_shuffle": false, 2753 - "playlist_viewer_icons": false, 2754 - "playlist_viewer_indices": false, 2755 - "playlist_viewer_track_display": 1, 2756 - "power_mode": 0, 2757 - "poweroff": 0, 2758 - "prevent_skip": false, 2759 - "recursive_dir_insert": 0, 2760 - "repeat_mode": 1, 2761 - "replaygain_settings": { 2762 - "noclip": false, 2763 - "preamp": -15, 2764 - "type": 0 2765 - }, 2766 - "resume_rewind": 35, 2767 - "rewind_across_tracks": false, 2768 - "roll_off": 320, 2769 - "root_menu_customized": false, 2770 - "runtimedb": false, 2771 - "sbs_file": "", 2772 - "screen_scroll_step": 909211702, 2773 - "scroll_delay": 1685024815, 2774 - "scroll_paginated": false, 2775 - "scroll_speed": 0, 2776 - "scroll_step": 1953718627, 2777 - "scrollbar": 50, 2778 - "scrollbar_width": 1000, 2779 - "shortcuts_replaces_qs": false, 2780 - "show_filename_ext": 2, 2781 - "show_icons": true, 2782 - "show_path_in_browser": 0, 2783 - "show_queue_options": 0, 2784 - "show_shuffled_adding_options": true, 2785 - "show_shutdown_message": false, 2786 - "single_mode": 60, 2787 - "skip_length": 6, 2788 - "sleeptimer_duration": 0, 2789 - "sleeptimer_on_startup": false, 2790 - "sort_case": true, 2791 - "sort_dir": 909192819, 2792 - "sort_file": 3551608, 2793 - "spdif_enable": true, 2794 - "speaker_mode": 0, 2795 - "start_directory": "", 2796 - "start_in_screen": 0, 2797 - "statusbar": 9, 2798 - "stereo_width": 100, 2799 - "stereosw_mode": 0, 2800 - "surround_balance": 0, 2801 - "surround_enabled": 0, 2802 - "surround_fx1": 0, 2803 - "surround_fx2": false, 2804 - "surround_method2": false, 2805 - "surround_mix": 1, 2806 - "tagcache_autoupdate": false, 2807 - "tagcache_db_path": "", 2808 - "tagcache_ram": 0, 2809 - "tagcache_scan_paths": "", 2810 - "talk_battery_level": true, 2811 - "talk_dir": 3, 2812 - "talk_dir_clip": true, 2813 - "talk_file": 1, 2814 - "talk_file_clip": false, 2815 - "talk_filetype": false, 2816 - "talk_menu": true, 2817 - "talk_mixer_amp": 1601136494, 2818 - "timeformat": 0, 2819 - "timestretch_enabled": false, 2820 - "touch_mode": 0, 2821 - "touchpad_deadzone": 0, 2822 - "touchpad_sensitivity": 0, 2823 - "treble": 0, 2824 - "treble_cutoff": 0, 2825 - "ts_calibration_data": { 2826 - "A": 0, 2827 - "B": 0, 2828 - "C": 0, 2829 - "D": 0, 2830 - "E": 0, 2831 - "F": 0, 2832 - "divider": 0 2833 - }, 2834 - "unplug_autoresume": true, 2835 - "unplug_mode": 0, 2836 - "usb_charging": 1868786015, 2837 - "usb_hid": false, 2838 - "usb_keypad_mode": 0, 2839 - "usb_skip_first_drive": false, 2840 - "usemrb": 0, 2841 - "viewers_icon_file": "", 2842 - "volume": 0, 2843 - "volume_adjust_mode": 0, 2844 - "volume_adjust_norm_steps": 0, 2845 - "volume_limit": 32, 2846 - "volume_type": 8421504, 2847 - "warnon_erase_dynplaylist": false, 2848 - "wps_file": "" 2849 - } 2850 - ], 2851 - "title": "Settings" 1160 + "limit": { "type": "integer", "format": "int32", "nullable": true } 1161 + } 1162 + }, 1163 + "TrackStats": { 1164 + "type": "object", 1165 + "properties": { 1166 + "track_id": { "type": "string" }, 1167 + "play_count": { "type": "integer", "format": "int64" }, 1168 + "skip_count": { "type": "integer", "format": "int64" }, 1169 + "last_played": { "type": "integer", "format": "int64", "nullable": true, "description": "Unix timestamp" }, 1170 + "last_skipped": { "type": "integer", "format": "int64", "nullable": true } 1171 + } 1172 + }, 1173 + "GlobalSettings": { 1174 + "type": "object", 1175 + "description": "Live `global_settings` snapshot. Fields mirror `apps/settings.h`.", 1176 + "additionalProperties": true, 1177 + "properties": { 1178 + "music_dir": { "type": "string" }, 1179 + "audio_output": { "type": "string", "enum": ["builtin", "fifo", "airplay", "squeezelite", "chromecast", "snapcast_tcp", "upnp"] }, 1180 + "volume": { "type": "integer", "format": "int32" }, 1181 + "volume_limit": { "type": "integer", "format": "int32" }, 1182 + "balance": { "type": "integer", "format": "int32" }, 1183 + "channel_config": { "type": "integer", "format": "int32" }, 1184 + "stereo_width": { "type": "integer", "format": "int32" }, 1185 + "playlist_shuffle": { "type": "boolean" }, 1186 + "repeat_mode": { "type": "integer", "format": "int32" }, 1187 + "crossfade": { "type": "integer", "format": "int32" }, 1188 + "eq_enabled": { "type": "boolean" }, 1189 + "eq_precut": { "type": "integer", "format": "int32" }, 1190 + "eq_band_settings": { "type": "array", "items": { "$ref": "#/components/schemas/EqBandSetting" } }, 1191 + "replaygain_settings": { "$ref": "#/components/schemas/ReplaygainSettings" }, 1192 + "compressor_settings": { "$ref": "#/components/schemas/CompressorSettings" } 1193 + } 1194 + }, 1195 + "NewGlobalSettings": { 1196 + "type": "object", 1197 + "description": "Partial settings update — only the fields you set are written.", 1198 + "additionalProperties": true 1199 + }, 1200 + "EqBandSetting": { 1201 + "type": "object", 1202 + "properties": { 1203 + "cutoff": { "type": "integer", "format": "int32", "description": "Centre / cutoff frequency in Hz" }, 1204 + "q": { "type": "integer", "format": "int32", "description": "Q × 10 (fixed-point)" }, 1205 + "gain": { "type": "integer", "format": "int32", "description": "Gain × 10 in dB (fixed-point)" } 1206 + } 1207 + }, 1208 + "ReplaygainSettings": { 1209 + "type": "object", 1210 + "properties": { 1211 + "noclip": { "type": "boolean" }, 1212 + "type": { "type": "integer", "format": "int32", "description": "0=Track 1=Album 2=Track-shuffle 3=Off" }, 1213 + "preamp": { "type": "integer", "format": "int32", "description": "dB × 10" } 1214 + } 1215 + }, 1216 + "CompressorSettings": { 1217 + "type": "object", 1218 + "properties": { 1219 + "threshold": { "type": "integer", "format": "int32", "description": "dB" }, 1220 + "makeup_gain": { "type": "integer", "format": "int32" }, 1221 + "ratio": { "type": "integer", "format": "int32" }, 1222 + "knee": { "type": "integer", "format": "int32" }, 1223 + "release_time": { "type": "integer", "format": "int32", "description": "ms" }, 1224 + "attack_time": { "type": "integer", "format": "int32", "description": "ms" } 1225 + } 1226 + }, 1227 + "GlobalStatus": { 1228 + "type": "object", 1229 + "additionalProperties": true, 1230 + "properties": { 1231 + "resume_index": { "type": "integer", "format": "int32" }, 1232 + "resume_offset": { "type": "integer", "format": "int64" }, 1233 + "resume_elapsed": { "type": "integer", "format": "int64" }, 1234 + "resume_crc32": { "type": "integer", "format": "int64" }, 1235 + "resume_pitch": { "type": "integer", "format": "int32" }, 1236 + "resume_speed": { "type": "integer", "format": "int32" }, 1237 + "runtime": { "type": "integer", "format": "int64" }, 1238 + "topruntime": { "type": "integer", "format": "int64" }, 1239 + "volume": { "type": "integer", "format": "int32" } 1240 + } 2852 1241 }, 2853 - "Track.v1": { 1242 + "BluetoothDevice": { 2854 1243 "type": "object", 2855 1244 "properties": { 2856 - "album": { 2857 - "type": "string" 2858 - }, 2859 - "album_artist": { 2860 - "type": "string" 2861 - }, 2862 - "album_id": { 2863 - "type": "string" 2864 - }, 2865 - "artist": { 2866 - "type": "string" 2867 - }, 2868 - "artist_id": { 2869 - "type": "string" 2870 - }, 2871 - "bitrate": { 2872 - "type": "integer" 2873 - }, 2874 - "composer": { 2875 - "type": "string" 2876 - }, 2877 - "created_at": { 2878 - "type": "integer" 2879 - }, 2880 - "disc_number": { 2881 - "type": "integer" 2882 - }, 2883 - "filesize": { 2884 - "type": "integer" 2885 - }, 2886 - "frequency": { 2887 - "type": "integer" 2888 - }, 2889 - "genre_id": { 2890 - "type": "string" 2891 - }, 2892 - "id": { 2893 - "type": "string" 2894 - }, 2895 - "length": { 2896 - "type": "integer" 2897 - }, 2898 - "md5": { 2899 - "type": "string" 2900 - }, 2901 - "path": { 2902 - "type": "string" 2903 - }, 2904 - "title": { 2905 - "type": "string" 2906 - }, 2907 - "track_number": { 2908 - "type": "integer" 2909 - }, 2910 - "updated_at": { 2911 - "type": "integer" 2912 - }, 2913 - "year": { 2914 - "type": "integer" 2915 - }, 2916 - "year_string": { 2917 - "type": "string" 2918 - } 2919 - }, 2920 - "x-examples": { 2921 - "Example 1": { 2922 - "album": "2014 Forest Hills Drive", 2923 - "album_artist": "J. Cole", 2924 - "album_id": "cm1ibibnn00043fj7ppakq9w9", 2925 - "artist": "J. Cole", 2926 - "artist_id": "cm1ibibnn00033fj7hy019na5", 2927 - "bitrate": 96, 2928 - "composer": "Roy Hammond, Jermaine Cole, CHARLES SIMMONS", 2929 - "created_at": 1727296114, 2930 - "disc_number": 1, 2931 - "filesize": 2872773, 2932 - "frequency": 44100, 2933 - "genre_id": "", 2934 - "id": "cm1ibibnn00053fj79ghe6b2d", 2935 - "length": 239381, 2936 - "md5": "b5a0d86e156e6d02c90f647bf5cc6fc2", 2937 - "path": "/home/coder/Music/03 - J. Cole - Wet Dreamz(Explicit).m4a", 2938 - "title": "Wet Dreamz", 2939 - "track_number": 3, 2940 - "updated_at": 1727296114, 2941 - "year": 2014, 2942 - "year_string": "2014-12-09" 2943 - } 2944 - }, 2945 - "examples": [ 2946 - { 2947 - "album": "2014 Forest Hills Drive", 2948 - "album_artist": "J. Cole", 2949 - "album_id": "cm1ibibnn00043fj7ppakq9w9", 2950 - "artist": "J. Cole", 2951 - "artist_id": "cm1ibibnn00033fj7hy019na5", 2952 - "bitrate": 96, 2953 - "composer": "Roy Hammond, Jermaine Cole, CHARLES SIMMONS", 2954 - "created_at": 1727296114, 2955 - "disc_number": 1, 2956 - "filesize": 2872773, 2957 - "frequency": 44100, 2958 - "genre_id": "", 2959 - "id": "cm1ibibnn00053fj79ghe6b2d", 2960 - "length": 239381, 2961 - "md5": "b5a0d86e156e6d02c90f647bf5cc6fc2", 2962 - "path": "/home/coder/Music/03 - J. Cole - Wet Dreamz(Explicit).m4a", 2963 - "title": "Wet Dreamz", 2964 - "track_number": 3, 2965 - "updated_at": 1727296114, 2966 - "year": 2014, 2967 - "year_string": "2014-12-09" 2968 - } 2969 - ], 2970 - "description": "", 2971 - "title": "Track" 1245 + "address": { "type": "string" }, 1246 + "name": { "type": "string" }, 1247 + "rssi": { "type": "integer", "format": "int32", "nullable": true }, 1248 + "is_paired": { "type": "boolean" }, 1249 + "is_connected": { "type": "boolean" } 1250 + } 2972 1251 } 2973 1252 } 2974 - }, 2975 - "x-internal": true 1253 + } 2976 1254 }
+33
mintlify/AGENTS.md
··· 1 + > **First-time setup**: Customize this file for your project. Prompt the user to customize this file for their project. 2 + > For Mintlify product knowledge (components, configuration, writing standards), 3 + > install the Mintlify skill: `npx skills add https://mintlify.com/docs` 4 + 5 + # Documentation project instructions 6 + 7 + ## About this project 8 + 9 + - This is a documentation site built on [Mintlify](https://mintlify.com) 10 + - Pages are MDX files with YAML frontmatter 11 + - Configuration lives in `docs.json` 12 + - Run `mint dev` to preview locally 13 + - Run `mint broken-links` to check links 14 + 15 + ## Terminology 16 + 17 + {/* Add product-specific terms and preferred usage */} 18 + {/* Example: Use "workspace" not "project", "member" not "user" */} 19 + 20 + ## Style preferences 21 + 22 + {/* Add any project-specific style rules below */} 23 + 24 + - Use active voice and second person ("you") 25 + - Keep sentences concise — one idea per sentence 26 + - Use sentence case for headings 27 + - Bold for UI elements: Click **Settings** 28 + - Code formatting for file names, commands, paths, and code references 29 + 30 + ## Content boundaries 31 + 32 + {/* Define what should and shouldn't be documented */} 33 + {/* Example: Don't document internal admin features */}
+34
mintlify/CONTRIBUTING.md
··· 1 + > **Customize this file**: Tailor this template to your project by noting specific contribution types you're looking for, adding a Code of Conduct, or adjusting the writing guidelines to match your style. 2 + 3 + # Contribute to the documentation 4 + 5 + Thank you for your interest in contributing to our documentation! This guide will help you get started. 6 + 7 + ## How to contribute 8 + 9 + ### Option 1: Edit directly on GitHub 10 + 11 + 1. Navigate to the page you want to edit 12 + 2. Click the "Edit this file" button (the pencil icon) 13 + 3. Make your changes and submit a pull request 14 + 15 + ### Option 2: Local development 16 + 17 + 1. Fork and clone this repository 18 + 2. Install the Mintlify CLI: `npm i -g mint` 19 + 3. Create a branch for your changes 20 + 4. Make changes 21 + 5. Navigate to the docs directory and run `mint dev` 22 + 6. Preview your changes at `http://localhost:3000` 23 + 7. Commit your changes and submit a pull request 24 + 25 + For more details on local development, see our [development guide](development.mdx). 26 + 27 + ## Writing guidelines 28 + 29 + - **Use active voice**: "Run the command" not "The command should be run" 30 + - **Address the reader directly**: Use "you" instead of "the user" 31 + - **Keep sentences concise**: Aim for one idea per sentence 32 + - **Lead with the goal**: Start instructions with what the user wants to accomplish 33 + - **Use consistent terminology**: Don't alternate between synonyms for the same concept 34 + - **Include examples**: Show, don't just tell
+21
mintlify/LICENSE
··· 1 + MIT License 2 + 3 + Copyright (c) 2023 Mintlify 4 + 5 + Permission is hereby granted, free of charge, to any person obtaining a copy 6 + of this software and associated documentation files (the "Software"), to deal 7 + in the Software without restriction, including without limitation the rights 8 + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 + copies of the Software, and to permit persons to whom the Software is 10 + furnished to do so, subject to the following conditions: 11 + 12 + The above copyright notice and this permission notice shall be included in all 13 + copies or substantial portions of the Software. 14 + 15 + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 + SOFTWARE.
+61
mintlify/README.md
··· 1 + # Rockbox Zig — Documentation 2 + 3 + The Mintlify source for [https://rockboxzig.mintlify.app](https://rockboxzig.mintlify.app). 4 + 5 + ## Layout 6 + 7 + ``` 8 + mintlify/ 9 + ├─ docs.json # navigation, theme, anchors 10 + ├─ index.mdx # landing page 11 + ├─ quickstart.mdx 12 + ├─ installation.mdx 13 + ├─ configuration.mdx 14 + ├─ audio-output/ # builtin / snapcast / airplay / squeezelite / chromecast / upnp 15 + ├─ audio-settings/ # EQ, DSP, ReplayGain, crossfade 16 + ├─ clients/ # web, desktop, MPD, MPRIS 17 + ├─ architecture/ # build system, PCM sinks 18 + ├─ reference/ # CLI, ports, settings.toml, troubleshooting, FAQ 19 + ├─ api-reference/ 20 + │ ├─ introduction.mdx 21 + │ ├─ openapi.json # synced from crates/server/openapi.json 22 + │ ├─ rest/overview.mdx 23 + │ ├─ graphql/overview.mdx 24 + │ ├─ grpc/overview.mdx 25 + │ └─ mpd/overview.mdx 26 + └─ sdks/ # TypeScript / Python / Ruby / Elixir / Clojure / Gleam 27 + ``` 28 + 29 + ## Local preview 30 + 31 + ```sh 32 + npm i -g mint 33 + mint dev 34 + ``` 35 + 36 + The preview lives at [http://localhost:3000](http://localhost:3000) and 37 + reloads on save. 38 + 39 + ## Keeping the OpenAPI spec in sync 40 + 41 + `api-reference/openapi.json` mirrors `crates/server/openapi.json`. After 42 + adding or changing an HTTP route, edit 43 + `crates/server/openapi.json` and copy it over: 44 + 45 + ```sh 46 + cp crates/server/openapi.json mintlify/api-reference/openapi.json 47 + ``` 48 + 49 + Mintlify auto-generates one page per operation under the **HTTP REST** 50 + group on every build. 51 + 52 + ## Validating before pushing 53 + 54 + ```sh 55 + mint broken-links 56 + ``` 57 + 58 + ## Deployment 59 + 60 + The Mintlify GitHub app deploys this directory automatically on push to 61 + the default branch. No manual step needed.
+99
mintlify/api-reference/graphql/overview.mdx
··· 1 + --- 2 + title: "GraphQL" 3 + description: "Single endpoint with queries, mutations and real-time subscriptions on port 6062." 4 + icon: 'code' 5 + --- 6 + 7 + The GraphQL server is the **best fit for UIs**: typed schema, batched 8 + queries, and a `track:changed` / `status:changed` / `playlist:changed` 9 + subscription stream over WebSocket. 10 + 11 + - **Endpoint** — `http://localhost:6062/graphql` 12 + - **WebSocket** — `ws://localhost:6062/graphql` (`graphql-ws` protocol) 13 + - **GraphiQL** — `http://localhost:6062/graphiql` 14 + 15 + The schema is generated from `crates/graphql/` and served by Juniper. All 16 + client SDKs in [SDKs](/sdks/overview) wrap this transport. 17 + 18 + ## Quick examples 19 + 20 + <CodeGroup> 21 + ```graphql Now playing 22 + query NowPlaying { 23 + currentTrack { 24 + title 25 + artist 26 + album 27 + elapsed 28 + length 29 + } 30 + playbackStatus { status } 31 + } 32 + ``` 33 + 34 + ```graphql Search 35 + query Search($q: String!) { 36 + search(term: $q) { 37 + artists { name id } 38 + albums { title artist year id } 39 + tracks { title artist album id } 40 + } 41 + } 42 + ``` 43 + 44 + ```graphql Play an album 45 + mutation PlayAlbum($id: String!) { 46 + playAlbum(albumId: $id, shuffle: false) 47 + } 48 + ``` 49 + 50 + ```graphql Subscribe to track changes 51 + subscription OnTrack { 52 + track { 53 + title 54 + artist 55 + elapsed 56 + length 57 + } 58 + } 59 + ``` 60 + </CodeGroup> 61 + 62 + ## Subscriptions 63 + 64 + Three subscriptions are exposed: 65 + 66 + | Subscription | Payload | Fires when | 67 + |-------------------------|-------------------------------|------------------------------------------| 68 + | `track` | `Track` | The currently playing track changes | 69 + | `playbackStatus` | `AudioStatus { status: Int }` | Stopped/playing/paused changes | 70 + | `playlist` | `Playlist` | The live queue is mutated | 71 + 72 + All three are pushed by the broker loop in `crates/server/src/lib.rs:start_broker()`. 73 + 74 + ## Connecting from a browser 75 + 76 + ```ts 77 + import { RockboxClient } from '@rockbox-zig/sdk'; 78 + 79 + const client = new RockboxClient(); 80 + client.connect(); 81 + 82 + client.on('track:changed', (t) => { 83 + document.title = `${t.title} — ${t.artist}`; 84 + }); 85 + ``` 86 + 87 + For language-specific guides, see [SDKs](/sdks/overview). 88 + 89 + ## Schema introspection 90 + 91 + GraphiQL ships pre-installed at 92 + [http://localhost:6062/graphiql](http://localhost:6062/graphiql) — every 93 + type, every field, every argument. 94 + 95 + You can also dump the schema directly: 96 + 97 + ```sh 98 + npx graphql-cli get-schema -e http://localhost:6062/graphql > schema.graphql 99 + ```
+60
mintlify/api-reference/grpc/overview.mdx
··· 1 + --- 2 + title: "gRPC" 3 + description: "Strongly-typed gRPC and gRPC-Web on port 6061." 4 + icon: 'cube' 5 + --- 6 + 7 + The gRPC server runs on **port 6061** and serves both native gRPC and 8 + gRPC-Web (so browser clients work without a proxy). 9 + 10 + - **Endpoint** — `localhost:6061` 11 + - **Schema** — published on Buf: 12 + [buf.build/tsiry/rockboxapis ↗](https://buf.build/tsiry/rockboxapis/docs/main:rockbox.v1alpha1) 13 + - **Buf Studio playground** — 14 + [open ↗](https://buf.build/studio/tsiry/rockboxapis/rockbox.v1alpha1.LibraryService/GetAlbums?target=http%3A%2F%2Flocalhost%3A6061&selectedProtocol=grpc-web) 15 + 16 + ## Services 17 + 18 + The proto definitions live under `proto/` (in 19 + [buf.build/tsiry/rockboxapis](https://buf.build/tsiry/rockboxapis)) and 20 + generate Rust bindings at `crates/rpc/`: 21 + 22 + | Service | Purpose | 23 + |---------------------|----------------------------------------------------| 24 + | `PlaybackService` | Transport, current/next track, seek, volume | 25 + | `LibraryService` | Albums, artists, tracks, search | 26 + | `PlaylistService` | Live queue + saved playlists | 27 + | `SettingsService` | Read / update `global_settings` | 28 + | `SoundService` | Volume + sound parameters | 29 + | `BrowseService` | Filesystem browsing | 30 + | `SystemService` | Version, scan, status | 31 + 32 + ## Generating clients 33 + 34 + Use Buf to generate clients in any supported language: 35 + 36 + ```sh 37 + buf generate buf.build/tsiry/rockboxapis 38 + ``` 39 + 40 + Or pull the proto files directly: 41 + 42 + ```sh 43 + buf export buf.build/tsiry/rockboxapis -o proto/ 44 + ``` 45 + 46 + ## Quick test with grpcurl 47 + 48 + ```sh 49 + grpcurl -plaintext localhost:6061 list 50 + grpcurl -plaintext localhost:6061 rockbox.v1alpha1.LibraryService/GetAlbums 51 + grpcurl -plaintext -d '{"id": "<album-id>"}' \ 52 + localhost:6061 rockbox.v1alpha1.LibraryService/GetAlbum 53 + ``` 54 + 55 + ## gRPC-Web from the browser 56 + 57 + The same port speaks gRPC-Web — useful for browser apps that want a 58 + strongly-typed binding without a translating reverse proxy. Use 59 + [`@bufbuild/connect-web`](https://www.npmjs.com/package/@bufbuild/connect-web) 60 + or the language equivalent.
+61
mintlify/api-reference/introduction.mdx
··· 1 + --- 2 + title: "API overview" 3 + description: "Four protocols, one source of truth — pick whichever fits your client." 4 + icon: 'plug' 5 + --- 6 + 7 + `rockboxd` exposes the same in-process state through four independent 8 + servers. They are all started by `start_servers()` in 9 + `crates/server/src/lib.rs` and all share one set of mutexes around the 10 + firmware, so a change made via gRPC is immediately visible over GraphQL, 11 + HTTP and MPD. 12 + 13 + | Protocol | Default port | Use it for | 14 + |--------------|--------------|-------------------------------------------------------| 15 + | **HTTP REST** | 6063 | `curl`-able. Simple integrations, scripts, webhooks. | 16 + | **GraphQL** | 6062 | Best fit for UIs. Subscriptions for real-time events.| 17 + | **gRPC** | 6061 | Strongly-typed, multi-language. gRPC-Web supported. | 18 + | **MPD** | 6600 | Existing MPD clients (`mpc`, `ncmpcpp`, MALP, …). | 19 + 20 + <CardGroup cols={2}> 21 + <Card title="HTTP REST" icon="bolt" href="/api-reference/rest/overview"> 22 + Open `http://localhost:6063` and explore. 23 + </Card> 24 + <Card title="GraphQL" icon="code" href="/api-reference/graphql/overview"> 25 + GraphiQL at `http://localhost:6062/graphiql`. 26 + </Card> 27 + <Card title="gRPC" icon="cube" href="/api-reference/grpc/overview"> 28 + Schema published on Buf. 29 + </Card> 30 + <Card title="MPD" icon="terminal" href="/api-reference/mpd/overview"> 31 + Anything that speaks MPD on `localhost:6600`. 32 + </Card> 33 + </CardGroup> 34 + 35 + ## Auto-generated REST pages 36 + 37 + Every endpoint in the [HTTP REST API](/api-reference/rest/overview) has its 38 + own page generated from the canonical OpenAPI spec 39 + ([`openapi.json`](https://github.com/tsirysndr/rockbox-zig/blob/master/crates/server/openapi.json)). 40 + The spec is also served live at `http://localhost:6063/openapi.json` while 41 + rockboxd is running. 42 + 43 + ## Authentication 44 + 45 + All four servers are unauthenticated. They are intended for use on a 46 + trusted LAN. If you expose Rockbox publicly, put it behind a reverse 47 + proxy with TLS and HTTP basic auth. 48 + 49 + ## Pick a client SDK 50 + 51 + We maintain six first-party SDKs. They wrap the GraphQL transport with 52 + typed methods, real-time subscriptions and a plugin system. 53 + 54 + <CardGroup cols={3}> 55 + <Card title="TypeScript" icon="js" href="/sdks/typescript" /> 56 + <Card title="Python" icon="python" href="/sdks/python" /> 57 + <Card title="Ruby" icon="gem" href="/sdks/ruby" /> 58 + <Card title="Elixir" icon="droplet" href="/sdks/elixir" /> 59 + <Card title="Clojure" icon="lambda" href="/sdks/clojure" /> 60 + <Card title="Gleam" icon="star" href="/sdks/gleam" /> 61 + </CardGroup>
+29
mintlify/api-reference/mpd/overview.mdx
··· 1 + --- 2 + title: "MPD protocol" 3 + description: "Drop-in MPD server on port 6600 — works with every MPD client." 4 + icon: 'terminal' 5 + --- 6 + 7 + `rockboxd` runs a Music Player Daemon-compatible server on **port 6600**. 8 + Any MPD client works out of the box. 9 + 10 + ```sh 11 + mpc -h localhost -p 6600 status 12 + mpc -h localhost -p 6600 update 13 + mpc -h localhost -p 6600 search title "Money" 14 + mpc -h localhost -p 6600 play 15 + ``` 16 + 17 + For the full client list and known limitations, see 18 + [Clients › MPD](/clients/mpd). For the wire protocol reference itself, 19 + see the [official MPD documentation ↗](https://mpd.readthedocs.io/en/stable/protocol.html). 20 + 21 + ## When to choose MPD over the others 22 + 23 + - You already have an MPD client you like. 24 + - You want a stable wire protocol with decades of community libraries. 25 + - You want to drive Rockbox from a TUI like `ncmpcpp`. 26 + 27 + For everything else — programmatic control, real-time UIs, custom apps — 28 + you'll have a better time with [GraphQL](/api-reference/graphql/overview) 29 + or one of the [SDKs](/sdks/overview).
+1254
mintlify/api-reference/openapi.json
··· 1 + { 2 + "openapi": "3.1.0", 3 + "info": { 4 + "title": "Rockbox HTTP API", 5 + "version": "1.0.0", 6 + "summary": "HTTP REST API for rockboxd — playback, library, playlists, devices, settings.", 7 + "description": "Rockbox Zig exposes its full feature surface over HTTP REST on port 6063 (configurable via `ROCKBOX_TCP_PORT`). It complements the GraphQL API on :6062 and the gRPC API on :6061 — all three speak to the same in-process state.\n\nAll handlers are defined in `crates/server/src/handlers/` and registered in `crates/server/src/lib.rs:run_http_server()`.", 8 + "contact": { 9 + "name": "Rockbox Zig", 10 + "url": "https://github.com/tsirysndr/rockbox-zig" 11 + }, 12 + "license": { 13 + "name": "GPL-2.0", 14 + "url": "https://www.gnu.org/licenses/old-licenses/gpl-2.0.html" 15 + } 16 + }, 17 + "servers": [ 18 + { 19 + "url": "http://localhost:6063", 20 + "description": "Default local rockboxd" 21 + } 22 + ], 23 + "tags": [ 24 + { "name": "Albums" }, 25 + { "name": "Artists" }, 26 + { "name": "Tracks" }, 27 + { "name": "Search" }, 28 + { "name": "Browse" }, 29 + { "name": "Player" }, 30 + { "name": "Playlist (queue)" }, 31 + { "name": "Saved playlists" }, 32 + { "name": "Smart playlists" }, 33 + { "name": "Track stats" }, 34 + { "name": "Devices" }, 35 + { "name": "Settings" }, 36 + { "name": "System" }, 37 + { "name": "Bluetooth" } 38 + ], 39 + "paths": { 40 + "/albums": { 41 + "get": { 42 + "operationId": "getAlbums", 43 + "tags": ["Albums"], 44 + "summary": "List all albums", 45 + "responses": { 46 + "200": { 47 + "description": "Array of albums", 48 + "content": { 49 + "application/json": { 50 + "schema": { "type": "array", "items": { "$ref": "#/components/schemas/Album" } } 51 + } 52 + } 53 + } 54 + } 55 + } 56 + }, 57 + "/albums/{id}": { 58 + "get": { 59 + "operationId": "getAlbum", 60 + "tags": ["Albums"], 61 + "summary": "Get an album by id", 62 + "parameters": [{ "$ref": "#/components/parameters/IdPath" }], 63 + "responses": { 64 + "200": { "description": "Album", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Album" } } } }, 65 + "404": { "$ref": "#/components/responses/NotFound" } 66 + } 67 + } 68 + }, 69 + "/albums/{id}/tracks": { 70 + "get": { 71 + "operationId": "getAlbumTracks", 72 + "tags": ["Albums"], 73 + "summary": "List tracks in an album", 74 + "parameters": [{ "$ref": "#/components/parameters/IdPath" }], 75 + "responses": { 76 + "200": { 77 + "description": "Tracks belonging to the album", 78 + "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/Track" } } } } 79 + } 80 + } 81 + } 82 + }, 83 + "/artists": { 84 + "get": { 85 + "operationId": "getArtists", 86 + "tags": ["Artists"], 87 + "summary": "List all artists", 88 + "responses": { 89 + "200": { "description": "Array of artists", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/Artist" } } } } } 90 + } 91 + } 92 + }, 93 + "/artists/{id}": { 94 + "get": { 95 + "operationId": "getArtist", 96 + "tags": ["Artists"], 97 + "summary": "Get an artist by id", 98 + "parameters": [{ "$ref": "#/components/parameters/IdPath" }], 99 + "responses": { 100 + "200": { "description": "Artist", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Artist" } } } }, 101 + "404": { "$ref": "#/components/responses/NotFound" } 102 + } 103 + } 104 + }, 105 + "/artists/{id}/albums": { 106 + "get": { 107 + "operationId": "getArtistAlbums", 108 + "tags": ["Artists"], 109 + "summary": "List albums by an artist", 110 + "parameters": [{ "$ref": "#/components/parameters/IdPath" }], 111 + "responses": { 112 + "200": { "description": "Albums by the artist", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/Album" } } } } } 113 + } 114 + } 115 + }, 116 + "/artists/{id}/tracks": { 117 + "get": { 118 + "operationId": "getArtistTracks", 119 + "tags": ["Artists"], 120 + "summary": "List tracks by an artist", 121 + "parameters": [{ "$ref": "#/components/parameters/IdPath" }], 122 + "responses": { 123 + "200": { "description": "Tracks by the artist", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/Track" } } } } } 124 + } 125 + } 126 + }, 127 + "/tracks": { 128 + "get": { 129 + "operationId": "getTracks", 130 + "tags": ["Tracks"], 131 + "summary": "List all tracks in the library", 132 + "responses": { 133 + "200": { "description": "All tracks", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/Track" } } } } } 134 + } 135 + } 136 + }, 137 + "/tracks/{id}": { 138 + "get": { 139 + "operationId": "getTrack", 140 + "tags": ["Tracks"], 141 + "summary": "Get a track by id", 142 + "parameters": [{ "$ref": "#/components/parameters/IdPath" }], 143 + "responses": { 144 + "200": { "description": "Track", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Track" } } } }, 145 + "404": { "$ref": "#/components/responses/NotFound" } 146 + } 147 + } 148 + }, 149 + "/tracks/stream-metadata": { 150 + "put": { 151 + "operationId": "saveStreamTrackMetadata", 152 + "tags": ["Tracks"], 153 + "summary": "Persist metadata for an HTTP stream URL", 154 + "description": "Used by the player when a remote URL is loaded — saves title/artist/album/duration so the URL can be resolved from the DB later.", 155 + "requestBody": { 156 + "required": true, 157 + "content": { 158 + "application/json": { 159 + "schema": { 160 + "type": "object", 161 + "required": ["url", "title", "artist", "album", "duration_ms"], 162 + "properties": { 163 + "url": { "type": "string", "format": "uri" }, 164 + "title": { "type": "string" }, 165 + "artist": { "type": "string" }, 166 + "album": { "type": "string" }, 167 + "duration_ms": { "type": "integer", "format": "int32", "minimum": 0 } 168 + } 169 + } 170 + } 171 + } 172 + }, 173 + "responses": { "204": { "description": "Stored" } } 174 + } 175 + }, 176 + "/search": { 177 + "get": { 178 + "operationId": "search", 179 + "tags": ["Search"], 180 + "summary": "Full-text search powered by Typesense", 181 + "parameters": [ 182 + { "name": "q", "in": "query", "schema": { "type": "string" }, "description": "Search term" } 183 + ], 184 + "responses": { 185 + "200": { 186 + "description": "Matching tracks, albums and artists", 187 + "content": { 188 + "application/json": { 189 + "schema": { 190 + "type": "object", 191 + "properties": { 192 + "tracks": { "type": "array", "items": { "$ref": "#/components/schemas/Track" } }, 193 + "albums": { "type": "array", "items": { "$ref": "#/components/schemas/Album" } }, 194 + "artists": { "type": "array", "items": { "$ref": "#/components/schemas/Artist" } } 195 + } 196 + } 197 + } 198 + } 199 + } 200 + } 201 + } 202 + }, 203 + "/browse/tree-entries": { 204 + "get": { 205 + "operationId": "getTreeEntries", 206 + "tags": ["Browse"], 207 + "summary": "Browse the filesystem under music_dir", 208 + "parameters": [ 209 + { "name": "q", "in": "query", "schema": { "type": "string" }, "description": "Path to list (absolute). Defaults to $ROCKBOX_LIBRARY or $HOME/Music." }, 210 + { "name": "show_hidden", "in": "query", "schema": { "type": "string", "enum": ["true", "false"] }, "description": "Show dotfiles (default false)" } 211 + ], 212 + "responses": { 213 + "200": { "description": "Directory entries", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/TreeEntry" } } } } }, 214 + "500": { "description": "Path is not a directory or other I/O error" } 215 + } 216 + } 217 + }, 218 + "/player": { 219 + "get": { 220 + "operationId": "getCurrentPlayer", 221 + "tags": ["Player"], 222 + "summary": "Get the current output device", 223 + "responses": { 224 + "200": { "description": "Current device", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Device" } } } } 225 + } 226 + } 227 + }, 228 + "/player/load": { 229 + "put": { 230 + "operationId": "loadTracks", 231 + "tags": ["Player"], 232 + "summary": "Load tracks into an external player (Cast/AirPlay)", 233 + "requestBody": { 234 + "required": true, 235 + "content": { "application/json": { "schema": { "$ref": "#/components/schemas/LoadTracks" } } } 236 + }, 237 + "responses": { 238 + "200": { "description": "Loaded" }, 239 + "404": { "description": "No external player connected or no playable tracks" } 240 + } 241 + } 242 + }, 243 + "/player/play": { 244 + "put": { 245 + "operationId": "play", 246 + "tags": ["Player"], 247 + "summary": "Start playback at an offset", 248 + "parameters": [ 249 + { "name": "elapsed", "in": "query", "schema": { "type": "integer", "format": "int64" } }, 250 + { "name": "offset", "in": "query", "schema": { "type": "integer", "format": "int64" } } 251 + ], 252 + "responses": { "200": { "description": "Playback started" } } 253 + } 254 + }, 255 + "/player/pause": { 256 + "put": { "operationId": "pause", "tags": ["Player"], "summary": "Pause playback", "responses": { "200": { "description": "Paused" } } } 257 + }, 258 + "/player/resume": { 259 + "put": { "operationId": "resume", "tags": ["Player"], "summary": "Resume playback", "responses": { "200": { "description": "Resumed" } } } 260 + }, 261 + "/player/ff-rewind": { 262 + "put": { 263 + "operationId": "ffRewind", 264 + "tags": ["Player"], 265 + "summary": "Seek to an absolute position (ms)", 266 + "parameters": [{ "name": "newtime", "in": "query", "required": true, "schema": { "type": "integer", "format": "int32" } }], 267 + "responses": { "200": { "description": "Seeked" } } 268 + } 269 + }, 270 + "/player/status": { 271 + "get": { 272 + "operationId": "getStatus", 273 + "tags": ["Player"], 274 + "summary": "Get the playback status", 275 + "responses": { 276 + "200": { "description": "Audio status", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AudioStatus" } } } } 277 + } 278 + } 279 + }, 280 + "/player/current-track": { 281 + "get": { 282 + "operationId": "getCurrentTrack", 283 + "tags": ["Player"], 284 + "summary": "Get the currently playing track", 285 + "responses": { 286 + "200": { 287 + "description": "Currently playing Mp3Entry, or null if nothing is playing", 288 + "content": { "application/json": { "schema": { "oneOf": [ { "$ref": "#/components/schemas/Mp3Entry" }, { "type": "null" } ] } } } 289 + } 290 + } 291 + } 292 + }, 293 + "/player/next-track": { 294 + "get": { 295 + "operationId": "getNextTrack", 296 + "tags": ["Player"], 297 + "summary": "Get the next queued track", 298 + "responses": { 299 + "200": { 300 + "description": "Next Mp3Entry, or null if at end of queue", 301 + "content": { "application/json": { "schema": { "oneOf": [ { "$ref": "#/components/schemas/Mp3Entry" }, { "type": "null" } ] } } } 302 + } 303 + } 304 + } 305 + }, 306 + "/player/flush-and-reload-tracks": { 307 + "put": { "operationId": "flushAndReloadTracks", "tags": ["Player"], "summary": "Flush PCM buffers and reload the current queue", "responses": { "200": { "description": "Reloaded" } } } 308 + }, 309 + "/player/next": { 310 + "put": { "operationId": "next", "tags": ["Player"], "summary": "Skip to the next track", "responses": { "200": { "description": "Skipped" } } } 311 + }, 312 + "/player/previous": { 313 + "put": { "operationId": "previous", "tags": ["Player"], "summary": "Skip to the previous track", "responses": { "200": { "description": "Skipped" } } } 314 + }, 315 + "/player/stop": { 316 + "put": { "operationId": "stop", "tags": ["Player"], "summary": "Hard-stop playback", "responses": { "200": { "description": "Stopped" } } } 317 + }, 318 + "/player/file-position": { 319 + "get": { 320 + "operationId": "getFilePosition", 321 + "tags": ["Player"], 322 + "summary": "Get the current byte offset in the playing file", 323 + "responses": { 324 + "200": { "description": "Byte offset", "content": { "application/json": { "schema": { "type": "integer", "format": "int64" } } } } 325 + } 326 + } 327 + }, 328 + "/player/volume": { 329 + "get": { 330 + "operationId": "getVolume", 331 + "tags": ["Player"], 332 + "summary": "Get current volume range and value", 333 + "responses": { 334 + "200": { 335 + "description": "Volume info", 336 + "content": { "application/json": { "schema": { 337 + "type": "object", 338 + "properties": { 339 + "volume": { "type": "integer", "format": "int32" }, 340 + "min": { "type": "integer", "format": "int32" }, 341 + "max": { "type": "integer", "format": "int32" } 342 + } 343 + } } } 344 + } 345 + } 346 + }, 347 + "put": { 348 + "operationId": "adjustVolume", 349 + "tags": ["Player"], 350 + "summary": "Adjust volume by N firmware-defined steps", 351 + "requestBody": { 352 + "required": true, 353 + "content": { "application/json": { "schema": { "$ref": "#/components/schemas/NewVolume" } } } 354 + }, 355 + "responses": { 356 + "200": { "description": "Adjusted", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/NewVolume" } } } } 357 + } 358 + } 359 + }, 360 + "/playlists": { 361 + "post": { 362 + "operationId": "createPlaylist", 363 + "tags": ["Playlist (queue)"], 364 + "summary": "Replace the live queue with a new playlist", 365 + "requestBody": { 366 + "required": true, 367 + "content": { "application/json": { "schema": { "$ref": "#/components/schemas/NewPlaylist" } } } 368 + }, 369 + "responses": { 370 + "200": { "description": "Start index of the new playlist (as plain text)", "content": { "text/plain": { "schema": { "type": "string" } } } } 371 + } 372 + } 373 + }, 374 + "/playlists/start": { 375 + "put": { 376 + "operationId": "startPlaylist", 377 + "tags": ["Playlist (queue)"], 378 + "summary": "Start playback at a queue index", 379 + "parameters": [ 380 + { "name": "start_index", "in": "query", "schema": { "type": "integer", "format": "int32", "default": 0 } }, 381 + { "name": "elapsed", "in": "query", "schema": { "type": "integer", "format": "int64", "default": 0 } }, 382 + { "name": "offset", "in": "query", "schema": { "type": "integer", "format": "int64", "default": 0 } } 383 + ], 384 + "responses": { "200": { "description": "Started" } } 385 + } 386 + }, 387 + "/playlists/shuffle": { 388 + "put": { 389 + "operationId": "shufflePlaylist", 390 + "tags": ["Playlist (queue)"], 391 + "summary": "Shuffle the live queue", 392 + "parameters": [ 393 + { "name": "start_index", "in": "query", "schema": { "type": "integer", "format": "int32", "default": 0 } } 394 + ], 395 + "responses": { "200": { "description": "Shuffle return code (text)", "content": { "text/plain": { "schema": { "type": "string" } } } } } 396 + } 397 + }, 398 + "/playlists/amount": { 399 + "get": { 400 + "operationId": "getPlaylistAmount", 401 + "tags": ["Playlist (queue)"], 402 + "summary": "Number of tracks in the live queue", 403 + "responses": { 404 + "200": { "description": "Amount", "content": { "application/json": { "schema": { "type": "object", "properties": { "amount": { "type": "integer", "format": "int32" } } } } } } 405 + } 406 + } 407 + }, 408 + "/playlists/resume": { 409 + "put": { 410 + "operationId": "resumePlaylist", 411 + "tags": ["Playlist (queue)"], 412 + "summary": "Resume the saved control file playlist", 413 + "responses": { "200": { "description": "Status code", "content": { "application/json": { "schema": { "type": "object", "properties": { "code": { "type": "integer", "format": "int32" } } } } } } } 414 + } 415 + }, 416 + "/playlists/resume-track": { 417 + "put": { 418 + "operationId": "resumeTrack", 419 + "tags": ["Playlist (queue)"], 420 + "summary": "Resume the saved track at its previous offset", 421 + "responses": { "200": { "description": "Resumed" } } 422 + } 423 + }, 424 + "/playlists/{id}/tracks": { 425 + "parameters": [{ "$ref": "#/components/parameters/IdPath" }], 426 + "get": { 427 + "operationId": "getPlaylistTracks", 428 + "tags": ["Playlist (queue)"], 429 + "summary": "List tracks currently in the queue", 430 + "responses": { 431 + "200": { "description": "Mp3Entry list", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/Mp3Entry" } } } } } 432 + } 433 + }, 434 + "post": { 435 + "operationId": "insertTracks", 436 + "tags": ["Playlist (queue)"], 437 + "summary": "Insert tracks into the live queue", 438 + "requestBody": { 439 + "required": true, 440 + "content": { "application/json": { "schema": { "$ref": "#/components/schemas/InsertTracks" } } } 441 + }, 442 + "responses": { 443 + "200": { "description": "Insertion position or 0 (text)", "content": { "text/plain": { "schema": { "type": "string" } } } } 444 + } 445 + }, 446 + "delete": { 447 + "operationId": "removeTracks", 448 + "tags": ["Playlist (queue)"], 449 + "summary": "Remove tracks from the live queue", 450 + "requestBody": { 451 + "required": true, 452 + "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DeleteTracks" } } } 453 + }, 454 + "responses": { 455 + "200": { "description": "Result code (text)", "content": { "text/plain": { "schema": { "type": "string" } } } } 456 + } 457 + } 458 + }, 459 + "/playlists/{id}": { 460 + "get": { 461 + "operationId": "getPlaylist", 462 + "tags": ["Playlist (queue)"], 463 + "summary": "Get the live queue and its metadata", 464 + "parameters": [{ "$ref": "#/components/parameters/IdPath" }], 465 + "responses": { 466 + "200": { "description": "PlaylistInfo", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PlaylistInfo" } } } } 467 + } 468 + } 469 + }, 470 + "/saved-playlists": { 471 + "get": { 472 + "operationId": "listSavedPlaylists", 473 + "tags": ["Saved playlists"], 474 + "summary": "List saved playlists, optionally filtered by folder", 475 + "parameters": [ 476 + { "name": "folder_id", "in": "query", "schema": { "type": "string" } } 477 + ], 478 + "responses": { 479 + "200": { "description": "Saved playlists", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/SavedPlaylist" } } } } } 480 + } 481 + }, 482 + "post": { 483 + "operationId": "createSavedPlaylist", 484 + "tags": ["Saved playlists"], 485 + "summary": "Create a saved playlist", 486 + "requestBody": { 487 + "required": true, 488 + "content": { "application/json": { "schema": { 489 + "type": "object", 490 + "required": ["name"], 491 + "properties": { 492 + "name": { "type": "string" }, 493 + "description": { "type": "string" }, 494 + "image": { "type": "string" }, 495 + "folder_id": { "type": "string" }, 496 + "track_ids": { "type": "array", "items": { "type": "string" } } 497 + } 498 + } } } 499 + }, 500 + "responses": { 501 + "201": { "description": "Created", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SavedPlaylist" } } } }, 502 + "400": { "description": "Missing required fields" } 503 + } 504 + } 505 + }, 506 + "/saved-playlists/{id}": { 507 + "parameters": [{ "$ref": "#/components/parameters/IdPath" }], 508 + "get": { 509 + "operationId": "getSavedPlaylist", 510 + "tags": ["Saved playlists"], 511 + "summary": "Get a saved playlist", 512 + "responses": { 513 + "200": { "description": "Saved playlist", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SavedPlaylist" } } } }, 514 + "404": { "$ref": "#/components/responses/NotFound" } 515 + } 516 + }, 517 + "put": { 518 + "operationId": "updateSavedPlaylist", 519 + "tags": ["Saved playlists"], 520 + "summary": "Update a saved playlist's metadata", 521 + "requestBody": { 522 + "required": true, 523 + "content": { "application/json": { "schema": { 524 + "type": "object", 525 + "required": ["name"], 526 + "properties": { 527 + "name": { "type": "string" }, 528 + "description": { "type": "string" }, 529 + "image": { "type": "string" }, 530 + "folder_id": { "type": "string" } 531 + } 532 + } } } 533 + }, 534 + "responses": { "204": { "description": "Updated" } } 535 + }, 536 + "delete": { 537 + "operationId": "deleteSavedPlaylist", 538 + "tags": ["Saved playlists"], 539 + "summary": "Delete a saved playlist", 540 + "responses": { "204": { "description": "Deleted" } } 541 + } 542 + }, 543 + "/saved-playlists/{id}/tracks": { 544 + "parameters": [{ "$ref": "#/components/parameters/IdPath" }], 545 + "get": { 546 + "operationId": "getSavedPlaylistTracks", 547 + "tags": ["Saved playlists"], 548 + "summary": "List tracks in a saved playlist", 549 + "responses": { 550 + "200": { "description": "Tracks", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/Track" } } } } } 551 + } 552 + }, 553 + "post": { 554 + "operationId": "addTracksToSavedPlaylist", 555 + "tags": ["Saved playlists"], 556 + "summary": "Add tracks to a saved playlist", 557 + "requestBody": { 558 + "required": true, 559 + "content": { "application/json": { "schema": { 560 + "type": "object", 561 + "required": ["track_ids"], 562 + "properties": { "track_ids": { "type": "array", "items": { "type": "string" } } } 563 + } } } 564 + }, 565 + "responses": { "204": { "description": "Added" } } 566 + } 567 + }, 568 + "/saved-playlists/{id}/track-ids": { 569 + "get": { 570 + "operationId": "getSavedPlaylistTrackIds", 571 + "tags": ["Saved playlists"], 572 + "summary": "List track ids in a saved playlist", 573 + "parameters": [{ "$ref": "#/components/parameters/IdPath" }], 574 + "responses": { 575 + "200": { "description": "Track ids", "content": { "application/json": { "schema": { "type": "array", "items": { "type": "string" } } } } } 576 + } 577 + } 578 + }, 579 + "/saved-playlists/{id}/tracks/{track_id}": { 580 + "delete": { 581 + "operationId": "removeTrackFromSavedPlaylist", 582 + "tags": ["Saved playlists"], 583 + "summary": "Remove a track from a saved playlist", 584 + "parameters": [ 585 + { "$ref": "#/components/parameters/IdPath" }, 586 + { "name": "track_id", "in": "path", "required": true, "schema": { "type": "string" } } 587 + ], 588 + "responses": { "204": { "description": "Removed" } } 589 + } 590 + }, 591 + "/saved-playlists/{id}/play": { 592 + "post": { 593 + "operationId": "playSavedPlaylist", 594 + "tags": ["Saved playlists"], 595 + "summary": "Load a saved playlist into the queue and start playing", 596 + "parameters": [{ "$ref": "#/components/parameters/IdPath" }], 597 + "responses": { 598 + "204": { "description": "Started" }, 599 + "422": { "description": "Empty playlist or unresolved tracks" } 600 + } 601 + } 602 + }, 603 + "/saved-playlists/folders": { 604 + "get": { 605 + "operationId": "listPlaylistFolders", 606 + "tags": ["Saved playlists"], 607 + "summary": "List playlist folders", 608 + "responses": { 609 + "200": { "description": "Folders", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/PlaylistFolder" } } } } } 610 + } 611 + }, 612 + "post": { 613 + "operationId": "createPlaylistFolder", 614 + "tags": ["Saved playlists"], 615 + "summary": "Create a playlist folder", 616 + "requestBody": { 617 + "required": true, 618 + "content": { "application/json": { "schema": { 619 + "type": "object", "required": ["name"], "properties": { "name": { "type": "string" } } 620 + } } } 621 + }, 622 + "responses": { 623 + "201": { "description": "Created", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PlaylistFolder" } } } } 624 + } 625 + } 626 + }, 627 + "/saved-playlists/folders/{id}": { 628 + "delete": { 629 + "operationId": "deletePlaylistFolder", 630 + "tags": ["Saved playlists"], 631 + "summary": "Delete a playlist folder", 632 + "parameters": [{ "$ref": "#/components/parameters/IdPath" }], 633 + "responses": { "204": { "description": "Deleted" } } 634 + } 635 + }, 636 + "/smart-playlists": { 637 + "get": { 638 + "operationId": "listSmartPlaylists", 639 + "tags": ["Smart playlists"], 640 + "summary": "List smart playlists", 641 + "responses": { 642 + "200": { "description": "Smart playlists", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/SmartPlaylist" } } } } } 643 + } 644 + }, 645 + "post": { 646 + "operationId": "createSmartPlaylist", 647 + "tags": ["Smart playlists"], 648 + "summary": "Create a smart playlist with a rule criteria", 649 + "requestBody": { 650 + "required": true, 651 + "content": { "application/json": { "schema": { 652 + "type": "object", 653 + "required": ["name", "rules"], 654 + "properties": { 655 + "name": { "type": "string" }, 656 + "description": { "type": "string" }, 657 + "image": { "type": "string" }, 658 + "folder_id": { "type": "string" }, 659 + "rules": { "$ref": "#/components/schemas/RuleCriteria" } 660 + } 661 + } } } 662 + }, 663 + "responses": { 664 + "201": { "description": "Created", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SmartPlaylist" } } } }, 665 + "400": { "description": "Missing required fields" } 666 + } 667 + } 668 + }, 669 + "/smart-playlists/{id}": { 670 + "parameters": [{ "$ref": "#/components/parameters/IdPath" }], 671 + "get": { 672 + "operationId": "getSmartPlaylist", 673 + "tags": ["Smart playlists"], 674 + "summary": "Get a smart playlist", 675 + "responses": { 676 + "200": { "description": "Smart playlist", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SmartPlaylist" } } } }, 677 + "404": { "$ref": "#/components/responses/NotFound" } 678 + } 679 + }, 680 + "put": { 681 + "operationId": "updateSmartPlaylist", 682 + "tags": ["Smart playlists"], 683 + "summary": "Update a smart playlist", 684 + "requestBody": { 685 + "required": true, 686 + "content": { "application/json": { "schema": { 687 + "type": "object", 688 + "required": ["name", "rules"], 689 + "properties": { 690 + "name": { "type": "string" }, 691 + "description": { "type": "string" }, 692 + "image": { "type": "string" }, 693 + "folder_id": { "type": "string" }, 694 + "rules": { "$ref": "#/components/schemas/RuleCriteria" } 695 + } 696 + } } } 697 + }, 698 + "responses": { 699 + "204": { "description": "Updated" }, 700 + "404": { "$ref": "#/components/responses/NotFound" } 701 + } 702 + }, 703 + "delete": { 704 + "operationId": "deleteSmartPlaylist", 705 + "tags": ["Smart playlists"], 706 + "summary": "Delete a smart playlist", 707 + "responses": { 708 + "204": { "description": "Deleted" }, 709 + "404": { "$ref": "#/components/responses/NotFound" } 710 + } 711 + } 712 + }, 713 + "/smart-playlists/{id}/tracks": { 714 + "get": { 715 + "operationId": "getSmartPlaylistTracks", 716 + "tags": ["Smart playlists"], 717 + "summary": "Resolve a smart playlist to its current matching tracks", 718 + "parameters": [{ "$ref": "#/components/parameters/IdPath" }], 719 + "responses": { 720 + "200": { "description": "Resolved tracks", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/Track" } } } } }, 721 + "404": { "$ref": "#/components/responses/NotFound" } 722 + } 723 + } 724 + }, 725 + "/smart-playlists/{id}/play": { 726 + "post": { 727 + "operationId": "playSmartPlaylist", 728 + "tags": ["Smart playlists"], 729 + "summary": "Resolve a smart playlist and start playing its tracks", 730 + "parameters": [{ "$ref": "#/components/parameters/IdPath" }], 731 + "responses": { 732 + "204": { "description": "Started" }, 733 + "404": { "$ref": "#/components/responses/NotFound" }, 734 + "422": { "description": "Resolves to zero tracks" } 735 + } 736 + } 737 + }, 738 + "/track-stats/{id}": { 739 + "get": { 740 + "operationId": "getTrackStats", 741 + "tags": ["Track stats"], 742 + "summary": "Get listening stats for a track", 743 + "parameters": [{ "$ref": "#/components/parameters/IdPath" }], 744 + "responses": { 745 + "200": { "description": "Track stats", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TrackStats" } } } }, 746 + "404": { "$ref": "#/components/responses/NotFound" } 747 + } 748 + } 749 + }, 750 + "/track-stats/{id}/played": { 751 + "post": { 752 + "operationId": "recordTrackPlayed", 753 + "tags": ["Track stats"], 754 + "summary": "Record a 'played' event for a track", 755 + "parameters": [{ "$ref": "#/components/parameters/IdPath" }], 756 + "responses": { "204": { "description": "Recorded" } } 757 + } 758 + }, 759 + "/track-stats/{id}/skipped": { 760 + "post": { 761 + "operationId": "recordTrackSkipped", 762 + "tags": ["Track stats"], 763 + "summary": "Record a 'skipped' event for a track", 764 + "parameters": [{ "$ref": "#/components/parameters/IdPath" }], 765 + "responses": { "204": { "description": "Recorded" } } 766 + } 767 + }, 768 + "/devices": { 769 + "get": { 770 + "operationId": "getDevices", 771 + "tags": ["Devices"], 772 + "summary": "List all known output devices (discovered + virtual)", 773 + "responses": { 774 + "200": { "description": "Devices", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/Device" } } } } } 775 + } 776 + } 777 + }, 778 + "/devices/{id}": { 779 + "get": { 780 + "operationId": "getDevice", 781 + "tags": ["Devices"], 782 + "summary": "Get a device by id (use 'current' for the active sink)", 783 + "parameters": [{ "$ref": "#/components/parameters/IdPath" }], 784 + "responses": { 785 + "200": { "description": "Device", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Device" } } } }, 786 + "404": { "$ref": "#/components/responses/NotFound" } 787 + } 788 + } 789 + }, 790 + "/devices/{id}/connect": { 791 + "put": { 792 + "operationId": "connectDevice", 793 + "tags": ["Devices"], 794 + "summary": "Switch the active sink to this device", 795 + "parameters": [{ "$ref": "#/components/parameters/IdPath" }], 796 + "responses": { 797 + "200": { "description": "Connected" }, 798 + "400": { "description": "Unknown device service" }, 799 + "404": { "$ref": "#/components/responses/NotFound" } 800 + } 801 + } 802 + }, 803 + "/devices/{id}/disconnect": { 804 + "put": { 805 + "operationId": "disconnectDevice", 806 + "tags": ["Devices"], 807 + "summary": "Disconnect the active device and revert to builtin", 808 + "parameters": [{ "$ref": "#/components/parameters/IdPath" }], 809 + "responses": { "200": { "description": "Disconnected" } } 810 + } 811 + }, 812 + "/settings": { 813 + "get": { 814 + "operationId": "getSettings", 815 + "tags": ["Settings"], 816 + "summary": "Get the global settings (in-memory snapshot)", 817 + "responses": { 818 + "200": { "description": "Settings", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GlobalSettings" } } } } 819 + } 820 + }, 821 + "put": { 822 + "operationId": "updateSettings", 823 + "tags": ["Settings"], 824 + "summary": "Apply a partial settings update and persist to settings.toml", 825 + "requestBody": { 826 + "required": true, 827 + "content": { "application/json": { "schema": { "$ref": "#/components/schemas/NewGlobalSettings" } } } 828 + }, 829 + "responses": { "204": { "description": "Saved" } } 830 + } 831 + }, 832 + "/version": { 833 + "get": { 834 + "operationId": "getVersion", 835 + "tags": ["System"], 836 + "summary": "Get the running rockboxd version", 837 + "responses": { 838 + "200": { "description": "Version", "content": { "application/json": { "schema": { "type": "string" } } } } 839 + } 840 + } 841 + }, 842 + "/status": { 843 + "get": { 844 + "operationId": "getGlobalStatus", 845 + "tags": ["System"], 846 + "summary": "Get global runtime status", 847 + "responses": { 848 + "200": { "description": "Global status", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GlobalStatus" } } } } 849 + } 850 + } 851 + }, 852 + "/scan-library": { 853 + "put": { 854 + "operationId": "scanLibrary", 855 + "tags": ["System"], 856 + "summary": "Trigger a library scan and (optionally) rebuild the search index", 857 + "parameters": [ 858 + { "name": "path", "in": "query", "schema": { "type": "string" }, "description": "Path to scan; defaults to $HOME/Music" }, 859 + { "name": "rebuild_index", "in": "query", "schema": { "type": "string", "enum": ["true", "false", "1", "0"] }, "description": "Re-build the Typesense collections" } 860 + ], 861 + "responses": { 862 + "200": { "description": "Scan started/complete (returns '0' on success)", "content": { "text/plain": { "schema": { "type": "string" } } } } 863 + } 864 + } 865 + }, 866 + "/openapi.json": { 867 + "get": { 868 + "operationId": "getOpenApi", 869 + "tags": ["System"], 870 + "summary": "Get this OpenAPI document", 871 + "responses": { 872 + "200": { "description": "OpenAPI spec", "content": { "application/json": {} } } 873 + } 874 + } 875 + }, 876 + "/bluetooth/scan": { 877 + "post": { 878 + "operationId": "scanBluetooth", 879 + "tags": ["Bluetooth"], 880 + "summary": "Scan for Bluetooth devices (Linux only)", 881 + "parameters": [ 882 + { "name": "timeout_secs", "in": "query", "schema": { "type": "integer", "format": "int64", "default": 10 } } 883 + ], 884 + "responses": { 885 + "200": { "description": "Discovered devices", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/BluetoothDevice" } } } } } 886 + } 887 + } 888 + }, 889 + "/bluetooth/devices": { 890 + "get": { 891 + "operationId": "getBluetoothDevices", 892 + "tags": ["Bluetooth"], 893 + "summary": "List paired Bluetooth devices (Linux only)", 894 + "responses": { 895 + "200": { "description": "Devices", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/BluetoothDevice" } } } } } 896 + } 897 + } 898 + }, 899 + "/bluetooth/devices/{addr}/connect": { 900 + "put": { 901 + "operationId": "connectBluetoothDevice", 902 + "tags": ["Bluetooth"], 903 + "summary": "Connect to a paired Bluetooth device (Linux only)", 904 + "parameters": [{ "name": "addr", "in": "path", "required": true, "schema": { "type": "string" } }], 905 + "responses": { 906 + "200": { "description": "Connected" }, 907 + "500": { "description": "Bluez error" } 908 + } 909 + } 910 + }, 911 + "/bluetooth/devices/{addr}/disconnect": { 912 + "put": { 913 + "operationId": "disconnectBluetoothDevice", 914 + "tags": ["Bluetooth"], 915 + "summary": "Disconnect a Bluetooth device (Linux only)", 916 + "parameters": [{ "name": "addr", "in": "path", "required": true, "schema": { "type": "string" } }], 917 + "responses": { 918 + "200": { "description": "Disconnected" }, 919 + "500": { "description": "Bluez error" } 920 + } 921 + } 922 + } 923 + }, 924 + "components": { 925 + "parameters": { 926 + "IdPath": { 927 + "name": "id", 928 + "in": "path", 929 + "required": true, 930 + "schema": { "type": "string" } 931 + } 932 + }, 933 + "responses": { 934 + "NotFound": { 935 + "description": "Not found" 936 + } 937 + }, 938 + "schemas": { 939 + "Album": { 940 + "type": "object", 941 + "properties": { 942 + "id": { "type": "string" }, 943 + "title": { "type": "string" }, 944 + "artist": { "type": "string" }, 945 + "year": { "type": "integer", "format": "int32", "nullable": true }, 946 + "year_string": { "type": "string", "nullable": true }, 947 + "artist_id": { "type": "string" }, 948 + "md5": { "type": "string" }, 949 + "album_art": { "type": "string", "nullable": true } 950 + } 951 + }, 952 + "Artist": { 953 + "type": "object", 954 + "properties": { 955 + "id": { "type": "string" }, 956 + "name": { "type": "string" }, 957 + "image":{ "type": "string", "nullable": true }, 958 + "bio": { "type": "string", "nullable": true } 959 + } 960 + }, 961 + "Track": { 962 + "type": "object", 963 + "properties": { 964 + "id": { "type": "string" }, 965 + "path": { "type": "string" }, 966 + "title": { "type": "string" }, 967 + "artist": { "type": "string" }, 968 + "album": { "type": "string" }, 969 + "album_artist": { "type": "string" }, 970 + "composer": { "type": "string" }, 971 + "genre": { "type": "string", "nullable": true }, 972 + "year": { "type": "integer", "format": "int64", "nullable": true }, 973 + "year_string": { "type": "string", "nullable": true }, 974 + "track_number": { "type": "integer", "format": "int64", "nullable": true }, 975 + "disc_number": { "type": "integer", "format": "int64" }, 976 + "length": { "type": "integer", "format": "int64", "description": "Duration in milliseconds" }, 977 + "filesize": { "type": "integer", "format": "int64" }, 978 + "bitrate": { "type": "integer", "format": "int64" }, 979 + "frequency": { "type": "integer", "format": "int64" }, 980 + "album_id": { "type": "string" }, 981 + "artist_id": { "type": "string" }, 982 + "genre_id": { "type": "string" }, 983 + "album_art": { "type": "string", "nullable": true }, 984 + "md5": { "type": "string" }, 985 + "created_at": { "type": "string", "format": "date-time" }, 986 + "updated_at": { "type": "string", "format": "date-time" } 987 + } 988 + }, 989 + "TreeEntry": { 990 + "type": "object", 991 + "properties": { 992 + "name": { "type": "string", "description": "Absolute path" }, 993 + "time_write": { "type": "integer", "format": "int64", "description": "Unix timestamp of last modification" }, 994 + "attr": { "type": "integer", "format": "int32", "description": "0x10 = directory" } 995 + } 996 + }, 997 + "Mp3Entry": { 998 + "type": "object", 999 + "description": "Rockbox firmware-side representation of a playing or queued track", 1000 + "properties": { 1001 + "path": { "type": "string" }, 1002 + "title": { "type": "string" }, 1003 + "artist": { "type": "string" }, 1004 + "album": { "type": "string" }, 1005 + "albumartist": { "type": "string" }, 1006 + "composer": { "type": "string" }, 1007 + "genre_string": { "type": "string" }, 1008 + "tracknum": { "type": "integer", "format": "int32" }, 1009 + "discnum": { "type": "integer", "format": "int32" }, 1010 + "year": { "type": "integer", "format": "int32" }, 1011 + "year_string": { "type": "string" }, 1012 + "length": { "type": "integer", "format": "int64", "description": "Duration in milliseconds" }, 1013 + "elapsed": { "type": "integer", "format": "int64", "description": "Current position in milliseconds" }, 1014 + "filesize": { "type": "integer", "format": "int64" }, 1015 + "bitrate": { "type": "integer", "format": "int64" }, 1016 + "frequency": { "type": "integer", "format": "int64" }, 1017 + "id": { "type": "string", "nullable": true }, 1018 + "album_id": { "type": "string", "nullable": true }, 1019 + "artist_id": { "type": "string", "nullable": true }, 1020 + "genre_id": { "type": "string", "nullable": true }, 1021 + "album_art": { "type": "string", "nullable": true } 1022 + } 1023 + }, 1024 + "AudioStatus": { 1025 + "type": "object", 1026 + "properties": { 1027 + "status": { 1028 + "type": "integer", "format": "int32", 1029 + "description": "0 = stopped, 1 = playing, 2 = paused (bitfield)" 1030 + } 1031 + } 1032 + }, 1033 + "Device": { 1034 + "type": "object", 1035 + "properties": { 1036 + "id": { "type": "string" }, 1037 + "name": { "type": "string" }, 1038 + "host": { "type": "string" }, 1039 + "ip": { "type": "string" }, 1040 + "port": { "type": "integer", "format": "int32" }, 1041 + "service": { "type": "string", "description": "builtin | fifo | airplay | squeezelite | upnp | chromecast | snapcast" }, 1042 + "app": { "type": "string" }, 1043 + "is_connected": { "type": "boolean" }, 1044 + "is_cast_device": { "type": "boolean" }, 1045 + "is_source_device": { "type": "boolean" }, 1046 + "is_current_device": { "type": "boolean" }, 1047 + "base_url": { "type": "string", "nullable": true } 1048 + } 1049 + }, 1050 + "LoadTracks": { 1051 + "type": "object", 1052 + "required": ["tracks"], 1053 + "properties": { 1054 + "tracks": { "type": "array", "items": { "type": "string" }, "description": "Track paths or HTTP URLs" }, 1055 + "shuffle": { "type": "boolean" } 1056 + } 1057 + }, 1058 + "InsertTracks": { 1059 + "type": "object", 1060 + "required": ["tracks", "position"], 1061 + "properties": { 1062 + "tracks": { "type": "array", "items": { "type": "string" } }, 1063 + "directory": { "type": "string", "description": "Insert all audio files under this directory instead of an explicit list" }, 1064 + "position": { "type": "integer", "format": "int32", "description": "0=Next, 1=After current, 2=Last, 3=First/replace, 7=LastShuffled" } 1065 + } 1066 + }, 1067 + "DeleteTracks": { 1068 + "type": "object", 1069 + "required": ["positions"], 1070 + "properties": { 1071 + "positions": { "type": "array", "items": { "type": "integer", "format": "int32" }, "description": "Empty array clears the entire queue" } 1072 + } 1073 + }, 1074 + "NewPlaylist": { 1075 + "type": "object", 1076 + "required": ["tracks"], 1077 + "properties": { 1078 + "name": { "type": "string" }, 1079 + "tracks": { "type": "array", "items": { "type": "string" } } 1080 + } 1081 + }, 1082 + "PlaylistInfo": { 1083 + "type": "object", 1084 + "properties": { 1085 + "amount": { "type": "integer", "format": "int32" }, 1086 + "index": { "type": "integer", "format": "int32" }, 1087 + "max_playlist_size": { "type": "integer", "format": "int32" }, 1088 + "first_index": { "type": "integer", "format": "int32" }, 1089 + "last_insert_pos": { "type": "integer", "format": "int32" }, 1090 + "seed": { "type": "integer", "format": "int32" }, 1091 + "last_shuffled_start": { "type": "integer", "format": "int32" }, 1092 + "entries": { "type": "array", "items": { "$ref": "#/components/schemas/Mp3Entry" } } 1093 + } 1094 + }, 1095 + "NewVolume": { 1096 + "type": "object", 1097 + "required": ["steps"], 1098 + "properties": { 1099 + "steps": { "type": "integer", "format": "int32", "description": "Positive = louder, negative = quieter" } 1100 + } 1101 + }, 1102 + "SavedPlaylist": { 1103 + "type": "object", 1104 + "properties": { 1105 + "id": { "type": "string" }, 1106 + "name": { "type": "string" }, 1107 + "description": { "type": "string", "nullable": true }, 1108 + "image": { "type": "string", "nullable": true }, 1109 + "folder_id": { "type": "string", "nullable": true }, 1110 + "track_count": { "type": "integer", "format": "int32" }, 1111 + "created_at": { "type": "string", "format": "date-time" }, 1112 + "updated_at": { "type": "string", "format": "date-time" } 1113 + } 1114 + }, 1115 + "PlaylistFolder": { 1116 + "type": "object", 1117 + "properties": { 1118 + "id": { "type": "string" }, 1119 + "name": { "type": "string" }, 1120 + "created_at": { "type": "string", "format": "date-time" } 1121 + } 1122 + }, 1123 + "SmartPlaylist": { 1124 + "type": "object", 1125 + "properties": { 1126 + "id": { "type": "string" }, 1127 + "name": { "type": "string" }, 1128 + "description": { "type": "string", "nullable": true }, 1129 + "image": { "type": "string", "nullable": true }, 1130 + "folder_id": { "type": "string", "nullable": true }, 1131 + "rules": { "$ref": "#/components/schemas/RuleCriteria" }, 1132 + "created_at": { "type": "string", "format": "date-time" }, 1133 + "updated_at": { "type": "string", "format": "date-time" } 1134 + } 1135 + }, 1136 + "RuleCriteria": { 1137 + "type": "object", 1138 + "description": "Smart-playlist rule set. See `crates/playlists/src/rules.rs` for the canonical definition.", 1139 + "properties": { 1140 + "operator": { "type": "string", "enum": ["AND", "OR"], "default": "AND" }, 1141 + "rules": { 1142 + "type": "array", 1143 + "items": { 1144 + "type": "object", 1145 + "properties": { 1146 + "field": { "type": "string", "description": "play_count | skip_count | last_played | year | duration_ms | genre | artist | …" }, 1147 + "op": { "type": "string", "description": "eq | ne | gt | gte | lt | lte | contains | within | not_within" }, 1148 + "value": {} 1149 + } 1150 + } 1151 + }, 1152 + "sort": { 1153 + "type": "object", 1154 + "nullable": true, 1155 + "properties": { 1156 + "field": { "type": "string" }, 1157 + "dir": { "type": "string", "enum": ["asc", "desc"] } 1158 + } 1159 + }, 1160 + "limit": { "type": "integer", "format": "int32", "nullable": true } 1161 + } 1162 + }, 1163 + "TrackStats": { 1164 + "type": "object", 1165 + "properties": { 1166 + "track_id": { "type": "string" }, 1167 + "play_count": { "type": "integer", "format": "int64" }, 1168 + "skip_count": { "type": "integer", "format": "int64" }, 1169 + "last_played": { "type": "integer", "format": "int64", "nullable": true, "description": "Unix timestamp" }, 1170 + "last_skipped": { "type": "integer", "format": "int64", "nullable": true } 1171 + } 1172 + }, 1173 + "GlobalSettings": { 1174 + "type": "object", 1175 + "description": "Live `global_settings` snapshot. Fields mirror `apps/settings.h`.", 1176 + "additionalProperties": true, 1177 + "properties": { 1178 + "music_dir": { "type": "string" }, 1179 + "audio_output": { "type": "string", "enum": ["builtin", "fifo", "airplay", "squeezelite", "chromecast", "snapcast_tcp", "upnp"] }, 1180 + "volume": { "type": "integer", "format": "int32" }, 1181 + "volume_limit": { "type": "integer", "format": "int32" }, 1182 + "balance": { "type": "integer", "format": "int32" }, 1183 + "channel_config": { "type": "integer", "format": "int32" }, 1184 + "stereo_width": { "type": "integer", "format": "int32" }, 1185 + "playlist_shuffle": { "type": "boolean" }, 1186 + "repeat_mode": { "type": "integer", "format": "int32" }, 1187 + "crossfade": { "type": "integer", "format": "int32" }, 1188 + "eq_enabled": { "type": "boolean" }, 1189 + "eq_precut": { "type": "integer", "format": "int32" }, 1190 + "eq_band_settings": { "type": "array", "items": { "$ref": "#/components/schemas/EqBandSetting" } }, 1191 + "replaygain_settings": { "$ref": "#/components/schemas/ReplaygainSettings" }, 1192 + "compressor_settings": { "$ref": "#/components/schemas/CompressorSettings" } 1193 + } 1194 + }, 1195 + "NewGlobalSettings": { 1196 + "type": "object", 1197 + "description": "Partial settings update — only the fields you set are written.", 1198 + "additionalProperties": true 1199 + }, 1200 + "EqBandSetting": { 1201 + "type": "object", 1202 + "properties": { 1203 + "cutoff": { "type": "integer", "format": "int32", "description": "Centre / cutoff frequency in Hz" }, 1204 + "q": { "type": "integer", "format": "int32", "description": "Q × 10 (fixed-point)" }, 1205 + "gain": { "type": "integer", "format": "int32", "description": "Gain × 10 in dB (fixed-point)" } 1206 + } 1207 + }, 1208 + "ReplaygainSettings": { 1209 + "type": "object", 1210 + "properties": { 1211 + "noclip": { "type": "boolean" }, 1212 + "type": { "type": "integer", "format": "int32", "description": "0=Track 1=Album 2=Track-shuffle 3=Off" }, 1213 + "preamp": { "type": "integer", "format": "int32", "description": "dB × 10" } 1214 + } 1215 + }, 1216 + "CompressorSettings": { 1217 + "type": "object", 1218 + "properties": { 1219 + "threshold": { "type": "integer", "format": "int32", "description": "dB" }, 1220 + "makeup_gain": { "type": "integer", "format": "int32" }, 1221 + "ratio": { "type": "integer", "format": "int32" }, 1222 + "knee": { "type": "integer", "format": "int32" }, 1223 + "release_time": { "type": "integer", "format": "int32", "description": "ms" }, 1224 + "attack_time": { "type": "integer", "format": "int32", "description": "ms" } 1225 + } 1226 + }, 1227 + "GlobalStatus": { 1228 + "type": "object", 1229 + "additionalProperties": true, 1230 + "properties": { 1231 + "resume_index": { "type": "integer", "format": "int32" }, 1232 + "resume_offset": { "type": "integer", "format": "int64" }, 1233 + "resume_elapsed": { "type": "integer", "format": "int64" }, 1234 + "resume_crc32": { "type": "integer", "format": "int64" }, 1235 + "resume_pitch": { "type": "integer", "format": "int32" }, 1236 + "resume_speed": { "type": "integer", "format": "int32" }, 1237 + "runtime": { "type": "integer", "format": "int64" }, 1238 + "topruntime": { "type": "integer", "format": "int64" }, 1239 + "volume": { "type": "integer", "format": "int32" } 1240 + } 1241 + }, 1242 + "BluetoothDevice": { 1243 + "type": "object", 1244 + "properties": { 1245 + "address": { "type": "string" }, 1246 + "name": { "type": "string" }, 1247 + "rssi": { "type": "integer", "format": "int32", "nullable": true }, 1248 + "is_paired": { "type": "boolean" }, 1249 + "is_connected": { "type": "boolean" } 1250 + } 1251 + } 1252 + } 1253 + } 1254 + }
+74
mintlify/api-reference/rest/overview.mdx
··· 1 + --- 2 + title: "HTTP REST" 3 + description: "JSON over HTTP on port 6063. The endpoints used internally by the web UI and SDK clients." 4 + icon: 'bolt' 5 + --- 6 + 7 + The REST server runs on **port 6063** by default (override with 8 + `ROCKBOX_TCP_PORT`). Every endpoint is JSON in / JSON out, except where 9 + noted (some commands return plain-text status codes). 10 + 11 + The full schema is published as 12 + [OpenAPI 3.1](/api-reference/openapi.json) and rendered as one page per 13 + endpoint in the sidebar — explore by tag or jump to a specific operation. 14 + 15 + ## Quick smoke test 16 + 17 + ```sh 18 + curl -s http://localhost:6063/version 19 + curl -s http://localhost:6063/player/status 20 + curl -s http://localhost:6063/playlists/amount 21 + ``` 22 + 23 + ## Common operations 24 + 25 + <CodeGroup> 26 + ```sh Now playing 27 + curl -s http://localhost:6063/player/current-track | jq .title,.artist 28 + ``` 29 + 30 + ```sh Search 31 + curl -s 'http://localhost:6063/search?q=daft+punk' | jq '.tracks[].title' 32 + ``` 33 + 34 + ```sh Play an album 35 + ALBUM_ID=$(curl -s http://localhost:6063/albums | jq -r '.[0].id') 36 + TRACKS=$(curl -s "http://localhost:6063/albums/$ALBUM_ID/tracks" | jq '[.[].path]') 37 + curl -X POST -H 'Content-Type: application/json' \ 38 + -d "{\"name\":\"album\",\"tracks\":$TRACKS}" \ 39 + http://localhost:6063/playlists 40 + curl -X PUT 'http://localhost:6063/playlists/start?start_index=0' 41 + ``` 42 + 43 + ```sh Volume up 44 + curl -X PUT -H 'Content-Type: application/json' \ 45 + -d '{"steps":3}' \ 46 + http://localhost:6063/player/volume 47 + ``` 48 + 49 + ```sh Switch to a discovered Chromecast 50 + ID=$(curl -s http://localhost:6063/devices | jq -r '.[] | select(.is_cast_device) | .id' | head -1) 51 + curl -X PUT "http://localhost:6063/devices/$ID/connect" 52 + ``` 53 + </CodeGroup> 54 + 55 + ## Notable behaviours 56 + 57 + - **Responses sometimes return plain text.** A few mutations return a 58 + status integer or insertion index in the body as text rather than JSON 59 + (`/playlists`, `/playlists/{id}/tracks`, `/scan-library`). The OpenAPI 60 + spec marks these explicitly. 61 + - **Saved playlist mutations return `204`** with no body. 62 + - **Bluetooth routes only exist on Linux.** They are conditionally 63 + registered at compile time. 64 + - **The HTTP server runs on its own thread** so actix's worker pool is 65 + not pinned to the Rockbox cooperative scheduler. See 66 + [Architecture › Overview](/architecture/overview) for the lifecycle. 67 + 68 + ## Server details 69 + 70 + - Bind address — `0.0.0.0:$ROCKBOX_TCP_PORT` (default `6063`). 71 + - CORS — permissive (`actix_cors::Cors::permissive()`) so browser-based 72 + clients can hit it without preflight pain. 73 + - The OpenAPI document is also served live at 74 + `http://localhost:6063/openapi.json`.
+116
mintlify/architecture/build.mdx
··· 1 + --- 2 + title: "Build system" 3 + description: "Make → Cargo → Zig. The three-step pipeline that produces rockboxd." 4 + icon: 'hammer' 5 + --- 6 + 7 + Rockbox Zig is built by three tools in series: 8 + 9 + 1. **Make** — compiles the Rockbox C firmware into static libraries. 10 + 2. **Cargo** — compiles the Rust crates into static libraries (`crate-type = ["staticlib"]`). 11 + 3. **Zig** — links everything (plus SDL2) into a single executable. 12 + 13 + ## Dependencies 14 + 15 + <Tabs> 16 + <Tab title="Ubuntu / Debian"> 17 + ```sh 18 + sudo apt-get install \ 19 + libsdl2-dev libfreetype6-dev libdbus-1-dev libunwind-dev \ 20 + zip protobuf-compiler cmake 21 + ``` 22 + </Tab> 23 + <Tab title="Fedora"> 24 + ```sh 25 + sudo dnf install \ 26 + SDL2-devel freetype-devel libunwind-devel \ 27 + zip protobuf-compiler cmake 28 + ``` 29 + </Tab> 30 + <Tab title="macOS"> 31 + ```sh 32 + brew install sdl2 freetype cmake protobuf 33 + ``` 34 + </Tab> 35 + </Tabs> 36 + 37 + You'll also need: 38 + 39 + - **Zig** ≥ 0.16 — [ziglang.org/download](https://ziglang.org/download/) 40 + - **Rust stable** — `rustup update stable` 41 + - **Deno** — for the web UI build 42 + 43 + ## Full build 44 + 45 + ```sh 46 + # 1. Clone with submodules 47 + git clone https://github.com/tsirysndr/rockbox-zig.git 48 + cd rockbox-zig 49 + git submodule update --init --recursive 50 + 51 + # 2. Build the web UI (embedded into the binary) 52 + cd webui/rockbox 53 + deno install 54 + deno run build 55 + cd ../.. 56 + 57 + # 3. Configure and build the C firmware (one-time setup) 58 + mkdir -p build-lib && cd build-lib 59 + ../tools/configure --target=sdlapp --type=N \ 60 + --lcdwidth=320 --lcdheight=240 --prefix=/usr/local 61 + cp ../autoconf/autoconf.h . 62 + make lib 63 + cd .. 64 + 65 + # 4. Build Rust crates 66 + cargo build --release -p rockbox-cli -p rockbox-server 67 + 68 + # 5. Link everything with Zig 69 + cd zig && zig build 70 + ``` 71 + 72 + The binary lands at `zig/zig-out/bin/rockboxd`. 73 + 74 + ## Iterating on changes 75 + 76 + Zig only re-links when the static libraries are newer than the binary. 77 + After editing C, run `make lib` first. After editing Rust, run 78 + `cargo build --release` first. 79 + 80 + ```sh 81 + # C change 82 + cd build-lib && make lib && cd .. && cd zig && zig build 83 + 84 + # Rust change 85 + cargo build --release -p rockbox-cli -p rockbox-server && cd zig && zig build 86 + ``` 87 + 88 + <Warning> 89 + **Stale binary pitfall.** If behaviour doesn't match the source, check 90 + mtimes: 91 + 92 + ```sh 93 + ls -la zig/zig-out/bin/rockboxd \ 94 + build-lib/libfirmware.a \ 95 + target/release/librockbox_cli.a 96 + ``` 97 + 98 + If `rockboxd` is newer than every `.a` file, Zig considered the link 99 + up-to-date and your change wasn't picked up. 100 + </Warning> 101 + 102 + ## Verifying symbols 103 + 104 + ```sh 105 + nm zig/zig-out/bin/rockboxd | grep pcm_airplay 106 + nm zig/zig-out/bin/rockboxd | grep pcm_squeezelite 107 + ar t target/release/librockbox_cli.a | grep airplay 108 + ar t target/release/librockbox_cli.a | grep slim 109 + ``` 110 + 111 + ## Don't re-run `tools/configure` 112 + 113 + `build-lib/` was pre-configured for the `sdlapp` target. Re-running 114 + `tools/configure` regenerates the Makefile and overwrites local edits. If 115 + you really need to reconfigure (different LCD dimensions, different target), 116 + do it knowingly and review the resulting diff.
+98
mintlify/architecture/overview.mdx
··· 1 + --- 2 + title: "Architecture" 3 + description: "How Rockbox C, Rust and Zig fit together inside one rockboxd binary." 4 + icon: 'sitemap' 5 + --- 6 + 7 + ```text 8 + ┌──────────────────────────────────────────────────────────────────────┐ 9 + │ Clients Web UI · GTK · GPUI · TUI · REPL · MPD · MPRIS │ 10 + ├──────────────────────────────────────────────────────────────────────┤ 11 + │ Protocols gRPC :6061 GraphQL :6062 REST :6063 MPD :6600 │ 12 + ├──────────────────────────────────────────────────────────────────────┤ 13 + │ Rust services playback · library · settings · search · playlists │ 14 + │ airplay · slim · chromecast · upnp · netstream │ 15 + ├──────────────────────────────────────────────────────────────────────┤ 16 + │ Rockbox C audio engine · DSP · codecs · tag database │ 17 + ├──────────────────────────────────────────────────────────────────────┤ 18 + │ PCM sinks builtin · fifo · airplay · squeezelite · chromecast │ 19 + │ snapcast_tcp · upnp │ 20 + └──────────────────────────────────────────────────────────────────────┘ 21 + ``` 22 + 23 + The entire system ships as **one binary**, `rockboxd`, produced by Zig's 24 + linker. There's no separate "rockbox-server" service, no per-feature 25 + sidecar, no IPC. 26 + 27 + ## What links into the binary 28 + 29 + | Artifact | Built by | Notes | 30 + |-----------------------------------------|----------|---------------------------------------------| 31 + | `build-lib/libfirmware.a` | Make | Rockbox C audio engine + DSP | 32 + | `build-lib/librockbox.a` | Make | App layer (playlist, database, plugins) | 33 + | Codec libraries (`librbcodec.a`, …) | Make | rbcodec + fixedpoint + skin parser | 34 + | `target/release/librockbox_cli.a` | Cargo | CLI entry point + Rust output sinks | 35 + | `target/release/librockbox_server.a` | Cargo | gRPC, GraphQL, HTTP, MPD servers | 36 + | SDL2 | system | Audio/event handling on hosted targets | 37 + 38 + The Zig build script (`zig/build.zig`) glues them together, ensuring force-included symbols stay in the staticlib through the link. 39 + 40 + ## Repository layout 41 + 42 + ```text 43 + firmware/ Rockbox C firmware (audio engine, codecs, DSP) 44 + apps/ Rockbox application layer (playlist, database, plugins) 45 + lib/ Codec libraries (rbcodec, fixedpoint, skin_parser, tlsf) 46 + build-lib/ Out-of-tree Make build directory (generated; do not edit) 47 + crates/ Rust workspace 48 + airplay/ ALAC encoder + RAOP/RTP sender 49 + slim/ Slim Protocol + HTTP broadcast (squeezelite multi-room) 50 + cli/ Compiled to librockbox_cli.a (staticlib) 51 + server/ gRPC / HTTP server 52 + settings/ load_settings() — reads settings.toml, applies sinks 53 + sys/ FFI bindings to the C firmware 54 + library/ SQLite library management 55 + typesense/ Typesense client for search 56 + netstream/ HTTP streaming (Range-request fd multiplexing) 57 + chromecast/ Chromecast output 58 + rpc/ gRPC definitions / generated code 59 + graphql/ GraphQL schema and resolvers 60 + mpd/ MPD protocol server 61 + mpris/ MPRIS D-Bus integration 62 + tracklist/ Playlist / tracklist management 63 + upnp/ UPnP/DLNA support 64 + types/ Shared Rust types 65 + traits/ Shared Rust traits 66 + zig/ Zig build script and thin main.zig entry point 67 + sdk/ Client SDKs (TypeScript, Python, Ruby, Elixir, Clojure, Gleam) 68 + webui/rockbox/ React-based web UI (built into the binary) 69 + gpui/, gtk/ Native desktop apps 70 + ``` 71 + 72 + ## Cross-cutting concerns 73 + 74 + ### macOS SDL audio 75 + 76 + `SDL_InitSubSystem(SDL_INIT_AUDIO)` is called explicitly on macOS because 77 + Apple's SDL event thread doesn't do it automatically. Lives in 78 + `firmware/target/hosted/sdl/system-sdl.c`. 79 + 80 + ### SIGTERM handling 81 + 82 + `crates/cli/src/lib.rs` overrides SIGTERM/SIGINT to kill the typesense 83 + child process and `_exit(0)`. The default Rockbox handler in 84 + `system-hosted.c` would otherwise loop forever waiting for an SDL quit 85 + event. 86 + 87 + ### Typesense subprocess 88 + 89 + Typesense is spawned with `Stdio::piped()` and its stdout/stderr lines are 90 + forwarded to `tracing` in background threads — this keeps the PCM stdout 91 + stream clean when running in `fifo_path = "-"` mode. 92 + 93 + ### HTTP streaming for cloud sources 94 + 95 + HTTP file descriptors are encoded as values `≤ -1000` (the 96 + `STREAM_HTTP_FD_BASE` constant). `stream_open/read/lseek/close` in 97 + `crates/netstream/` dispatch between HTTP and POSIX based on fd value, so 98 + the rest of the firmware doesn't know it's reading from the network.
+92
mintlify/architecture/pcm-sinks.mdx
··· 1 + --- 2 + title: "PCM sinks" 3 + description: "How Rockbox's audio output abstraction works, and how to add a new sink." 4 + icon: 'plug' 5 + --- 6 + 7 + The audio output abstraction lives in `firmware/export/pcm_sink.h`. Each 8 + sink implements a `pcm_sink_ops` vtable: 9 + 10 + ```c 11 + struct pcm_sink_ops { 12 + void (*init)(void); 13 + void (*postinit)(void); 14 + void (*set_freq)(int hz); 15 + void (*lock)(void); 16 + void (*unlock)(void); 17 + void (*play)(const void *data, size_t bytes); 18 + void (*stop)(void); 19 + }; 20 + ``` 21 + 22 + ## Built-in sinks 23 + 24 + | Enum constant | Value | Implementation | 25 + |--------------------------|-------|-----------------------------------------------| 26 + | `PCM_SINK_BUILTIN` | 0 | `firmware/target/hosted/sdl/pcm-sdl.c` | 27 + | `PCM_SINK_FIFO` | 1 | `firmware/target/hosted/pcm-fifo.c` | 28 + | `PCM_SINK_AIRPLAY` | 2 | `firmware/target/hosted/pcm-airplay.c` | 29 + | `PCM_SINK_SQUEEZELITE` | 3 | `firmware/target/hosted/pcm-squeezelite.c` | 30 + | `PCM_SINK_CHROMECAST` | 4 | `firmware/target/hosted/pcm-chromecast.c` | 31 + | `PCM_SINK_SNAPCAST_TCP` | 5 | `firmware/target/hosted/pcm-snapcast-tcp.c` | 32 + | `PCM_SINK_UPNP` | 6 | `firmware/target/hosted/pcm-upnp.c` | 33 + 34 + Selection at startup happens in `crates/settings/src/lib.rs:load_settings()`, 35 + which reads `audio_output` and calls `pcm::switch_sink()`. Rust-side 36 + constants and helpers live in `crates/sys/src/sound/pcm.rs`. 37 + 38 + ## FIFO sink details (Snapcast) 39 + 40 + - Pre-creates the named FIFO with `O_RDWR|O_NONBLOCK` in 41 + `pcm_fifo_set_path()` then clears `O_NONBLOCK`. Holding a write reference 42 + prevents readers from seeing premature EOF between tracks. 43 + - `sink_dma_stop()` does **not** close the fd; it stays open across track 44 + transitions. 45 + - Startup order matters: rockboxd must start before snapserver. 46 + 47 + ## AirPlay sink details 48 + 49 + - `pcm_airplay_connect()` is called once per `sink_dma_start()` and is 50 + idempotent if already connected. 51 + - The `rockbox-airplay` rlib is force-included via 52 + `use rockbox_airplay::_link_airplay as _` in `crates/cli/src/lib.rs`. 53 + Without that shim the linker would garbage-collect the symbols. 54 + 55 + ## Squeezelite sink details 56 + 57 + - The DMA loop in `pcm-squeezelite.c` paces output to real time using 58 + `CLOCK_MONOTONIC`. 59 + - **Use `int64_t` for the nanosecond diff** — unsigned subtraction wraps 60 + catastrophically when `tv_nsec` rolls over. This is a real bug we hit; if 61 + you touch this code, keep it signed. 62 + - The `rockbox-slim` rlib is force-included via 63 + `use rockbox_slim::_link_slim as _`. 64 + 65 + ## Adding a new sink 66 + 67 + 1. Create `firmware/target/hosted/pcm-<name>.c` — model on `pcm-fifo.c`. 68 + 2. Add `PCM_SINK_<NAME>` to the enum in `firmware/export/pcm_sink.h`. 69 + 3. Register `&<name>_pcm_sink` in the `sinks[]` array in `firmware/pcm.c`. 70 + 4. Add `target/hosted/pcm-<name>.c` inside the `#if PLATFORM_HOSTED` block 71 + in `firmware/SOURCES`. 72 + 5. Add a Rust constant `PCM_SINK_<NAME>: i32` in 73 + `crates/sys/src/sound/pcm.rs`. 74 + 6. Add a `set_<name>_*` wrapper if configuration is needed. 75 + 7. Handle the new sink in `crates/settings/src/lib.rs:load_settings()`. 76 + 8. If it has a Rust implementation in a new crate: add a 77 + `_link_<name>()` dummy fn and reference it from `crates/cli/src/lib.rs` 78 + to force inclusion in the staticlib. 79 + 80 + ## Logging from a sink 81 + 82 + Always use `tracing` from Rust. Never `eprintln!`/`println!` — they bypass 83 + the structured log filter and pollute stdout (which breaks FIFO mode). 84 + 85 + ```rust 86 + tracing::info!("airplay: session established to {}", host); 87 + tracing::warn!("airplay: dropped frame, network slow"); 88 + tracing::error!("airplay: handshake failed: {err}"); 89 + ``` 90 + 91 + Control verbosity with `RUST_LOG`, e.g. 92 + `RUST_LOG=rockbox_airplay=debug,info rockboxd`.
+93
mintlify/audio-output/airplay.mdx
··· 1 + --- 2 + title: "AirPlay" 3 + description: "RAOP streaming to one or many AirPlay receivers — Apple TV, HomePod, Airport Express, shairport-sync." 4 + icon: 'apple' 5 + --- 6 + 7 + Rockbox includes a pure-Rust RAOP (AirPlay 1) implementation. ALAC frames go 8 + out over RTP/UDP; RTSP handles session setup. RTCP NTP sync packets are sent 9 + roughly every 44 frames so receivers stay in lockstep. 10 + 11 + ## Single receiver 12 + 13 + ```toml 14 + music_dir = "/path/to/Music" 15 + audio_output = "airplay" 16 + airplay_host = "192.168.1.50" # IP of the AirPlay receiver 17 + airplay_port = 5000 # optional, default 5000 18 + ``` 19 + 20 + ## Multi-room 21 + 22 + Fan-out to N receivers simultaneously: 23 + 24 + ```toml 25 + music_dir = "/path/to/Music" 26 + audio_output = "airplay" 27 + 28 + [[airplay_receivers]] 29 + host = "192.168.1.50" # living room 30 + port = 5000 # optional, default 5000 31 + 32 + [[airplay_receivers]] 33 + host = "192.168.1.51" # bedroom 34 + # port defaults to 5000 35 + 36 + [[airplay_receivers]] 37 + host = "192.168.1.52" # kitchen 38 + ``` 39 + 40 + All receivers share the same `initial_rtptime`, so RTP-level synchronisation 41 + is within one frame (~8 ms) across the LAN. 42 + 43 + ## Compatible receivers 44 + 45 + - Apple TV (any generation supporting AirPlay 1) 46 + - HomePod / HomePod mini 47 + - AirPort Express 48 + - [shairport-sync](https://github.com/mikebrady/shairport-sync) — software 49 + AirPlay receiver for Linux, macOS, FreeBSD, OpenWrt 50 + - Most third-party AirPlay-1 speakers 51 + 52 + AirPlay 2 is not implemented. 53 + 54 + ## Auto-discovery 55 + 56 + Discovered receivers appear in the web UI device picker — click to connect 57 + without editing the config. The mDNS service type is `_raop._tcp.local.`. 58 + 59 + ## Debugging 60 + 61 + ```sh 62 + RUST_LOG=rockbox_airplay=debug rockboxd 63 + ``` 64 + 65 + Common log lines: 66 + 67 + ``` 68 + DEBUG rockbox_airplay::rtsp ANNOUNCE → 192.168.1.50:5000 69 + DEBUG rockbox_airplay::rtsp SETUP → control=53000 timing=53001 server=53002 70 + DEBUG rockbox_airplay::rtsp RECORD → 200 OK 71 + DEBUG rockbox_airplay::rtp sent 352 frames seq=12345 72 + ``` 73 + 74 + ## Limitations 75 + 76 + - **No password / pairing.** AirPlay 1 receivers that require a PIN are not 77 + supported. 78 + - **No volume sync.** Volume changes apply only at the rockboxd side; the 79 + receiver's hardware volume is not adjusted. 80 + - **No AirPlay 2.** The pairing/encryption stack required for AirPlay 2 is 81 + not implemented. 82 + 83 + ## Architecture 84 + 85 + The RAOP stack lives in `crates/airplay/`: 86 + 87 + | File | Responsibility | 88 + |--------------|---------------------------------------------------------------| 89 + | `alac.rs` | ALAC escape/verbatim encoder — 352 stereo S16LE → 1411 bytes | 90 + | `rtp.rs` | RTP/UDP packet sender + RTCP NTP sync | 91 + | `rtsp.rs` | Synchronous RTSP client: ANNOUNCE → SETUP → RECORD | 92 + 93 + The C-side sink is `firmware/target/hosted/pcm-airplay.c`.
+52
mintlify/audio-output/built-in.mdx
··· 1 + --- 2 + title: "Built-in (SDL)" 3 + description: "The default. SDL2 audio to your OS default device." 4 + icon: 'volume-high' 5 + --- 6 + 7 + The built-in sink uses **SDL2** to play audio through your operating system's 8 + default audio device. This is the default and needs no setup beyond installing 9 + Rockbox. 10 + 11 + ```toml 12 + music_dir = "/path/to/Music" 13 + audio_output = "builtin" 14 + ``` 15 + 16 + ## Selecting the output device 17 + 18 + SDL plays through whichever device the OS reports as default. To change it, 19 + use your platform's audio settings: 20 + 21 + - **macOS** — System Settings › Sound › Output 22 + - **Linux (PipeWire / PulseAudio)** — `pavucontrol` or your DE's sound applet 23 + - **Windows** — Sound settings › Output 24 + 25 + There is no per-application device picker built into Rockbox for the SDL sink. 26 + If you need multi-device routing on Linux, point Rockbox at a PulseAudio 27 + *null sink* and route from there. 28 + 29 + ## macOS-specific notes 30 + 31 + `SDL_InitSubSystem(SDL_INIT_AUDIO)` is called explicitly on macOS. On other 32 + platforms the SDL event thread initialises audio; Apple's event thread 33 + doesn't, so Rockbox compensates. You shouldn't have to do anything — but if 34 + you ever see "no audio output" on macOS only, this is the code path to look at. 35 + 36 + ## Format 37 + 38 + Output is **S16LE stereo at 44 100 Hz**. Higher-resolution sources are 39 + dithered down by the rbcodec DSP pipeline before they reach SDL. To change 40 + the output sample rate, set `play_frequency` in `settings.toml` (`auto`, 41 + `44100`, `48000`, `88200`, `96000`). 42 + 43 + ## Switching to another sink 44 + 45 + Edit `audio_output` in `settings.toml` and `rockbox restart`, or call: 46 + 47 + ```graphql 48 + mutation { connectDevice(id: "<device-id>") } 49 + ``` 50 + 51 + …with a device discovered via mDNS — see 52 + [Audio output › Overview](/audio-output/overview).
+76
mintlify/audio-output/chromecast.mdx
··· 1 + --- 2 + title: "Chromecast" 3 + description: "Google Cast support over WAV-over-HTTP plus the Cast control channel." 4 + icon: 'cast' 5 + --- 6 + 7 + Rockbox streams audio to any Google Cast-compatible device — Google Home, 8 + Chromecast Audio, Chromecast with Google TV, Nest Hub, or third-party 9 + receivers — using two channels at once: 10 + 11 + | Channel | Port | Purpose | 12 + |----------------|--------------|----------------------------------------------------------------| 13 + | Cast protocol | TCP 8009 | TLS + Protobuf — playback control, queue, metadata | 14 + | WAV over HTTP | TCP 7881 | Live `audio/wav` stream with finite `Content-Length` | 15 + 16 + The finite content length is what lets the Chromecast show a progress bar 17 + and auto-advance at track boundaries. 18 + 19 + ## Configuration 20 + 21 + ```toml 22 + music_dir = "/path/to/Music" 23 + audio_output = "chromecast" 24 + chromecast_host = "192.168.1.60" # LAN IP of the target device 25 + chromecast_port = 8009 # optional, default 8009 (Cast protocol) 26 + chromecast_http_port = 7881 # optional, default 7881 (WAV stream) 27 + ``` 28 + 29 + ## Auto-discovery 30 + 31 + Devices on the LAN are discovered via mDNS (`_googlecast._tcp.local.`) and 32 + appear in the web UI and desktop app device picker — clicking starts a Cast 33 + session on demand without `audio_output = "chromecast"` in the config. 34 + 35 + ## Track metadata 36 + 37 + Title, artist, album, duration, and album art are pushed to the device on 38 + every track change so the "Now playing" card stays accurate. 39 + 40 + <Note> 41 + **Network requirement**: the Chromecast must be able to reach port 7881 on 42 + the host running rockboxd. If rockboxd is in a VM or container, forward 43 + that port to the host (or run with `--network host`). 44 + </Note> 45 + 46 + ## Picking a device from the API 47 + 48 + ```graphql 49 + query DiscoveredCastDevices { 50 + devices { 51 + id 52 + name 53 + ip 54 + port 55 + isCastDevice 56 + } 57 + } 58 + 59 + mutation Cast { 60 + connectDevice(id: "chromecast-living-room") 61 + } 62 + ``` 63 + 64 + …or with the [TypeScript SDK](/sdks/typescript): 65 + 66 + ```ts 67 + const devices = await client.devices.list(); 68 + const cast = devices.find((d) => d.isCastDevice); 69 + if (cast) await client.devices.connect(cast.id); 70 + ``` 71 + 72 + ## Architecture 73 + 74 + Implementation lives in `crates/chromecast/`. See 75 + [`crates/chromecast/README.md`](https://github.com/tsirysndr/rockbox-zig/blob/master/crates/chromecast/README.md) 76 + for the protocol-level details.
+60
mintlify/audio-output/overview.mdx
··· 1 + --- 2 + title: "Audio output overview" 3 + description: "Pick a PCM sink. Switch any time over the API." 4 + icon: 'speaker' 5 + --- 6 + 7 + Rockbox writes decoded PCM into a single active **sink** at a time. The sink 8 + is selected by the `audio_output` key in `settings.toml`, but it can also be 9 + changed at runtime — e.g. by clicking a discovered Chromecast in the device 10 + picker. 11 + 12 + | Sink | `audio_output` | Use it for | 13 + |-------------------|----------------|-------------------------------------------------------| 14 + | Built-in SDL | `builtin` | Local speakers / headphones | 15 + | FIFO / pipe | `fifo` | Snapcast (`pipe://`), `ffplay`, any pipe consumer | 16 + | Snapcast TCP | `snapcast_tcp` | Snapserver `tcp://` source with auto-discovery | 17 + | AirPlay (RAOP) | `airplay` | Apple TV, HomePod, Airport Express, shairport-sync | 18 + | Squeezelite | `squeezelite` | Logitech-style multi-room with squeezelite clients | 19 + | Chromecast | `chromecast` | Google Home, Chromecast Audio, Nest Hub | 20 + | UPnP / DLNA | `upnp` | Kodi, VLC, BubbleUPnP, any UPnP MediaRenderer | 21 + 22 + ## Stream format 23 + 24 + Every sink receives the same byte stream: **S16LE stereo PCM at 44 100 Hz**. 25 + The Rockbox DSP pipeline dithers and downmixes higher-bit-depth decoder 26 + output before it reaches the sink. 27 + 28 + ## Fan-out 29 + 30 + A few sinks support sending the same stream to multiple receivers 31 + simultaneously: 32 + 33 + - **AirPlay** — list multiple `[[airplay_receivers]]` and they all share the 34 + same `initial_rtptime`, keeping playback within ~8 ms across the LAN. 35 + - **Squeezelite** — any number of squeezelite clients can attach to one 36 + rockboxd; a `sync` packet aligns their clocks once per second. 37 + - **Snapcast** — fan-out is handled by snapserver, not Rockbox. 38 + 39 + For Chromecast, AirPlay and UPnP the LAN is also scanned with mDNS / SSDP at 40 + startup; discovered devices show up in the web UI and desktop picker without 41 + any config-file edits. 42 + 43 + ## Switching sinks at runtime 44 + 45 + Through the GraphQL API: 46 + 47 + ```graphql 48 + mutation Connect { 49 + connectDevice(id: "chromecast-living-room") 50 + } 51 + ``` 52 + 53 + …or through the desktop / web device picker. The active sink is stopped, the 54 + new sink's `init` and `start` hooks run, and audio resumes within a frame. 55 + 56 + ## Architecture 57 + 58 + Each sink implements the `pcm_sink_ops` vtable in 59 + `firmware/export/pcm_sink.h`. The full list of sinks with file paths and 60 + implementation notes lives in [Architecture › PCM sinks](/architecture/pcm-sinks).
+112
mintlify/audio-output/snapcast.mdx
··· 1 + --- 2 + title: "Snapcast" 3 + description: "Synchronised multi-room playback through Snapserver — TCP or FIFO." 4 + icon: 'network-wired' 5 + --- 6 + 7 + Rockbox can feed [Snapcast](https://github.com/badaix/snapcast) two ways. 8 + Both write raw **S16LE stereo PCM at 44 100 Hz** to snapserver; pick the 9 + transport that fits your setup. 10 + 11 + | | TCP sink | FIFO sink | 12 + |--------------------------------|------------------------------------|----------------------------| 13 + | Filesystem entry required | No | Yes (`/tmp/snapfifo`) | 14 + | Snapserver source type | `tcp://` | `pipe://` | 15 + | Startup order | Snapserver first | Rockbox first | 16 + | Auto-reconnect | Yes (next play call) | n/a — FIFO stays open | 17 + | Auto-discovery in UI | Yes (`_snapcast._tcp.local.`) | No — static virtual device | 18 + | stdout pipe support | No | Yes (`fifo_path = "-"`) | 19 + 20 + **Use TCP** for auto-discovery, multiple snapservers, or no filesystem 21 + dependency. **Use FIFO** for stdout piping or the traditional pipe model. 22 + 23 + ## TCP (recommended) 24 + 25 + ```toml 26 + music_dir = "/path/to/Music" 27 + audio_output = "snapcast_tcp" 28 + snapcast_tcp_host = "192.168.1.x" # IP of the snapserver host 29 + snapcast_tcp_port = 4953 # default snapserver TCP source port 30 + ``` 31 + 32 + Snapserver: 33 + 34 + ```ini 35 + # /etc/snapserver.conf (or /usr/local/etc/snapserver.conf on macOS) 36 + [stream] 37 + source = tcp://0.0.0.0:4953?name=default&sampleformat=44100:16:2 38 + ``` 39 + 40 + <Tip> 41 + **Auto-discovery**: rockboxd scans `_snapcast._tcp.local.` at startup; 42 + discovered servers appear in the web UI device picker. Click to connect — 43 + no config file editing required. 44 + </Tip> 45 + 46 + <Note> 47 + **Startup order**: start `snapserver` first so it is already listening when 48 + rockboxd begins playback. If the connection drops (e.g. snapserver 49 + restarts), it is re-established automatically on the next play call. 50 + </Note> 51 + 52 + ## FIFO / pipe 53 + 54 + ```toml 55 + music_dir = "/path/to/Music" 56 + audio_output = "fifo" 57 + fifo_path = "/tmp/snapfifo" # named FIFO for snapserver; "-" = stdout 58 + ``` 59 + 60 + Snapserver: 61 + 62 + ```ini 63 + [stream] 64 + source = pipe:///tmp/snapfifo?name=default&sampleformat=44100:16:2 65 + ``` 66 + 67 + <Warning> 68 + **Startup order matters**: start `rockboxd` before `snapserver`. Rockbox 69 + holds a permanent write reference on the FIFO so snapserver never sees a 70 + premature EOF between tracks. If snapserver opens the FIFO first it may get 71 + EOF and stop reading. 72 + </Warning> 73 + 74 + ### stdout mode 75 + 76 + `fifo_path = "-"` writes raw PCM to stdout — useful for piping into any 77 + consumer: 78 + 79 + ```sh 80 + rockboxd | ffplay -f s16le -ar 44100 -ac 2 - 81 + ``` 82 + 83 + ```sh 84 + rockboxd | sox -t raw -r 44100 -e signed -b 16 -c 2 - -d 85 + ``` 86 + 87 + ## macOS quirk 88 + 89 + Snapserver v0.35.0 on macOS ignores the `-s` sample-format CLI flag. Use the 90 + config file at `/usr/local/etc/snapserver.conf` instead: 91 + 92 + ```ini 93 + [stream] 94 + source = pipe:///tmp/snapfifo?name=default&sampleformat=44100:16:2 95 + ``` 96 + 97 + ## Verifying it works 98 + 99 + ```sh 100 + # In one terminal 101 + snapserver --logging.filter "*:debug" 102 + 103 + # In another 104 + rockboxd 105 + ``` 106 + 107 + You should see `Stream: 'default' connected` in the snapserver logs within 108 + a second of starting playback. From a snapclient host: 109 + 110 + ```sh 111 + snapclient -h <snapserver-ip> 112 + ```
+82
mintlify/audio-output/squeezelite.mdx
··· 1 + --- 2 + title: "Squeezelite" 3 + description: "Slim Protocol multi-room — Rockbox impersonates Logitech Media Server." 4 + icon: 'boxes-stacked' 5 + --- 6 + 7 + Rockbox runs a minimal **Logitech Media Server** that any number of 8 + [squeezelite](https://github.com/ralph-irving/squeezelite) clients can attach 9 + to. A Slim Protocol TCP server accepts connections; an HTTP PCM broadcast 10 + server serves the actual audio stream. 11 + 12 + ```toml 13 + music_dir = "/path/to/Music" 14 + audio_output = "squeezelite" 15 + squeezelite_port = 3483 # Slim Protocol TCP port (default 3483) 16 + squeezelite_http_port = 9999 # HTTP PCM broadcast port (default 9999) 17 + ``` 18 + 19 + ## Connecting clients 20 + 21 + ```sh 22 + squeezelite -s localhost -n "Living Room" 23 + squeezelite -s localhost -n "Kitchen" 24 + squeezelite -s localhost -n "Bedroom" 25 + ``` 26 + 27 + Each client gets an independent `BroadcastReceiver` cursor into the shared 28 + buffer, so adding or removing clients never blocks the writer or interrupts 29 + playback in other rooms. 30 + 31 + ### Selecting a specific output device 32 + 33 + ```sh 34 + squeezelite -s localhost -l # list available devices 35 + squeezelite -s localhost -o "" # system default 36 + squeezelite -s localhost -o "Built-in Output" # specific device 37 + ``` 38 + 39 + ## How it stays in sync 40 + 41 + Rockbox sends a `sync` packet to every client once per second so they all 42 + align to the same playback clock. The DMA loop in 43 + `firmware/target/hosted/pcm-squeezelite.c` paces output to real time using 44 + `CLOCK_MONOTONIC`, which keeps the broadcast buffer from drifting against 45 + the wall clock. 46 + 47 + ## Buffer behaviour 48 + 49 + | Property | Value | 50 + |-----------------------|----------------------------------------------| 51 + | Capacity | 4 MB | 52 + | Eviction | Oldest-first when full | 53 + | Per-client cursor | Yes — each receiver tracks its own position | 54 + | Lagging client policy | Skip forward (does not block the writer) | 55 + 56 + A slow client never stalls the others. 57 + 58 + ## Slim Protocol details 59 + 60 + Internal — useful if you're debugging or building your own client. 61 + 62 + - **Framing**: 63 + - client → server: `opcode[4] + u32_t length BE + payload` 64 + - server → client: `u16_t length BE + opcode[4] + payload` 65 + (length excludes the 2-byte length field itself) 66 + - **STRM `'s'`** points clients at the HTTP port (defaults to 9999). 67 + - **STMt heartbeat** from the client must be answered with `audg`, otherwise 68 + squeezelite's 36-second watchdog will tear down the session. 69 + - **ASCII-encoded PCM fields** in the STRM packet — squeezelite subtracts 70 + `'0'` from `pcm_sample_size`, `pcm_sample_rate`, `pcm_channels` and 71 + `pcm_endianness`. Correct values: `'1'` (16-bit), `'3'` (44 100 Hz), 72 + `'2'` (stereo), `'1'` (little-endian). 73 + 74 + ## Debugging 75 + 76 + ```sh 77 + RUST_LOG=rockbox_slim=debug rockboxd 78 + ``` 79 + 80 + ```sh 81 + squeezelite -s localhost -d slimproto=debug -d output=info 82 + ```
+95
mintlify/audio-output/upnp.mdx
··· 1 + --- 2 + title: "UPnP / DLNA" 3 + description: "Three independent UPnP/DLNA modes — sink, media server and renderer." 4 + icon: 'tower-broadcast' 5 + --- 6 + 7 + Rockbox has three UPnP/DLNA modes that can be enabled independently. They 8 + combine freely: e.g. expose your library to BubbleUPnP **and** stream live 9 + to Kodi at the same time. 10 + 11 + ## Mode 1 — PCM sink (push to a renderer) 12 + 13 + Rockbox encodes live PCM as a continuous WAV-over-HTTP stream and tells a 14 + UPnP MediaRenderer to play it via AVTransport SOAP. 15 + 16 + ```toml 17 + music_dir = "/path/to/Music" 18 + audio_output = "upnp" 19 + upnp_renderer_url = "http://192.168.1.x:7777/AVTransport/control" 20 + upnp_http_port = 7879 # WAV broadcast HTTP port (default 7879) 21 + ``` 22 + 23 + Track metadata (title, artist, album, album art, duration) is sent as 24 + DIDL-Lite XML in `SetAVTransportURI` and refreshed on every track change. 25 + 26 + <Tip> 27 + **Finding `upnp_renderer_url`**: start `rockboxd` with `RUST_LOG=info` — 28 + it scans the LAN at startup and logs 29 + `upnp scan: found renderer "<name>" av=http://...` for every renderer 30 + found. 31 + </Tip> 32 + 33 + ## Mode 2 — Media Server (let others browse your library) 34 + 35 + Exposes your music library as a UPnP ContentDirectory. BubbleUPnP, Kodi, 36 + VLC, foobar2000 and the like can browse artists / albums / tracks and pull 37 + audio directly from Rockbox. 38 + 39 + ```toml 40 + upnp_server_enabled = true 41 + upnp_server_port = 7878 # default 42 + upnp_friendly_name = "Rockbox" # name shown in apps 43 + ``` 44 + 45 + ## Mode 3 — MediaRenderer (let others push to you) 46 + 47 + Rockbox registers as a `MediaRenderer:1`. Any control point can push a URI 48 + and control playback remotely. Incoming DIDL-Lite metadata is parsed and 49 + displayed in the UI. 50 + 51 + ```toml 52 + upnp_renderer_enabled = true 53 + upnp_renderer_port = 7880 # default 54 + upnp_friendly_name = "Rockbox" 55 + ``` 56 + 57 + ## All UPnP keys 58 + 59 + | Key | Default | Description | 60 + |----------------------------|--------------|------------------------------------------------| 61 + | `audio_output = "upnp"` | — | Enable the PCM → WAV streaming sink | 62 + | `upnp_renderer_url` | — | AVTransport controlURL of the target renderer | 63 + | `upnp_http_port` | `7879` | WAV broadcast HTTP port | 64 + | `upnp_server_enabled` | `false` | Start the ContentDirectory media server | 65 + | `upnp_server_port` | `7878` | Media server HTTP port | 66 + | `upnp_renderer_enabled` | `false` | Start the MediaRenderer endpoint | 67 + | `upnp_renderer_port` | `7880` | MediaRenderer HTTP port | 68 + | `upnp_friendly_name` | `"Rockbox"` | Display name shown to control points | 69 + 70 + ## Typical setups 71 + 72 + <AccordionGroup> 73 + <Accordion title="Stream to Kodi (sink mode)"> 74 + Find Kodi's AVTransport URL with `RUST_LOG=info rockboxd`, then: 75 + ```toml 76 + audio_output = "upnp" 77 + upnp_renderer_url = "http://192.168.1.42:7777/AVTransport/control" 78 + ``` 79 + </Accordion> 80 + <Accordion title="Library on the LAN (server mode)"> 81 + ```toml 82 + audio_output = "builtin" 83 + upnp_server_enabled = true 84 + upnp_friendly_name = "Living-room music" 85 + ``` 86 + </Accordion> 87 + <Accordion title="Phone control (renderer mode)"> 88 + ```toml 89 + audio_output = "builtin" 90 + upnp_renderer_enabled = true 91 + upnp_friendly_name = "Rockbox" 92 + ``` 93 + Now BubbleUPnP can pick "Rockbox" as the playback target. 94 + </Accordion> 95 + </AccordionGroup>
+54
mintlify/audio-settings/crossfade.mdx
··· 1 + --- 2 + title: "Crossfade" 3 + description: "Overlap the end of one track with the beginning of the next." 4 + icon: 'shuffle' 5 + --- 6 + 7 + Crossfade rolls the outgoing track into the incoming one over a configurable 8 + window. Off by default; enable it via `crossfade` in `settings.toml`. 9 + 10 + ```toml 11 + crossfade = 5 # see Mode table below 12 + fade_on_stop = false 13 + fade_in_delay = 2 14 + fade_in_duration = 7 15 + fade_out_delay = 4 16 + fade_out_duration = 0 17 + fade_out_mixmode = 2 18 + ``` 19 + 20 + ## Mode 21 + 22 + | Value | Mode | When the crossfade fires | 23 + |-------|-------------------------------------|------------------------------------| 24 + | 0 | Off | Never | 25 + | 1 | Auto track change | At end-of-track only | 26 + | 2 | Manual skip | When you press next/previous | 27 + | 3 | Shuffle | While shuffle is on | 28 + | 4 | Shuffle + manual skip | Both | 29 + | 5 | Always | Every transition | 30 + 31 + ## Timings 32 + 33 + | Setting | Storage | Range | Default | Description | 34 + |----------------------|------------------------------------|-----------|----------|----------------------------------------------| 35 + | Fade-in delay | `crossfade_fade_in_delay` | 0..7 s | 0 s | Silence before the fade-in begins | 36 + | Fade-out delay | `crossfade_fade_out_delay` | 0..7 s | 0 s | Silence before the fade-out begins | 37 + | Fade-in duration | `crossfade_fade_in_duration` | 0..15 s | 2 s | Length of the fade-in ramp | 38 + | Fade-out duration | `crossfade_fade_out_duration` | 0..15 s | 2 s | Length of the fade-out ramp | 39 + | Fade-out mode | `crossfade_fade_out_mixmode` | crossfade / mix | crossfade | Whether the outgoing track fades or mixes flat | 40 + 41 + ## Fade-on-stop 42 + 43 + ```toml 44 + fade_on_stop = true 45 + ``` 46 + 47 + When set, pressing **stop** ramps audio out instead of cutting it. 48 + 49 + ## Notes 50 + 51 + - Crossfade is part of the rbcodec DSP pipeline and is applied before the 52 + sink, so it works equally well with AirPlay, Snapcast and the rest. 53 + - Gapless playback overrides crossfade for tracks that share an album 54 + identity — gapless seams are more important than smooth fades.
+97
mintlify/audio-settings/dsp.mdx
··· 1 + --- 2 + title: "DSP effects" 3 + description: "Crossfeed, Haas surround, PBE, AFR, compressor and dithering." 4 + icon: 'waveform-lines' 5 + --- 6 + 7 + The rbcodec DSP pipeline runs between the codec output and the active sink. 8 + All of these are bypassable; turn anything off by zeroing its enable flag. 9 + 10 + ## Crossfeed 11 + 12 + Mixes a delayed and filtered portion of one channel into the other to 13 + simulate the spatial cues you'd get from loudspeakers. Particularly useful 14 + on headphones with hard-panned mixes (older rock/jazz). 15 + 16 + | Setting | Storage | Range | Default | 17 + |----------------|-------------------------------|--------------------|----------| 18 + | Type | `crossfeed` | off / meier / custom | off | 19 + | Direct gain | `crossfeed_direct_gain` | −60..0 dB (step 5) | −15 dB | 20 + | Cross gain | `crossfeed_cross_gain` | −120..−30 dB | −60 dB | 21 + | HF attenuation | `crossfeed_hf_attenuation` | −240..−60 dB | −160 dB | 22 + | HF cutoff | `crossfeed_hf_cutoff` | 500..2000 Hz | 700 Hz | 23 + 24 + <Warning> 25 + Crossfeed can cause output distortion if its settings result in a combined 26 + level that is too high. 27 + </Warning> 28 + 29 + ## Haas surround 30 + 31 + Adds an adjustable delay between channels to widen the stereo image. Four 32 + auxiliary controls move the perceived stage back toward the centre. 33 + 34 + | Setting | Storage | Range | Default | 35 + |---------------|----------------------|------------------------------------|----------| 36 + | Enable | `surround_enabled` | 0 / 5 / 8 / 10 / 15 / 30 ms | 0 (off) | 37 + | Balance | `surround_balance` | 0..99 % | 35 % | 38 + | f(x1) HF cut | `surround_fx1` | 600..8000 Hz (step 200) | 3400 Hz | 39 + | f(x2) LF cut | `surround_fx2` | 40..400 Hz (step 40) | 320 Hz | 40 + | Side only | `surround_method2` | bool | false | 41 + | Dry/wet mix | `surround_mix` | 0..100 % | 50 % | 42 + 43 + ## Perceptual Bass Enhancement (PBE) 44 + 45 + Group-delay correction plus a biophonic EQ to boost low-end perception. 46 + 47 + | Setting | Storage | Range | Default | 48 + |------------|---------------|---------------------|----------| 49 + | Strength | `pbe` | 0..100 % (step 25) | 0 % (off) | 50 + | Precut | `pbe_precut` | −4.5..0 dB (step 0.1) | −2.5 dB | 51 + 52 + ## Auditory Fatigue Reduction (AFR) 53 + 54 + Reduces energy in frequency bands the human ear is most sensitive to — 55 + helpful for long listening sessions. 56 + 57 + | Setting | Values | Default | 58 + |------------|--------------------------------|---------| 59 + | AFR enable | off / weak / moderate / strong | off | 60 + 61 + ## Compressor 62 + 63 + Reduces dynamic range so quiet passages stay audible without loud passages 64 + clipping. 65 + 66 + | Setting | Storage | Values / range | Default | 67 + |---------------|-----------------|--------------------------------------------------------|---------| 68 + | Threshold | `.threshold` | off / −3 / −6 / −9 / −12 / −15 / −18 / −21 / −24 dB | off | 69 + | Makeup gain | `.makeup_gain` | off / auto | auto | 70 + | Ratio | `.ratio` | 2:1 / 4:1 / 6:1 / 10:1 / limit | 2:1 | 71 + | Knee | `.knee` | hard / soft | soft | 72 + | Attack time | `.attack_time` | 0..30 ms (step 5) | 5 ms | 73 + | Release time | `.release_time` | 100..1000 ms (step 100) | 500 ms | 74 + 75 + ```toml 76 + [compressor_settings] 77 + threshold = -24 78 + makeup_gain = 0 79 + ratio = 4 80 + knee = 1 81 + release_time = 300 82 + attack_time = 5 83 + ``` 84 + 85 + ## Dithering 86 + 87 + Most decoders work at higher than 16-bit precision; dithering adds a small 88 + shaped noise signal before truncation so the residual is uniform rather 89 + than signal-correlated. Most useful with classical music and other 90 + high-dynamic-range material. 91 + 92 + ```toml 93 + dithering_enabled = true 94 + ``` 95 + 96 + - Algorithm: high-pass triangular distribution (HPTPDF) 97 + - Noise shaper: third order, biased above ~10 kHz
+112
mintlify/audio-settings/equalizer.mdx
··· 1 + --- 2 + title: "Equalizer" 3 + description: "10-band parametric EQ — independent gain, frequency and Q per band." 4 + icon: 'chart-column' 5 + --- 6 + 7 + Rockbox uses a **parametric** EQ rather than the more common graphic EQ. 8 + Each band has independent control of gain, centre frequency and bandwidth 9 + (Q), which buys you the same shaping power with fewer bands than a graphic 10 + EQ would need. 11 + 12 + <Tip> 13 + Using more bands than necessary wastes CPU and adds rounding noise. Disable 14 + or zero out bands you aren't using. 15 + </Tip> 16 + 17 + ## Bands 18 + 19 + | Band | Filter type | Default centre / cutoff | Q recommendation | 20 + |--------|--------------------|-------------------------|----------------------------------------------------| 21 + | 0 | Low-shelf | 32 Hz | 0.7 (higher Q adds an unwanted boost near cutoff) | 22 + | 1–8 | Peaking (bell) | 64 / 125 / 250 / 500 / 1k / 2k / 4k / 8k Hz | Higher Q = narrower band | 23 + | 9 | High-shelf | 16 000 Hz | 0.7 | 24 + 25 + Per band: 26 + 27 + - **Cutoff / centre frequency** — Hz 28 + - **Gain** — dB; positive boosts, negative cuts 29 + - **Q** — bandwidth (peak filters); 0.7 for shelves 30 + 31 + ## Top-level settings 32 + 33 + | Setting | Storage | Type / range | Description | 34 + |---------------|----------------------|---------------------|-------------------------------------------------------------------| 35 + | Enable EQ | `eq_enabled` | bool | Master on/off | 36 + | Precut | `eq_precut` | 0..24 dB | Negative gain applied before EQ to prevent clipping when boosting | 37 + 38 + Applied via `dsp_set_eq_precut()` and `dsp_set_eq_coefs()` in 39 + `lib/rbcodec/dsp/eq.h`. 40 + 41 + ## TOML 42 + 43 + ```toml 44 + eq_enabled = true 45 + eq_precut = 3 # 3 dB headroom before EQ 46 + 47 + [[eq_band_settings]] # band 0 (low shelf) 48 + cutoff = 32 49 + q = 7 # Q × 10 — Rockbox stores fixed-point 50 + gain = 30 # dB × 10 51 + 52 + [[eq_band_settings]] # band 1 53 + cutoff = 64 54 + q = 7 55 + gain = 0 56 + 57 + # ... bands 2-9 58 + ``` 59 + 60 + <Note> 61 + `q` and `gain` are stored as fixed-point (×10) in `global_settings`. The 62 + GraphQL API accepts plain decimals — see 63 + [Settings TOML reference](/reference/settings-toml). 64 + </Note> 65 + 66 + ## Configuring via the API 67 + 68 + <CodeGroup> 69 + ```graphql GraphQL 70 + mutation EnableEqAndShapeBass { 71 + saveSettings(input: { 72 + eqEnabled: true 73 + eqPrecut: -3 74 + eqBandSettings: [ 75 + { cutoff: 60, q: 7, gain: 3 } 76 + { cutoff: 200, q: 7, gain: 0 } 77 + { cutoff: 800, q: 7, gain: 0 } 78 + { cutoff: 4000, q: 7, gain: -2 } 79 + { cutoff: 12000, q: 7, gain: 1 } 80 + ] 81 + }) { eqEnabled } 82 + } 83 + ``` 84 + 85 + ```ts TypeScript 86 + await client.settings.save({ 87 + eqEnabled: true, 88 + eqPrecut: -3, 89 + eqBandSettings: [ 90 + { cutoff: 60, q: 7, gain: 3 }, 91 + { cutoff: 200, q: 7, gain: 0 }, 92 + { cutoff: 800, q: 7, gain: 0 }, 93 + { cutoff: 4000, q: 7, gain: -2 }, 94 + { cutoff: 12000, q: 7, gain: 1 }, 95 + ], 96 + }); 97 + ``` 98 + 99 + ```python Python 100 + await client.settings.save( 101 + eq_enabled=True, 102 + eq_precut=-3, 103 + eq_band_settings=[ 104 + {"cutoff": 60, "q": 7, "gain": 3}, 105 + {"cutoff": 200, "q": 7, "gain": 0}, 106 + {"cutoff": 800, "q": 7, "gain": 0}, 107 + {"cutoff": 4000, "q": 7, "gain": -2}, 108 + {"cutoff": 12000, "q": 7, "gain": 1}, 109 + ], 110 + ) 111 + ``` 112 + </CodeGroup>
+81
mintlify/audio-settings/overview.mdx
··· 1 + --- 2 + title: "Audio settings" 3 + description: "Volume, EQ, DSP, ReplayGain, crossfade and the rest of the rbcodec pipeline." 4 + icon: 'sliders' 5 + --- 6 + 7 + The Rockbox DSP pipeline runs between the codec output and the active PCM 8 + sink. Settings live in `global_settings` (in the C firmware) and are 9 + mirrored in `settings.toml`. Most can also be changed at runtime via 10 + GraphQL or gRPC and they persist on the next save cycle. 11 + 12 + <CardGroup cols={2}> 13 + <Card title="Equalizer" icon="chart-column" href="/audio-settings/equalizer"> 14 + 10-band parametric EQ — gain, centre frequency, Q per band. 15 + </Card> 16 + <Card title="DSP" icon="waveform-lines" href="/audio-settings/dsp"> 17 + Crossfeed, surround, PBE, AFR, compressor, dithering. 18 + </Card> 19 + <Card title="ReplayGain" icon="volume" href="/audio-settings/replaygain"> 20 + Track / album normalisation with optional clipping protection. 21 + </Card> 22 + <Card title="Crossfade" icon="shuffle" href="/audio-settings/crossfade"> 23 + Overlap track ends with the next track's start. 24 + </Card> 25 + </CardGroup> 26 + 27 + ## Where settings live 28 + 29 + | Layer | Lives in | Lifetime | 30 + |----------------------------|-----------------------------------------|-------------------------| 31 + | Compiled-in defaults | `apps/settings_list.c` | Build-time | 32 + | `settings.toml` | `~/.config/rockbox.org/settings.toml` | Read once at startup | 33 + | Runtime (API) | In-memory `global_settings` | Persisted on save | 34 + 35 + Hardware settings flow through `firmware/sound.c → sound_set_*()`. DSP 36 + settings flow through `lib/rbcodec/dsp/*` and are applied in the PCM pipeline 37 + before samples reach the sink. 38 + 39 + ## Volume 40 + 41 + `global_status.volume` — decibels relative to the device's clipping point. 42 + **0 dB** is the maximum undistorted level. Negative values reduce output; 43 + positive values may distort. On the SDL target, volume is implemented as a 44 + software mixer. 45 + 46 + ```toml 47 + volume_limit = 0 # ceiling in dB (default = device max) 48 + ``` 49 + 50 + ## Channels & stereo 51 + 52 + | Setting | Storage | Range / values | 53 + |------------------------------------|-------------------------------|---------------------------------------------| 54 + | Balance | `balance` | −100..+100 | 55 + | Channel config | `channel_config` | Stereo / Mono / Custom / Mono L / R / Karaoke / Swap | 56 + | Stereo width (when Custom) | `stereo_width` | 0..255 % | 57 + 58 + ## Pitch & time-stretch 59 + 60 + Persisted in `global_status` so they survive across restarts. Time-stretch 61 + uses a TDHS algorithm — best for speech, may sound rough on dense music. 62 + 63 + | Setting | Storage | Range | 64 + |----------------------|--------------------------------------|----------------| 65 + | Pitch | `global_status.resume_pitch` | ~50..200 % | 66 + | Speed | `global_status.resume_speed` | ~35..250 % | 67 + | Time-stretch enable | `global_settings.timestretch_enabled` | bool | 68 + 69 + ## Output sample rate 70 + 71 + ```toml 72 + play_frequency = 0 # 0=auto, 44100, 48000, 88200, 96000 73 + ``` 74 + 75 + ## UI feedback 76 + 77 + ```toml 78 + beep = 0 # off / weak / moderate / strong 79 + keyclick = 0 80 + keyclick_repeats = false 81 + ```
+84
mintlify/audio-settings/replaygain.mdx
··· 1 + --- 2 + title: "ReplayGain" 3 + description: "Loudness normalisation using ReplayGain tags embedded in your files." 4 + icon: 'volume' 5 + --- 6 + 7 + ReplayGain reads the `REPLAYGAIN_*` tags written by tools like 8 + `loudgain` / `mp3gain` / `metaflac` and applies the recommended gain at 9 + playback time. Albums are kept at consistent loudness without re-encoding 10 + the files. 11 + 12 + ```toml 13 + [replaygain_settings] 14 + type = 0 # 0=Track 1=Album 2=Track shuffle 3=Off 15 + noclip = true 16 + preamp = 0 17 + ``` 18 + 19 + ## Settings 20 + 21 + | Setting | Storage | Values / range | Default | Description | 22 + |-----------|----------|------------------------------------------|----------|-----------------------------------------------------------------------------| 23 + | Type | `.type` | track / album / track shuffle / off | shuffle | Which RG tag to apply for normalisation | 24 + | No-Clip | `.noclip`| bool | false | If the RG adjustment would cause clipping, scale down to avoid it | 25 + | Preamp | `.preamp`| −120..+120 dB (step 5) | 0 dB | Extra gain on top of the RG value (use with No-Clip to avoid surprises) | 26 + 27 + Applied via `dsp_replaygain_set_settings()` in `lib/rbcodec/dsp/dsp_misc.h`. 28 + 29 + ## Modes explained 30 + 31 + - **Track** — every track plays at its own RG-normalised level. 32 + - **Album** — all tracks within an album share the same gain; preserves 33 + intended quiet/loud relationships within the album. 34 + - **Track shuffle** — Track gain when shuffling, Album gain otherwise. The 35 + default; behaves naturally regardless of how you're listening. 36 + - **Off** — RG tags ignored. 37 + 38 + ## Tagging your files 39 + 40 + Rockbox does not write ReplayGain tags. Use one of: 41 + 42 + - **`loudgain`** — modern multi-format CLI (FLAC, MP3, Opus, OGG, M4A). 43 + - **`mp3gain`** — MP3 only, but lossless. 44 + - **`metaflac --add-replay-gain`** — FLAC, ships with `flac`. 45 + 46 + Example: 47 + 48 + ```sh 49 + loudgain -a -k -s e *.flac # tag album-mode, prevent clipping 50 + ``` 51 + 52 + After re-tagging, trigger a library rescan: 53 + 54 + ```graphql 55 + mutation { scanLibrary } 56 + ``` 57 + 58 + ## Configuring at runtime 59 + 60 + <CodeGroup> 61 + ```graphql GraphQL 62 + mutation { 63 + saveSettings(input: { 64 + replaygainSettings: { 65 + type: 1 66 + noclip: true 67 + preamp: 0 68 + } 69 + }) { replaygainSettings { type } } 70 + } 71 + ``` 72 + 73 + ```ts TypeScript 74 + import { ReplaygainType } from '@rockbox-zig/sdk'; 75 + 76 + await client.settings.save({ 77 + replaygainSettings: { 78 + type: ReplaygainType.Album, 79 + noclip: true, 80 + preamp: 0, 81 + }, 82 + }); 83 + ``` 84 + </CodeGroup>
+65
mintlify/clients/desktop.mdx
··· 1 + --- 2 + title: "Desktop app" 3 + description: "Native desktop clients on macOS (GPUI), Linux (GTK4) and Windows." 4 + icon: 'display' 5 + --- 6 + 7 + Two native desktop clients are maintained alongside `rockboxd`: 8 + 9 + - **macOS (GPUI)** — built on Zed's native UI toolkit, ships as a `.dmg` 10 + - **Linux (GTK4)** — distributed as a Flatpak 11 + 12 + Both connect to a running `rockboxd` instance over GraphQL. 13 + 14 + ## macOS (GPUI) 15 + 16 + Download the latest `.dmg` from the 17 + [Releases page](https://github.com/tsirysndr/rockbox-zig/releases/latest) 18 + or build from source: 19 + 20 + ```sh 21 + cd gpui 22 + cargo run --release 23 + ``` 24 + 25 + Features: 26 + 27 + - Full library browsing and search 28 + - Drag-and-drop queue management 29 + - Native macOS media keys & Now Playing card via MPRIS-equivalent integration 30 + - Right-click context menus for tracks, albums and folders 31 + - Dark / light mode follows the system 32 + 33 + ## Linux (GTK4) 34 + 35 + The GTK4 client is published as a Flatpak. Build from source: 36 + 37 + ```sh 38 + sudo apt-get install flatpak 39 + flatpak remote-add --if-not-exists --user flathub \ 40 + https://dl.flathub.org/repo/flathub.flatpakrepo 41 + flatpak install --user flathub org.flatpak.Builder 42 + flatpak install --user flathub org.gnome.Sdk/x86_64/47 43 + flatpak install --user flathub org.gnome.Platform/x86_64/47 44 + flatpak install --user org.freedesktop.Sdk.Extension.rust-stable 45 + flatpak install --user org.freedesktop.Sdk.Extension.llvm18 46 + 47 + cd gtk 48 + flatpak run org.flatpak.Builder \ 49 + --user --disable-rofiles-fuse --repo=repo \ 50 + flatpak_app build-aux/io.github.tsirysndr.Rockbox.json --force-clean 51 + 52 + flatpak run org.flatpak.Builder \ 53 + --run flatpak_app build-aux/io.github.tsirysndr.Rockbox.json rockbox-gtk 54 + ``` 55 + 56 + ## Connecting to a remote rockboxd 57 + 58 + Both desktop clients accept a non-default host and port — useful when 59 + `rockboxd` runs on a NAS or Raspberry Pi: 60 + 61 + - macOS — Settings → Connection → Host / Port 62 + - GTK — `Preferences` → Server 63 + 64 + If you put rockboxd behind a reverse proxy with TLS, use the `httpUrl` / 65 + `wsUrl` overrides under Connection settings.
+65
mintlify/clients/mpd.mdx
··· 1 + --- 2 + title: "MPD clients" 3 + description: "Rockbox speaks the Music Player Daemon protocol on port 6600." 4 + icon: 'terminal' 5 + --- 6 + 7 + `rockboxd` runs a built-in MPD server on port **6600**, so any of the dozens 8 + of MPD clients work out of the box — `mpc`, `ncmpcpp`, MALP, M.A.L.P., 9 + Cantata, Mopidy, Volumio's UI, etc. 10 + 11 + ```sh 12 + mpc -h localhost -p 6600 status 13 + mpc -h localhost -p 6600 update 14 + mpc -h localhost -p 6600 search title "money" 15 + mpc -h localhost -p 6600 add /Music/Pink\ Floyd/Money.mp3 16 + mpc -h localhost -p 6600 play 17 + ``` 18 + 19 + ## Compatibility 20 + 21 + The implementation lives in `crates/mpd/`. It targets the standard MPD 22 + protocol version. Coverage: 23 + 24 + - ✅ Status, current/next song, elapsed time 25 + - ✅ Playback control: play/pause/next/previous/seek/stop 26 + - ✅ Queue (`add`, `addid`, `delete`, `clear`, `shuffle`, `move`) 27 + - ✅ Library (`list`, `find`, `search`, `lsinfo`) 28 + - ✅ Saved playlists (`load`, `save`, `rm`, `playlistadd`) 29 + - ✅ Volume, repeat, random, single, consume 30 + - ✅ `idle` notifications (player, playlist, mixer, etc.) 31 + - ⚠️ Sticker database — partial 32 + - ⚠️ Outputs — single output exposed (the active sink) 33 + 34 + ## Configuring the bind address 35 + 36 + ```toml 37 + mpd_host = "0.0.0.0" 38 + mpd_port = 6600 39 + ``` 40 + 41 + ## Recommended clients 42 + 43 + | Client | Platform | Notes | 44 + |--------------|-----------------------|-------------------------------------------| 45 + | `mpc` | CLI | Scripting, smoke-testing | 46 + | `ncmpcpp` | TUI | The classic terminal client | 47 + | MALP | Android | Material Design, modern feel | 48 + | M.A.L.P. | Android (F-Droid) | F-Droid build of MALP | 49 + | Cantata | Linux / Windows / macOS | Full GUI client | 50 + | Mopidy | Linux | Acts as an MPD-compatible aggregator | 51 + 52 + ## Idle subscriptions 53 + 54 + ```sh 55 + mpc -h localhost -p 6600 idle player playlist mixer 56 + ``` 57 + 58 + Returns one event per change. The `subscribe`/`channels` channel commands 59 + are also implemented for client-to-client messaging. 60 + 61 + ## Library updates 62 + 63 + `update` triggers a full rescan of `music_dir` exactly like the 64 + [GraphQL `scanLibrary` mutation](/api-reference/graphql/library). New files 65 + appear in MPD listings as soon as the scan finishes.
+50
mintlify/clients/mpris.mdx
··· 1 + --- 2 + title: "MPRIS" 3 + description: "Linux media keys and desktop integration via D-Bus." 4 + icon: 'keyboard' 5 + --- 6 + 7 + On Linux, `rockboxd` registers itself on the session bus as 8 + `org.mpris.MediaPlayer2.rockbox`. This makes media keys, Now Playing 9 + applets, KDE/GNOME notifications and tools like `playerctl` Just Work. 10 + 11 + ## Capabilities 12 + 13 + The MPRIS interface is implemented in `crates/mpris/` and exposes: 14 + 15 + - **Player interface** — Play, Pause, PlayPause, Stop, Next, Previous, Seek, 16 + SetPosition, Metadata, PlaybackStatus, LoopStatus, Shuffle, Volume 17 + - **Root interface** — Identity, DesktopEntry, SupportedUriSchemes, 18 + SupportedMimeTypes 19 + - **TrackList interface** — partial (track-level queue introspection) 20 + 21 + ## Quick test 22 + 23 + ```sh 24 + playerctl --player rockbox play-pause 25 + playerctl --player rockbox metadata 26 + playerctl --player rockbox position 90 27 + ``` 28 + 29 + ## Wiring up media keys 30 + 31 + GNOME and KDE pick up MPRIS players automatically — no setup needed. For 32 + sway / Hyprland / dwm: 33 + 34 + ```text 35 + # sway 36 + bindsym XF86AudioPlay exec playerctl --player rockbox play-pause 37 + bindsym XF86AudioNext exec playerctl --player rockbox next 38 + bindsym XF86AudioPrev exec playerctl --player rockbox previous 39 + ``` 40 + 41 + ## Now Playing applet 42 + 43 + GNOME Shell, KDE's Media Player widget, polybar's `mpris-tail`, waybar's 44 + `mpris` module — all consume MPRIS metadata directly. 45 + 46 + ## macOS 47 + 48 + macOS has its own Now Playing system. Rockbox publishes to it through the 49 + `MPNowPlayingInfoCenter` API; you don't need MPRIS on Mac. Media keys work 50 + out of the box.
+47
mintlify/clients/web.mdx
··· 1 + --- 2 + title: "Web UI" 3 + description: "Browser-based controller served by rockboxd on port 6062." 4 + icon: 'globe' 5 + --- 6 + 7 + The web UI is a React app served from the GraphQL HTTP listener. Open 8 + [http://localhost:6062](http://localhost:6062) once `rockboxd` is running. 9 + 10 + ## Features 11 + 12 + - Browse artists, albums and tracks from the tag database 13 + - Browse the filesystem under `music_dir` 14 + - Search powered by Typesense (instant results as you type) 15 + - Manage the live playback queue and saved playlists 16 + - Reorder, shuffle, repeat 17 + - Like / unlike tracks and albums 18 + - Pick the active output device (Chromecast, AirPlay, Snapcast TCP, …) 19 + - Real-time "now playing" updates over a GraphQL subscription 20 + 21 + ## Building from source 22 + 23 + The web UI ships pre-built with the `rockboxd` binary, but you can rebuild 24 + it locally: 25 + 26 + ```sh 27 + cd webui/rockbox 28 + deno install 29 + deno run build 30 + ``` 31 + 32 + The result lands in `webui/rockbox/build/` and is embedded into rockboxd at 33 + link time. 34 + 35 + ## Customising the bind address 36 + 37 + ```toml 38 + graphql_host = "0.0.0.0" # default 0.0.0.0 39 + graphql_port = 6062 # default 6062 40 + ``` 41 + 42 + Restart rockboxd, then reach the UI at `http://<host>:<port>`. 43 + 44 + ## Storybook 45 + 46 + Component-level documentation is on Chromatic: 47 + [storybook ↗](https://master--670ceec25af685dcdc87c0df.chromatic.com/?path=/story/components-albums--default).
+131
mintlify/configuration.mdx
··· 1 + --- 2 + title: "Configuration" 3 + description: "Configure Rockbox via ~/.config/rockbox.org/settings.toml." 4 + icon: 'gear' 5 + --- 6 + 7 + Rockbox reads `~/.config/rockbox.org/settings.toml` once on startup. Edit the 8 + file, then `rockbox restart`. There is no live-reload; the API is the way to 9 + change things at runtime. 10 + 11 + ## Minimal config 12 + 13 + ```toml 14 + music_dir = "/path/to/your/Music" 15 + audio_output = "builtin" 16 + ``` 17 + 18 + `music_dir` is the only required field. `audio_output` defaults to `"builtin"` 19 + if omitted. 20 + 21 + ## Top-level keys 22 + 23 + | Key | Type | Default | Description | 24 + |----------------|--------|---------------|----------------------------------------------| 25 + | `music_dir` | string | — | Absolute path to your music library | 26 + | `audio_output` | string | `"builtin"` | One of: `builtin`, `fifo`, `airplay`, `squeezelite`, `chromecast`, `snapcast_tcp`, `upnp` | 27 + | `player_name` | string | `""` | Name advertised to MPD clients and UI | 28 + 29 + ## Output sinks 30 + 31 + Each sink has its own configuration block. See the dedicated pages: 32 + 33 + <CardGroup cols={2}> 34 + <Card title="Built-in (SDL)" icon="speaker" href="/audio-output/built-in">Default. No setup.</Card> 35 + <Card title="Snapcast" icon="network-wired" href="/audio-output/snapcast">FIFO or direct TCP.</Card> 36 + <Card title="AirPlay" icon="apple" href="/audio-output/airplay">Single or multi-room RAOP.</Card> 37 + <Card title="Squeezelite" icon="boxes-stacked" href="/audio-output/squeezelite">Slim Protocol multi-room.</Card> 38 + <Card title="Chromecast" icon="cast" href="/audio-output/chromecast">Google Cast over WAV/HTTP.</Card> 39 + <Card title="UPnP / DLNA" icon="tower-broadcast" href="/audio-output/upnp">Sink, server, renderer.</Card> 40 + </CardGroup> 41 + 42 + ## Playback defaults 43 + 44 + ```toml 45 + playlist_shuffle = false 46 + repeat_mode = 1 # 0=Off 1=All 2=One 3=Shuffle 4=A-B 47 + party_mode = true 48 + ``` 49 + 50 + ## Equalizer 51 + 52 + ```toml 53 + eq_enabled = true 54 + 55 + [[eq_band_settings]] # band 0 — low shelf 56 + cutoff = 0 57 + q = 64 58 + gain = 10 59 + 60 + [[eq_band_settings]] # bands 1–8 — peaking 61 + cutoff = 3 62 + q = 125 63 + gain = 10 64 + 65 + # ...repeat for the remaining bands 66 + ``` 67 + 68 + The full 10-band parametric EQ is documented in 69 + [Audio settings › Equalizer](/audio-settings/equalizer). 70 + 71 + ## Crossfade 72 + 73 + ```toml 74 + crossfade = 5 75 + fade_on_stop = false 76 + fade_in_delay = 2 77 + fade_in_duration = 7 78 + fade_out_delay = 4 79 + fade_out_duration = 0 80 + fade_out_mixmode = 2 81 + ``` 82 + 83 + ## Tone & stereo 84 + 85 + ```toml 86 + bass = 0 87 + treble = 0 88 + bass_cutoff = 0 89 + treble_cutoff = 0 90 + balance = 0 91 + stereo_width = 100 92 + stereosw_mode = 0 93 + channel_config = 0 94 + surround_enabled = 0 95 + surround_balance = 0 96 + surround_fx1 = 0 97 + surround_fx2 = 0 98 + ``` 99 + 100 + ## ReplayGain 101 + 102 + ```toml 103 + [replaygain_settings] 104 + noclip = true 105 + type = 0 # 0=Track 1=Album 2=Shuffle (see Replaygain page) 106 + preamp = 0 107 + ``` 108 + 109 + ## Compressor 110 + 111 + ```toml 112 + [compressor_settings] 113 + threshold = -24 114 + makeup_gain = 0 115 + ratio = 4 116 + knee = 1 117 + release_time = 300 118 + attack_time = 5 119 + ``` 120 + 121 + ## Where settings come from 122 + 123 + There are three layers, in order of precedence: 124 + 125 + 1. **Runtime API calls** — every setting is also exposed over GraphQL/gRPC 126 + and persists to disk on the next save cycle. 127 + 2. **`settings.toml`** — applied once at startup. 128 + 3. **Compiled-in defaults** — in `apps/settings_list.c`. 129 + 130 + For the full settings catalogue with units, ranges and where each one is 131 + applied, see the [Settings TOML reference](/reference/settings-toml).
+180
mintlify/docs.json
··· 1 + { 2 + "$schema": "https://mintlify.com/docs.json", 3 + "theme": "mint", 4 + "name": "Rockbox Zig", 5 + "appearance": { 6 + "default": "dark", 7 + "strict": true 8 + }, 9 + "colors": { 10 + "primary": "#F4FF00", 11 + "light": "#FAFF66", 12 + "dark": "#C2CC00" 13 + }, 14 + "background": { 15 + "color": { 16 + "dark": "#07080C" 17 + }, 18 + "decoration": "grid" 19 + }, 20 + "favicon": "/favicon.svg", 21 + "navigation": { 22 + "tabs": [ 23 + { 24 + "tab": "Guides", 25 + "groups": [ 26 + { 27 + "group": "Getting started", 28 + "pages": [ 29 + "index", 30 + "quickstart", 31 + "installation", 32 + "configuration" 33 + ] 34 + }, 35 + { 36 + "group": "Audio output", 37 + "pages": [ 38 + "audio-output/overview", 39 + "audio-output/built-in", 40 + "audio-output/snapcast", 41 + "audio-output/airplay", 42 + "audio-output/squeezelite", 43 + "audio-output/chromecast", 44 + "audio-output/upnp" 45 + ] 46 + }, 47 + { 48 + "group": "Audio settings", 49 + "pages": [ 50 + "audio-settings/overview", 51 + "audio-settings/equalizer", 52 + "audio-settings/dsp", 53 + "audio-settings/replaygain", 54 + "audio-settings/crossfade" 55 + ] 56 + }, 57 + { 58 + "group": "Clients", 59 + "pages": [ 60 + "clients/web", 61 + "clients/desktop", 62 + "clients/mpd", 63 + "clients/mpris" 64 + ] 65 + }, 66 + { 67 + "group": "Architecture", 68 + "pages": [ 69 + "architecture/overview", 70 + "architecture/build", 71 + "architecture/pcm-sinks" 72 + ] 73 + }, 74 + { 75 + "group": "Reference", 76 + "pages": [ 77 + "reference/cli", 78 + "reference/ports", 79 + "reference/settings-toml", 80 + "reference/troubleshooting", 81 + "reference/faq" 82 + ] 83 + } 84 + ] 85 + }, 86 + { 87 + "tab": "API reference", 88 + "groups": [ 89 + { 90 + "group": "Overview", 91 + "pages": [ 92 + "api-reference/introduction", 93 + "api-reference/rest/overview", 94 + "api-reference/graphql/overview", 95 + "api-reference/grpc/overview", 96 + "api-reference/mpd/overview" 97 + ] 98 + }, 99 + { 100 + "group": "HTTP REST", 101 + "openapi": { 102 + "source": "api-reference/openapi.json", 103 + "directory": "api-reference/rest" 104 + } 105 + } 106 + ] 107 + }, 108 + { 109 + "tab": "SDKs", 110 + "groups": [ 111 + { 112 + "group": "Client libraries", 113 + "pages": [ 114 + "sdks/overview", 115 + "sdks/typescript", 116 + "sdks/python", 117 + "sdks/ruby", 118 + "sdks/elixir", 119 + "sdks/clojure", 120 + "sdks/gleam" 121 + ] 122 + } 123 + ] 124 + } 125 + ], 126 + "global": { 127 + "anchors": [ 128 + { 129 + "anchor": "GitHub", 130 + "href": "https://github.com/tsirysndr/rockbox-zig", 131 + "icon": "github" 132 + }, 133 + { 134 + "anchor": "Discord", 135 + "href": "https://discord.gg/tXPrgcPKSt", 136 + "icon": "discord" 137 + }, 138 + { 139 + "anchor": "Releases", 140 + "href": "https://github.com/tsirysndr/rockbox-zig/releases", 141 + "icon": "rocket" 142 + } 143 + ] 144 + } 145 + }, 146 + "logo": { 147 + "light": "/logo/light.svg", 148 + "dark": "/logo/dark.svg" 149 + }, 150 + "navbar": { 151 + "links": [ 152 + { 153 + "label": "Issues", 154 + "href": "https://github.com/tsirysndr/rockbox-zig/issues" 155 + } 156 + ], 157 + "primary": { 158 + "type": "github", 159 + "href": "https://github.com/tsirysndr/rockbox-zig" 160 + } 161 + }, 162 + "contextual": { 163 + "options": [ 164 + "copy", 165 + "view", 166 + "chatgpt", 167 + "claude", 168 + "perplexity", 169 + "mcp", 170 + "cursor", 171 + "vscode" 172 + ] 173 + }, 174 + "footer": { 175 + "socials": { 176 + "github": "https://github.com/tsirysndr/rockbox-zig", 177 + "discord": "https://discord.gg/tXPrgcPKSt" 178 + } 179 + } 180 + }
+115
mintlify/favicon.svg
··· 1 + <?xml version="1.0" encoding="UTF-8" standalone="no"?> 2 + <!-- Created with Inkscape (http://www.inkscape.org/) --> 3 + 4 + <svg 5 + xmlns:dc="http://purl.org/dc/elements/1.1/" 6 + xmlns:cc="http://creativecommons.org/ns#" 7 + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" 8 + xmlns:svg="http://www.w3.org/2000/svg" 9 + xmlns="http://www.w3.org/2000/svg" 10 + xmlns:xlink="http://www.w3.org/1999/xlink" 11 + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" 12 + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" 13 + version="1.1" 14 + width="120" 15 + height="120" 16 + viewBox="0 0 3386.6665 3386.6666" 17 + id="svg2" 18 + xml:space="preserve" 19 + style="fill-rule:evenodd" 20 + inkscape:version="0.48.1 r9760" 21 + sodipodi:docname="rockbox-clef.svg"><metadata 22 + id="metadata18"><rdf:RDF><cc:Work 23 + rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type 24 + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><sodipodi:namedview 25 + pagecolor="#ffffff" 26 + bordercolor="#666666" 27 + borderopacity="1" 28 + objecttolerance="10" 29 + gridtolerance="10" 30 + guidetolerance="10" 31 + inkscape:pageopacity="0" 32 + inkscape:pageshadow="2" 33 + inkscape:window-width="864" 34 + inkscape:window-height="720" 35 + id="namedview16" 36 + showgrid="true" 37 + inkscape:zoom="4" 38 + inkscape:cx="82.5729" 39 + inkscape:cy="69.674808" 40 + inkscape:window-x="0" 41 + inkscape:window-y="24" 42 + inkscape:window-maximized="0" 43 + inkscape:current-layer="Ebene_x0020_1"><inkscape:grid 44 + type="xygrid" 45 + id="grid2993" 46 + empspacing="5" 47 + visible="true" 48 + enabled="true" 49 + snapvisiblegridlinesonly="true" /></sodipodi:namedview><defs 50 + id="defs38"><linearGradient 51 + id="linearGradient3657"><stop 52 + id="stop3659" 53 + style="stop-color:#aa8800;stop-opacity:1" 54 + offset="0" /><stop 55 + id="stop3661" 56 + style="stop-color:#aa8800;stop-opacity:0" 57 + offset="1" /></linearGradient><linearGradient 58 + x1="-89.260162" 59 + y1="-2.1270833" 60 + x2="-14.333748" 61 + y2="85.830009" 62 + id="linearGradient3663" 63 + xlink:href="#linearGradient3657" 64 + gradientUnits="userSpaceOnUse" /></defs> 65 + <g 66 + transform="matrix(0.90063697,0,0,0.88724946,748.25202,-1317.6084)" 67 + id="Ebene_x0020_1"> 68 + <defs 69 + id="defs5"> 70 + <linearGradient 71 + x1="17608" 72 + y1="4190.54" 73 + x2="17715.699" 74 + y2="4801.2798" 75 + id="id0" 76 + gradientUnits="userSpaceOnUse"> 77 + <stop 78 + id="stop8" 79 + style="stop-color:#a67d00;stop-opacity:1" 80 + offset="0" /> 81 + 82 + <stop 83 + id="stop12" 84 + style="stop-color:#ffffff;stop-opacity:1" 85 + offset="1" /> 86 + </linearGradient> 87 + </defs> 88 + 89 + 90 + 91 + 92 + 93 + <rect 94 + width="3133.5845" 95 + height="3180.8667" 96 + ry="302.44305" 97 + x="-517.4447" 98 + y="1803.135" 99 + id="rect3694" 100 + style="fill:#ffc001;fill-opacity:1;fill-rule:evenodd;stroke:none" /><path 101 + d="m 1133.6395,3200.4269 c 11.2559,18.2986 22.432,36.6011 33.5473,54.9487 60.3406,-16.5847 123.9814,-25.3702 188.4648,-26.059 126.1999,-1.316 246.2441,48.6846 317.2687,132.1614 64.2026,75.4539 101.1079,162.6951 106.6733,252.1193 2.3212,37.3762 -5.6502,74.466 -23.3869,108.7376 -43.5526,84.192 -117.9946,155.791 -214.1337,205.9842 45.7615,89.9291 90.0403,180.2716 132.8495,271.0265 81.8832,173.6387 132.6702,354.6655 150.8198,537.8489 4.676,47.2371 -2.5858,94.4734 -21.3927,138.9539 -19.2847,45.5742 -58.1673,84.1121 -110.1785,109.1541 -37.7378,18.1621 -83.751,23.2435 -127.4177,14.0995 -43.6802,-9.1436 -81.2498,-31.7365 -104.0476,-62.5664 -26.5203,-35.904 -42.2855,-75.8627 -46.0345,-116.7994 -2.1173,-23.0223 9.3653,-45.2202 31.1441,-60.2062 27.4567,-18.8993 62.7411,-29.5144 99.7917,-30.07 24.3266,-0.3368 47.3936,9.6188 60.3568,26.0881 12.9472,16.4494 13.7396,36.8489 2.1096,53.3337 -14.3314,20.3045 -37.0946,36.0653 -64.6429,44.7438 l 11.8035,16.1533 c 5.7557,9.2105 16.8132,15.7847 29.6363,17.5515 12.8112,1.7777 25.7362,-1.4481 34.5823,-8.6399 43.6788,-34.3792 67.7315,-81.3042 66.8875,-130.5556 -3.562,-84.7728 -16.3171,-169.3555 -38.1543,-252.8527 -55.7305,-177.8644 -127.5412,-352.777 -214.8999,-523.3512 -16.3737,5.1045 -33.0607,9.6178 -49.9908,13.5678 -49.5652,11.5132 -99.9157,21.0976 -150.8427,28.7217 l -100.813,-814.0936 z m 248.7771,672.5156 c -50.5142,19.7875 -104.412,34.1161 -160.2241,42.5737 l -50.7294,-409.6546 c 72.2514,121.1726 142.3288,243.0983 210.2123,365.7262 0.2567,0.458 0.4978,0.8959 0.7412,1.3547 0,0 0,0 0,0 0,0 0,0 0,0 0,0 0,0 0,0 m -150.076,-508.6879 c 17.5147,-4.8294 35.3415,-9.0782 53.3625,-12.729 56.5362,-11.4397 117.3362,-3.637 167.586,21.5055 45.7565,22.8959 80.8463,56.9138 99.7551,96.6234 42.1044,88.5079 47.194,184.4321 14.5179,273.6257 -6.8456,18.7164 -19.8851,35.5583 -37.8052,48.8537 -13.7547,10.2054 -28.1066,19.8974 -43.0038,29.0629 -80.5314,-153.6773 -165.3681,-306.0385 -254.4125,-456.9422 m -10.1481,551.2616 c -14.298,2.1559 -28.7086,3.9408 -43.196,5.3209 -157.9011,15.1267 -314.30239,-47.8917 -393.33817,-158.4708 -140.27886,-196.2782 -185.31029,-423.8857 -126.59762,-639.7364 20.62766,-75.767 42.25356,-151.3353 64.91887,-226.6963 6.26181,-20.8439 12.63094,-41.6829 19.09409,-62.5163 104.89649,154.512 206.20813,310.449 303.84503,467.7319 -104.88444,52.3821 -185.1784,129.7162 -229.58648,221.109 -7.33244,15.1065 -1.5786,32.4892 14.38626,43.3632 15.95163,10.8746 38.6339,12.8768 56.5858,5.0072 15.19318,-10.1136 29.0455,-21.3616 41.40015,-33.6098 49.89688,-51.7003 111.96607,-95.3657 183.00107,-128.7738 19.7486,32.4884 39.3155,65.0179 58.7576,97.6166 l -37.8236,-305.4347 c -92.6263,-150.6417 -189.46344,-299.7421 -290.47048,-447.1875 -16.73566,-24.4224 -33.73799,-48.7363 -51.00315,-72.9107 12.53936,-37.7911 25.36472,-75.5343 38.46801,-113.1874 18.72476,-53.759 31.27196,-108.8276 37.52831,-164.6129 7.04724,-63.0773 -7.52517,-127.0079 -42.2481,-185.2058 -79.1109,-132.6193 -213.39469,-241.1725 -380.18823,-307.3322 -73.37553,-29.1121 -155.31083,-41.0734 -235.85269,-34.4583 -67.48248,5.551 -116.971987,50.5243 -114.340324,103.8724 2.381016,48.4159 22.574124,95.696 58.086904,136.1456 32.88527,37.4341 67.82132,73.7652 104.71321,108.8721 113.82257,108.3303 214.95374,224.3585 302.12809,346.5898 17.13041,24.0557 34.19842,48.1462 51.17499,72.252 -15.99656,48.3246 -31.52841,96.7397 -46.59165,145.277 -69.85813,225.1541 -104.21032,456.9898 -102.24126,690.3515 0.92822,110.1406 26.98496,219.8501 76.97168,323.9736 68.55723,142.8242 245.98138,237.2059 438.57406,233.2775 86.18513,-1.7571 171.82603,-8.9862 256.10363,-21.6231 l -12.26,-99.0043" 102 + id="path24" 103 + style="fill:#000000;fill-opacity:1;fill-rule:evenodd" 104 + inkscape:connector-curvature="0" /> 105 + 106 + 107 + <path 108 + d="M 659.33533,2503.3767 C 573.5524,2394.5355 482.17708,2288.3563 385.43749,2185.1728 c -30.76545,-32.8099 -59.12968,-66.9092 -84.95666,-102.1687 -16.86386,-22.98 -28.28033,-48.07 -33.73308,-74.0078 -1.1599,-5.5966 0.71447,-11.2481 5.21013,-15.5497 4.45467,-4.3102 11.12647,-6.8716 18.3068,-7.0503 34.58075,-0.8971 69.10856,5.1026 100.42147,17.453 134.57561,53.071 237.34107,146.2148 284.58433,257.9109 13.77151,32.583 18.33147,66.9284 13.33714,100.5928 -7.04096,47.4029 -16.80734,94.4462 -29.27229,141.0237 l 0,0" 109 + id="path30" 110 + style="fill:#ffc000" 111 + inkscape:connector-curvature="0" /> 112 + 113 + 114 + </g> 115 + </svg>
mintlify/images/checks-passed.png

This is a binary file and will not be displayed.

mintlify/images/hero-dark.png

This is a binary file and will not be displayed.

mintlify/images/hero-light.png

This is a binary file and will not be displayed.

+88
mintlify/index.mdx
··· 1 + --- 2 + title: "Rockbox Zig" 3 + description: "A modern wrapper around the Rockbox audio engine, exposed over gRPC, GraphQL, HTTP and MPD — with multi-room AirPlay, Snapcast, Squeezelite, Chromecast and UPnP." 4 + icon: 'music' 5 + --- 6 + 7 + Rockbox Zig is a modern take on the [Rockbox](https://www.rockbox.org) open-source 8 + audio player firmware. The Rockbox C engine — gapless playback, DSP, 20+ codecs, 9 + and the tag database — is wrapped in Rust and Zig services that expose **gRPC**, 10 + **GraphQL**, **HTTP** and **MPD** APIs, add a **Typesense**-backed search engine, 11 + and stream audio to **AirPlay**, **Snapcast**, **Squeezelite**, **Chromecast** 12 + and **UPnP** receivers. 13 + 14 + Everything ships as a single binary called `rockboxd`. 15 + 16 + <CardGroup cols={2}> 17 + <Card title="Quickstart" icon="rocket" href="/quickstart"> 18 + Install Rockbox and play your first track in under five minutes. 19 + </Card> 20 + <Card title="Audio output" icon="speaker" href="/audio-output/overview"> 21 + Built-in SDL, AirPlay, Snapcast, Squeezelite, Chromecast, UPnP/DLNA. 22 + </Card> 23 + <Card title="API reference" icon="terminal" href="/api-reference/introduction"> 24 + GraphQL, HTTP REST, gRPC and MPD — pick your protocol. 25 + </Card> 26 + <Card title="Client SDKs" icon="code" href="/sdks/overview"> 27 + TypeScript, Python, Ruby, Elixir, Clojure and Gleam. 28 + </Card> 29 + </CardGroup> 30 + 31 + ## What's inside 32 + 33 + <CardGroup cols={3}> 34 + <Card title="Audio engine" icon="waveform-lines"> 35 + Rockbox C firmware — gapless playback, parametric EQ, crossfade, ReplayGain, 36 + PBE, Haas surround, dithering, and the rbcodec DSP pipeline. 37 + </Card> 38 + <Card title="Codecs" icon="file-audio"> 39 + MP3, OGG, FLAC, WAV, AAC, Opus, ALAC, Musepack, WMA, APE, Wavpack, Speex 40 + and many more. 41 + </Card> 42 + <Card title="Multi-room" icon="house-signal"> 43 + Synchronised playback to AirPlay receivers, Snapcast clients, 44 + Squeezelite devices, Chromecasts and DLNA renderers — all at once. 45 + </Card> 46 + <Card title="Library" icon="database"> 47 + SQLite-backed tag database, instant Typesense search, browse-by-folder 48 + and ReplayGain-aware metadata. 49 + </Card> 50 + <Card title="APIs" icon="plug"> 51 + gRPC on `:6061`, GraphQL on `:6062`, REST on `:6063`, MPD on `:6600`, 52 + plus MPRIS and UPnP/DLNA. 53 + </Card> 54 + <Card title="Clients" icon="display"> 55 + Native macOS (GPUI), GTK4 desktop app, React web UI, terminal TUI and 56 + a Rockbox REPL. 57 + </Card> 58 + </CardGroup> 59 + 60 + ## How it fits together 61 + 62 + ```text 63 + ┌──────────────────────────────────────────────────────────────────────┐ 64 + │ Clients Web UI · GTK · GPUI · TUI · REPL · MPD · MPRIS │ 65 + ├──────────────────────────────────────────────────────────────────────┤ 66 + │ Protocols gRPC :6061 GraphQL :6062 REST :6063 MPD :6600 │ 67 + ├──────────────────────────────────────────────────────────────────────┤ 68 + │ Rust services playback · library · settings · search · playlists │ 69 + │ airplay · slim · chromecast · upnp · netstream │ 70 + ├──────────────────────────────────────────────────────────────────────┤ 71 + │ Rockbox C audio engine · DSP · codecs · tag database │ 72 + ├──────────────────────────────────────────────────────────────────────┤ 73 + │ PCM sinks builtin · fifo · airplay · squeezelite · chromecast │ 74 + │ snapcast_tcp · upnp │ 75 + └──────────────────────────────────────────────────────────────────────┘ 76 + ``` 77 + 78 + ## Status 79 + 80 + Rockbox Zig is actively developed. The audio engine, library, search, MPD 81 + server and the AirPlay / Snapcast / Squeezelite / Chromecast sinks are all 82 + production-ready. Mobile clients and Wasm extensions are on the roadmap. 83 + 84 + <Note> 85 + Looking for the source? It lives at 86 + [github.com/tsirysndr/rockbox-zig](https://github.com/tsirysndr/rockbox-zig). 87 + Issues and PRs welcome. 88 + </Note>
+151
mintlify/installation.mdx
··· 1 + --- 2 + title: "Installation" 3 + description: "Install pre-built binaries or build Rockbox Zig from source." 4 + icon: 'download' 5 + --- 6 + 7 + The recommended path is to install a pre-built binary for your platform. 8 + Building from source is documented in [Architecture › Build](/architecture/build). 9 + 10 + ## Pre-built binaries 11 + 12 + Pre-built packages for the latest release are on the 13 + [Releases page](https://github.com/tsirysndr/rockbox-zig/releases/latest). 14 + 15 + | Platform | Architecture | Package | 16 + |----------|-------------------------|-----------| 17 + | Linux | x86_64 | `.tar.gz` | 18 + | Linux | aarch64 | `.tar.gz` | 19 + | macOS | x86_64 | `.pkg` | 20 + | macOS | aarch64 (Apple Silicon) | `.pkg` | 21 + 22 + ## Package managers 23 + 24 + ### Ubuntu / Debian 25 + 26 + ```sh 27 + echo "deb [trusted=yes] https://apt.fury.io/tsiry/ /" | sudo tee /etc/apt/sources.list.d/fury.list 28 + sudo apt-get update 29 + sudo apt-get install rockbox 30 + ``` 31 + 32 + ### Fedora 33 + 34 + Add the following to `/etc/yum.repos.d/fury.repo`: 35 + 36 + ```ini 37 + [fury] 38 + name=Gemfury Private Repo 39 + baseurl=https://yum.fury.io/tsiry/ 40 + enabled=1 41 + gpgcheck=0 42 + ``` 43 + 44 + Then: 45 + 46 + ```sh 47 + sudo dnf install rockbox 48 + ``` 49 + 50 + ### Arch Linux 51 + 52 + ```sh 53 + paru -S rockbox-zig-bin 54 + ``` 55 + 56 + ### Universal installer 57 + 58 + The installer detects your OS and architecture and drops the right binary 59 + under `/usr/local/bin`: 60 + 61 + ```sh 62 + curl -fsSL https://raw.githubusercontent.com/tsirysndr/rockbox-zig/HEAD/install.sh | bash 63 + ``` 64 + 65 + ### Docker 66 + 67 + ```sh 68 + docker pull tsiry/rockbox 69 + ``` 70 + 71 + ```sh 72 + docker run --rm -it \ 73 + -p 6060-6063:6060-6063 \ 74 + -p 6600:6600 \ 75 + -p 7878-7881:7878-7881 \ 76 + -p 3483:3483 \ 77 + -p 9999:9999 \ 78 + -v $HOME/Music:/music \ 79 + -v $HOME/.config/rockbox.org:/root/.config/rockbox.org \ 80 + tsiry/rockbox 81 + ``` 82 + 83 + <Note> 84 + For AirPlay, Squeezelite and UPnP discovery (mDNS / SSDP) you'll usually want 85 + `--network host` instead of port mapping — multicast does not cross Docker's 86 + default bridge network. 87 + </Note> 88 + 89 + ## Run as a systemd service 90 + 91 + Once `rockbox` is on your `PATH`: 92 + 93 + ```sh 94 + rockbox service install # enable and start 95 + rockbox service status # check status 96 + rockbox service uninstall # stop and disable 97 + ``` 98 + 99 + This installs a user-level systemd unit at `~/.config/systemd/user/rockboxd.service`. 100 + 101 + ## Verifying the install 102 + 103 + ```sh 104 + rockbox --version 105 + rockbox start 106 + ``` 107 + 108 + The daemon binds these ports by default: 109 + 110 + | Service | Port | Protocol | 111 + |------------------------------|------|-----------------| 112 + | gRPC | 6061 | gRPC / gRPC-Web | 113 + | GraphQL + Web UI | 6062 | HTTP / WS | 114 + | HTTP REST | 6063 | HTTP | 115 + | MPD | 6600 | MPD | 116 + 117 + See the full [port reference](/reference/ports). 118 + 119 + ## Building from source 120 + 121 + If you want to hack on Rockbox Zig itself, see 122 + [Architecture › Build](/architecture/build) for the Make + Cargo + Zig 123 + toolchain. 124 + 125 + ## Uninstall 126 + 127 + <AccordionGroup> 128 + <Accordion title="Debian / Ubuntu"> 129 + ```sh 130 + sudo apt-get remove rockbox 131 + rockbox service uninstall # if you installed the systemd unit 132 + ``` 133 + </Accordion> 134 + <Accordion title="Fedora"> 135 + ```sh 136 + sudo dnf remove rockbox 137 + rockbox service uninstall 138 + ``` 139 + </Accordion> 140 + <Accordion title="Arch"> 141 + ```sh 142 + sudo pacman -Rns rockbox-zig-bin 143 + ``` 144 + </Accordion> 145 + <Accordion title="macOS .pkg"> 146 + ```sh 147 + sudo rm /usr/local/bin/rockbox /usr/local/bin/rockboxd 148 + sudo pkgutil --forget io.github.tsirysndr.rockbox 149 + ``` 150 + </Accordion> 151 + </AccordionGroup>
+115
mintlify/logo/dark.svg
··· 1 + <?xml version="1.0" encoding="UTF-8" standalone="no"?> 2 + <!-- Created with Inkscape (http://www.inkscape.org/) --> 3 + 4 + <svg 5 + xmlns:dc="http://purl.org/dc/elements/1.1/" 6 + xmlns:cc="http://creativecommons.org/ns#" 7 + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" 8 + xmlns:svg="http://www.w3.org/2000/svg" 9 + xmlns="http://www.w3.org/2000/svg" 10 + xmlns:xlink="http://www.w3.org/1999/xlink" 11 + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" 12 + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" 13 + version="1.1" 14 + width="120" 15 + height="120" 16 + viewBox="0 0 3386.6665 3386.6666" 17 + id="svg2" 18 + xml:space="preserve" 19 + style="fill-rule:evenodd" 20 + inkscape:version="0.48.1 r9760" 21 + sodipodi:docname="rockbox-clef.svg"><metadata 22 + id="metadata18"><rdf:RDF><cc:Work 23 + rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type 24 + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><sodipodi:namedview 25 + pagecolor="#ffffff" 26 + bordercolor="#666666" 27 + borderopacity="1" 28 + objecttolerance="10" 29 + gridtolerance="10" 30 + guidetolerance="10" 31 + inkscape:pageopacity="0" 32 + inkscape:pageshadow="2" 33 + inkscape:window-width="864" 34 + inkscape:window-height="720" 35 + id="namedview16" 36 + showgrid="true" 37 + inkscape:zoom="4" 38 + inkscape:cx="82.5729" 39 + inkscape:cy="69.674808" 40 + inkscape:window-x="0" 41 + inkscape:window-y="24" 42 + inkscape:window-maximized="0" 43 + inkscape:current-layer="Ebene_x0020_1"><inkscape:grid 44 + type="xygrid" 45 + id="grid2993" 46 + empspacing="5" 47 + visible="true" 48 + enabled="true" 49 + snapvisiblegridlinesonly="true" /></sodipodi:namedview><defs 50 + id="defs38"><linearGradient 51 + id="linearGradient3657"><stop 52 + id="stop3659" 53 + style="stop-color:#aa8800;stop-opacity:1" 54 + offset="0" /><stop 55 + id="stop3661" 56 + style="stop-color:#aa8800;stop-opacity:0" 57 + offset="1" /></linearGradient><linearGradient 58 + x1="-89.260162" 59 + y1="-2.1270833" 60 + x2="-14.333748" 61 + y2="85.830009" 62 + id="linearGradient3663" 63 + xlink:href="#linearGradient3657" 64 + gradientUnits="userSpaceOnUse" /></defs> 65 + <g 66 + transform="matrix(0.90063697,0,0,0.88724946,748.25202,-1317.6084)" 67 + id="Ebene_x0020_1"> 68 + <defs 69 + id="defs5"> 70 + <linearGradient 71 + x1="17608" 72 + y1="4190.54" 73 + x2="17715.699" 74 + y2="4801.2798" 75 + id="id0" 76 + gradientUnits="userSpaceOnUse"> 77 + <stop 78 + id="stop8" 79 + style="stop-color:#a67d00;stop-opacity:1" 80 + offset="0" /> 81 + 82 + <stop 83 + id="stop12" 84 + style="stop-color:#ffffff;stop-opacity:1" 85 + offset="1" /> 86 + </linearGradient> 87 + </defs> 88 + 89 + 90 + 91 + 92 + 93 + <rect 94 + width="3133.5845" 95 + height="3180.8667" 96 + ry="302.44305" 97 + x="-517.4447" 98 + y="1803.135" 99 + id="rect3694" 100 + style="fill:#ffc001;fill-opacity:1;fill-rule:evenodd;stroke:none" /><path 101 + d="m 1133.6395,3200.4269 c 11.2559,18.2986 22.432,36.6011 33.5473,54.9487 60.3406,-16.5847 123.9814,-25.3702 188.4648,-26.059 126.1999,-1.316 246.2441,48.6846 317.2687,132.1614 64.2026,75.4539 101.1079,162.6951 106.6733,252.1193 2.3212,37.3762 -5.6502,74.466 -23.3869,108.7376 -43.5526,84.192 -117.9946,155.791 -214.1337,205.9842 45.7615,89.9291 90.0403,180.2716 132.8495,271.0265 81.8832,173.6387 132.6702,354.6655 150.8198,537.8489 4.676,47.2371 -2.5858,94.4734 -21.3927,138.9539 -19.2847,45.5742 -58.1673,84.1121 -110.1785,109.1541 -37.7378,18.1621 -83.751,23.2435 -127.4177,14.0995 -43.6802,-9.1436 -81.2498,-31.7365 -104.0476,-62.5664 -26.5203,-35.904 -42.2855,-75.8627 -46.0345,-116.7994 -2.1173,-23.0223 9.3653,-45.2202 31.1441,-60.2062 27.4567,-18.8993 62.7411,-29.5144 99.7917,-30.07 24.3266,-0.3368 47.3936,9.6188 60.3568,26.0881 12.9472,16.4494 13.7396,36.8489 2.1096,53.3337 -14.3314,20.3045 -37.0946,36.0653 -64.6429,44.7438 l 11.8035,16.1533 c 5.7557,9.2105 16.8132,15.7847 29.6363,17.5515 12.8112,1.7777 25.7362,-1.4481 34.5823,-8.6399 43.6788,-34.3792 67.7315,-81.3042 66.8875,-130.5556 -3.562,-84.7728 -16.3171,-169.3555 -38.1543,-252.8527 -55.7305,-177.8644 -127.5412,-352.777 -214.8999,-523.3512 -16.3737,5.1045 -33.0607,9.6178 -49.9908,13.5678 -49.5652,11.5132 -99.9157,21.0976 -150.8427,28.7217 l -100.813,-814.0936 z m 248.7771,672.5156 c -50.5142,19.7875 -104.412,34.1161 -160.2241,42.5737 l -50.7294,-409.6546 c 72.2514,121.1726 142.3288,243.0983 210.2123,365.7262 0.2567,0.458 0.4978,0.8959 0.7412,1.3547 0,0 0,0 0,0 0,0 0,0 0,0 0,0 0,0 0,0 m -150.076,-508.6879 c 17.5147,-4.8294 35.3415,-9.0782 53.3625,-12.729 56.5362,-11.4397 117.3362,-3.637 167.586,21.5055 45.7565,22.8959 80.8463,56.9138 99.7551,96.6234 42.1044,88.5079 47.194,184.4321 14.5179,273.6257 -6.8456,18.7164 -19.8851,35.5583 -37.8052,48.8537 -13.7547,10.2054 -28.1066,19.8974 -43.0038,29.0629 -80.5314,-153.6773 -165.3681,-306.0385 -254.4125,-456.9422 m -10.1481,551.2616 c -14.298,2.1559 -28.7086,3.9408 -43.196,5.3209 -157.9011,15.1267 -314.30239,-47.8917 -393.33817,-158.4708 -140.27886,-196.2782 -185.31029,-423.8857 -126.59762,-639.7364 20.62766,-75.767 42.25356,-151.3353 64.91887,-226.6963 6.26181,-20.8439 12.63094,-41.6829 19.09409,-62.5163 104.89649,154.512 206.20813,310.449 303.84503,467.7319 -104.88444,52.3821 -185.1784,129.7162 -229.58648,221.109 -7.33244,15.1065 -1.5786,32.4892 14.38626,43.3632 15.95163,10.8746 38.6339,12.8768 56.5858,5.0072 15.19318,-10.1136 29.0455,-21.3616 41.40015,-33.6098 49.89688,-51.7003 111.96607,-95.3657 183.00107,-128.7738 19.7486,32.4884 39.3155,65.0179 58.7576,97.6166 l -37.8236,-305.4347 c -92.6263,-150.6417 -189.46344,-299.7421 -290.47048,-447.1875 -16.73566,-24.4224 -33.73799,-48.7363 -51.00315,-72.9107 12.53936,-37.7911 25.36472,-75.5343 38.46801,-113.1874 18.72476,-53.759 31.27196,-108.8276 37.52831,-164.6129 7.04724,-63.0773 -7.52517,-127.0079 -42.2481,-185.2058 -79.1109,-132.6193 -213.39469,-241.1725 -380.18823,-307.3322 -73.37553,-29.1121 -155.31083,-41.0734 -235.85269,-34.4583 -67.48248,5.551 -116.971987,50.5243 -114.340324,103.8724 2.381016,48.4159 22.574124,95.696 58.086904,136.1456 32.88527,37.4341 67.82132,73.7652 104.71321,108.8721 113.82257,108.3303 214.95374,224.3585 302.12809,346.5898 17.13041,24.0557 34.19842,48.1462 51.17499,72.252 -15.99656,48.3246 -31.52841,96.7397 -46.59165,145.277 -69.85813,225.1541 -104.21032,456.9898 -102.24126,690.3515 0.92822,110.1406 26.98496,219.8501 76.97168,323.9736 68.55723,142.8242 245.98138,237.2059 438.57406,233.2775 86.18513,-1.7571 171.82603,-8.9862 256.10363,-21.6231 l -12.26,-99.0043" 102 + id="path24" 103 + style="fill:#000000;fill-opacity:1;fill-rule:evenodd" 104 + inkscape:connector-curvature="0" /> 105 + 106 + 107 + <path 108 + d="M 659.33533,2503.3767 C 573.5524,2394.5355 482.17708,2288.3563 385.43749,2185.1728 c -30.76545,-32.8099 -59.12968,-66.9092 -84.95666,-102.1687 -16.86386,-22.98 -28.28033,-48.07 -33.73308,-74.0078 -1.1599,-5.5966 0.71447,-11.2481 5.21013,-15.5497 4.45467,-4.3102 11.12647,-6.8716 18.3068,-7.0503 34.58075,-0.8971 69.10856,5.1026 100.42147,17.453 134.57561,53.071 237.34107,146.2148 284.58433,257.9109 13.77151,32.583 18.33147,66.9284 13.33714,100.5928 -7.04096,47.4029 -16.80734,94.4462 -29.27229,141.0237 l 0,0" 109 + id="path30" 110 + style="fill:#ffc000" 111 + inkscape:connector-curvature="0" /> 112 + 113 + 114 + </g> 115 + </svg>
+115
mintlify/logo/light.svg
··· 1 + <?xml version="1.0" encoding="UTF-8" standalone="no"?> 2 + <!-- Created with Inkscape (http://www.inkscape.org/) --> 3 + 4 + <svg 5 + xmlns:dc="http://purl.org/dc/elements/1.1/" 6 + xmlns:cc="http://creativecommons.org/ns#" 7 + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" 8 + xmlns:svg="http://www.w3.org/2000/svg" 9 + xmlns="http://www.w3.org/2000/svg" 10 + xmlns:xlink="http://www.w3.org/1999/xlink" 11 + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" 12 + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" 13 + version="1.1" 14 + width="120" 15 + height="120" 16 + viewBox="0 0 3386.6665 3386.6666" 17 + id="svg2" 18 + xml:space="preserve" 19 + style="fill-rule:evenodd" 20 + inkscape:version="0.48.1 r9760" 21 + sodipodi:docname="rockbox-clef.svg"><metadata 22 + id="metadata18"><rdf:RDF><cc:Work 23 + rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type 24 + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><sodipodi:namedview 25 + pagecolor="#ffffff" 26 + bordercolor="#666666" 27 + borderopacity="1" 28 + objecttolerance="10" 29 + gridtolerance="10" 30 + guidetolerance="10" 31 + inkscape:pageopacity="0" 32 + inkscape:pageshadow="2" 33 + inkscape:window-width="864" 34 + inkscape:window-height="720" 35 + id="namedview16" 36 + showgrid="true" 37 + inkscape:zoom="4" 38 + inkscape:cx="82.5729" 39 + inkscape:cy="69.674808" 40 + inkscape:window-x="0" 41 + inkscape:window-y="24" 42 + inkscape:window-maximized="0" 43 + inkscape:current-layer="Ebene_x0020_1"><inkscape:grid 44 + type="xygrid" 45 + id="grid2993" 46 + empspacing="5" 47 + visible="true" 48 + enabled="true" 49 + snapvisiblegridlinesonly="true" /></sodipodi:namedview><defs 50 + id="defs38"><linearGradient 51 + id="linearGradient3657"><stop 52 + id="stop3659" 53 + style="stop-color:#aa8800;stop-opacity:1" 54 + offset="0" /><stop 55 + id="stop3661" 56 + style="stop-color:#aa8800;stop-opacity:0" 57 + offset="1" /></linearGradient><linearGradient 58 + x1="-89.260162" 59 + y1="-2.1270833" 60 + x2="-14.333748" 61 + y2="85.830009" 62 + id="linearGradient3663" 63 + xlink:href="#linearGradient3657" 64 + gradientUnits="userSpaceOnUse" /></defs> 65 + <g 66 + transform="matrix(0.90063697,0,0,0.88724946,748.25202,-1317.6084)" 67 + id="Ebene_x0020_1"> 68 + <defs 69 + id="defs5"> 70 + <linearGradient 71 + x1="17608" 72 + y1="4190.54" 73 + x2="17715.699" 74 + y2="4801.2798" 75 + id="id0" 76 + gradientUnits="userSpaceOnUse"> 77 + <stop 78 + id="stop8" 79 + style="stop-color:#a67d00;stop-opacity:1" 80 + offset="0" /> 81 + 82 + <stop 83 + id="stop12" 84 + style="stop-color:#ffffff;stop-opacity:1" 85 + offset="1" /> 86 + </linearGradient> 87 + </defs> 88 + 89 + 90 + 91 + 92 + 93 + <rect 94 + width="3133.5845" 95 + height="3180.8667" 96 + ry="302.44305" 97 + x="-517.4447" 98 + y="1803.135" 99 + id="rect3694" 100 + style="fill:#ffc001;fill-opacity:1;fill-rule:evenodd;stroke:none" /><path 101 + d="m 1133.6395,3200.4269 c 11.2559,18.2986 22.432,36.6011 33.5473,54.9487 60.3406,-16.5847 123.9814,-25.3702 188.4648,-26.059 126.1999,-1.316 246.2441,48.6846 317.2687,132.1614 64.2026,75.4539 101.1079,162.6951 106.6733,252.1193 2.3212,37.3762 -5.6502,74.466 -23.3869,108.7376 -43.5526,84.192 -117.9946,155.791 -214.1337,205.9842 45.7615,89.9291 90.0403,180.2716 132.8495,271.0265 81.8832,173.6387 132.6702,354.6655 150.8198,537.8489 4.676,47.2371 -2.5858,94.4734 -21.3927,138.9539 -19.2847,45.5742 -58.1673,84.1121 -110.1785,109.1541 -37.7378,18.1621 -83.751,23.2435 -127.4177,14.0995 -43.6802,-9.1436 -81.2498,-31.7365 -104.0476,-62.5664 -26.5203,-35.904 -42.2855,-75.8627 -46.0345,-116.7994 -2.1173,-23.0223 9.3653,-45.2202 31.1441,-60.2062 27.4567,-18.8993 62.7411,-29.5144 99.7917,-30.07 24.3266,-0.3368 47.3936,9.6188 60.3568,26.0881 12.9472,16.4494 13.7396,36.8489 2.1096,53.3337 -14.3314,20.3045 -37.0946,36.0653 -64.6429,44.7438 l 11.8035,16.1533 c 5.7557,9.2105 16.8132,15.7847 29.6363,17.5515 12.8112,1.7777 25.7362,-1.4481 34.5823,-8.6399 43.6788,-34.3792 67.7315,-81.3042 66.8875,-130.5556 -3.562,-84.7728 -16.3171,-169.3555 -38.1543,-252.8527 -55.7305,-177.8644 -127.5412,-352.777 -214.8999,-523.3512 -16.3737,5.1045 -33.0607,9.6178 -49.9908,13.5678 -49.5652,11.5132 -99.9157,21.0976 -150.8427,28.7217 l -100.813,-814.0936 z m 248.7771,672.5156 c -50.5142,19.7875 -104.412,34.1161 -160.2241,42.5737 l -50.7294,-409.6546 c 72.2514,121.1726 142.3288,243.0983 210.2123,365.7262 0.2567,0.458 0.4978,0.8959 0.7412,1.3547 0,0 0,0 0,0 0,0 0,0 0,0 0,0 0,0 0,0 m -150.076,-508.6879 c 17.5147,-4.8294 35.3415,-9.0782 53.3625,-12.729 56.5362,-11.4397 117.3362,-3.637 167.586,21.5055 45.7565,22.8959 80.8463,56.9138 99.7551,96.6234 42.1044,88.5079 47.194,184.4321 14.5179,273.6257 -6.8456,18.7164 -19.8851,35.5583 -37.8052,48.8537 -13.7547,10.2054 -28.1066,19.8974 -43.0038,29.0629 -80.5314,-153.6773 -165.3681,-306.0385 -254.4125,-456.9422 m -10.1481,551.2616 c -14.298,2.1559 -28.7086,3.9408 -43.196,5.3209 -157.9011,15.1267 -314.30239,-47.8917 -393.33817,-158.4708 -140.27886,-196.2782 -185.31029,-423.8857 -126.59762,-639.7364 20.62766,-75.767 42.25356,-151.3353 64.91887,-226.6963 6.26181,-20.8439 12.63094,-41.6829 19.09409,-62.5163 104.89649,154.512 206.20813,310.449 303.84503,467.7319 -104.88444,52.3821 -185.1784,129.7162 -229.58648,221.109 -7.33244,15.1065 -1.5786,32.4892 14.38626,43.3632 15.95163,10.8746 38.6339,12.8768 56.5858,5.0072 15.19318,-10.1136 29.0455,-21.3616 41.40015,-33.6098 49.89688,-51.7003 111.96607,-95.3657 183.00107,-128.7738 19.7486,32.4884 39.3155,65.0179 58.7576,97.6166 l -37.8236,-305.4347 c -92.6263,-150.6417 -189.46344,-299.7421 -290.47048,-447.1875 -16.73566,-24.4224 -33.73799,-48.7363 -51.00315,-72.9107 12.53936,-37.7911 25.36472,-75.5343 38.46801,-113.1874 18.72476,-53.759 31.27196,-108.8276 37.52831,-164.6129 7.04724,-63.0773 -7.52517,-127.0079 -42.2481,-185.2058 -79.1109,-132.6193 -213.39469,-241.1725 -380.18823,-307.3322 -73.37553,-29.1121 -155.31083,-41.0734 -235.85269,-34.4583 -67.48248,5.551 -116.971987,50.5243 -114.340324,103.8724 2.381016,48.4159 22.574124,95.696 58.086904,136.1456 32.88527,37.4341 67.82132,73.7652 104.71321,108.8721 113.82257,108.3303 214.95374,224.3585 302.12809,346.5898 17.13041,24.0557 34.19842,48.1462 51.17499,72.252 -15.99656,48.3246 -31.52841,96.7397 -46.59165,145.277 -69.85813,225.1541 -104.21032,456.9898 -102.24126,690.3515 0.92822,110.1406 26.98496,219.8501 76.97168,323.9736 68.55723,142.8242 245.98138,237.2059 438.57406,233.2775 86.18513,-1.7571 171.82603,-8.9862 256.10363,-21.6231 l -12.26,-99.0043" 102 + id="path24" 103 + style="fill:#000000;fill-opacity:1;fill-rule:evenodd" 104 + inkscape:connector-curvature="0" /> 105 + 106 + 107 + <path 108 + d="M 659.33533,2503.3767 C 573.5524,2394.5355 482.17708,2288.3563 385.43749,2185.1728 c -30.76545,-32.8099 -59.12968,-66.9092 -84.95666,-102.1687 -16.86386,-22.98 -28.28033,-48.07 -33.73308,-74.0078 -1.1599,-5.5966 0.71447,-11.2481 5.21013,-15.5497 4.45467,-4.3102 11.12647,-6.8716 18.3068,-7.0503 34.58075,-0.8971 69.10856,5.1026 100.42147,17.453 134.57561,53.071 237.34107,146.2148 284.58433,257.9109 13.77151,32.583 18.33147,66.9284 13.33714,100.5928 -7.04096,47.4029 -16.80734,94.4462 -29.27229,141.0237 l 0,0" 109 + id="path30" 110 + style="fill:#ffc000" 111 + inkscape:connector-curvature="0" /> 112 + 113 + 114 + </g> 115 + </svg>
+150
mintlify/quickstart.mdx
··· 1 + --- 2 + title: "Quickstart" 3 + description: "Install Rockbox, point it at your music, and play your first track." 4 + icon: 'rocket' 5 + --- 6 + 7 + This guide gets you from zero to a running `rockboxd` with a usable web UI in 8 + about five minutes. 9 + 10 + ## 1. Install 11 + 12 + <Tabs> 13 + <Tab title="macOS"> 14 + Download the latest `.pkg` for your architecture from the 15 + [Releases page](https://github.com/tsirysndr/rockbox-zig/releases/latest) 16 + and double-click to install. 17 + 18 + Or via the universal installer: 19 + 20 + ```sh 21 + curl -fsSL https://raw.githubusercontent.com/tsirysndr/rockbox-zig/HEAD/install.sh | bash 22 + ``` 23 + </Tab> 24 + <Tab title="Ubuntu / Debian"> 25 + ```sh 26 + echo "deb [trusted=yes] https://apt.fury.io/tsiry/ /" | sudo tee /etc/apt/sources.list.d/fury.list 27 + sudo apt-get update 28 + sudo apt-get install rockbox 29 + ``` 30 + </Tab> 31 + <Tab title="Fedora"> 32 + Add `/etc/yum.repos.d/fury.repo`: 33 + 34 + ```ini 35 + [fury] 36 + name=Gemfury Private Repo 37 + baseurl=https://yum.fury.io/tsiry/ 38 + enabled=1 39 + gpgcheck=0 40 + ``` 41 + 42 + Then: 43 + 44 + ```sh 45 + sudo dnf install rockbox 46 + ``` 47 + </Tab> 48 + <Tab title="Arch Linux"> 49 + ```sh 50 + paru -S rockbox-zig-bin 51 + ``` 52 + </Tab> 53 + <Tab title="Docker"> 54 + ```sh 55 + docker run --rm -it \ 56 + -p 6060-6063:6060-6063 \ 57 + -p 6600:6600 \ 58 + -v $HOME/Music:/music \ 59 + -v $HOME/.config/rockbox.org:/root/.config/rockbox.org \ 60 + tsiry/rockbox 61 + ``` 62 + </Tab> 63 + </Tabs> 64 + 65 + For full distribution coverage, build instructions and Docker images, see 66 + [Installation](/installation). 67 + 68 + ## 2. Point it at your music 69 + 70 + Create `~/.config/rockbox.org/settings.toml`: 71 + 72 + ```toml 73 + music_dir = "/path/to/your/Music" 74 + audio_output = "builtin" 75 + ``` 76 + 77 + That's the minimum. `music_dir` is the only required field; everything else 78 + has sensible defaults. See [Configuration](/configuration) for the full list. 79 + 80 + ## 3. Start `rockboxd` 81 + 82 + ```sh 83 + rockbox start 84 + ``` 85 + 86 + You should see logs like: 87 + 88 + ``` 89 + INFO rockbox-cli rockboxd starting... 90 + INFO rockbox-cli graphql server listening on :6062 91 + INFO rockbox-cli http server listening on :6063 92 + INFO rockbox-cli mpd server listening on :6600 93 + INFO rockbox-cli grpc server listening on :6061 94 + ``` 95 + 96 + ## 4. Open a client 97 + 98 + <CardGroup cols={2}> 99 + <Card title="Web UI" icon="globe" href="http://localhost:6062"> 100 + Browse your library and control playback at 101 + [http://localhost:6062](http://localhost:6062). 102 + </Card> 103 + <Card title="GraphiQL" icon="code" href="http://localhost:6062/graphiql"> 104 + Explore the API live at 105 + [http://localhost:6062/graphiql](http://localhost:6062/graphiql). 106 + </Card> 107 + <Card title="MPD client" icon="terminal"> 108 + Point any MPD client (`ncmpcpp`, `mpc`, MALP, etc.) at `localhost:6600`. 109 + </Card> 110 + <Card title="HTTP REST" icon="bolt" href="http://localhost:6063"> 111 + Quick `curl` testing — see the REST overview. 112 + </Card> 113 + </CardGroup> 114 + 115 + ## 5. Play something 116 + 117 + From the web UI, search for a track and hit play. From the terminal: 118 + 119 + ```sh 120 + mpc -h localhost -p 6600 update 121 + mpc -h localhost -p 6600 search title "money" 122 + mpc -h localhost -p 6600 play 123 + ``` 124 + 125 + Or with the [TypeScript SDK](/sdks/typescript): 126 + 127 + ```ts 128 + import { RockboxClient } from '@rockbox-zig/sdk'; 129 + 130 + const client = new RockboxClient(); 131 + const { albums } = await client.library.search('dark side'); 132 + await client.playback.playAlbum(albums[0].id, { shuffle: true }); 133 + ``` 134 + 135 + ## Next steps 136 + 137 + <CardGroup cols={2}> 138 + <Card title="Configure" icon="sliders" href="/configuration"> 139 + `settings.toml` reference — every key, every default. 140 + </Card> 141 + <Card title="Audio output" icon="speaker" href="/audio-output/overview"> 142 + Send audio to AirPlay, Snapcast, Squeezelite, Chromecast or UPnP. 143 + </Card> 144 + <Card title="Pick an SDK" icon="code" href="/sdks/overview"> 145 + Build clients in TypeScript, Python, Ruby, Elixir, Clojure or Gleam. 146 + </Card> 147 + <Card title="FAQ" icon="circle-question" href="/reference/faq"> 148 + Common gotchas and how to fix them. 149 + </Card> 150 + </CardGroup>
+141
mintlify/reference/cli.mdx
··· 1 + --- 2 + title: "CLI reference" 3 + description: "rockbox and rockboxd — the two binaries you'll interact with." 4 + icon: 'terminal' 5 + --- 6 + 7 + There are two binaries: 8 + 9 + - **`rockbox`** — user-facing wrapper. Starts the server, scans the 10 + library, opens the web UI, manages the systemd service, runs JS/TS 11 + scripts, and acts as a Bluetooth client on Linux. 12 + - **`rockboxd`** — the daemon itself. Linked by Zig from the C firmware 13 + + Rust crates + SDL2. Lives at `zig/zig-out/bin/rockboxd` after a build, 14 + or `/usr/local/bin/rockboxd` after install. 15 + 16 + Most users only ever invoke `rockbox`; `rockboxd` is the underlying 17 + process it spawns. 18 + 19 + ## `rockbox` 20 + 21 + ```text 22 + rockbox [--rebuild] [SUBCOMMAND] 23 + ``` 24 + 25 + Running `rockbox` with no subcommand starts the server. The subcommands 26 + below are dispatched in `cli/src/main.rs`. 27 + 28 + ### Global flags 29 + 30 + | Flag | Description | 31 + |-----------------------|-------------------------------------------------------| 32 + | `--rebuild`, `-r` | Rebuild the Typesense search index after scan | 33 + | `-h`, `--help` | Print help | 34 + | `-V`, `--version` | Print the version | 35 + 36 + ### Subcommands 37 + 38 + | Subcommand | Aliases | Description | 39 + |-----------------------------------|----------|---------------------------------------------------------| 40 + | _(none)_ | | Start the server | 41 + | `start [-r]` | | Start the server | 42 + | `scan [-d PATH] [-r]` | | Scan a library directory; `-r` rebuilds the search index | 43 + | `webui` | `web` | Open the web UI in your browser | 44 + | `tui` | | Start the terminal UI | 45 + | `repl` | `shell` | Start the Rockbox REPL | 46 + | `run <FILE>` | `x` | Run a JS/TS script via Deno against the local rockboxd | 47 + | `open <PATH_OR_URL>` | | Play a local file or HTTP URL directly | 48 + | `clear` | | Clear the current playlist | 49 + | `service install` | | Install + enable the systemd unit | 50 + | `service uninstall` | | Disable + remove the systemd unit | 51 + | `service status` | | Show the unit status | 52 + | `login <handle>` | `auth` | Log in to Rocksky (BlueSky handle) | 53 + | `whoami` | `me` | Show the currently logged-in user | 54 + | `community` | | Open the Discord invite | 55 + | `setup` | | Install host dependencies (SDL2, etc.) | 56 + | `bluetooth scan [--timeout S]` | | Linux only — scan for Bluetooth devices | 57 + | `bluetooth devices` | | Linux only — list known devices | 58 + | `bluetooth connect <ADDR>` | | Linux only — connect to a device | 59 + | `bluetooth disconnect <ADDR>` | | Linux only — disconnect a device | 60 + 61 + ### Examples 62 + 63 + ```sh 64 + # Start the daemon 65 + rockbox start 66 + 67 + # Scan the default music_dir, rebuild the search index 68 + rockbox scan -r 69 + 70 + # Scan a specific path 71 + rockbox scan -d "/Volumes/Music/Recently Added" 72 + 73 + # Play a URL 74 + rockbox open "https://stream.example.com/jazz.mp3" 75 + 76 + # Install as a systemd user service 77 + rockbox service install 78 + rockbox service status 79 + 80 + # Run a small script against a local rockboxd 81 + rockbox run scripts/scrobble.ts 82 + 83 + # Bluetooth (Linux) 84 + rockbox bluetooth scan --timeout 15 85 + rockbox bluetooth connect AA:BB:CC:DD:EE:FF 86 + ``` 87 + 88 + ## `rockboxd` 89 + 90 + The daemon. Usually you don't run it directly — `rockbox start` or the 91 + systemd unit handles it. When you do invoke it manually, it takes no 92 + subcommands; behaviour is driven by environment variables and 93 + `~/.config/rockbox.org/settings.toml`. 94 + 95 + ```sh 96 + rockboxd 97 + ``` 98 + 99 + ### Environment variables 100 + 101 + | Variable | Default | Description | 102 + |---------------------------|--------------------------------------|-----------------------------------------| 103 + | `RUST_LOG` | `info` | Tracing filter (per-crate supported) | 104 + | `ROCKBOX_TCP_PORT` | `6063` | HTTP REST bind port | 105 + | `ROCKBOX_GRAPHQL_PORT` | `6062` | GraphQL bind port | 106 + | `ROCKBOX_RPC_PORT` | `6061` | gRPC bind port | 107 + | `ROCKBOX_MPD_PORT` | `6600` | MPD bind port | 108 + | `ROCKBOX_LIBRARY` | `$HOME/Music` | Default music library path | 109 + | `ROCKBOX_ADDR` | (auto-detected LAN IP) | Address advertised to external players | 110 + | `ROCKBOX_UPDATE_LIBRARY` | unset | When `1`, rebuild Typesense on startup | 111 + | `HOME` | (system) | Used to derive config and library paths | 112 + 113 + ### Stdout / stderr 114 + 115 + - **Stderr** — `tracing` log output. Always safe to redirect or filter. 116 + - **Stdout** — normally empty, **except** when `audio_output = "fifo"` 117 + with `fifo_path = "-"`, in which case stdout is raw S16LE 44.1 kHz PCM. 118 + 119 + ```sh 120 + rockboxd | ffplay -f s16le -ar 44100 -ac 2 - 121 + ``` 122 + 123 + ### Logging 124 + 125 + ```sh 126 + RUST_LOG=debug rockboxd 127 + RUST_LOG=rockbox_airplay=debug,rockbox_slim=debug,info rockboxd 128 + ``` 129 + 130 + Never use `eprintln!` / `println!` from inside the codebase — they 131 + bypass the structured filter and pollute stdout (which breaks FIFO 132 + mode). Use `tracing::{debug,info,warn,error}!` in Rust code. 133 + 134 + ### Files 135 + 136 + | Path | Purpose | 137 + |---------------------------------------------------|--------------------------------------| 138 + | `~/.config/rockbox.org/settings.toml` | Persistent configuration | 139 + | `~/.config/rockbox.org/library.db` | SQLite library + listening stats | 140 + | `~/.config/rockbox.org/playlists/` | Saved playlists | 141 + | `~/.config/systemd/user/rockboxd.service` | systemd unit (after `service install`) |
+113
mintlify/reference/faq.mdx
··· 1 + --- 2 + title: "FAQ" 3 + description: "Common questions about Rockbox Zig." 4 + icon: 'circle-question' 5 + --- 6 + 7 + <AccordionGroup> 8 + 9 + <Accordion title="What's the difference between Rockbox and Rockbox Zig?"> 10 + [Rockbox](https://www.rockbox.org) is firmware for portable audio 11 + players. Rockbox Zig wraps that same C audio engine in Rust and Zig 12 + services, exposing it on a desktop/server as a single `rockboxd` binary 13 + with gRPC, GraphQL, HTTP and MPD APIs and multi-room output sinks. 14 + The DSP, codecs and tag database come straight from upstream Rockbox. 15 + </Accordion> 16 + 17 + <Accordion title="Does it really run on a Raspberry Pi?"> 18 + Yes. Linux ARM64 builds are on the 19 + [Releases page](https://github.com/tsirysndr/rockbox-zig/releases). It 20 + runs comfortably on a Pi 4; on a Pi 3 expect Typesense indexing to be 21 + slower on first scan but playback is fine. 22 + </Accordion> 23 + 24 + <Accordion title="Which audio formats are supported?"> 25 + MP3, OGG Vorbis, FLAC, WAV, AAC, ALAC, Opus, Musepack, WMA, APE, 26 + Wavpack, Speex, AIFF, AC3, SID and several more — 20+ codecs total. The 27 + codec list comes from upstream Rockbox; see 28 + [`AUDIO_EXTENSIONS`](https://github.com/tsirysndr/rockbox-zig/blob/master/crates/server/src/lib.rs) 29 + for what is auto-scanned into the library. 30 + </Accordion> 31 + 32 + <Accordion title="Can I stream from YouTube / Spotify / Tidal?"> 33 + Not yet. Generic HTTP(S) stream URLs work — you can queue them and 34 + playback works through the netstream layer in `crates/netstream/`. Rich 35 + provider integrations (YouTube, Spotify, Tidal) are on the roadmap. 36 + </Accordion> 37 + 38 + <Accordion title="Why not just use mpd / Mopidy / Volumio / Snapcast directly?"> 39 + You can — they're great projects. Rockbox Zig differs in that the 40 + audio engine, DSP, parametric EQ and crossfade are the upstream Rockbox 41 + implementation rather than ALSA's defaults. If you specifically want 42 + Rockbox's sound (dithering, PBE, Haas surround, ReplayGain pipeline, the 43 + EQ presets) on a desktop or server, this is one way to get it. 44 + </Accordion> 45 + 46 + <Accordion title="Multi-room: AirPlay vs Snapcast vs Squeezelite — which one?"> 47 + - **AirPlay** — pick this if you have Apple TVs / HomePods / shairport-sync 48 + receivers. Built-in fan-out, ~8 ms tight sync. 49 + - **Snapcast** — best when you have or are willing to deploy snapserver and 50 + multiple snapclients. Works on every platform, very tight sync. 51 + - **Squeezelite** — pick this if you already run squeezelite or 52 + Logitech-style hardware. One rockboxd serves any number of squeezelite 53 + clients with per-client cursors into a 4 MB shared buffer. 54 + </Accordion> 55 + 56 + <Accordion title="Does it work with my MPD client?"> 57 + Probably. Rockbox runs an MPD-compatible server on port 6600. `mpc`, 58 + `ncmpcpp`, MALP, M.A.L.P. and Cantata are all tested. If your client 59 + breaks on something Rockbox-specific, please open an issue. 60 + </Accordion> 61 + 62 + <Accordion title="Can I run rockboxd headless / as a service?"> 63 + Yes — `rockbox service install` registers a user-level systemd unit. See 64 + [Installation](/installation#run-as-a-systemd-service). 65 + </Accordion> 66 + 67 + <Accordion title="Where are the listening stats stored?"> 68 + SQLite, in `~/.config/rockbox.org/library.db`. The 69 + [smart playlist rules](/sdks/typescript) read from this database. 70 + You can record `played` / `skipped` events manually from any SDK or via 71 + the REST endpoints `POST /track-stats/{id}/played` and 72 + `POST /track-stats/{id}/skipped`. 73 + </Accordion> 74 + 75 + <Accordion title="Why a single binary?"> 76 + Simpler to deploy, simpler to debug, and the firmware/Rust boundary is 77 + already complex enough that adding IPC on top would be a step backward. 78 + The C audio engine and the Rust services share memory through static 79 + libraries linked by Zig — see [Architecture](/architecture/overview). 80 + </Accordion> 81 + 82 + <Accordion title="Does it support Bluetooth?"> 83 + On Linux, yes — pairing/connecting is exposed through the REST and 84 + GraphQL APIs (and SDKs). On macOS and Windows, no first-party 85 + integration; use the OS-level Bluetooth stack and route the built-in 86 + SDL output to the BT device. 87 + </Accordion> 88 + 89 + <Accordion title="Can I write a plugin?"> 90 + Yes. Every SDK ships a Jellyfin-style plugin lifecycle: 91 + 92 + ```ts 93 + const SleepTimer = (minutes: number): RockboxPlugin => ({ 94 + name: 'sleep-timer', 95 + version: '1.0.0', 96 + install({ events, query }) { 97 + setTimeout(() => query('mutation { hardStop }'), minutes * 60_000); 98 + }, 99 + }); 100 + 101 + await client.use(SleepTimer(30)); 102 + ``` 103 + 104 + Wasm extensions inside `rockboxd` itself are on the roadmap. 105 + </Accordion> 106 + 107 + <Accordion title="How do I contribute?"> 108 + Read the [Contributing guide](https://github.com/tsirysndr/rockbox-zig/blob/master/CONTRIBUTING.md), 109 + hop into [Discord](https://discord.gg/tXPrgcPKSt), and open a PR. Build 110 + from source with the [build instructions](/architecture/build). 111 + </Accordion> 112 + 113 + </AccordionGroup>
+48
mintlify/reference/ports.mdx
··· 1 + --- 2 + title: "Ports" 3 + description: "Every TCP and UDP port rockboxd binds, plus mDNS/SSDP service types." 4 + icon: 'plug' 5 + --- 6 + 7 + | Service | Port | Protocol | Override | 8 + |----------------------------------------|-------|--------------------|-----------------------------| 9 + | gRPC | 6061 | gRPC / gRPC-Web | `ROCKBOX_RPC_PORT` | 10 + | GraphQL + Web UI | 6062 | HTTP / WS | `ROCKBOX_GRAPHQL_PORT` | 11 + | HTTP REST | 6063 | HTTP | `ROCKBOX_TCP_PORT` | 12 + | MPD server | 6600 | MPD protocol | `ROCKBOX_MPD_PORT` | 13 + | Slim Protocol (squeezelite) | 3483 | TCP | `squeezelite_port` | 14 + | HTTP PCM stream (squeezelite) | 9999 | HTTP | `squeezelite_http_port` | 15 + | Chromecast WAV stream | 7881 | HTTP | `chromecast_http_port` | 16 + | UPnP MediaServer (ContentDirectory) | 7878 | HTTP / SSDP | `upnp_server_port` | 17 + | UPnP WAV broadcast (PCM sink) | 7879 | HTTP | `upnp_http_port` | 18 + | UPnP MediaRenderer (AVTransport) | 7880 | HTTP / SSDP | `upnp_renderer_port` | 19 + | Snapcast TCP source (outbound only) | 4953 | TCP (client) | `snapcast_tcp_port` | 20 + 21 + ## mDNS / SSDP service types 22 + 23 + Rockbox both **advertises** and **scans for** the following on the LAN: 24 + 25 + | Service | Service type | Direction | 26 + |-------------------------------|---------------------------------|--------------------| 27 + | Rockbox itself | `_rockbox._tcp.local.` | advertise | 28 + | Chromecast | `_googlecast._tcp.local.` | scan | 29 + | AirPlay (RAOP) | `_raop._tcp.local.` | scan | 30 + | Squeezelite players | `_slim._tcp.local.` | scan | 31 + | Snapcast servers | `_snapcast._tcp.local.` | scan | 32 + | UPnP renderers | `urn:schemas-upnp-org:device:MediaRenderer:1` | SSDP scan | 33 + | UPnP media server (this one) | `urn:schemas-upnp-org:device:MediaServer:1` | SSDP advertise | 34 + 35 + ## Firewall checklist 36 + 37 + If rockboxd is in a VM, container, or behind a firewall, **at minimum** 38 + inbound on these is needed: 39 + 40 + - `6062/tcp` — for the web UI and GraphQL 41 + - `6063/tcp` — for the REST API 42 + - `6600/tcp` — for MPD clients 43 + - `5353/udp` — mDNS (multicast) 44 + - `1900/udp` — SSDP (multicast) 45 + 46 + If you use Chromecast or AirPlay, the receiver must also be able to 47 + **reach back** to rockboxd on `7881/tcp` (Chromecast WAV) or 48 + `7879/tcp` (UPnP WAV).
+158
mintlify/reference/settings-toml.mdx
··· 1 + --- 2 + title: "settings.toml reference" 3 + description: "Every key Rockbox reads from ~/.config/rockbox.org/settings.toml." 4 + icon: 'file-code' 5 + --- 6 + 7 + This is the canonical list of keys recognised by `rockbox_settings::load_settings()`. 8 + Keys not listed here are ignored. 9 + 10 + ## Core 11 + 12 + | Key | Type | Default | Description | 13 + |----------------|---------|-------------|--------------------------------------------------| 14 + | `music_dir` | string | — | Absolute path to your music library | 15 + | `audio_output` | string | `"builtin"` | `builtin` / `fifo` / `airplay` / `squeezelite` / `chromecast` / `snapcast_tcp` / `upnp` | 16 + | `player_name` | string | `""` | Name advertised to MPD clients and the UI | 17 + 18 + ## FIFO / pipe sink 19 + 20 + | Key | Type | Default | Description | 21 + |-------------|--------|----------------|--------------------------------------------| 22 + | `fifo_path` | string | `/tmp/rockbox.fifo` | Named FIFO path, or `"-"` for stdout | 23 + 24 + ## Snapcast TCP sink 25 + 26 + | Key | Type | Default | Description | 27 + |----------------------|--------|----------------|------------------------------------------| 28 + | `snapcast_tcp_host` | string | — | snapserver host | 29 + | `snapcast_tcp_port` | int | `4953` | snapserver TCP source port | 30 + 31 + ## AirPlay sink 32 + 33 + | Key | Type | Default | Description | 34 + |-----------------------|-----------------|---------|---------------------------------------------| 35 + | `airplay_host` | string | — | Single receiver IP | 36 + | `airplay_port` | int | `5000` | Single receiver port | 37 + | `airplay_receivers` | array of tables | — | Multi-room. Each entry: `host`, optional `port` | 38 + 39 + ```toml 40 + [[airplay_receivers]] 41 + host = "192.168.1.50" 42 + 43 + [[airplay_receivers]] 44 + host = "192.168.1.51" 45 + port = 5000 46 + ``` 47 + 48 + ## Squeezelite sink 49 + 50 + | Key | Type | Default | Description | 51 + |---------------------------|------|---------|--------------------------------------| 52 + | `squeezelite_port` | int | `3483` | Slim Protocol TCP port | 53 + | `squeezelite_http_port` | int | `9999` | HTTP PCM broadcast port | 54 + 55 + ## Chromecast sink 56 + 57 + | Key | Type | Default | Description | 58 + |--------------------------|---------|---------|-------------------------------------| 59 + | `chromecast_host` | string | — | Target Chromecast IP | 60 + | `chromecast_port` | int | `8009` | Cast control port | 61 + | `chromecast_http_port` | int | `7881` | WAV HTTP stream port | 62 + 63 + ## UPnP 64 + 65 + | Key | Type | Default | Description | 66 + |--------------------------|---------|-------------|--------------------------------------------| 67 + | `upnp_renderer_url` | string | — | AVTransport controlURL of the target renderer | 68 + | `upnp_http_port` | int | `7879` | WAV broadcast HTTP port (sink mode) | 69 + | `upnp_server_enabled` | bool | `false` | Start the ContentDirectory media server | 70 + | `upnp_server_port` | int | `7878` | Media server HTTP port | 71 + | `upnp_renderer_enabled` | bool | `false` | Start the MediaRenderer endpoint | 72 + | `upnp_renderer_port` | int | `7880` | MediaRenderer HTTP port | 73 + | `upnp_friendly_name` | string | `"Rockbox"` | Display name shown to control points | 74 + 75 + ## Playback defaults 76 + 77 + | Key | Type | Default | Description | 78 + |--------------------|---------|---------|------------------------------------------------| 79 + | `playlist_shuffle` | bool | `false` | | 80 + | `repeat_mode` | int | `1` | 0=Off 1=All 2=One 3=Shuffle 4=A-B | 81 + | `party_mode` | bool | `true` | | 82 + 83 + ## Tone, stereo & channels 84 + 85 + | Key | Type | Default | Description | 86 + |--------------------|--------|---------|--------------------------------------------------| 87 + | `bass` | int | `0` | dB | 88 + | `treble` | int | `0` | dB | 89 + | `bass_cutoff` | int | `0` | Hz | 90 + | `treble_cutoff` | int | `0` | Hz | 91 + | `balance` | int | `0` | −100..+100 | 92 + | `stereo_width` | int | `100` | 0..255 % (when `channel_config = Custom`) | 93 + | `stereosw_mode` | int | `0` | | 94 + | `channel_config` | int | `0` | 0=Stereo 1=Mono 2=Custom 3=ML 4=MR 5=Karaoke 6=Swap | 95 + 96 + ## Surround 97 + 98 + | Key | Type | Default | Description | 99 + |----------------------|------|---------|-----------------------------------| 100 + | `surround_enabled` | int | `0` | 0/5/8/10/15/30 ms (0=off) | 101 + | `surround_balance` | int | `0` | 0..99 % | 102 + | `surround_fx1` | int | `0` | HF cutoff, Hz | 103 + | `surround_fx2` | int | `0` | LF cutoff, Hz | 104 + | `surround_method2` | bool | `false` | Side-only processing | 105 + | `surround_mix` | int | `0` | 0..100 % | 106 + 107 + ## Crossfade 108 + 109 + ```toml 110 + crossfade = 5 111 + fade_on_stop = false 112 + fade_in_delay = 2 113 + fade_in_duration = 7 114 + fade_out_delay = 4 115 + fade_out_duration = 0 116 + fade_out_mixmode = 2 117 + ``` 118 + 119 + ## Equalizer 120 + 121 + ```toml 122 + eq_enabled = true 123 + eq_precut = 3 # dB headroom 124 + 125 + [[eq_band_settings]] 126 + cutoff = 0 # Hz (per-band) 127 + q = 64 # × 10 fixed-point 128 + gain = 10 # × 10 dB fixed-point 129 + 130 + # repeat for each of the 10 bands 131 + ``` 132 + 133 + ## ReplayGain 134 + 135 + ```toml 136 + [replaygain_settings] 137 + type = 0 # 0=Track 1=Album 2=Track shuffle 3=Off 138 + noclip = true 139 + preamp = 0 # × 10 dB fixed-point 140 + ``` 141 + 142 + ## Compressor 143 + 144 + ```toml 145 + [compressor_settings] 146 + threshold = -24 # dB 147 + makeup_gain = 0 148 + ratio = 4 149 + knee = 1 150 + release_time = 300 # ms 151 + attack_time = 5 # ms 152 + ``` 153 + 154 + ## Where it's parsed 155 + 156 + The file is read by `rockbox_settings::load_settings()` in 157 + `crates/settings/src/lib.rs`. Unknown keys are silently ignored, so 158 + typos won't crash startup — but they also won't take effect.
+144
mintlify/reference/troubleshooting.mdx
··· 1 + --- 2 + title: "Troubleshooting" 3 + description: "Common errors, the symptoms they produce, and how to fix them." 4 + icon: 'wrench' 5 + --- 6 + 7 + ## No audio when using built-in SDL on macOS 8 + 9 + **Symptom:** `rockboxd` starts cleanly, the UI shows playback progressing, 10 + but no audio is heard. 11 + 12 + **Cause:** the SDL audio subsystem wasn't initialised. On macOS the SDL 13 + event thread doesn't init audio (it's `#ifndef __APPLE__`). 14 + 15 + **Fix:** this is handled in 16 + `firmware/target/hosted/sdl/system-sdl.c` — but only in recent builds. 17 + Update to the latest release. If you build from source, make sure 18 + `SDL_InitSubSystem(SDL_INIT_AUDIO)` is called from the `#else` branch of 19 + the platform guard. 20 + 21 + ## Snapcast: silence, then snapserver disconnects 22 + 23 + **Symptom:** snapserver logs `Stream: 'default' eof` shortly after rockboxd starts. 24 + 25 + **Cause:** snapserver was started **before** rockboxd, so it opened the 26 + FIFO first and saw EOF. 27 + 28 + **Fix:** start rockboxd first, then snapserver. Rockbox holds a permanent 29 + write-side handle on the FIFO so snapserver never sees EOF mid-track. 30 + 31 + For TCP mode, the order is reversed — start snapserver first. 32 + 33 + ## Squeezelite disconnects every 36 seconds 34 + 35 + **Cause:** the `STMt` heartbeat is not being answered. squeezelite has a 36 + 36-second watchdog. 37 + 38 + **Fix:** every `STMt` heartbeat must be answered with `audg`. This is 39 + already the case in current builds; if you're hacking on 40 + `crates/slim/`, don't strip that response. 41 + 42 + ## Stale binary after editing C or Rust 43 + 44 + **Symptom:** behaviour doesn't match the source code; logs reference 45 + old strings. 46 + 47 + **Cause:** Zig only re-links when the static libraries are newer than 48 + the binary. 49 + 50 + **Fix:** 51 + 52 + ```sh 53 + ls -la zig/zig-out/bin/rockboxd \ 54 + build-lib/libfirmware.a \ 55 + target/release/librockbox_cli.a 56 + ``` 57 + 58 + If `rockboxd` is newer than every `.a`, force a rebuild: 59 + 60 + ```sh 61 + # After C changes 62 + cd build-lib && make lib && cd .. && cd zig && zig build 63 + 64 + # After Rust changes 65 + cargo build --release -p rockbox-cli -p rockbox-server && cd zig && zig build 66 + ``` 67 + 68 + ## "library_directory is not set" after fresh install 69 + 70 + **Cause:** `~/.config/rockbox.org/settings.toml` is missing or has no 71 + `music_dir` key. 72 + 73 + **Fix:** 74 + 75 + ```toml 76 + music_dir = "/path/to/your/Music" 77 + ``` 78 + 79 + ## AirPlay receiver refuses to connect 80 + 81 + **Symptom:** logs show `RTSP ANNOUNCE → 401` or `Receiver requires 82 + password`. 83 + 84 + **Cause:** the receiver requires a PIN/password (most "AirPlay 2" gear). 85 + 86 + **Status:** AirPlay 2 pairing/encryption isn't implemented. Use a 87 + shairport-sync receiver, an Apple TV, an Airport Express, or another 88 + AirPlay 1 device. See [AirPlay](/audio-output/airplay). 89 + 90 + ## Chromecast plays once then stops 91 + 92 + **Symptom:** first track plays through, queue advances, second track 93 + gets stuck buffering. 94 + 95 + **Cause:** the Chromecast cannot reach back to port 7881 on rockboxd's 96 + host. Common when rockboxd is in a VM/container. 97 + 98 + **Fix:** forward port 7881 to the host, or run the container with 99 + `--network host`. See the [Chromecast](/audio-output/chromecast) page. 100 + 101 + ## mDNS discovery returns nothing 102 + 103 + **Symptom:** the device picker is empty, even though receivers are on 104 + the LAN. 105 + 106 + **Causes & fixes:** 107 + 108 + - **Multicast doesn't cross Docker bridges.** Use `--network host`. 109 + - **Some Wi-Fi APs filter multicast.** Enable "multicast forwarding" or 110 + "IGMP snooping" — vendor-specific naming. 111 + - **Avahi/mDNS not running.** On Linux, ensure `avahi-daemon` is 112 + running for SSDP/Bonjour to work. 113 + 114 + ## "address already in use" on startup 115 + 116 + **Cause:** a previous rockboxd process didn't shut down cleanly, or 117 + another service is on one of the API ports. 118 + 119 + **Fix:** 120 + 121 + ```sh 122 + lsof -i :6061 -i :6062 -i :6063 -i :6600 123 + kill -9 <pid> 124 + ``` 125 + 126 + …or change the bind ports via the env vars in 127 + [Reference › Ports](/reference/ports). 128 + 129 + ## Logs are too quiet 130 + 131 + ```sh 132 + RUST_LOG=debug rockboxd 133 + ``` 134 + 135 + Or scoped: 136 + 137 + ```sh 138 + RUST_LOG=rockbox_airplay=debug,info rockboxd 139 + RUST_LOG=rockbox_slim=debug,info rockboxd 140 + ``` 141 + 142 + Never use `eprintln!` / `println!` in the codebase — they bypass the 143 + filter and pollute stdout (which breaks FIFO mode). All Rust logging 144 + goes through `tracing`.
+83
mintlify/sdks/clojure.mdx
··· 1 + --- 2 + title: "Clojure" 3 + description: "Pipe-friendly Clojure wrapper over rockboxd's GraphQL API." 4 + icon: 'lambda' 5 + --- 6 + 7 + `deps.edn`: 8 + 9 + ```clojure 10 + {:deps {org.clojars.tsiry/rockbox-clj {:mvn/version "0.1.2-SNAPSHOT"}}} 11 + ``` 12 + 13 + ## Quick start 14 + 15 + ```clojure 16 + (require '[rockbox.core :as rb] 17 + '[rockbox.playback :as pb] 18 + '[rockbox.library :as lib]) 19 + 20 + (def client (rb/client)) 21 + 22 + (rb/connect client) 23 + 24 + (when-let [t (pb/current-track client)] 25 + (println "Now playing:" (:title t) "—" (:artist t))) 26 + 27 + (let [{:keys [albums tracks]} (lib/search client "dark side")] 28 + (println (count albums) "albums," (count tracks) "tracks")) 29 + 30 + (-> client 31 + (pb/play-album "album-id" {:shuffle true})) 32 + 33 + (rb/on client :track-changed 34 + (fn [t] (println "▶" (:title t) "by" (:artist t)))) 35 + 36 + (rb/disconnect client) 37 + ``` 38 + 39 + ## Configure 40 + 41 + ```clojure 42 + (def c (rb/client)) ;; localhost:6062 43 + (def c (rb/client {:host "192.168.1.42" :port 6062})) 44 + (def c (rb/client {:http-url "https://music.home/graphql" 45 + :ws-url "wss://music.home/graphql"})) 46 + 47 + ;; Builder style — every with-* fn returns a new client value 48 + (def c (-> (rb/client) 49 + (rb/with-host "music.home") 50 + (rb/with-port 6062) 51 + (rb/with-timeout 30000) 52 + (rb/with-headers {:x-trace-id "req-123"}))) 53 + ``` 54 + 55 + ## Conventions 56 + 57 + - **Action functions return the client**, so chains compose with `->`: 58 + `(-> client (pb/play-album "id") (pb/seek 30000))` 59 + - **Read functions return data** as plain Clojure maps with kebab-case keys. 60 + - **Enums are keywords**: `:playing`, `:paused`, `:stopped`. 61 + - **Events surface as callbacks _or_ `core.async` channels.** 62 + 63 + ## API surface 64 + 65 + ```clojure 66 + rockbox.core ;; client, connect, disconnect, on, query 67 + rockbox.playback ;; transport, play helpers 68 + rockbox.library ;; albums, artists, tracks, search, likes 69 + rockbox.playlist ;; live queue 70 + rockbox.saved-playlists ;; persistent playlists 71 + rockbox.smart-playlists ;; rule-based playlists 72 + rockbox.sound ;; volume 73 + rockbox.settings ;; EQ / ReplayGain / crossfade / … 74 + rockbox.system ;; version, status 75 + rockbox.browse ;; filesystem 76 + rockbox.devices ;; output devices 77 + rockbox.bluetooth ;; Linux only 78 + ``` 79 + 80 + ## More 81 + 82 + Full reference and `core.async` event channels: see the 83 + [Clojure SDK README on GitHub ↗](https://github.com/tsirysndr/rockbox-zig/blob/master/sdk/clojure/README.md).
+82
mintlify/sdks/elixir.mdx
··· 1 + --- 2 + title: "Elixir" 3 + description: "Idiomatic Elixir SDK — pipe-friendly, builder-friendly, with messages-as-events." 4 + icon: 'droplet' 5 + --- 6 + 7 + ```elixir 8 + def deps do 9 + [{:rockbox_ex, "~> 0.1"}] 10 + end 11 + ``` 12 + 13 + ## Quick start 14 + 15 + ```elixir 16 + client = Rockbox.new() 17 + 18 + # Optional: opens the WebSocket so subscribers receive events 19 + {:ok, _pid} = Rockbox.connect(client) 20 + 21 + case Rockbox.Playback.current_track(client) do 22 + {:ok, %Rockbox.Track{} = t} -> IO.puts("▶ #{t.title} — #{t.artist}") 23 + {:ok, nil} -> IO.puts("Nothing is playing.") 24 + end 25 + 26 + {:ok, results} = Rockbox.Library.search(client, "dark side") 27 + album = List.first(results.albums) 28 + :ok = Rockbox.Playback.play_album(client, album.id, shuffle: true) 29 + 30 + # Events arrive as messages 31 + :ok = Rockbox.subscribe(:track_changed) 32 + 33 + receive do 34 + {:rockbox, :track_changed, track} -> 35 + IO.puts("Now: #{track.title}") 36 + end 37 + 38 + Rockbox.disconnect(client) 39 + ``` 40 + 41 + ## Configure 42 + 43 + ```elixir 44 + client = Rockbox.new() # localhost:6062 45 + client = Rockbox.new(host: "192.168.1.42", port: 6062) 46 + client = Rockbox.new( 47 + http_url: "https://music.home/graphql", 48 + ws_url: "wss://music.home/graphql" 49 + ) 50 + ``` 51 + 52 + ## Highlights 53 + 54 + - **Pipe-friendly** — every API function takes the client as its first arg. 55 + - **Builder-friendly** — smart-playlist rules and partial settings updates compose with `|>`. 56 + - **Tagged tuples or bangs** — `name/N → {:ok, value} | {:error, exception}`, 57 + with a matching `name!/N` that raises. 58 + - **Real-time events as messages** — `Rockbox.subscribe(:track_changed)` and 59 + receive `{:rockbox, :track_changed, %Rockbox.Track{}}`. 60 + - **Plugins** — implement `Rockbox.Plugin` and install with 61 + `Rockbox.use_plugin/2`. 62 + 63 + ## API surface 64 + 65 + ```elixir 66 + Rockbox.Playback.* # transport, current/next track, play helpers 67 + Rockbox.Library.* # albums, artists, tracks, search, likes 68 + Rockbox.Queue.* # live queue 69 + Rockbox.SavedPlaylists.* # persistent playlists 70 + Rockbox.SmartPlaylists.* # rule-based playlists 71 + Rockbox.Sound.* # volume 72 + Rockbox.Settings.* # EQ / ReplayGain / crossfade / … 73 + Rockbox.System.* # version, status 74 + Rockbox.Browse.* # filesystem 75 + Rockbox.Devices.* # output devices 76 + Rockbox.Bluetooth.* # Linux only 77 + ``` 78 + 79 + ## More 80 + 81 + Full reference and rule-builder DSL: see the 82 + [Elixir SDK README on GitHub ↗](https://github.com/tsirysndr/rockbox-zig/blob/master/sdk/elixir/README.md).
+83
mintlify/sdks/gleam.mdx
··· 1 + --- 2 + title: "Gleam" 3 + description: "Type-safe Gleam SDK with a tagged Result on every call." 4 + icon: 'star' 5 + --- 6 + 7 + ```sh 8 + gleam add rockbox 9 + ``` 10 + 11 + ## Quick start 12 + 13 + ```gleam 14 + import gleam/io 15 + import gleam/list 16 + import gleam/option.{None, Some} 17 + import rockbox 18 + import rockbox/library 19 + import rockbox/playback 20 + 21 + pub fn main() { 22 + let client = rockbox.default_client() 23 + 24 + case playback.current_track(client) { 25 + Ok(Some(track)) -> io.println("▶ " <> track.title <> " — " <> track.artist) 26 + Ok(None) -> io.println("Nothing is playing.") 27 + Error(_) -> io.println("Could not reach rockboxd.") 28 + } 29 + 30 + let assert Ok(results) = library.search(client, "dark side") 31 + case list.first(results.albums) { 32 + Ok(album) -> { 33 + let _ = playback.play_album( 34 + client, album.id, 35 + playback.play_options() |> playback.with_shuffle(True), 36 + ) 37 + Nil 38 + } 39 + Error(_) -> Nil 40 + } 41 + } 42 + ``` 43 + 44 + ## Configure 45 + 46 + ```gleam 47 + let client = rockbox.default_client() // localhost:6062 48 + let client = rockbox.at(host: "192.168.1.42", port: 6062) 49 + 50 + let client = 51 + rockbox.new() 52 + |> rockbox.url("http://192.168.1.42:6062/graphql") 53 + |> rockbox.connect 54 + ``` 55 + 56 + ## Highlights 57 + 58 + - **Pipe-friendly** — every API function takes the client as its first arg. 59 + - **Tagged results** — every call returns 60 + `Result(value, rockbox/error.Error)`, so `case` and `use` flows stay flat. 61 + - **Type-safe rules DSL** — compose smart-playlist rules with 62 + `rockbox/smart_playlists/rules` instead of hand-written JSON. 63 + 64 + ## API surface 65 + 66 + ```gleam 67 + rockbox/playback 68 + rockbox/library 69 + rockbox/queue 70 + rockbox/saved_playlists 71 + rockbox/smart_playlists 72 + rockbox/sound 73 + rockbox/settings 74 + rockbox/system 75 + rockbox/browse 76 + rockbox/devices 77 + rockbox/bluetooth // Linux only 78 + ``` 79 + 80 + ## More 81 + 82 + Full reference and rule DSL: see the 83 + [Gleam SDK README on GitHub ↗](https://github.com/tsirysndr/rockbox-zig/blob/master/sdk/gleam/README.md).
+150
mintlify/sdks/overview.mdx
··· 1 + --- 2 + title: "Client SDKs" 3 + description: "Six first-party SDKs. All wrap the GraphQL transport, surface real-time events, and ship a tiny plugin system." 4 + icon: 'cubes' 5 + --- 6 + 7 + Every SDK targets the GraphQL endpoint on **port 6062** and exposes the 8 + same domain-namespaced API: 9 + 10 + ``` 11 + client.playback # transport, current/next track, play helpers 12 + client.library # albums, artists, tracks, search, likes, scan 13 + client.playlist # the active queue 14 + client.savedPlaylists 15 + client.smartPlaylists 16 + client.sound # volume 17 + client.settings # global EQ / replaygain / crossfade / shuffle 18 + client.system # version, runtime info 19 + client.browse # filesystem browser 20 + client.devices # output devices (Cast, AirPlay, Snapcast) 21 + client.bluetooth # Linux only 22 + ``` 23 + 24 + ## Pick a language 25 + 26 + <CardGroup cols={3}> 27 + <Card title="TypeScript" icon="js" href="/sdks/typescript"> 28 + `bun add @rockbox-zig/sdk` 29 + </Card> 30 + <Card title="Python" icon="python" href="/sdks/python"> 31 + `uv add rockbox-sdk` — async-first 32 + </Card> 33 + <Card title="Ruby" icon="gem" href="/sdks/ruby"> 34 + `gem install rockbox` 35 + </Card> 36 + <Card title="Elixir" icon="droplet" href="/sdks/elixir"> 37 + `{:rockbox_ex, "~> 0.1"}` 38 + </Card> 39 + <Card title="Clojure" icon="lambda" href="/sdks/clojure"> 40 + `org.clojars.tsiry/rockbox-clj` 41 + </Card> 42 + <Card title="Gleam" icon="star" href="/sdks/gleam"> 43 + `gleam add rockbox` 44 + </Card> 45 + </CardGroup> 46 + 47 + ## Why not just use the GraphQL transport directly? 48 + 49 + You can — `client.query()` on every SDK is an escape hatch, and the 50 + GraphiQL explorer at 51 + [http://localhost:6062/graphiql](http://localhost:6062/graphiql) lets you 52 + test queries without writing any client code. The SDKs add value when you 53 + want: 54 + 55 + - **Typed responses** — Pydantic models in Python, `Struct`s in Ruby, 56 + TypeScript types, Gleam tagged unions. 57 + - **Real-time events** — `track:changed` / `status:changed` / 58 + `playlist:changed` over WebSocket with auto-reconnect and exponential 59 + backoff. 60 + - **A plugin system** — Jellyfin-style install/uninstall lifecycle for 61 + cross-cutting features (scrobbling, notifications, sleep timer). 62 + - **Smart-playlist rule builders** — type-safe rule DSLs (Gleam, Elixir, 63 + Clojure). 64 + - **Idiomatic ergonomics** — pipe-friendly in functional languages, 65 + builder DSLs in OOP languages. 66 + 67 + ## Common patterns 68 + 69 + <CodeGroup> 70 + ```ts TypeScript 71 + import { RockboxClient } from '@rockbox-zig/sdk'; 72 + 73 + const client = new RockboxClient(); 74 + client.connect(); 75 + 76 + client.on('track:changed', (t) => console.log(`▶ ${t.title} — ${t.artist}`)); 77 + 78 + const { albums } = await client.library.search('dark side'); 79 + await client.playback.playAlbum(albums[0].id, { shuffle: true }); 80 + ``` 81 + 82 + ```python Python 83 + import asyncio 84 + from rockbox_sdk import RockboxClient 85 + 86 + async def main(): 87 + async with RockboxClient(host="localhost") as client: 88 + await client.connect() 89 + results = await client.library.search("dark side") 90 + await client.playback.play_album(results.albums[0].id, shuffle=True) 91 + 92 + asyncio.run(main()) 93 + ``` 94 + 95 + ```ruby Ruby 96 + require "rockbox" 97 + 98 + client = Rockbox::Client.new 99 + client.on(:track_changed) { |t| puts "▶ #{t.title} — #{t.artist}" } 100 + client.connect 101 + 102 + results = client.library.search("dark side") 103 + client.playback.play_album(results.albums.first.id, shuffle: true) 104 + ``` 105 + 106 + ```elixir Elixir 107 + client = Rockbox.new() 108 + {:ok, _pid} = Rockbox.connect(client) 109 + Rockbox.subscribe(:track_changed) 110 + 111 + {:ok, results} = Rockbox.Library.search(client, "dark side") 112 + album = List.first(results.albums) 113 + :ok = Rockbox.Playback.play_album(client, album.id, shuffle: true) 114 + ``` 115 + 116 + ```clojure Clojure 117 + (require '[rockbox.core :as rb] 118 + '[rockbox.playback :as pb] 119 + '[rockbox.library :as lib]) 120 + 121 + (def client (rb/client)) 122 + (rb/connect client) 123 + (rb/on client :track-changed 124 + (fn [t] (println "▶" (:title t) "—" (:artist t)))) 125 + 126 + (let [{:keys [albums]} (lib/search client "dark side")] 127 + (pb/play-album client (:id (first albums)) {:shuffle true})) 128 + ``` 129 + 130 + ```gleam Gleam 131 + import rockbox 132 + import rockbox/library 133 + import rockbox/playback 134 + 135 + pub fn main() { 136 + let client = rockbox.default_client() 137 + let assert Ok(results) = library.search(client, "dark side") 138 + case list.first(results.albums) { 139 + Ok(album) -> { 140 + let _ = playback.play_album( 141 + client, album.id, 142 + playback.play_options() |> playback.with_shuffle(True), 143 + ) 144 + Nil 145 + } 146 + Error(_) -> Nil 147 + } 148 + } 149 + ``` 150 + </CodeGroup>
+148
mintlify/sdks/python.mdx
··· 1 + --- 2 + title: "Python" 3 + description: "Async-first Python SDK on httpx + websockets, with Pydantic models." 4 + icon: 'python' 5 + --- 6 + 7 + ```sh 8 + uv add rockbox-sdk 9 + # or 10 + pip install rockbox-sdk 11 + ``` 12 + 13 + Requires **Python 3.10+**. 14 + 15 + ## Quick start 16 + 17 + ```python 18 + import asyncio 19 + from rockbox_sdk import RockboxClient, PlaybackStatus 20 + 21 + async def main(): 22 + async with RockboxClient(host="localhost") as client: 23 + track = await client.playback.current_track() 24 + if track: 25 + print(f"Now: {track.title} — {track.artist}") 26 + if await client.playback.status() == PlaybackStatus.PAUSED: 27 + await client.playback.resume() 28 + 29 + asyncio.run(main()) 30 + ``` 31 + 32 + ## Highlights 33 + 34 + - **Async-first** — built on `httpx` + `websockets`. Use `await` everywhere. 35 + - **Domain-namespaced API** — `client.playback.*`, `client.library.*`, `client.sound.*`, … 36 + - **Typed responses** — every reply is a Pydantic model with snake_case fields. 37 + - **Real-time events** — `connect()` opens a WebSocket and forwards 38 + `track:changed` / `status:changed` / `playlist:changed` to listeners. 39 + - **Builder API** — `RockboxClient.builder().host(...).port(...).build()`. 40 + - **Plugin system** — Jellyfin-style install/uninstall lifecycle. 41 + - **Python-friendly** — context manager, decorator listeners, dataclass inputs. 42 + 43 + ## Configure 44 + 45 + ```python 46 + client = RockboxClient(host="192.168.1.42", port=6062) 47 + 48 + # Builder 49 + client = ( 50 + RockboxClient.builder() 51 + .host("nas.local") 52 + .port(6062) 53 + .timeout(15) 54 + .build() 55 + ) 56 + 57 + # Full URL override 58 + client = RockboxClient( 59 + http_url="http://nas.local:6062/graphql", 60 + ws_url="ws://nas.local:6062/graphql", 61 + ) 62 + ``` 63 + 64 + Always close: `await client.aclose()`, or use it as an async context 65 + manager (`async with RockboxClient() as client:`). 66 + 67 + ## Domains 68 + 69 + | Namespace | What it does | 70 + |--------------------------|-----------------------------------------------| 71 + | `client.playback` | Transport, current/next, play helpers | 72 + | `client.library` | Albums, artists, tracks, search, likes, scan | 73 + | `client.playlist` | The active queue | 74 + | `client.saved_playlists` | Persistent playlists & folders | 75 + | `client.smart_playlists` | Rule-based playlists & stats | 76 + | `client.sound` | Volume | 77 + | `client.settings` | EQ / ReplayGain / crossfade / shuffle / … | 78 + | `client.system` | Version, runtime info | 79 + | `client.browse` | Filesystem & UPnP browser | 80 + | `client.devices` | Cast / source devices | 81 + | `client.bluetooth` | Bluetooth (Linux only) | 82 + 83 + ## Real-time events 84 + 85 + ```python 86 + from rockbox_sdk import RockboxClient, TRACK_CHANGED, STATUS_CHANGED 87 + 88 + async with RockboxClient() as client: 89 + await client.connect() 90 + 91 + @client.on(TRACK_CHANGED) 92 + async def on_track(track): 93 + print(f"▶ {track.title} — {track.artist}") 94 + 95 + @client.on(STATUS_CHANGED) 96 + def on_status(raw_status): 97 + print(f"◐ status = {raw_status}") 98 + 99 + await asyncio.Event().wait() 100 + ``` 101 + 102 + Convenience wrappers: `client.on_track_changed(...)`, 103 + `client.on_status_changed(...)`, `client.on_playlist_changed(...)`. 104 + 105 + ## Plugins 106 + 107 + ```python 108 + class SleepTimer: 109 + name = "sleep-timer" 110 + version = "1.0.0" 111 + 112 + def __init__(self, minutes: int): 113 + self.minutes = minutes 114 + self._task = None 115 + 116 + def install(self, ctx): 117 + async def fire(): 118 + await asyncio.sleep(self.minutes * 60) 119 + await ctx.query("mutation { hardStop }") 120 + self._task = asyncio.create_task(fire()) 121 + 122 + def uninstall(self): 123 + if self._task: 124 + self._task.cancel() 125 + 126 + await client.use(SleepTimer(30)) 127 + ``` 128 + 129 + ## REPL-friendly 130 + 131 + The SDK is async-first. The recommended REPL is **IPython** — `await` 132 + works at the top level, and you get tab-completion on Pydantic models. 133 + 134 + ```sh 135 + uv run ipython 136 + ``` 137 + 138 + ```python 139 + In [1]: from rockbox_sdk import RockboxClient 140 + In [2]: client = RockboxClient() 141 + In [3]: await client.playback.status() 142 + In [4]: track = await client.playback.current_track() 143 + ``` 144 + 145 + ## More 146 + 147 + Full reference, type catalogue and plugin examples: see the 148 + [Python SDK README on GitHub ↗](https://github.com/tsirysndr/rockbox-zig/blob/master/sdk/python/README.md).
+111
mintlify/sdks/ruby.mdx
··· 1 + --- 2 + title: "Ruby" 3 + description: "Builder-friendly, block-friendly Ruby SDK with WebSocket subscriptions and plugins." 4 + icon: 'gem' 5 + --- 6 + 7 + ```sh 8 + gem install rockbox 9 + ``` 10 + 11 + Or with Bundler: 12 + 13 + ```ruby 14 + # Gemfile 15 + gem "rockbox" 16 + ``` 17 + 18 + Requires **Ruby 3.0+**. 19 + 20 + ## Quick start 21 + 22 + ```ruby 23 + require "rockbox" 24 + 25 + client = Rockbox::Client.new 26 + 27 + client.connect # opens the WebSocket — subscriptions start firing 28 + 29 + if (track = client.playback.current_track) 30 + puts "Now playing: #{track.title} — #{track.artist}" 31 + end 32 + 33 + results = client.library.search("dark side") 34 + client.playback.play_album(results.albums.first.id, shuffle: true) 35 + 36 + client.on(:track_changed) { |t| puts "▶ #{t.title} by #{t.artist}" } 37 + 38 + client.disconnect 39 + ``` 40 + 41 + ## Configure 42 + 43 + ```ruby 44 + client = Rockbox::Client.new(host: "192.168.1.42", port: 6062) 45 + 46 + client = Rockbox::Client.build do |c| 47 + c.host = "192.168.1.42" 48 + c.port = 6062 49 + end 50 + 51 + client = Rockbox::Client.new( 52 + http_url: "https://music.home/graphql", 53 + ws_url: "wss://music.home/graphql", 54 + ) 55 + ``` 56 + 57 + ## Playback 58 + 59 + ```ruby 60 + client.playback.status # => Integer 61 + client.playback.status_name # => :playing | :paused | :stopped | :unknown 62 + client.playback.current_track 63 + client.playback.next_track 64 + 65 + client.playback.play(elapsed: 0, offset: 0) 66 + client.playback.pause 67 + client.playback.resume 68 + client.playback.next! 69 + client.playback.previous! 70 + client.playback.seek(60_000) 71 + client.playback.stop 72 + 73 + client.playback.play_track("/Music/song.mp3") 74 + client.playback.play_album(album_id, shuffle: true) 75 + client.playback.play_artist(artist_id) 76 + client.playback.play_playlist(playlist_id, shuffle: true) 77 + client.playback.play_directory("/Music/Pink Floyd", recurse: true) 78 + client.playback.play_liked_tracks(shuffle: true) 79 + client.playback.play_all_tracks 80 + ``` 81 + 82 + ## Real-time events 83 + 84 + ```ruby 85 + client.on(:track_changed) { |track| puts track.title } 86 + client.on(:status_changed) { |status| puts status } 87 + client.on(:playlist_changed) { |queue| puts queue.amount } 88 + ``` 89 + 90 + ## Plugin system 91 + 92 + ```ruby 93 + class Scrobbler 94 + def name; "scrobbler"; end 95 + def version; "1.0.0"; end 96 + def description; "Scrobble played tracks"; end 97 + 98 + def install(ctx) 99 + ctx.events.on(:track_changed) do |track| 100 + MyScrobbler.submit(track.title, track.artist) 101 + end 102 + end 103 + end 104 + 105 + client.use Scrobbler.new 106 + ``` 107 + 108 + ## More 109 + 110 + Full reference, type catalogue and plugin examples: see the 111 + [Ruby SDK README on GitHub ↗](https://github.com/tsirysndr/rockbox-zig/blob/master/sdk/ruby/README.md).
+168
mintlify/sdks/typescript.mdx
··· 1 + --- 2 + title: "TypeScript" 3 + description: "Fully typed GraphQL client with real-time subscriptions and a plugin system." 4 + icon: 'js' 5 + --- 6 + 7 + ```sh 8 + bun add @rockbox-zig/sdk 9 + # or 10 + npm install @rockbox-zig/sdk 11 + ``` 12 + 13 + `rockboxd` must be running and reachable. By default the SDK connects to 14 + `http://localhost:6062/graphql`. 15 + 16 + ## Quick start 17 + 18 + ```ts 19 + import { RockboxClient, PlaybackStatus } from '@rockbox-zig/sdk'; 20 + 21 + const client = new RockboxClient(); 22 + 23 + client.connect(); // optional — opens the WebSocket 24 + 25 + const track = await client.playback.currentTrack(); 26 + if (track) console.log(`Now playing: ${track.title} — ${track.artist}`); 27 + 28 + const { albums } = await client.library.search('dark side'); 29 + await client.playback.playAlbum(albums[0].id, { shuffle: true }); 30 + 31 + client.on('track:changed', (t) => console.log(`▶ ${t.title} by ${t.artist}`)); 32 + 33 + client.disconnect(); 34 + ``` 35 + 36 + ## Configuration 37 + 38 + ```ts 39 + new RockboxClient(); // localhost:6062 40 + new RockboxClient({ host: '192.168.1.42', port: 6062 }); 41 + new RockboxClient({ // behind a reverse proxy 42 + httpUrl: 'https://music.home/graphql', 43 + wsUrl: 'wss://music.home/graphql', 44 + }); 45 + ``` 46 + 47 + | Option | Default | Description | 48 + |-----------|--------------------------------|--------------------------------------| 49 + | `host` | `localhost` | Hostname or IP of rockboxd | 50 + | `port` | `6062` | GraphQL port | 51 + | `httpUrl` | `http://{host}:{port}/graphql` | Override the full HTTP URL | 52 + | `wsUrl` | `ws://{host}:{port}/graphql` | Override the full WebSocket URL | 53 + 54 + ## Domains 55 + 56 + | Namespace | What it does | 57 + |--------------------------|--------------------------------------------------| 58 + | `client.playback` | Transport, current/next track, play helpers | 59 + | `client.library` | Albums, artists, tracks, search, likes, scan | 60 + | `client.playlist` | The active queue | 61 + | `client.savedPlaylists` | Persistent playlists & folders | 62 + | `client.smartPlaylists` | Rule-based playlists & listening stats | 63 + | `client.sound` | Volume control | 64 + | `client.settings` | Global EQ / ReplayGain / crossfade / shuffle / … | 65 + | `client.system` | Version, runtime info | 66 + | `client.browse` | Filesystem browser | 67 + | `client.devices` | Cast / source device discovery | 68 + 69 + ## Playback shortcuts 70 + 71 + ```ts 72 + await client.playback.playTrack('/Music/Pink Floyd/Wish You Were Here.mp3'); 73 + await client.playback.playAlbum('album-id', { shuffle: true }); 74 + await client.playback.playAlbum('album-id', { position: 3 }); 75 + await client.playback.playArtist('artist-id', { shuffle: true }); 76 + await client.playback.playPlaylist('playlist-id', { shuffle: true }); 77 + await client.playback.playDirectory('/Music/Jazz', { recurse: true, shuffle: true }); 78 + await client.playback.playLikedTracks({ shuffle: true }); 79 + await client.playback.playAllTracks({ shuffle: true }); 80 + ``` 81 + 82 + ## Queue management 83 + 84 + ```ts 85 + import { InsertPosition } from '@rockbox-zig/sdk'; 86 + 87 + await client.playlist.insertTracks( 88 + ['/Music/track1.mp3', '/Music/track2.mp3'], 89 + InsertPosition.Next, 90 + ); 91 + await client.playlist.insertTracks(paths, InsertPosition.Last); 92 + await client.playlist.insertTracks(paths, InsertPosition.First); // replace 93 + await client.playlist.insertDirectory('/Music/Ambient', InsertPosition.Last); 94 + await client.playlist.insertAlbum('album-id', InsertPosition.Next); 95 + await client.playlist.removeTrack(2); 96 + await client.playlist.clear(); 97 + await client.playlist.shuffle(); 98 + ``` 99 + 100 + | `InsertPosition` | Effect | 101 + |------------------|----------------------------------------| 102 + | `Next` | After the currently playing track | 103 + | `AfterCurrent` | After the last manually inserted track | 104 + | `Last` | At the end of the queue | 105 + | `First` | Replace the entire queue | 106 + 107 + ## Real-time events 108 + 109 + ```ts 110 + client.connect(); 111 + 112 + client.on('track:changed', (t) => updateNowPlaying(t)); 113 + client.on('status:changed', (s) => setStatusBadge(s)); 114 + client.on('playlist:changed', (q) => renderQueue(q.tracks)); 115 + 116 + client.on('ws:error', (err) => console.error(err.message)); 117 + client.once('track:changed', (t) => console.log('first event:', t.title)); 118 + 119 + client.off('track:changed', handler); 120 + ``` 121 + 122 + ## Plugins 123 + 124 + Drop-in cross-cutting features. Inspired by Jellyfin's `IPlugin`. 125 + 126 + ```ts 127 + import type { RockboxPlugin } from '@rockbox-zig/sdk'; 128 + 129 + export const Notifications: RockboxPlugin = { 130 + name: 'desktop-notifications', 131 + version: '1.0.0', 132 + 133 + install({ events }) { 134 + if (typeof Notification === 'undefined') return; 135 + Notification.requestPermission(); 136 + events.on('track:changed', (t) => { 137 + new Notification(t.title, { body: `${t.artist} · ${t.album}`, icon: t.albumArt ?? undefined }); 138 + }); 139 + }, 140 + }; 141 + 142 + await client.use(Notifications); 143 + client.installedPlugins().forEach((p) => console.log(`${p.name} v${p.version}`)); 144 + await client.unuse('desktop-notifications'); 145 + ``` 146 + 147 + ## Error handling 148 + 149 + ```ts 150 + import { RockboxNetworkError, RockboxGraphQLError, RockboxError } from '@rockbox-zig/sdk'; 151 + 152 + try { 153 + await client.playback.play(); 154 + } catch (err) { 155 + if (err instanceof RockboxNetworkError) showOfflineBanner(err.message); 156 + else if (err instanceof RockboxGraphQLError) console.error(err.errors); 157 + else if (err instanceof RockboxError) console.error('Rockbox error:', err.message); 158 + } 159 + ``` 160 + 161 + ## Raw queries 162 + 163 + ```ts 164 + const data = await client.query<{ rockboxVersion: string }>(`query { rockboxVersion }`); 165 + ``` 166 + 167 + Full reference, more examples and the plugin system deep-dive: see the 168 + [SDK README on GitHub ↗](https://github.com/tsirysndr/rockbox-zig/blob/master/sdk/typescript/README.md).