Playback Model
Synchronized watching is driven by server-side room state. A client should not treat its local player state as the source of truth. It follows room playback state and submits control actions only when the user has permission.
Two Kinds of State
Section titled “Two Kinds of State”| State | Owner | Used for |
|---|---|---|
| Current playback state | SyncTV server | Current media, play/pause, position, speed, and version |
| Playback info | Provider and SyncTV | Playback URL, proxy URL, headers, subtitles, variants, and expiry |
Current playback state answers “what is the room watching and where is it?”. A playback info answers “how should this client play that media now?”.
Realtime Flow
Section titled “Realtime Flow”Room WebSocket connections carry playback controls, chat, WebRTC signaling, and resource observation events. After reconnecting, clients should fetch key resources again instead of assuming old subscriptions are still active.
- Enter a room.
- Fetch current playback state and a playback info.
- Connect Realtime.
- Receive play, pause, seek, or media-change events.
- Refresh the playback info when the media URL expires, Provider credentials change, or room resources change.
Direct and Proxy Playback
Section titled “Direct and Proxy Playback”| Mode | Good for | Risk |
|---|---|---|
| Direct | Clients can reach the upstream URL and set required headers | Browsers may block required headers |
| SyncTV proxy | Hide upstream credentials, normalize headers, handle Range, or cross client limits | SyncTV carries egress bandwidth and proxy latency |
Providers explicitly return the headers to use. The proxy does not forward arbitrary client headers to upstream services.
One playback result can contain direct modes and proxy_* modes at the same time. Each Provider decides these modes, the default mode, header exposure, and signed proxy URLs while generating playback info. Shared helpers only generate standard proxy sibling URLs; they do not replace provider-owned playback decisions.
HTTP and gRPC are transport entry points. They parse paths, query parameters, JSON/protobuf, headers, and streaming bodies, then call synctv-api/src/impls. Permissions, playback state, Provider calls, caching, fanout, duration merging, and resource lifecycle handling live in impls and core services so both transports share one behavior path.
Range and Cache
Section titled “Range and Cache”Seeking usually depends on HTTP Range. When upstream supports Range, proxy slice cache can cache fixed byte slices. If upstream does not support Range, SyncTV bypasses slice cache and does not write full-file cache entries.
Playback Background Workers
Section titled “Playback Background Workers”Duration probing and playback auto-advance are driven by local active rooms. A room enters a node’s playback background scan set only while the current process has a Realtime connection for that room.
These workers run on every node so background work follows the real connection lifecycle. When several cluster nodes host the same room, the database provides concurrency safety: duration probes claim work, and auto-advance uses playback state transactions with optimistic versions.
The worker input set comes from ConnectionRuntime::active_room_ids(). Presence hot-room statistics serve room lists, admin views, and metrics. Playback lifecycle workers use the node-local realtime connection set, while database locks, SKIP LOCKED, and playback-state version writes converge duplicate cross-node attempts.
Dynamic playlist background work also stays bound to the current target. Repository queries keep both room_id = ANY(active_room_ids) and the current room_playback_progress.target_hash join so probing, caching, and auto-advance operate on the item the room is currently playing.
Next Steps
Section titled “Next Steps”- User playback issues: Synchronized Playback and User Troubleshooting.
- Client implementation: Client Integration Guide.
- Proxy cache configuration: Proxy Slice Cache.