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, codec, sampleRate / 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.



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

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:



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

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.



109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/rocksky/remote.rb', line 109

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.



104
105
106
# File 'lib/rocksky/remote.rb', line 104

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.



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

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.



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

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.



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

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. Optional audio info — codec (audio codec/container, e.g. "mp3", "flac") and sampleRate (sample rate in Hz, e.g. 44100) — is included in the track payload when set, and omitted otherwise.



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

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.



98
99
100
# File 'lib/rocksky/remote.rb', line 98

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".



92
93
94
# File 'lib/rocksky/remote.rb', line 92

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