Overview
Watch Together enables synchronized video playback across multiple users. It uses a WebSocket relay server to coordinate play/pause/seek events and maintain a shared playback state.
Architecture :
WebSocket relay server handles rooms and sync messages
MPV instances connect via IPC for precise position control
Server broadcasts authoritative position updates every ~1 second
Latency compensation keeps everyone in sync (±200ms tolerance)
Room Management
create_watch_together_room
Create a new Watch Together room as host.
#[tauri :: command]
async fn wt_create_room (
state : State <' _ , AppState >,
window : Window ,
media_id : i64 ,
title : String ,
media_match_key : Option < String >,
nickname : String ,
) -> Result < watch_together :: RoomInfo , String >
Media title (for display)
Cross-platform media identifier (e.g., "tmdb:603" or "imdb:tt0133093")
Your display name in the room
6-character room code (e.g., "ABC123")
Array of participants (see Participant type below)
Whether playback has started
Current playback position in seconds
As the host, only you can start playback (wt_start_playback) once all participants are ready.
join_watch_together_room
Join an existing Watch Together room.
#[tauri :: command]
async fn wt_join_room (
state : State <' _ , AppState >,
window : Window ,
room_code : String ,
media_id : i64 ,
media_title : String ,
media_match_key : Option < String >,
nickname : String ,
) -> Result < watch_together :: RoomInfo , String >
6-character room code from host
Media identifier (should match host’s)
All participants must have the same media file (same duration) for sync to work correctly. Use media_match_key to verify everyone has the right content.
leave_watch_together_room
Leave the current Watch Together session.
#[tauri :: command]
async fn wt_leave_room (
state : State <' _ , AppState >,
) -> Result <(), String >
If the host leaves, the room is destroyed and all participants are disconnected.
Playback Control
wt_set_ready
Mark yourself as ready to start (after loading the video).
#[tauri :: command]
async fn wt_set_ready (
state : State <' _ , AppState >,
duration : f64 ,
) -> Result <(), String >
Video duration in seconds (for validation)
All participants must call wt_set_ready before the host can start playback.
wt_start_playback
Start synchronized playback (host only).
#[tauri :: command]
async fn wt_start_playback (
state : State <' _ , AppState >,
) -> Result <(), String >
This broadcasts a PlaybackStarted event to all participants, triggering synchronized playback start.
send_friend_request
Not implemented. Watch Together uses ephemeral rooms - no persistent friend lists.
accept_friend_request
Not implemented. Use room codes instead.
get_activity_feed
Alias for get_watch_stats and get_recent_watch_activities.
See Library Management for watch history commands.
set_privacy_settings
Not implemented. Watch Together rooms are private by default (require 6-character code to join).
Sync Commands
wt_send_sync
Send a playback command (play/pause/seek) to all participants.
#[tauri :: command]
async fn wt_send_sync (
state : State <' _ , AppState >,
action : String ,
position : f64 ,
) -> Result <(), String >
action
'play' | 'pause' | 'seek'
required
Sync command type
Playback position in seconds
All participants receive this command and should apply it to their local player.
wt_launch_mpv
Launch MPV in Watch Together sync mode.
#[tauri :: command]
async fn wt_launch_mpv (
state : State <' _ , AppState >,
media_id : i64 ,
session_id : String ,
start_position : f64 ,
) -> Result < u32 , String >
Watch Together session ID (room code)
Initial position in seconds
MPV is launched with IPC enabled. The Watch Together controller sends sync commands via named pipe.
wt_send_mpv_command
Send a sync command to MPV via IPC.
#[tauri :: command]
async fn wt_send_mpv_command (
state : State <' _ , AppState >,
session_id : String ,
action : String ,
position : f64 ,
) -> Result <(), String >
State Queries
wt_get_room_state
Get current room information.
#[tauri :: command]
async fn wt_get_room_state (
state : State <' _ , AppState >,
) -> Result < Option < watch_together :: RoomInfo >, String >
wt_is_active
Check if in an active Watch Together session.
#[tauri :: command]
async fn wt_is_active (
state : State <' _ , AppState >,
) -> Result < bool , String >
wt_get_client_id
Get your unique client ID for the current session.
#[tauri :: command]
async fn wt_get_client_id (
state : State <' _ , AppState >,
) -> Result < String , String >
Events
room_updated
Emitted when room state changes (participant joins/leaves, etc.).
import { listen } from '@tauri-apps/api/event' ;
await listen < WatchEvent >( 'watch-together-event' , ( event ) => {
if ( event . payload . type === 'room_updated' ) {
console . log ( 'Room updated:' , event . payload . room );
}
});
sync_command
Emitted when a sync command is received.
await listen < WatchEvent >( 'watch-together-event' , ( event ) => {
if ( event . payload . type === 'sync_command' ) {
const cmd = event . payload . command ;
if ( cmd . action === 'seek' ) {
videoElement . currentTime = cmd . position ;
} else if ( cmd . action === 'pause' ) {
videoElement . pause ();
} else if ( cmd . action === 'play' ) {
videoElement . play ();
}
}
});
state_update
Server broadcasts authoritative position every second for sync correction.
await listen < WatchEvent >( 'watch-together-event' , ( event ) => {
if ( event . payload . type === 'state_update' ) {
const serverPosition = event . payload . position ;
const localPosition = videoElement . currentTime ;
const drift = Math . abs ( serverPosition - localPosition );
// Correct drift if > 200ms
if ( drift > 0.2 ) {
videoElement . currentTime = serverPosition ;
}
}
});
Types
Participant
interface Participant {
id : string ; // UUID
nickname : string ; // Display name
is_host : boolean ; // Whether this is the room host
is_ready : boolean ; // Whether ready to start
duration ?: number ; // Video duration (for validation)
}
WatchRoom
interface WatchRoom {
code : string ; // 6-char room code
host_id : string ; // Host UUID
media_title : string ; // Content title
media_id : number ; // Local media ID
participants : Participant [];
is_playing : boolean ; // Playback state
current_position : number ; // Position in seconds
}
SyncCommand
interface SyncCommand {
action : 'play' | 'pause' | 'seek' ;
position : number ; // Seconds
from ?: string ; // Sender UUID
timestamp ?: number ; // Unix timestamp (ms)
}