rocksky/remote_player

A Rocksky-controllable remote player over the remote-control WebSocket (see remote-ws/PROTOCOL.md). It registers as a device, advertises what you’re playing (now-playing / status / queue), and hands you the commands a miniplayer sends (play / pause / next / previous / seek / enqueue / queue actions). Heartbeat, reconnect, and the device-id handshake are handled by the native core.

It follows a poll model: build a player with connect, then call next_command in a loop on a background process — it blocks until the next command (or returns Ok(None) once disconnected):

let player = remote_player.connect(token, “My Player”) remote_player.set_status(player, remote_player.Playing) remote_player.set_now_playing(player, track) case remote_player.next_command(player) { Ok(Some(remote_player.Play)) -> engine_play() Ok(Some(remote_player.Seek(ms))) -> engine_seek(ms) _ -> Nil }

This module also owns the shared value types (NowPlaying, QueueItem, Status) and the {ok|error} envelope helpers that rocksky/remote_controller reuses.

Types

A command a controller sent to this player, as decoded from next_command.

pub type Command {
  Play
  Pause
  Next
  Previous
  Seek(position_ms: Int)
  QueueJump(index: Int)
  QueueRemove(index: Int)
  Enqueue(
    tracks: List(QueueItem),
    mode: String,
    shuffle: Bool,
    start_index: Int,
  )
}

Constructors

  • Play
  • Pause
  • Next
  • Previous
  • Seek(position_ms: Int)
  • QueueJump(index: Int)
  • QueueRemove(index: Int)
  • Enqueue(
      tracks: List(QueueItem),
      mode: String,
      shuffle: Bool,
      start_index: Int,
    )

Playback state you advertise with set_now_playing, and the shape carried by controller now-playing events.

pub type NowPlaying {
  NowPlaying(
    title: String,
    artist: String,
    album: String,
    album_artist: String,
    album_art: String,
    duration_ms: Int,
    elapsed_ms: Int,
    is_playing: Bool,
  )
}

Constructors

  • NowPlaying(
      title: String,
      artist: String,
      album: String,
      album_artist: String,
      album_art: String,
      duration_ms: Int,
      elapsed_ms: Int,
      is_playing: Bool,
    )

A single queue entry. Set upload_id (Rocksky uploads) or track_id (Navidrome id); the rest is display metadata.

pub type QueueItem {
  QueueItem(
    upload_id: String,
    track_id: String,
    title: String,
    artist: String,
    album: String,
    album_artist: String,
    album_art: String,
    duration_ms: Int,
    song_uri: String,
    album_uri: String,
    track_number: Int,
  )
}

Constructors

  • QueueItem(
      upload_id: String,
      track_id: String,
      title: String,
      artist: String,
      album: String,
      album_artist: String,
      album_art: String,
      duration_ms: Int,
      song_uri: String,
      album_uri: String,
      track_number: Int,
    )

Why a poll or state push failed.

pub type RemoteError {
  CoreError(message: String)
  MalformedResponse(json.DecodeError)
}

Constructors

  • CoreError(message: String)

    The native core returned {"error": …}.

  • MalformedResponse(json.DecodeError)

    The {ok|error} envelope (or its payload) could not be parsed/decoded.

An opaque handle to a connected remote player — a NIF resource freed by the BEAM GC. Build it with connect.

pub opaque type RemotePlayer

Transport state.

pub type Status {
  Playing
  Paused
  Stopped
}

Constructors

  • Playing
  • Paused
  • Stopped

Values

pub fn connect(token: String, name: String) -> RemotePlayer

Connect and register a controllable player in the background. token is a Rocksky access token; name is the label shown in the device picker.

pub fn connect_at(
  token: String,
  name: String,
  url: String,
) -> RemotePlayer

connect against a custom WebSocket endpoint.

pub fn decode_ack(raw: String) -> Result(Nil, RemoteError)

Parse an envelope whose ok payload is an ignored acknowledgement.

pub fn decode_envelope(
  raw: String,
  inner: decode.Decoder(a),
) -> Result(a, RemoteError)

Parse a {"ok": <value>} / {"error": "…"} JSON envelope, decoding the ok payload with inner.

pub const default_remote_ws: String

The default remote-control WebSocket endpoint (used when no URL is given).

pub fn disconnect(
  player: RemotePlayer,
) -> Result(Nil, RemoteError)

Disconnect and stop the background task. The handle stays valid until it is garbage-collected.

pub fn next_command(
  player: RemotePlayer,
) -> Result(option.Option(Command), RemoteError)

Block until the next controller command. Returns Ok(None) once the player is disconnected — the loop should stop then.

pub fn now_playing_decoder() -> decode.Decoder(NowPlaying)

Decoder for a now-playing block as emitted by the native core (durationMs / elapsedMs keys).

pub fn queue_item_decoder() -> decode.Decoder(QueueItem)

Decoder for a queue item as emitted by the native core. Note the emitted shape uses the duration key (ms), unlike the durationMs input key.

pub fn queue_items_to_json(items: List(QueueItem)) -> String

Encode queue items to the JSON array the native set_queue / enqueue entrypoints expect (input uses the durationMs key).

pub fn set_now_playing(
  player: RemotePlayer,
  track: NowPlaying,
) -> Result(Nil, RemoteError)

Advertise the current track. Call whenever it changes, and periodically with fresh elapsed_ms so controllers show smooth progress.

pub fn set_queue(
  player: RemotePlayer,
  items: List(QueueItem),
  index: Int,
) -> Result(Nil, RemoteError)

Advertise the current queue and the active index.

pub fn set_status(
  player: RemotePlayer,
  status: Status,
) -> Result(Nil, RemoteError)

Advertise transport Status.

pub fn status_decoder() -> decode.Decoder(Status)

Decoder for a transport-status string (playing / paused / stopped).

Search Document