Skip to main content

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_id
number
required
Local media item ID
title
string
required
Media title (for display)
media_match_key
string | null
Cross-platform media identifier (e.g., "tmdb:603" or "imdb:tt0133093")
nickname
string
required
Your display name in the room
RoomInfo
object
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>
room_code
string
required
6-character room code from host
media_id
number
required
Your local media item ID
media_title
string
required
Media title
media_match_key
string | null
Media identifier (should match host’s)
nickname
string
required
Your display name
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>
duration
number
required
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
position
number
required
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>
media_id
number
required
Media item to play
session_id
string
required
Watch Together session ID (room code)
start_position
number
required
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)
}

Build docs developers (and LLMs) love