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
}));
};
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
Connect - Establish WebSocket connection
Authenticate - Send auth token
Receive Identity - Server sends you message with client ID
Join Room - Send join request with client info
Receive Sync - Server sends initial room state
Active - Exchange messages bidirectionally
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