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

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.

Search Document