Skip to content

Latest commit

 

History

History
208 lines (156 loc) · 6.03 KB

File metadata and controls

208 lines (156 loc) · 6.03 KB

Wire Protocol v1

Transport is Phoenix Channels over a single WebSocket at wss://ws.frostbyte.tv/socket/websocket. The sync server lives on its own subdomain so the apex frostbyte.tv can be a pure static landing page with its own caching profile.

Phoenix Channels already handles framing, multiplexing, heartbeats, and reconnection. This document defines the event names and payload shapes that flow through the room:<room_code> topic.

All timestamps are integer milliseconds since the Unix epoch unless noted. Payloads are JSON (Phoenix's default serializer).

Topic

room:<room_code>

Where <room_code> is a 6-character alphanumeric string, e.g. ABC123.

Client → Server

phx_join (Phoenix built-in)

Sent when the extension joins the channel. Phoenix forwards this to FrostbyteWeb.RoomChannel.join/3.

{
  "user_name": "faris",
  "client_time_ms": 1744329600000,
  "capabilities": ["youtube", "netflix", "twitch"]
}
  • user_name: display name the other peers see. No uniqueness constraint.
  • client_time_ms: the client's wall clock at the moment of sending. Used by the server to estimate initial clock offset.
  • capabilities: list of platforms this client can play. Lets the server reject joins to a room whose current content the client can't handle.

Join reply (server → client, as the phx_join ok payload):

{
  "user_id": "u_8f2a",
  "role": "controller",
  "server_time_ms": 1744329600123,
  "session": {
    "content": { "platform": "youtube", "id": "dQw4w9WgXcQ" },
    "paused": true,
    "position_ms": 0,
    "rate": 1.0,
    "updated_at_server_ms": 1744329600000
  },
  "peers": [
    { "user_id": "u_8f2a", "user_name": "faris", "role": "controller" }
  ]
}
  • user_id: server-assigned, unique within the room.
  • role: controller (can send state_change) or viewer (read-only).
  • server_time_ms: server wall clock when the reply was built.
  • session: current authoritative session state. Client should seek to position_ms and apply paused + rate before listening for broadcasts.
  • peers: everyone currently in the room, including the joiner.

time_sync

Sent periodically by each client to measure and maintain clock offset from the server. Fire several times during the first few seconds, then slow to once every 30 seconds.

{ "client_time_ms": 1744329605123 }

Server replies (as the event's reply payload):

{
  "client_time_ms": 1744329605123,
  "server_time_ms": 1744329605145
}

The client uses the round trip to estimate RTT and the offset between its wall clock and the server's. Full algorithm in sync-algorithm.md.

state_change

Sent by a controller to change playback state. Viewers that send this receive an error reply and no broadcast happens.

{
  "action": "pause",
  "position_ms": 48230,
  "client_time_ms": 1744329650000
}
  • action: one of play, pause, seek.
  • position_ms: the video position at the moment the controller acted, from their local <video>.currentTime.
  • client_time_ms: the controller's wall clock at the moment they acted. The server uses this together with the controller's known clock offset to reconstruct the intended server-time the action should take effect.

Server replies { "ok": {} } if accepted, or an error payload if not.

set_role (controller only)

Grant or revoke controller permission on another user in the room.

{ "user_id": "u_3c11", "role": "controller" }

set_content (controller only)

Switch the room to a new piece of content. Resets position to 0 and pauses everyone.

{ "platform": "youtube", "id": "dQw4w9WgXcQ" }

Server → Client

state_broadcast

The authoritative session clock has changed. Every client should apply this at the specified execute_at_server_ms.

{
  "action": "pause",
  "position_ms": 48230,
  "rate": 1.0,
  "execute_at_server_ms": 1744329650200,
  "updated_at_server_ms": 1744329650045
}
  • execute_at_server_ms: the server time at which every client should apply this change. The client converts to its local time using its measured offset and schedules a setTimeout. The 200ms-ish buffer is chosen to exceed typical WS RTT so the effect lands simultaneously everywhere.
  • updated_at_server_ms: the server time at which this state became authoritative. Used for drift projection between broadcasts.

presence_diff

Sent automatically by Phoenix.Presence whenever the room membership changes. Standard Phoenix Presence format:

{
  "joins": { "u_3c11": { "metas": [{ "user_name": "jeff", "role": "viewer" }] } },
  "leaves": { "u_0a22": { "metas": [{ "user_name": "ali", "role": "viewer" }] } }
}

role_change

A user's role in the room changed. Separate from presence_diff so that clients can update the roster without a presence churn event.

{ "user_id": "u_3c11", "role": "controller" }

content_change

The room switched content. Clients should load the new content and wait for the next state_broadcast before playing.

{ "platform": "netflix", "id": "81123456" }

error

Transient or recoverable error. Fatal errors disconnect the channel instead.

{ "code": "not_controller", "message": "only controllers can pause" }

Defined error codes (v1):

code meaning
not_controller caller tried a controller-only action without role
bad_payload payload failed schema validation
content_mismatch caller's capabilities cannot play current content
rate_limited too many messages in the current window

Versioning

This is protocol v1. Breaking changes require bumping to v2. The version number is implicit in the Phoenix Channel vsn query parameter and in server-side channel module routing. Clients and server are expected to be updated together for v1. Forward compatibility is not a goal in v1.