Skip to main content
OpenTogetherTube uses WebSockets for real-time bidirectional communication between clients and the server. This enables instant synchronization of video playback, queue updates, and user actions.

Connection

Establishing a Connection

Connect to the WebSocket endpoint by upgrading an HTTP connection:
ws://localhost:8080/
wss://opentogethertube.com/
JavaScript Example
const ws = new WebSocket('wss://opentogethertube.com/');

ws.onopen = () => {
  console.log('Connected to OpenTogetherTube');
};

ws.onmessage = (event) => {
  const message = JSON.parse(event.data);
  console.log('Received:', message);
};

ws.onerror = (error) => {
  console.error('WebSocket error:', error);
};

ws.onclose = () => {
  console.log('Disconnected');
};
Python Example
import asyncio
import websockets
import json

async def connect():
    uri = "wss://opentogethertube.com/"
    async with websockets.connect(uri) as websocket:
        # Send authentication
        await websocket.send(json.dumps({
            "action": "auth",
            "token": "YOUR_TOKEN"
        }))
        
        # Receive messages
        async for message in websocket:
            data = json.loads(message)
            print(f"Received: {data}")

asyncio.run(connect())

Authentication

After connecting, authenticate using your token:
{
  "action": "auth",
  "token": "YOUR_AUTH_TOKEN"
}
Get a token from the REST API first:
curl https://opentogethertube.com/api/auth/grant
Full Example
const ws = new WebSocket('wss://opentogethertube.com/');

ws.onopen = async () => {
  // Get auth token
  const response = await fetch('https://opentogethertube.com/api/auth/grant');
  const { token } = await response.json();
  
  // Authenticate WebSocket
  ws.send(JSON.stringify({
    action: 'auth',
    token: token
  }));
};

Message Format

All messages are JSON objects with an action field that identifies the message type.

Client Messages

Messages sent from client to server:
type ClientMessage = 
  | { action: "auth", token: string }
  | { action: "status", status: PlayerStatus }
  | { action: "req", request: RoomRequest }
  | { action: "kickme", reason?: number }
  | { action: "notify", message: "usernameChanged" }

Server Messages

Messages sent from server to client:
type ServerMessage =
  | { action: "sync", /* room state */ }
  | { action: "event", /* user action */ }
  | { action: "chat", /* chat message */ }
  | { action: "user", /* user update */ }
  | { action: "you", info: { id: string } }
  | { action: "announcement", text: string }
  | { action: "eventcustom", text: string }
  | { action: "unload" }

Joining a Room

After authentication, join a room by sending a room request:
ws.send(JSON.stringify({
  action: 'req',
  request: {
    type: 0, // RoomRequestType.JoinRequest
    info: {
      id: clientId,
      username: 'MyUsername',
      status: 0 // PlayerStatus.none
    }
  }
}));
RoomRequestType Enum
enum RoomRequestType {
  JoinRequest = 0,
  LeaveRequest = 1,
  PlaybackRequest = 2,
  SkipRequest = 3,
  SeekRequest = 4,
  AddRequest = 5,
  RemoveRequest = 6,
  OrderRequest = 7,
  VoteRequest = 8,
  PromoteRequest = 9,
  UpdateUser = 10,
  ChatRequest = 11,
  UndoRequest = 12,
  ApplySettingsRequest = 13,
  PlayNowRequest = 14,
  ShuffleRequest = 15,
  PlaybackSpeedRequest = 16,
  RestoreQueueRequest = 17,
  KickRequest = 18
}

Player Status

Inform the server of your player’s readiness state:
ws.send(JSON.stringify({
  action: 'status',
  status: 1 // PlayerStatus.ready
}));
PlayerStatus Enum
enum PlayerStatus {
  none = 0,      // No player loaded
  buffering = 1, // Loading video
  ready = 2,     // Ready to play
  error = 3      // Error state
}

Connection Lifecycle

  1. Connect - Establish WebSocket connection
  2. Authenticate - Send auth token
  3. Receive Identity - Server sends you message with client ID
  4. Join Room - Send join request with client info
  5. Receive Sync - Server sends initial room state
  6. Active - Exchange messages bidirectionally
  7. Disconnect - Close connection gracefully
Complete Flow Example
let clientId = null;
let token = null;

const ws = new WebSocket('wss://opentogethertube.com/');

ws.onopen = async () => {
  // Step 1 & 2: Get token and authenticate
  const authResp = await fetch('https://opentogethertube.com/api/auth/grant');
  const authData = await authResp.json();
  token = authData.token;
  
  ws.send(JSON.stringify({
    action: 'auth',
    token: token
  }));
};

ws.onmessage = (event) => {
  const msg = JSON.parse(event.data);
  
  // Step 3: Receive client ID
  if (msg.action === 'you') {
    clientId = msg.info.id;
    console.log('Got client ID:', clientId);
    
    // Step 4: Join a room
    ws.send(JSON.stringify({
      action: 'req',
      request: {
        type: 0, // JoinRequest
        info: {
          id: clientId,
          username: 'Guest',
          status: 0
        }
      }
    }));
  }
  
  // Step 5: Receive initial room state
  else if (msg.action === 'sync') {
    console.log('Room state:', msg);
  }
  
  // Step 6: Handle other messages
  else if (msg.action === 'event') {
    console.log('User action:', msg);
  }
};

Heartbeat / Keep-Alive

The WebSocket connection is automatically maintained. No manual ping/pong is required.

Error Handling

ws.onerror = (error) => {
  console.error('WebSocket error:', error);
};

ws.onclose = (event) => {
  console.log('Connection closed:', event.code, event.reason);
  
  // Reconnect logic
  if (event.code !== 1000) { // 1000 = normal closure
    setTimeout(() => {
      console.log('Reconnecting...');
      // Recreate connection
    }, 5000);
  }
};
Close Codes
  • 1000 - Normal closure
  • 1001 - Going away (server shutdown)
  • 1006 - Abnormal closure (connection lost)
  • 1011 - Server error
  • 4000+ - Custom application codes

Rate Limiting

WebSocket messages may be rate-limited to prevent abuse. If you send too many messages too quickly, the connection may be closed.

Load Balancing

OpenTogetherTube uses a Rust-based load balancer to distribute WebSocket connections across multiple server instances. Clients are automatically routed to the appropriate server based on the room they’re joining.
The load balancer handles transparent failover and connection routing. No special client-side handling is required.

Next Steps

WebSocket Events

Complete event reference

Room Endpoints

REST API for rooms

Build docs developers (and LLMs) love