rocksky 0.8.1 → 0.9.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 +4 -4
- data/lib/rocksky/manifest.json +7 -7
- data/lib/rocksky/remote.rb +260 -0
- data/lib/rocksky/version.rb +1 -1
- data/lib/rocksky.rb +35 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e27b27d24d5d5c59963b65031fb641ecb90cb322a9107f3a62a431e6d1973a0c
|
|
4
|
+
data.tar.gz: 4a314bfa67627103279e453ab7f3563f3c0265c02ca0c1db1ca88259ca26cb33
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 498b7790bd6961f4a5e118ab4485bf124afb9283fb838a38f6c21195fa50ba47e22bc10ae98518e6804401e3b8de63e5580cb8957a3690f0dcf39a4e72d18de9
|
|
7
|
+
data.tar.gz: 3f1bb359b30802470a0c7d59ea921850ee43060bba3c67ba8f672eebb1d01c3d6814135462b47c029104bbfb3f5bb32de2b592ffab590c6521f703c3b37995f9
|
data/lib/rocksky/manifest.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"repo": "tsirysndr/rocksky",
|
|
3
|
-
"tag": "bindings-v0.
|
|
3
|
+
"tag": "bindings-v0.6.0",
|
|
4
4
|
"checksums": {
|
|
5
|
-
"aarch64-apple-darwin": "
|
|
6
|
-
"aarch64-linux-gnu": "
|
|
7
|
-
"x86_64-apple-darwin": "
|
|
8
|
-
"x86_64-linux-gnu": "
|
|
9
|
-
"x86_64-unknown-freebsd": "
|
|
10
|
-
"x86_64-unknown-netbsd": "
|
|
5
|
+
"aarch64-apple-darwin": "e2de89af96e1330427898524254ddb57728453947b607c4dd29fb8ed7c513b27",
|
|
6
|
+
"aarch64-linux-gnu": "37b8f5ec3812cb15f7eeb75beb44ffe857cca95c11064d1e781eaff6edf7c14d",
|
|
7
|
+
"x86_64-apple-darwin": "7a550b51fc75e1e4d7c5393423f2920dda980ea8f026c9bde8edd926d18d3795",
|
|
8
|
+
"x86_64-linux-gnu": "683e9c14579041c77fa69b53f73b43a44432719c608d7af74f950c9263c5c53b",
|
|
9
|
+
"x86_64-unknown-freebsd": "4ecf5a61000b46111aef2c66e241c97e712c8ef6af386a01afddfe2c05136893",
|
|
10
|
+
"x86_64-unknown-netbsd": "0c619e60f6cf95c42bbe61adb26be8550024c8cbea08be6de9fb3127db627900"
|
|
11
11
|
}
|
|
12
12
|
}
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rocksky
|
|
4
|
+
# RemotePlayer — build a Rocksky-controllable player in a few lines.
|
|
5
|
+
#
|
|
6
|
+
# It speaks the remote-control WebSocket protocol (see remote-ws/PROTOCOL.md)
|
|
7
|
+
# over the shared Rust core: it registers as a device, advertises what you're
|
|
8
|
+
# playing (now-playing / status / queue), and invokes your handlers when a
|
|
9
|
+
# miniplayer sends a command (play/pause/next/previous/seek/enqueue/queue
|
|
10
|
+
# actions). Heartbeat, reconnect, and the device-id handshake are handled for
|
|
11
|
+
# you inside the core.
|
|
12
|
+
#
|
|
13
|
+
# Commands arrive over a blocking poll (#next_command). Register handlers with
|
|
14
|
+
# #on and call #listen to spawn a background thread that dispatches them, so you
|
|
15
|
+
# never write the loop yourself.
|
|
16
|
+
#
|
|
17
|
+
# player = Rocksky::RemotePlayer.connect(token, "My Player")
|
|
18
|
+
# player.on(:play) { engine.play }
|
|
19
|
+
# player.on(:pause) { engine.pause }
|
|
20
|
+
# player.on(:seek) { |cmd| engine.seek(cmd[:position]) }
|
|
21
|
+
# player.listen
|
|
22
|
+
# # …then, as your engine plays:
|
|
23
|
+
# player.set_now_playing(title: "Chaser", artist: "Calibro 35",
|
|
24
|
+
# durationMs: 182_320, elapsedMs: 0, isPlaying: true)
|
|
25
|
+
# player.set_status("playing")
|
|
26
|
+
# player.set_queue(items, 0)
|
|
27
|
+
#
|
|
28
|
+
# Records (now-playing / queue items) are passed as Hashes with camelCase keys
|
|
29
|
+
# (title, artist, album, albumArtist, albumArt, durationMs, elapsedMs,
|
|
30
|
+
# isPlaying / songUri, albumUri, trackNumber), matching the wire record shape.
|
|
31
|
+
class RemotePlayer
|
|
32
|
+
# Connect and register a controllable player in the background. +token+ is a
|
|
33
|
+
# Rocksky access token (JWT); +name+ is the device-picker label; +url+
|
|
34
|
+
# defaults to the public endpoint when omitted.
|
|
35
|
+
def self.connect(token, name, url: nil)
|
|
36
|
+
ptr = C.rocksky_remote_player_connect(token.to_s, name.to_s, url.to_s)
|
|
37
|
+
raise Error, (Rocksky.take_string(C.rocksky_last_error()) || "connect failed") if ptr.null?
|
|
38
|
+
|
|
39
|
+
new(ptr)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def initialize(ptr)
|
|
43
|
+
@ptr = ptr
|
|
44
|
+
@handlers = {}
|
|
45
|
+
@thread = nil
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Register a handler for a command action, one of +:play+, +:pause+, +:next+,
|
|
49
|
+
# +:previous+, +:seek+, +:queue_jump+, +:queue_remove+, +:enqueue+. The block
|
|
50
|
+
# receives the full symbol-keyed command Hash (e.g. +{ action: "seek",
|
|
51
|
+
# position: 42000 }+). Returns +self+ for chaining.
|
|
52
|
+
def on(action, &block)
|
|
53
|
+
@handlers[action.to_sym] = block
|
|
54
|
+
self
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Block until the next controller command, returned as a symbol-keyed Hash
|
|
58
|
+
# (e.g. +{ action: "play" }+), or +nil+ once disconnected.
|
|
59
|
+
def next_command
|
|
60
|
+
cmd = Rocksky.unwrap(C.rocksky_remote_player_next_command(@ptr))
|
|
61
|
+
cmd && Rocksky.symbolize(cmd)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Spawn a background thread that polls #next_command and dispatches each
|
|
65
|
+
# command to its registered handler until the player disconnects. Returns the
|
|
66
|
+
# Thread.
|
|
67
|
+
def listen
|
|
68
|
+
@thread ||= Thread.new do
|
|
69
|
+
loop do
|
|
70
|
+
cmd = next_command
|
|
71
|
+
break if cmd.nil?
|
|
72
|
+
|
|
73
|
+
dispatch(cmd)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Advertise the currently-playing track. Call whenever it changes, and
|
|
79
|
+
# periodically (~every 1–4s) with a fresh +elapsedMs+ so controllers show
|
|
80
|
+
# smooth progress. +track+ is a camelCase Hash.
|
|
81
|
+
def set_now_playing(track)
|
|
82
|
+
Rocksky.unwrap(C.rocksky_remote_player_set_now_playing(@ptr, JSON.generate(track)))
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Advertise transport state: "playing", "paused", or "stopped".
|
|
86
|
+
def set_status(status)
|
|
87
|
+
Rocksky.unwrap(C.rocksky_remote_player_set_status(@ptr, status.to_s))
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Advertise the playback queue (+items+ is an array of camelCase queue-item
|
|
91
|
+
# Hashes) and the active +index+.
|
|
92
|
+
def set_queue(items, index)
|
|
93
|
+
Rocksky.unwrap(C.rocksky_remote_player_set_queue(@ptr, JSON.generate(items), index))
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Disconnect and stop the background task. #next_command then returns nil,
|
|
97
|
+
# which ends the #listen thread. The handle stays valid until #close.
|
|
98
|
+
def disconnect
|
|
99
|
+
Rocksky.unwrap(C.rocksky_remote_player_disconnect(@ptr))
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Release the native handle. The player is unusable afterwards.
|
|
103
|
+
def close
|
|
104
|
+
return if @ptr.null?
|
|
105
|
+
|
|
106
|
+
@thread&.kill
|
|
107
|
+
@thread = nil
|
|
108
|
+
C.rocksky_remote_player_free(@ptr)
|
|
109
|
+
@ptr = Fiddle::Pointer.new(0)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
private
|
|
113
|
+
|
|
114
|
+
def dispatch(cmd)
|
|
115
|
+
handler = @handlers[cmd[:action]&.to_sym]
|
|
116
|
+
handler&.call(cmd)
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# RemoteController — build a Rocksky remote UI in a few lines.
|
|
121
|
+
#
|
|
122
|
+
# The other half of the remote-control WebSocket protocol: where
|
|
123
|
+
# {RemotePlayer} is a controllable player, a controller lists the user's
|
|
124
|
+
# players, observes what each is playing (now-playing / status / queue), picks
|
|
125
|
+
# the primary device, and sends commands. Heartbeat, reconnect, and the
|
|
126
|
+
# register handshake are handled for you inside the core.
|
|
127
|
+
#
|
|
128
|
+
# Events arrive over a blocking poll (#next_event). Register handlers with #on
|
|
129
|
+
# and call #listen to spawn a background dispatch thread.
|
|
130
|
+
#
|
|
131
|
+
# controller = Rocksky::RemoteController.connect(token, "My Controller")
|
|
132
|
+
# controller.on(:devices) { |e| render(e[:devices]) }
|
|
133
|
+
# controller.on(:now_playing) { |e| show_track(e[:deviceId], e[:track]) }
|
|
134
|
+
# controller.listen
|
|
135
|
+
# # …then drive a device:
|
|
136
|
+
# controller.set_primary(device_id)
|
|
137
|
+
# controller.pause(device_id)
|
|
138
|
+
# controller.seek(device_id, 42_000)
|
|
139
|
+
#
|
|
140
|
+
# A +nil+ / omitted +target+ broadcasts a command to all of the user's devices.
|
|
141
|
+
class RemoteController
|
|
142
|
+
# Connect and register a controller in the background. +url+ defaults to the
|
|
143
|
+
# public endpoint when omitted.
|
|
144
|
+
def self.connect(token, name, url: nil)
|
|
145
|
+
ptr = C.rocksky_remote_controller_connect(token.to_s, name.to_s, url.to_s)
|
|
146
|
+
raise Error, (Rocksky.take_string(C.rocksky_last_error()) || "connect failed") if ptr.null?
|
|
147
|
+
|
|
148
|
+
new(ptr)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def initialize(ptr)
|
|
152
|
+
@ptr = ptr
|
|
153
|
+
@handlers = {}
|
|
154
|
+
@thread = nil
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Register a handler for a server event type, one of +:devices+,
|
|
158
|
+
# +:device_registered+, +:device_unregistered+, +:primary_changed+,
|
|
159
|
+
# +:now_playing+, +:status+, +:queue+. The block receives the full
|
|
160
|
+
# symbol-keyed event Hash. Returns +self+ for chaining.
|
|
161
|
+
def on(event_type, &block)
|
|
162
|
+
@handlers[event_type.to_sym] = block
|
|
163
|
+
self
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Block until the next update, returned as a symbol-keyed Hash (each carries a
|
|
167
|
+
# +:type+), or +nil+ once disconnected.
|
|
168
|
+
def next_event
|
|
169
|
+
ev = Rocksky.unwrap(C.rocksky_remote_controller_next_event(@ptr))
|
|
170
|
+
ev && Rocksky.symbolize(ev)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Spawn a background thread that polls #next_event and dispatches each event
|
|
174
|
+
# to its registered handler until the controller disconnects. Returns the
|
|
175
|
+
# Thread.
|
|
176
|
+
def listen
|
|
177
|
+
@thread ||= Thread.new do
|
|
178
|
+
loop do
|
|
179
|
+
ev = next_event
|
|
180
|
+
break if ev.nil?
|
|
181
|
+
|
|
182
|
+
dispatch(ev)
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Choose the primary (scrobble/profile) device.
|
|
188
|
+
def set_primary(device_id)
|
|
189
|
+
Rocksky.unwrap(C.rocksky_remote_controller_set_primary(@ptr, device_id.to_s))
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# +play+. +target+ is a device id, or omit/nil to broadcast to all devices.
|
|
193
|
+
def play(target = nil)
|
|
194
|
+
command("play", target)
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def pause(target = nil)
|
|
198
|
+
command("pause", target)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def next(target = nil)
|
|
202
|
+
command("next", target)
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def previous(target = nil)
|
|
206
|
+
command("previous", target)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Send a simple command ("play" | "pause" | "next" | "previous").
|
|
210
|
+
def command(action, target = nil)
|
|
211
|
+
Rocksky.unwrap(C.rocksky_remote_controller_command(@ptr, action.to_s, target.to_s))
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# Seek +target+ to +position_ms+ (nil target = broadcast).
|
|
215
|
+
def seek(target, position_ms)
|
|
216
|
+
Rocksky.unwrap(C.rocksky_remote_controller_seek(@ptr, target.to_s, position_ms))
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# Jump to +index+ in +target+'s queue (nil target = broadcast).
|
|
220
|
+
def queue_jump(target, index)
|
|
221
|
+
Rocksky.unwrap(C.rocksky_remote_controller_queue_jump(@ptr, target.to_s, index))
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# Remove queue item +index+ on +target+ (nil target = broadcast).
|
|
225
|
+
def queue_remove(target, index)
|
|
226
|
+
Rocksky.unwrap(C.rocksky_remote_controller_queue_remove(@ptr, target.to_s, index))
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# Enqueue +tracks+ (array of camelCase queue-item Hashes) on +target+. +mode+
|
|
230
|
+
# is "now" | "next" | "last"; nil target = broadcast.
|
|
231
|
+
def enqueue(target, tracks, mode: "now", shuffle: false, start_index: 0)
|
|
232
|
+
Rocksky.unwrap(C.rocksky_remote_controller_enqueue(
|
|
233
|
+
@ptr, target.to_s, JSON.generate(tracks), mode.to_s, shuffle ? 1 : 0, start_index
|
|
234
|
+
))
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# Disconnect and stop the background task. #next_event then returns nil, which
|
|
238
|
+
# ends the #listen thread. The handle stays valid until #close.
|
|
239
|
+
def disconnect
|
|
240
|
+
Rocksky.unwrap(C.rocksky_remote_controller_disconnect(@ptr))
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# Release the native handle. The controller is unusable afterwards.
|
|
244
|
+
def close
|
|
245
|
+
return if @ptr.null?
|
|
246
|
+
|
|
247
|
+
@thread&.kill
|
|
248
|
+
@thread = nil
|
|
249
|
+
C.rocksky_remote_controller_free(@ptr)
|
|
250
|
+
@ptr = Fiddle::Pointer.new(0)
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
private
|
|
254
|
+
|
|
255
|
+
def dispatch(event)
|
|
256
|
+
handler = @handlers[event[:type]&.to_sym]
|
|
257
|
+
handler&.call(event)
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
end
|
data/lib/rocksky/version.rb
CHANGED
data/lib/rocksky.rb
CHANGED
|
@@ -47,7 +47,26 @@ module Rocksky
|
|
|
47
47
|
"char* rocksky_agent_shout(void*, const char*, const char*, const char*)",
|
|
48
48
|
"char* rocksky_agent_shout_with_gif(void*, const char*, const char*, const char*, const char*)",
|
|
49
49
|
"char* rocksky_agent_reply_shout_with_gif(void*, const char*, const char*, const char*, const char*, const char*, const char*)",
|
|
50
|
-
"char* rocksky_agent_refresh_session(void*)"
|
|
50
|
+
"char* rocksky_agent_refresh_session(void*)",
|
|
51
|
+
# ---- remote player (a controllable device) ----
|
|
52
|
+
"void* rocksky_remote_player_connect(const char*, const char*, const char*)",
|
|
53
|
+
"char* rocksky_remote_player_next_command(void*)",
|
|
54
|
+
"char* rocksky_remote_player_set_now_playing(void*, const char*)",
|
|
55
|
+
"char* rocksky_remote_player_set_status(void*, const char*)",
|
|
56
|
+
"char* rocksky_remote_player_set_queue(void*, const char*, unsigned int)",
|
|
57
|
+
"char* rocksky_remote_player_disconnect(void*)",
|
|
58
|
+
"void rocksky_remote_player_free(void*)",
|
|
59
|
+
# ---- remote controller (a remote UI: drive & observe players) ----
|
|
60
|
+
"void* rocksky_remote_controller_connect(const char*, const char*, const char*)",
|
|
61
|
+
"char* rocksky_remote_controller_next_event(void*)",
|
|
62
|
+
"char* rocksky_remote_controller_set_primary(void*, const char*)",
|
|
63
|
+
"char* rocksky_remote_controller_command(void*, const char*, const char*)",
|
|
64
|
+
"char* rocksky_remote_controller_seek(void*, const char*, unsigned long long)",
|
|
65
|
+
"char* rocksky_remote_controller_queue_jump(void*, const char*, unsigned int)",
|
|
66
|
+
"char* rocksky_remote_controller_queue_remove(void*, const char*, unsigned int)",
|
|
67
|
+
"char* rocksky_remote_controller_enqueue(void*, const char*, const char*, const char*, char, unsigned int)",
|
|
68
|
+
"char* rocksky_remote_controller_disconnect(void*)",
|
|
69
|
+
"void rocksky_remote_controller_free(void*)"
|
|
51
70
|
].each { |sig| extern sig }
|
|
52
71
|
end
|
|
53
72
|
private_constant :C
|
|
@@ -76,6 +95,20 @@ module Rocksky
|
|
|
76
95
|
parsed["ok"]
|
|
77
96
|
end
|
|
78
97
|
|
|
98
|
+
# Recursively convert Hash string keys to symbols (Arrays walked too). Used by
|
|
99
|
+
# the remote-control glue so commands/events reach handlers as symbol-keyed
|
|
100
|
+
# hashes (e.g. cmd[:action], event[:type]).
|
|
101
|
+
def self.symbolize(obj)
|
|
102
|
+
case obj
|
|
103
|
+
when Hash
|
|
104
|
+
obj.each_with_object({}) { |(k, v), acc| acc[k.to_sym] = symbolize(v) }
|
|
105
|
+
when Array
|
|
106
|
+
obj.map { |e| symbolize(e) }
|
|
107
|
+
else
|
|
108
|
+
obj
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
79
112
|
# ---- reads (unauthenticated; base: overrides the AppView URL) ----
|
|
80
113
|
|
|
81
114
|
def self.profile(actor, base: nil)
|
|
@@ -269,3 +302,4 @@ module Rocksky
|
|
|
269
302
|
end
|
|
270
303
|
|
|
271
304
|
require_relative "rocksky/library"
|
|
305
|
+
require_relative "rocksky/remote"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rocksky
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.9.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Rocksky
|
|
@@ -83,6 +83,7 @@ files:
|
|
|
83
83
|
- lib/rocksky/library.rb
|
|
84
84
|
- lib/rocksky/manifest.json
|
|
85
85
|
- lib/rocksky/native.rb
|
|
86
|
+
- lib/rocksky/remote.rb
|
|
86
87
|
- lib/rocksky/version.rb
|
|
87
88
|
- rocksky.gemspec
|
|
88
89
|
homepage: https://github.com/tsirysndr/rocksky
|