Class: Rocksky::RemotePlayer

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

Overview

RemotePlayer — build a Rocksky-controllable player in a few lines.

It speaks the remote-control WebSocket protocol (see remote-ws/PROTOCOL.md) over the shared Rust core: it registers as a device, advertises what you're playing (now-playing / status / queue), and invokes your handlers when a miniplayer sends a command (play/pause/next/previous/seek/enqueue/queue actions). Heartbeat, reconnect, and the device-id handshake are handled for you inside the core.

Commands arrive over a blocking poll (#next_command). Register handlers with #on and call #listen to spawn a background thread that dispatches them, so you never write the loop yourself.

player = Rocksky::RemotePlayer.connect(token, "My Player")
player.on(:play)  { engine.play }
player.on(:pause) { engine.pause }
player.on(:seek)  { |cmd| engine.seek(cmd[:position]) }
player.listen
# …then, as your engine plays:
player.set_now_playing(title: "Chaser", artist: "Calibro 35",
                     durationMs: 182_320, elapsedMs: 0, isPlaying: true)
player.set_status("playing")
player.set_queue(items, 0)

Records (now-playing / queue items) are passed as Hashes with camelCase keys (title, artist, album, albumArtist, albumArt, durationMs, elapsedMs, isPlaying / songUri, albumUri, trackNumber), matching the wire record shape.

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(ptr) ⇒ RemotePlayer

Returns a new instance of RemotePlayer.



42
43
44
45
46
# File 'lib/rocksky/remote.rb', line 42

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

Class Method Details

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

Connect and register a controllable player in the background. token is a Rocksky access token (JWT); name is the device-picker label; url defaults to the public endpoint when omitted.

Raises:



35
36
37
38
39
40
# File 'lib/rocksky/remote.rb', line 35

def self.connect(token, name, url: nil)
  ptr = C.rocksky_remote_player_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 player is unusable afterwards.



105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/rocksky/remote.rb', line 105

def close
  return if @ptr.null?

  # Stop the background task so a blocked #next_command 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_player_disconnect(@ptr)
  @thread&.join unless @thread == Thread.current
  @thread = nil
  C.rocksky_remote_player_free(@ptr)
  @ptr = Fiddle::Pointer.new(0)
end

#disconnectObject

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



100
101
102
# File 'lib/rocksky/remote.rb', line 100

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

#listenObject

Spawn a background thread that polls #next_command and dispatches each command to its registered handler until the player disconnects. Returns the Thread.



69
70
71
72
73
74
75
76
77
78
# File 'lib/rocksky/remote.rb', line 69

def listen
  @thread ||= Thread.new do
    loop do
      cmd = next_command
      break if cmd.nil?

      dispatch(cmd)
    end
  end
end

#next_commandObject

Block until the next controller command, returned as a symbol-keyed Hash (e.g. { action: "play" }), or nil once disconnected.



59
60
61
62
63
64
# File 'lib/rocksky/remote.rb', line 59

def next_command
  return nil if @ptr.null?

  cmd = Rocksky.unwrap(C.rocksky_remote_player_next_command(@ptr))
  cmd && Rocksky.symbolize(cmd)
end

#on(action, &block) ⇒ Object

Register a handler for a command action, one of :play, :pause, :next, :previous, :seek, :queue_jump, :queue_remove, :enqueue. The block receives the full symbol-keyed command Hash (e.g. +{ action: "seek", position: 42000 }+). Returns self for chaining.



52
53
54
55
# File 'lib/rocksky/remote.rb', line 52

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

#set_now_playing(track) ⇒ Object

Advertise the currently-playing track. Call whenever it changes, and periodically (~every 1–4s) with a fresh elapsedMs so controllers show smooth progress. track is a camelCase Hash.



83
84
85
# File 'lib/rocksky/remote.rb', line 83

def set_now_playing(track)
  Rocksky.unwrap(C.rocksky_remote_player_set_now_playing(@ptr, JSON.generate(track)))
end

#set_queue(items, index) ⇒ Object

Advertise the playback queue (+items+ is an array of camelCase queue-item Hashes) and the active index.



94
95
96
# File 'lib/rocksky/remote.rb', line 94

def set_queue(items, index)
  Rocksky.unwrap(C.rocksky_remote_player_set_queue(@ptr, JSON.generate(items), index))
end

#set_status(status) ⇒ Object

Advertise transport state: "playing", "paused", or "stopped".



88
89
90
# File 'lib/rocksky/remote.rb', line 88

def set_status(status)
  Rocksky.unwrap(C.rocksky_remote_player_set_status(@ptr, status.to_s))
end