rockbox 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +473 -0
- data/lib/rockbox/api/bluetooth.rb +52 -0
- data/lib/rockbox/api/browse.rb +31 -0
- data/lib/rockbox/api/devices.rb +38 -0
- data/lib/rockbox/api/library.rb +167 -0
- data/lib/rockbox/api/playback.rb +148 -0
- data/lib/rockbox/api/playlist.rb +127 -0
- data/lib/rockbox/api/saved_playlists.rb +163 -0
- data/lib/rockbox/api/settings.rb +118 -0
- data/lib/rockbox/api/smart_playlists.rb +115 -0
- data/lib/rockbox/api/sound.rb +31 -0
- data/lib/rockbox/api/system.rb +32 -0
- data/lib/rockbox/case_conversion.rb +43 -0
- data/lib/rockbox/client.rb +226 -0
- data/lib/rockbox/configuration.rb +38 -0
- data/lib/rockbox/errors.rb +33 -0
- data/lib/rockbox/events.rb +78 -0
- data/lib/rockbox/plugin.rb +60 -0
- data/lib/rockbox/transport.rb +202 -0
- data/lib/rockbox/types.rb +152 -0
- data/lib/rockbox/version.rb +5 -0
- data/lib/rockbox.rb +19 -0
- data/rockbox.gemspec +36 -0
- metadata +124 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 13e4b57d472b2417316bad50917e45c1976a80db4f42a556172f1dd599871e88
|
|
4
|
+
data.tar.gz: dde0b0010b7ae247cdcff0d118899586c33b95e4fee949ee4231135561149432
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: f3b1a4dd4254a20ff12c3aaa119a4955d5ddbca8872f130b351787673f5b558d355db951e3e5167e0f1acc09aa2d5e410bfe926f94953e89d2ef3db9c527bca8
|
|
7
|
+
data.tar.gz: 8869e601d30924d78273673dbfdfc97ca6d1a3d059e42e293bb7307090f1a13d58fa887c1cb26e83f21bb16c2dd79426d5d74b8c77dcd9c372dd145984c159b6
|
data/README.md
ADDED
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
# rockbox
|
|
2
|
+
|
|
3
|
+
[](https://rubygems.org/gems/rockbox)
|
|
4
|
+
[](https://www.ruby-lang.org/)
|
|
5
|
+
[](https://rubygems.org/gems/rockbox)
|
|
6
|
+
[](./LICENSE)
|
|
7
|
+
[](https://graphql.org/)
|
|
8
|
+
[](https://github.com/enhancv/websocket-client-simple)
|
|
9
|
+
[](#plugin-system)
|
|
10
|
+
[](https://github.com/tsirysndr/rockbox-zig)
|
|
11
|
+
|
|
12
|
+
Ruby SDK for [Rockbox Zig](https://github.com/tsirysndr/rockbox-zig) — a builder-friendly, block-friendly GraphQL client with real-time event subscriptions and a plugin system.
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
require "rockbox"
|
|
16
|
+
|
|
17
|
+
client = Rockbox::Client.build do |c|
|
|
18
|
+
c.host = "localhost"
|
|
19
|
+
c.port = 6062
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
client.on(:track_changed) { |t| puts "▶ #{t.title} — #{t.artist}" }
|
|
23
|
+
client.connect
|
|
24
|
+
|
|
25
|
+
results = client.library.search("dark side")
|
|
26
|
+
client.playback.play_album(results.albums.first.id, shuffle: true)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Table of contents
|
|
32
|
+
|
|
33
|
+
- [Installation](#installation)
|
|
34
|
+
- [Quick start](#quick-start)
|
|
35
|
+
- [Configuration](#configuration)
|
|
36
|
+
- [API reference](#api-reference)
|
|
37
|
+
- [Playback](#playback)
|
|
38
|
+
- [Library](#library)
|
|
39
|
+
- [Playlist (queue)](#playlist-queue)
|
|
40
|
+
- [Saved playlists](#saved-playlists)
|
|
41
|
+
- [Smart playlists](#smart-playlists)
|
|
42
|
+
- [Sound](#sound)
|
|
43
|
+
- [Settings](#settings)
|
|
44
|
+
- [System](#system)
|
|
45
|
+
- [Browse (filesystem)](#browse-filesystem)
|
|
46
|
+
- [Devices](#devices)
|
|
47
|
+
- [Bluetooth](#bluetooth)
|
|
48
|
+
- [Real-time events](#real-time-events)
|
|
49
|
+
- [Plugin system](#plugin-system)
|
|
50
|
+
- [Error handling](#error-handling)
|
|
51
|
+
- [Raw GraphQL queries](#raw-graphql-queries)
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Installation
|
|
56
|
+
|
|
57
|
+
```sh
|
|
58
|
+
gem install rockbox
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Or with Bundler:
|
|
62
|
+
|
|
63
|
+
```ruby
|
|
64
|
+
# Gemfile
|
|
65
|
+
gem "rockbox"
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
`rockboxd` must be running and reachable. The SDK targets **Ruby 3.0+** and connects to `http://localhost:6062/graphql` by default.
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Quick start
|
|
73
|
+
|
|
74
|
+
```ruby
|
|
75
|
+
require "rockbox"
|
|
76
|
+
|
|
77
|
+
client = Rockbox::Client.new
|
|
78
|
+
|
|
79
|
+
# Optional — start WebSocket subscriptions for real-time events.
|
|
80
|
+
client.connect
|
|
81
|
+
|
|
82
|
+
# What's playing?
|
|
83
|
+
if (track = client.playback.current_track)
|
|
84
|
+
puts "Now playing: #{track.title} — #{track.artist}"
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Search the library.
|
|
88
|
+
results = client.library.search("dark side")
|
|
89
|
+
puts "Found #{results.albums.size} albums and #{results.tracks.size} tracks"
|
|
90
|
+
|
|
91
|
+
# Play an album with shuffle.
|
|
92
|
+
client.playback.play_album(results.albums.first.id, shuffle: true)
|
|
93
|
+
|
|
94
|
+
# React to track changes.
|
|
95
|
+
client.on(:track_changed) do |track|
|
|
96
|
+
puts "▶ #{track.title} by #{track.artist}"
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Tear down when done.
|
|
100
|
+
client.disconnect
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Configuration
|
|
106
|
+
|
|
107
|
+
Three equivalent ways to configure the client. Pick the one that fits your style.
|
|
108
|
+
|
|
109
|
+
```ruby
|
|
110
|
+
# 1. Defaults — localhost:6062.
|
|
111
|
+
client = Rockbox::Client.new
|
|
112
|
+
|
|
113
|
+
# 2. Keyword arguments.
|
|
114
|
+
client = Rockbox::Client.new(host: "192.168.1.42", port: 6062)
|
|
115
|
+
|
|
116
|
+
# 3. Builder block (great for application initializers).
|
|
117
|
+
client = Rockbox::Client.build do |c|
|
|
118
|
+
c.host = "192.168.1.42"
|
|
119
|
+
c.port = 6062
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# 4. Fully custom URLs (useful behind a reverse proxy).
|
|
123
|
+
client = Rockbox::Client.new(
|
|
124
|
+
http_url: "https://music.home/graphql",
|
|
125
|
+
ws_url: "wss://music.home/graphql"
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
# Top-level shorthand.
|
|
129
|
+
client = Rockbox.new(host: "localhost")
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
| Option | Type | Default | Description |
|
|
133
|
+
|----------------|----------|---------------------------------|--------------------------------------|
|
|
134
|
+
| `host` | String | `"localhost"` | Hostname or IP of rockboxd |
|
|
135
|
+
| `port` | Integer | `6062` | GraphQL port |
|
|
136
|
+
| `http_url` | String | `http://{host}:{port}/graphql` | Override the full HTTP URL |
|
|
137
|
+
| `ws_url` | String | `ws://{host}:{port}/graphql` | Override the full WebSocket URL |
|
|
138
|
+
| `open_timeout` | Integer | `5` | HTTP connect timeout (seconds) |
|
|
139
|
+
| `read_timeout` | Integer | `30` | HTTP read timeout (seconds) |
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## API reference
|
|
144
|
+
|
|
145
|
+
The client exposes a domain namespace per concern (Mopidy-style). Every method returns idiomatic Ruby data — `Struct` instances with `snake_case` accessors.
|
|
146
|
+
|
|
147
|
+
### Playback
|
|
148
|
+
|
|
149
|
+
```ruby
|
|
150
|
+
client.playback
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
```ruby
|
|
154
|
+
# Status
|
|
155
|
+
client.playback.status # => Integer (Rockbox::PlaybackStatus::PLAYING, etc.)
|
|
156
|
+
client.playback.status_name # => :playing | :paused | :stopped | :unknown
|
|
157
|
+
|
|
158
|
+
# Current/next track
|
|
159
|
+
track = client.playback.current_track # => Rockbox::Track
|
|
160
|
+
client.playback.next_track
|
|
161
|
+
client.playback.file_position
|
|
162
|
+
|
|
163
|
+
# Transport controls
|
|
164
|
+
client.playback.play(elapsed: 0, offset: 0)
|
|
165
|
+
client.playback.pause
|
|
166
|
+
client.playback.resume
|
|
167
|
+
client.playback.next!
|
|
168
|
+
client.playback.previous!
|
|
169
|
+
client.playback.seek(60_000) # ms
|
|
170
|
+
client.playback.stop
|
|
171
|
+
client.playback.flush_and_reload
|
|
172
|
+
|
|
173
|
+
# One-shot play helpers
|
|
174
|
+
client.playback.play_track("/Music/song.mp3")
|
|
175
|
+
client.playback.play_album(album_id, shuffle: true)
|
|
176
|
+
client.playback.play_artist(artist_id)
|
|
177
|
+
client.playback.play_playlist(playlist_id, shuffle: true, position: 0)
|
|
178
|
+
client.playback.play_directory("/Music/Pink Floyd", recurse: true)
|
|
179
|
+
client.playback.play_liked_tracks(shuffle: true)
|
|
180
|
+
client.playback.play_all_tracks
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Library
|
|
184
|
+
|
|
185
|
+
```ruby
|
|
186
|
+
# Albums
|
|
187
|
+
client.library.albums # => Array<Rockbox::Album>
|
|
188
|
+
client.library.album(id) # => Rockbox::Album | nil
|
|
189
|
+
client.library.liked_albums
|
|
190
|
+
client.library.like_album(id)
|
|
191
|
+
client.library.unlike_album(id)
|
|
192
|
+
|
|
193
|
+
# Artists
|
|
194
|
+
client.library.artists
|
|
195
|
+
client.library.artist(id)
|
|
196
|
+
|
|
197
|
+
# Tracks
|
|
198
|
+
client.library.tracks
|
|
199
|
+
client.library.track(id)
|
|
200
|
+
client.library.liked_tracks
|
|
201
|
+
client.library.like_track(id)
|
|
202
|
+
client.library.unlike_track(id)
|
|
203
|
+
|
|
204
|
+
# Search
|
|
205
|
+
results = client.library.search("daft punk")
|
|
206
|
+
results.artists # => Array<Rockbox::Artist>
|
|
207
|
+
results.albums # => Array<Rockbox::Album>
|
|
208
|
+
results.tracks # => Array<Rockbox::Track>
|
|
209
|
+
results.liked_tracks
|
|
210
|
+
results.liked_albums
|
|
211
|
+
|
|
212
|
+
# Library scan
|
|
213
|
+
client.library.scan
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Playlist (queue)
|
|
217
|
+
|
|
218
|
+
```ruby
|
|
219
|
+
playlist = client.playlist.current
|
|
220
|
+
playlist.amount # 42
|
|
221
|
+
playlist.index # currently playing index
|
|
222
|
+
playlist.tracks # Array<Rockbox::Track>
|
|
223
|
+
|
|
224
|
+
client.playlist.amount # convenience for playlist.current.amount
|
|
225
|
+
|
|
226
|
+
# Inserts (paths or track IDs)
|
|
227
|
+
client.playlist.insert_tracks(["/Music/a.mp3", "/Music/b.mp3"],
|
|
228
|
+
position: Rockbox::InsertPosition::NEXT)
|
|
229
|
+
client.playlist.insert_directory("/Music/Pink Floyd", position: Rockbox::InsertPosition::LAST)
|
|
230
|
+
client.playlist.insert_album(album_id)
|
|
231
|
+
|
|
232
|
+
# Mutations
|
|
233
|
+
client.playlist.remove_track(3)
|
|
234
|
+
client.playlist.clear
|
|
235
|
+
client.playlist.shuffle
|
|
236
|
+
|
|
237
|
+
# Create + start a temporary playlist
|
|
238
|
+
client.playlist.create("Tonight", ["/Music/a.mp3", "/Music/b.mp3"])
|
|
239
|
+
client.playlist.start(start_index: 0)
|
|
240
|
+
client.playlist.resume
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
`Rockbox::InsertPosition` constants: `NEXT`, `AFTER_CURRENT`, `LAST`, `FIRST`.
|
|
244
|
+
|
|
245
|
+
### Saved playlists
|
|
246
|
+
|
|
247
|
+
```ruby
|
|
248
|
+
client.saved_playlists.list # => Array<Rockbox::SavedPlaylist>
|
|
249
|
+
client.saved_playlists.list(folder_id: "f_1")
|
|
250
|
+
client.saved_playlists.get("pl_42")
|
|
251
|
+
client.saved_playlists.track_ids("pl_42")
|
|
252
|
+
|
|
253
|
+
# Builder block (any field is optional)
|
|
254
|
+
playlist = client.saved_playlists.create(name: "Late nights") do |p|
|
|
255
|
+
p.description = "After-dark vibes"
|
|
256
|
+
p.image = "https://…/cover.png"
|
|
257
|
+
p.track_ids = ["abc", "def"]
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
# Or pass kwargs directly
|
|
261
|
+
client.saved_playlists.update("pl_42", name: "Renamed", description: "…")
|
|
262
|
+
client.saved_playlists.add_tracks("pl_42", ["abc", "def"])
|
|
263
|
+
client.saved_playlists.remove_track("pl_42", "abc")
|
|
264
|
+
client.saved_playlists.delete("pl_42")
|
|
265
|
+
client.saved_playlists.play("pl_42")
|
|
266
|
+
|
|
267
|
+
# Folders
|
|
268
|
+
client.saved_playlists.folders
|
|
269
|
+
client.saved_playlists.create_folder("Workout")
|
|
270
|
+
client.saved_playlists.delete_folder("f_1")
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### Smart playlists
|
|
274
|
+
|
|
275
|
+
```ruby
|
|
276
|
+
client.smart_playlists.list
|
|
277
|
+
client.smart_playlists.get(id)
|
|
278
|
+
client.smart_playlists.track_ids(id)
|
|
279
|
+
client.smart_playlists.create(name: "Heavy hitters", rules: rules_json)
|
|
280
|
+
client.smart_playlists.update(id, name: "…", rules: new_rules)
|
|
281
|
+
client.smart_playlists.delete(id)
|
|
282
|
+
client.smart_playlists.play(id)
|
|
283
|
+
|
|
284
|
+
# Listening stats
|
|
285
|
+
client.smart_playlists.track_stats(track_id)
|
|
286
|
+
client.smart_playlists.record_played(track_id)
|
|
287
|
+
client.smart_playlists.record_skipped(track_id)
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### Sound
|
|
291
|
+
|
|
292
|
+
```ruby
|
|
293
|
+
info = client.sound.volume # => Rockbox::VolumeInfo(volume:, min:, max:)
|
|
294
|
+
client.sound.adjust(3) # +3 steps
|
|
295
|
+
client.sound.up # +1 step
|
|
296
|
+
client.sound.down # -1 step
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### Settings
|
|
300
|
+
|
|
301
|
+
```ruby
|
|
302
|
+
settings = client.settings.get # => Rockbox::UserSettings (Struct)
|
|
303
|
+
settings.volume # -20
|
|
304
|
+
settings.shuffle # false
|
|
305
|
+
|
|
306
|
+
# Save with a builder block — only the fields you set are sent.
|
|
307
|
+
client.settings.save do |s|
|
|
308
|
+
s.volume = -10
|
|
309
|
+
s.bass = 4
|
|
310
|
+
s.shuffle = true
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
# Or with a plain Hash.
|
|
314
|
+
client.settings.save(volume: -10, shuffle: true)
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
### System
|
|
318
|
+
|
|
319
|
+
```ruby
|
|
320
|
+
client.system.version # "Rockbox v…"
|
|
321
|
+
client.system.status # Rockbox::SystemStatus
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
### Browse (filesystem)
|
|
325
|
+
|
|
326
|
+
```ruby
|
|
327
|
+
client.browse.entries("/Music") # => Array<Rockbox::Entry>
|
|
328
|
+
client.browse.directories("/Music") # only directories
|
|
329
|
+
client.browse.files("/Music/Pink Floyd") # only files
|
|
330
|
+
|
|
331
|
+
entry = client.browse.entries.first
|
|
332
|
+
Rockbox.directory?(entry) # => true/false
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
### Devices
|
|
336
|
+
|
|
337
|
+
```ruby
|
|
338
|
+
client.devices.list # => Array<Rockbox::Device>
|
|
339
|
+
client.devices.get(id)
|
|
340
|
+
client.devices.connect(id)
|
|
341
|
+
client.devices.disconnect(id)
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
### Bluetooth
|
|
345
|
+
|
|
346
|
+
> Linux only. macOS/Windows builds will return errors.
|
|
347
|
+
|
|
348
|
+
```ruby
|
|
349
|
+
client.bluetooth.devices
|
|
350
|
+
client.bluetooth.scan(timeout: 5)
|
|
351
|
+
client.bluetooth.connect("AA:BB:CC:DD:EE:FF")
|
|
352
|
+
client.bluetooth.disconnect("AA:BB:CC:DD:EE:FF")
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
---
|
|
356
|
+
|
|
357
|
+
## Real-time events
|
|
358
|
+
|
|
359
|
+
Call `#connect` to open the WebSocket and start receiving events. The client speaks the GraphQL `graphql-transport-ws` subprotocol.
|
|
360
|
+
|
|
361
|
+
```ruby
|
|
362
|
+
client = Rockbox::Client.new
|
|
363
|
+
client.connect
|
|
364
|
+
|
|
365
|
+
client.on(:track_changed) { |track| puts "▶ #{track.title}" }
|
|
366
|
+
client.on(:status_changed) { |status| puts Rockbox::PlaybackStatus.name(status) }
|
|
367
|
+
client.on(:playlist_changed) { |playlist| puts "queue: #{playlist.amount} tracks" }
|
|
368
|
+
client.on(:ws_open) { puts "connected" }
|
|
369
|
+
client.on(:ws_close) { puts "disconnected" }
|
|
370
|
+
client.on(:ws_error) { |err| warn err.message }
|
|
371
|
+
|
|
372
|
+
# Once-only listener
|
|
373
|
+
client.once(:track_changed) { |t| puts "first track: #{t.title}" }
|
|
374
|
+
|
|
375
|
+
# Remove a specific listener with the same Proc
|
|
376
|
+
listener = ->(t) { puts t.title }
|
|
377
|
+
client.on(:track_changed, &listener)
|
|
378
|
+
client.off(:track_changed, listener)
|
|
379
|
+
|
|
380
|
+
# Remove all listeners for an event (or all events)
|
|
381
|
+
client.remove_all_listeners(:track_changed)
|
|
382
|
+
client.remove_all_listeners
|
|
383
|
+
|
|
384
|
+
# Tear down
|
|
385
|
+
client.disconnect
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
| Event | Payload |
|
|
389
|
+
|--------------------|--------------------------------------|
|
|
390
|
+
| `:track_changed` | `Rockbox::Track` |
|
|
391
|
+
| `:status_changed` | `Integer` (`Rockbox::PlaybackStatus`)|
|
|
392
|
+
| `:playlist_changed`| `Rockbox::Playlist` |
|
|
393
|
+
| `:ws_open` | `nil` |
|
|
394
|
+
| `:ws_close` | `nil` |
|
|
395
|
+
| `:ws_error` | `Exception` |
|
|
396
|
+
|
|
397
|
+
---
|
|
398
|
+
|
|
399
|
+
## Plugin system
|
|
400
|
+
|
|
401
|
+
Plugins are duck-typed objects with `#name`, `#version`, and `#install(context)`. Inherit from `Rockbox::Plugin` for sane defaults.
|
|
402
|
+
|
|
403
|
+
```ruby
|
|
404
|
+
class ConsoleScrobbler < Rockbox::Plugin
|
|
405
|
+
def name; "console-scrobbler" end
|
|
406
|
+
def version; "0.1.0" end
|
|
407
|
+
def description; "Logs every track change" end
|
|
408
|
+
|
|
409
|
+
def install(ctx)
|
|
410
|
+
ctx.events.on(:track_changed) do |track|
|
|
411
|
+
puts "♪ #{track.artist} — #{track.title}"
|
|
412
|
+
end
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
def uninstall
|
|
416
|
+
# cleanup
|
|
417
|
+
end
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
client.use(ConsoleScrobbler.new)
|
|
421
|
+
client.installed_plugins # => [<ConsoleScrobbler ...>]
|
|
422
|
+
client.unuse("console-scrobbler")
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
The `PluginContext` exposes:
|
|
426
|
+
|
|
427
|
+
- `ctx.query.call(gql, variables = nil)` — issue raw GraphQL operations
|
|
428
|
+
- `ctx.events` — the same `Rockbox::EventEmitter` used by the client
|
|
429
|
+
|
|
430
|
+
---
|
|
431
|
+
|
|
432
|
+
## Error handling
|
|
433
|
+
|
|
434
|
+
```ruby
|
|
435
|
+
begin
|
|
436
|
+
client.library.album("does-not-exist")
|
|
437
|
+
rescue Rockbox::GraphQLError => e
|
|
438
|
+
e.errors.each { |err| warn err[:message] }
|
|
439
|
+
rescue Rockbox::NetworkError => e
|
|
440
|
+
warn "rockboxd unreachable: #{e.message}"
|
|
441
|
+
end
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
| Class | Raised when… |
|
|
445
|
+
|---------------------------|----------------------------------------------|
|
|
446
|
+
| `Rockbox::Error` | Base class for every SDK error. |
|
|
447
|
+
| `Rockbox::NetworkError` | rockboxd is unreachable / non-2xx response. |
|
|
448
|
+
| `Rockbox::GraphQLError` | rockboxd returns a GraphQL `errors` payload. |
|
|
449
|
+
|
|
450
|
+
---
|
|
451
|
+
|
|
452
|
+
## Raw GraphQL queries
|
|
453
|
+
|
|
454
|
+
For operations the SDK doesn't yet wrap, use `#query` directly. Variables are camelized on the way out, response keys are snakeized on the way in.
|
|
455
|
+
|
|
456
|
+
```ruby
|
|
457
|
+
data = client.query(
|
|
458
|
+
"query LikedSongs { likedTracks { id title } }"
|
|
459
|
+
)
|
|
460
|
+
data[:liked_tracks].each { |t| puts t[:title] }
|
|
461
|
+
|
|
462
|
+
data = client.query(<<~GQL, { id: "abc" })
|
|
463
|
+
query Track($id: String!) {
|
|
464
|
+
track(id: $id) { id title artist }
|
|
465
|
+
}
|
|
466
|
+
GQL
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
---
|
|
470
|
+
|
|
471
|
+
## License
|
|
472
|
+
|
|
473
|
+
MIT License. See [LICENSE](./LICENSE) for details.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../types"
|
|
4
|
+
|
|
5
|
+
module Rockbox
|
|
6
|
+
module Api
|
|
7
|
+
class Bluetooth
|
|
8
|
+
FIELDS = <<~GQL
|
|
9
|
+
fragment BluetoothDeviceFields on BluetoothDevice {
|
|
10
|
+
address name paired trusted connected rssi
|
|
11
|
+
}
|
|
12
|
+
GQL
|
|
13
|
+
|
|
14
|
+
def initialize(http)
|
|
15
|
+
@http = http
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# List paired/known Bluetooth devices (Linux only).
|
|
19
|
+
def devices
|
|
20
|
+
data = @http.execute("#{FIELDS}\nquery BluetoothDevices { bluetoothDevices { ...BluetoothDeviceFields } }")
|
|
21
|
+
Array(data[:bluetooth_devices]).map { |d| BluetoothDevice.from_hash(d) }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Scan for nearby devices (Linux only).
|
|
25
|
+
def scan(timeout: nil)
|
|
26
|
+
data = @http.execute(<<~GQL, timeout ? { timeout_secs: timeout } : nil)
|
|
27
|
+
#{FIELDS}
|
|
28
|
+
mutation BluetoothScan($timeoutSecs: Int) {
|
|
29
|
+
bluetoothScan(timeoutSecs: $timeoutSecs) { ...BluetoothDeviceFields }
|
|
30
|
+
}
|
|
31
|
+
GQL
|
|
32
|
+
Array(data[:bluetooth_scan]).map { |d| BluetoothDevice.from_hash(d) }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def connect(address)
|
|
36
|
+
@http.execute(
|
|
37
|
+
"mutation BluetoothConnect($address: String!) { bluetoothConnect(address: $address) }",
|
|
38
|
+
{ address: address }
|
|
39
|
+
)
|
|
40
|
+
nil
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def disconnect(address)
|
|
44
|
+
@http.execute(
|
|
45
|
+
"mutation BluetoothDisconnect($address: String!) { bluetoothDisconnect(address: $address) }",
|
|
46
|
+
{ address: address }
|
|
47
|
+
)
|
|
48
|
+
nil
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../types"
|
|
4
|
+
|
|
5
|
+
module Rockbox
|
|
6
|
+
module Api
|
|
7
|
+
class Browse
|
|
8
|
+
def initialize(http)
|
|
9
|
+
@http = http
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# @param path [String, nil] absolute path; nil for the music root.
|
|
13
|
+
# @return [Array<Rockbox::Entry>]
|
|
14
|
+
def entries(path = nil)
|
|
15
|
+
data = @http.execute(
|
|
16
|
+
"query Browse($path: String) { treeGetEntries(path: $path) { name attr timeWrite customaction displayName } }",
|
|
17
|
+
path ? { path: path } : nil
|
|
18
|
+
)
|
|
19
|
+
Array(data[:tree_get_entries]).map { |e| Entry.from_hash(e) }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def directories(path = nil)
|
|
23
|
+
entries(path).select { |e| Rockbox.directory?(e) }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def files(path = nil)
|
|
27
|
+
entries(path).reject { |e| Rockbox.directory?(e) }
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../types"
|
|
4
|
+
|
|
5
|
+
module Rockbox
|
|
6
|
+
module Api
|
|
7
|
+
class Devices
|
|
8
|
+
FIELDS = "id name host ip port service app isConnected baseUrl isCastDevice isSourceDevice isCurrentDevice"
|
|
9
|
+
|
|
10
|
+
def initialize(http)
|
|
11
|
+
@http = http
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def list
|
|
15
|
+
data = @http.execute("query Devices { devices { #{FIELDS} } }")
|
|
16
|
+
Array(data[:devices]).map { |d| Device.from_hash(d) }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def get(id)
|
|
20
|
+
data = @http.execute(
|
|
21
|
+
"query Device($id: String!) { device(id: $id) { #{FIELDS} } }",
|
|
22
|
+
{ id: id }
|
|
23
|
+
)
|
|
24
|
+
Device.from_hash(data[:device])
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def connect(id)
|
|
28
|
+
@http.execute("mutation ConnectDevice($id: String!) { connect(id: $id) }", { id: id })
|
|
29
|
+
nil
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def disconnect(id)
|
|
33
|
+
@http.execute("mutation DisconnectDevice($id: String!) { disconnect(id: $id) }", { id: id })
|
|
34
|
+
nil
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|