Rockbox open source high quality audio player as a Music Player Daemon
mpris
rockbox
mpd
libadwaita
audio
rust
zig
deno
1# rockbox-sdk
2
3[](https://pypi.org/project/rockbox-sdk/)
4[](https://pypi.org/project/rockbox-sdk/)
5[](https://pypi.org/project/rockbox-sdk/)
6[](./LICENSE)
7[](https://docs.python.org/3/library/asyncio.html)
8[](https://docs.pydantic.dev/)
9[](https://github.com/astral-sh/ruff)
10[](http://mypy-lang.org/)
11[](https://github.com/tsirysndr/rockbox-zig)
12
13Async Python SDK for [Rockbox Zig](https://github.com/tsirysndr/rockbox-zig) — a typed, batteries-included
14client for the GraphQL API exposed by `rockboxd`.
15
16```python
17import asyncio
18from rockbox_sdk import RockboxClient, PlaybackStatus
19
20async def main():
21 async with RockboxClient(host="localhost") as client:
22 track = await client.playback.current_track()
23 if track:
24 print(f"Now: {track.title} — {track.artist}")
25 if await client.playback.status() == PlaybackStatus.PAUSED:
26 await client.playback.resume()
27
28asyncio.run(main())
29```
30
31## Highlights
32
33- **Async-first** — built on `httpx` + `websockets`. Use `await` everywhere.
34- **Domain-namespaced API** — `client.playback.*`, `client.library.*`, `client.sound.*`, …
35- **Typed responses** — every reply is a Pydantic model with snake_case fields.
36- **Real-time events** — `connect()` opens a WebSocket and forwards
37 `track:changed` / `status:changed` / `playlist:changed` to listeners.
38- **Builder API** — `RockboxClient.builder().host(...).port(...).build()`.
39- **Plugin system** — Jellyfin-style install/uninstall lifecycle.
40- **Python-friendly** — context manager, decorator listeners, dataclass inputs.
41
42## Install
43
44```sh
45uv add rockbox-sdk
46# or
47pip install rockbox-sdk
48```
49
50Requires Python 3.10+ and a running `rockboxd` (default port 6062).
51
52## Try it in the REPL
53
54The SDK is async-first. The recommended REPL is **IPython** — `await` works at
55the top level, and you get tab-completion on models, inline docs with `?`, and
56`%timeit` for benchmarking:
57
58```sh
59uv run ipython
60```
61
62```python
63In [1]: from rockbox_sdk import RockboxClient, PlaybackStatus
64In [2]: client = RockboxClient(host="localhost", port=6062)
65In [3]: await client.playback.status()
66Out[3]: <PlaybackStatus.PLAYING: 1>
67In [4]: track = await client.playback.current_track()
68In [5]: track.title, track.artist
69Out[5]: ('Money', 'Pink Floyd')
70In [6]: await client.sound.get_volume()
71Out[6]: VolumeInfo(volume=-12, min=-74, max=6)
72In [7]: await client.library.search("daft punk")
73In [8]: await client.aclose()
74```
75
76You can also test offline — models, enums, and the builder don't need a server:
77
78```python
79In [1]: from rockbox_sdk import RockboxClient, Track, InsertPosition
80In [2]: Track.model_validate({"title": "Money", "albumArt": "x.jpg"}).album_art
81Out[2]: 'x.jpg'
82In [3]: RockboxClient.builder().host("nas.local").build()._config.resolve_http_url()
83Out[3]: 'http://nas.local:6062/graphql'
84```
85
86If you prefer the stdlib REPL, `python -m asyncio` also supports top-level
87`await`, or wrap each call in `asyncio.run(...)` in a plain `python` session:
88
89```sh
90uv run python -m asyncio
91```
92
93```python
94>>> from rockbox_sdk import RockboxClient
95>>> client = RockboxClient()
96>>> await client.playback.status()
97```
98
99Subscriptions (`await client.connect()`) keep firing in the background between
100prompts in both REPLs.
101
102## Configure
103
104```python
105from rockbox_sdk import RockboxClient
106
107# Direct kwargs
108client = RockboxClient(host="192.168.1.42", port=6062)
109
110# Or fluent builder
111client = (
112 RockboxClient.builder()
113 .host("nas.local")
114 .port(6062)
115 .timeout(15)
116 .build()
117)
118
119# Or full URL override
120client = RockboxClient(
121 http_url="http://nas.local:6062/graphql",
122 ws_url="ws://nas.local:6062/graphql",
123)
124```
125
126Always call `await client.aclose()` when you're done — or use it as an
127async context manager:
128
129```python
130async with RockboxClient() as client:
131 ...
132```
133
134## Domains
135
136| Namespace | What it does |
137| ------------------------ | ------------------------------------------------ |
138| `client.playback` | Transport (`play`/`pause`/`seek`), play helpers |
139| `client.library` | Albums, artists, tracks, search, likes, scan |
140| `client.playlist` | The active queue (insert/remove/shuffle/start) |
141| `client.saved_playlists` | Persistent playlists & folders |
142| `client.smart_playlists` | Rule-based playlists & listening stats |
143| `client.sound` | Volume control |
144| `client.settings` | Global EQ / replaygain / crossfade / shuffle / … |
145| `client.system` | Version, runtime info |
146| `client.browse` | Filesystem & UPnP browser |
147| `client.devices` | Cast / source device discovery |
148| `client.bluetooth` | Bluetooth pairing & scanning (Linux only) |
149
150## Real-time events
151
152```python
153from rockbox_sdk import RockboxClient, TRACK_CHANGED, STATUS_CHANGED
154
155async with RockboxClient() as client:
156 await client.connect() # opens the WebSocket
157
158 @client.on(TRACK_CHANGED)
159 async def on_track(track):
160 print(f"▶ {track.title} — {track.artist}")
161
162 @client.on(STATUS_CHANGED)
163 def on_status(raw_status):
164 print(f"◐ status = {raw_status}")
165
166 await asyncio.Event().wait() # run forever
167```
168
169Convenience wrappers exist (`client.on_track_changed(...)`,
170`client.on_status_changed(...)`, `client.on_playlist_changed(...)`).
171
172## Plugins
173
174A plugin is anything matching the `RockboxPlugin` protocol — a name, version,
175`install(context)`, and optionally `uninstall()`:
176
177```python
178from rockbox_sdk import RockboxClient, PlaybackStatus, RockboxPlugin
179
180class SleepTimer:
181 name = "sleep-timer"
182 version = "1.0.0"
183 description = "Stop playback after N minutes"
184
185 def __init__(self, minutes: int) -> None:
186 self.minutes = minutes
187 self._task: asyncio.Task | None = None
188
189 def install(self, ctx):
190 async def fire():
191 await asyncio.sleep(self.minutes * 60)
192 await ctx.query("mutation { hardStop }")
193
194 self._task = asyncio.create_task(fire())
195
196 @ctx.events.on("status:changed")
197 def cancel_on_stop(status: int):
198 if status == PlaybackStatus.STOPPED and self._task:
199 self._task.cancel()
200
201 def uninstall(self):
202 if self._task:
203 self._task.cancel()
204
205async with RockboxClient() as client:
206 await client.connect()
207 await client.use(SleepTimer(30))
208```
209
210## Raw GraphQL escape hatch
211
212```python
213data = await client.query(
214 "query Volume { volume { volume min max } }"
215)
216```
217
218## Examples
219
220See `examples/` for runnable scripts mirroring the TypeScript SDK examples:
221
222- `01-basic-playback.py`
223- `02-now-playing.py`
224- `03-library-search.py`
225- `04-queue-management.py`
226- `05-volume-control.py`
227- `06-plugin-sleep-timer.py`
228
229Run with:
230
231```sh
232uv run python examples/01_basic_playback.py
233```
234
235---
236
237## License
238
239MIT License. See [LICENSE](./LICENSE) for details.