rocksky/remote_controller
A Rocksky remote controller over the remote-control WebSocket (see
remote-ws/PROTOCOL.md) — the other half of rocksky/remote_player. It
lists the user’s players, observes what each is playing (now-playing /
status / queue), picks the primary device, and sends commands
(play / pause / next / previous / seek / queue actions / enqueue). Heartbeat,
reconnect, and the register handshake are handled by the native core.
Like the player it follows a poll model: build a controller with
connect, then call next_event in a loop on a
background process — it blocks until the next update (or returns Ok(None)
once disconnected):
let controller = remote_controller.connect(token, “My Controller”) case remote_controller.next_event(controller) { Ok(Some(remote_controller.Devices(_, devices))) -> render(devices) Ok(Some(remote_controller.NowPlayingEvent(id, _, track))) -> show(id, track) _ -> Nil } // …then drive a device (or pass None to broadcast to all): remote_controller.set_primary(controller, device_id) remote_controller.pause(controller, Some(device_id)) remote_controller.seek(controller, Some(device_id), 42_000)
The shared value types (NowPlaying,
QueueItem,
Status) and the {ok|error} envelope helpers
live in rocksky/remote_player and are reused here.
Types
A player device visible to the controller.
pub type Device {
Device(
device_id: String,
name: String,
now_playing: option.Option(remote_player.NowPlaying),
queue_index: Int,
queue: List(remote_player.QueueItem),
)
}
Constructors
-
Device( device_id: String, name: String, now_playing: option.Option(remote_player.NowPlaying), queue_index: Int, queue: List(remote_player.QueueItem), )
An update pushed to the controller, as decoded from next_event.
pub type Event {
Devices(
primary_device: option.Option(String),
devices: List(Device),
)
DeviceRegistered(device_id: String, name: String)
DeviceUnregistered(device_id: String)
PrimaryChanged(device_id: String)
NowPlayingEvent(
device_id: String,
device_name: String,
track: remote_player.NowPlaying,
)
StatusEvent(
device_id: String,
device_name: String,
status: remote_player.Status,
)
QueueEvent(
device_id: String,
device_name: String,
index: Int,
queue: List(remote_player.QueueItem),
)
}
Constructors
-
Devices( primary_device: option.Option(String), devices: List(Device), )The full device snapshot (on connect + whenever it changes).
-
DeviceRegistered(device_id: String, name: String) -
DeviceUnregistered(device_id: String) -
PrimaryChanged(device_id: String) -
NowPlayingEvent( device_id: String, device_name: String, track: remote_player.NowPlaying, ) -
StatusEvent( device_id: String, device_name: String, status: remote_player.Status, ) -
QueueEvent( device_id: String, device_name: String, index: Int, queue: List(remote_player.QueueItem), )
An opaque handle to a connected controller — a NIF resource freed by the BEAM GC. Build it with connect.
pub opaque type RemoteController
Values
pub fn connect(token: String, name: String) -> RemoteController
Connect and register a controller in the background. token is a Rocksky
access token; name is a registration label (controllers are hidden from
device lists).
pub fn connect_at(
token: String,
name: String,
url: String,
) -> RemoteController
connect against a custom WebSocket endpoint.
pub fn disconnect(
controller: RemoteController,
) -> Result(Nil, remote_player.RemoteError)
Disconnect and stop the background task.
pub fn enqueue(
controller: RemoteController,
target: option.Option(String),
tracks: List(remote_player.QueueItem),
mode: String,
shuffle: Bool,
start_index: Int,
) -> Result(Nil, remote_player.RemoteError)
Enqueue tracks on the target device. mode is "now" / "next" / "last";
None target broadcasts.
pub fn next(
controller: RemoteController,
target: option.Option(String),
) -> Result(Nil, remote_player.RemoteError)
next (see play for target).
pub fn next_event(
controller: RemoteController,
) -> Result(option.Option(Event), remote_player.RemoteError)
Block until the next update. Returns Ok(None) once the controller is
disconnected — the loop should stop then.
pub fn pause(
controller: RemoteController,
target: option.Option(String),
) -> Result(Nil, remote_player.RemoteError)
pause (see play for target).
pub fn play(
controller: RemoteController,
target: option.Option(String),
) -> Result(Nil, remote_player.RemoteError)
play. Pass Some(device_id) to target one device, or None to broadcast
to all of them.
pub fn previous(
controller: RemoteController,
target: option.Option(String),
) -> Result(Nil, remote_player.RemoteError)
previous (see play for target).
pub fn queue_jump(
controller: RemoteController,
target: option.Option(String),
index: Int,
) -> Result(Nil, remote_player.RemoteError)
Jump to index in the target device’s queue (None target broadcasts).
pub fn queue_remove(
controller: RemoteController,
target: option.Option(String),
index: Int,
) -> Result(Nil, remote_player.RemoteError)
Remove queue item index on the target device (None target broadcasts).
pub fn seek(
controller: RemoteController,
target: option.Option(String),
position_ms: Int,
) -> Result(Nil, remote_player.RemoteError)
Seek the target device to position_ms (None target broadcasts).
pub fn set_primary(
controller: RemoteController,
device_id: String,
) -> Result(Nil, remote_player.RemoteError)
Choose the primary (scrobble / profile) device.