Class: Rocksky::RemoteController

Inherits:
Object
  • Object
show all
Defined in:
lib/rocksky/remote.rb

Overview

RemoteController — build a Rocksky remote UI in a few lines.

The other half of the remote-control WebSocket protocol: where RemotePlayer is a controllable player, a controller lists the user's players, observes what each is playing (now-playing / status / queue), picks the primary device, and sends commands. Heartbeat, reconnect, and the register handshake are handled for you inside the core.

Events arrive over a blocking poll (#next_event). Register handlers with #on and call #listen to spawn a background dispatch thread.

controller = Rocksky::RemoteController.connect(token, "My Controller")
controller.on(:devices)     { |e| render(e[:devices]) }
controller.on(:now_playing) { |e| show_track(e[:deviceId], e[:track]) }
controller.listen
# …then drive a device:
controller.set_primary(device_id)
controller.pause(device_id)
controller.seek(device_id, 42_000)

A nil / omitted target broadcasts a command to all of the user's devices.

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(ptr) ⇒ RemoteController

Returns a new instance of RemoteController.



158
159
160
161
162
# File 'lib/rocksky/remote.rb', line 158

def initialize(ptr)
  @ptr = ptr
  @handlers = {}
  @thread = nil
end

Class Method Details

.connect(token, name, url: nil) ⇒ Object

Connect and register a controller in the background. url defaults to the public endpoint when omitted.

Raises:



151
152
153
154
155
156
# File 'lib/rocksky/remote.rb', line 151

def self.connect(token, name, url: nil)
  ptr = C.rocksky_remote_controller_connect(token.to_s, name.to_s, url.to_s)
  raise Error, (Rocksky.take_string(C.rocksky_last_error()) || "connect failed") if ptr.null?

  new(ptr)
end

Instance Method Details

#closeObject

Release the native handle. The controller is unusable afterwards.



253
254
255
256
257
258
259
260
261
262
263
264
265
# File 'lib/rocksky/remote.rb', line 253

def close
  return if @ptr.null?

  # Stop the background task so a blocked #next_event returns nil and the
  # #listen loop exits, then WAIT for that thread to leave the native call
  # before freeing — freeing the handle while a poll is in flight is a
  # use-after-free (segfault).
  C.rocksky_remote_controller_disconnect(@ptr)
  @thread&.join unless @thread == Thread.current
  @thread = nil
  C.rocksky_remote_controller_free(@ptr)
  @ptr = Fiddle::Pointer.new(0)
end

#command(action, target = nil) ⇒ Object

Send a simple command ("play" | "pause" | "next" | "previous").



219
220
221
# File 'lib/rocksky/remote.rb', line 219

def command(action, target = nil)
  Rocksky.unwrap(C.rocksky_remote_controller_command(@ptr, action.to_s, target.to_s))
end

#disconnectObject

Disconnect and stop the background task. #next_event then returns nil, which ends the #listen thread. The handle stays valid until #close.



248
249
250
# File 'lib/rocksky/remote.rb', line 248

def disconnect
  Rocksky.unwrap(C.rocksky_remote_controller_disconnect(@ptr))
end

#enqueue(target, tracks, mode: "now", shuffle: false, start_index: 0) ⇒ Object

Enqueue tracks (array of camelCase queue-item Hashes) on target. mode is "now" | "next" | "last"; nil target = broadcast.



240
241
242
243
244
# File 'lib/rocksky/remote.rb', line 240

def enqueue(target, tracks, mode: "now", shuffle: false, start_index: 0)
  Rocksky.unwrap(C.rocksky_remote_controller_enqueue(
                   @ptr, target.to_s, JSON.generate(tracks), mode.to_s, shuffle ? 1 : 0, start_index
                 ))
end

#listenObject

Spawn a background thread that polls #next_event and dispatches each event to its registered handler until the controller disconnects. Returns the Thread.



185
186
187
188
189
190
191
192
193
194
# File 'lib/rocksky/remote.rb', line 185

def listen
  @thread ||= Thread.new do
    loop do
      ev = next_event
      break if ev.nil?

      dispatch(ev)
    end
  end
end

#next(target = nil) ⇒ Object



210
211
212
# File 'lib/rocksky/remote.rb', line 210

def next(target = nil)
  command("next", target)
end

#next_eventObject

Block until the next update, returned as a symbol-keyed Hash (each carries a :type), or nil once disconnected.



175
176
177
178
179
180
# File 'lib/rocksky/remote.rb', line 175

def next_event
  return nil if @ptr.null?

  ev = Rocksky.unwrap(C.rocksky_remote_controller_next_event(@ptr))
  ev && Rocksky.symbolize(ev)
end

#on(event_type, &block) ⇒ Object

Register a handler for a server event type, one of :devices, :device_registered, :device_unregistered, :primary_changed, :now_playing, :status, :queue. The block receives the full symbol-keyed event Hash. Returns self for chaining.



168
169
170
171
# File 'lib/rocksky/remote.rb', line 168

def on(event_type, &block)
  @handlers[event_type.to_sym] = block
  self
end

#pause(target = nil) ⇒ Object



206
207
208
# File 'lib/rocksky/remote.rb', line 206

def pause(target = nil)
  command("pause", target)
end

#play(target = nil) ⇒ Object

play. target is a device id, or omit/nil to broadcast to all devices.



202
203
204
# File 'lib/rocksky/remote.rb', line 202

def play(target = nil)
  command("play", target)
end

#previous(target = nil) ⇒ Object



214
215
216
# File 'lib/rocksky/remote.rb', line 214

def previous(target = nil)
  command("previous", target)
end

#queue_jump(target, index) ⇒ Object

Jump to index in +target+'s queue (nil target = broadcast).



229
230
231
# File 'lib/rocksky/remote.rb', line 229

def queue_jump(target, index)
  Rocksky.unwrap(C.rocksky_remote_controller_queue_jump(@ptr, target.to_s, index))
end

#queue_remove(target, index) ⇒ Object

Remove queue item index on target (nil target = broadcast).



234
235
236
# File 'lib/rocksky/remote.rb', line 234

def queue_remove(target, index)
  Rocksky.unwrap(C.rocksky_remote_controller_queue_remove(@ptr, target.to_s, index))
end

#seek(target, position_ms) ⇒ Object

Seek target to position_ms (nil target = broadcast).



224
225
226
# File 'lib/rocksky/remote.rb', line 224

def seek(target, position_ms)
  Rocksky.unwrap(C.rocksky_remote_controller_seek(@ptr, target.to_s, position_ms))
end

#set_primary(device_id) ⇒ Object

Choose the primary (scrobble/profile) device.



197
198
199
# File 'lib/rocksky/remote.rb', line 197

def set_primary(device_id)
  Rocksky.unwrap(C.rocksky_remote_controller_set_primary(@ptr, device_id.to_s))
end