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