-
Notifications
You must be signed in to change notification settings - Fork 0
SyncPlay Internals
🌐 English · Deutsch
SyncPlay is a native, server-authoritative watch-together feature built entirely inside the app — it does not speak the external Syncplay protocol and needs no third-party server. All clients are browsers on the same instance.
| File | Role |
|---|---|
web/syncplay_rooms.py |
In-memory room/member service (the heart). Pure logic, unit-testable. |
web/transcoder.py |
HLS transcoder + shared/refcounted sessions for a room. |
web/app.py |
All /api/syncplay/* routes, guest-access gating, room persistence. |
web/static/syncplay_page.js |
Page controller: SSE client, player embedding, all UI. |
web/templates/syncplay.html |
The /syncplay page (lobby, room, picker, modals). |
web/static/syncplay.css |
Page-specific, responsive styling. |
web/static/player.js |
Reused player; forwards the room token to the stream API. |
-
Down: Server-Sent Events —
GET /api/syncplay/stream?token=…streams JSON events. -
Up: plain
POST /api/syncplay/*for actions. - The token is a per-member room session token (
secrets.token_urlsafe). It identifies the member and doubles as the guest auth token.
- Registry keyed by room name; members keyed by token (
_token_indexmaps token → room). - Host = the member who created the room; if the host leaves it passes to the longest-present remaining member.
- The server holds the authoritative playstate:
position+updated_at+paused.effective_position()advances while playing, so a mid-stream joiner gets the exact current position. - A seek is not echoed to its originator (
broadcast(..., exclude=token)) — they already sought locally, and an echo would needlessly restart their transcode.
| Constant | Value | Meaning |
|---|---|---|
MEMBER_TIMEOUT |
30 s | Drop a member with no heartbeat/poll. |
ROOM_GRACE |
60 s | (legacy) empty-room grace; rooms now persist and are only removed on explicit close. |
CHAT_HISTORY |
100 | Bounded chat backlog for late joiners. |
EVENT_BACKLOG |
500 | Per-member SSE queue cap → emits a resync instead of leaking. |
SHARE_EPSILON |
3 s | Position window within which viewers reuse one transcode session. |
MAX_TRANSCODE_SESSIONS |
8 | Global cap on concurrent ffmpeg sessions. |
All under /api/syncplay/. Guest-reachable endpoints are exempt from login_required and gated instead by a valid room token + syncplay_enabled. Host-only actions check token == room.host_token.
| Method | Path | Purpose |
|---|---|---|
| GET | config |
enabled, logged-in username, can_manage. |
| POST | join |
Create/join; returns token + room snapshot. |
| GET | stream?token= |
SSE event stream. |
| POST | control |
play / pause / seek. |
| POST | report |
Position heartbeat (+ buffering, current file). |
| POST | ready |
Ready toggle. |
| POST | chat |
Send a chat message. |
| POST | episode |
Host announces media; with countdown → synced auto-next. |
| GET | snapshot?token= |
Re-fetch state to resume after a page reload. |
| POST | leave |
Leave the room. |
| GET | rooms |
Room directory (name, count, watching, host, password/lock). |
| POST | close-room |
Close a room by name (logged-in owner only). |
| POST |
kick / ban / transfer-host / close
|
Host moderation. |
| POST |
host-lock / max / password
|
Room settings (host only). |
| POST |
away / typing / reaction / track
|
Presence & social. |
members, chat, left, play, pause, seek, sync, waiting, buffering, all_ready, media, history, countdown, host, host_lock, denied, typing, reaction, track, kicked, closed, resync.
- Guests (not logged in) reach the SyncPlay page and API because those endpoints are exempt; security comes from the room token + the
syncplay_enabledflag. - For video, the relevant
/api/stream/*endpoints (check,start,index.m3u8, segments,status,stop,active) are exempt and gated by abefore_requestguard: logged-in OR a validsp_guestsession token (stashed at join).
transcoder.start_or_join_session(file, start_pos, share_key):
-
share_key = "sp:" + room_name(derived server-side inapi_stream_startfrom thesyncplay_tokenthe client sends). - Reuses the room's current session when the same file is requested within
SHARE_EPSILONof itsstart_pos; otherwise starts a fresh one and points_shared[share_key]at it. - Sessions are refcounted:
stop_sessiondecrements and only kills ffmpeg at zero.player.jsdoes stop-then-start on a seek, so refs stay balanced; synced seeks make all members converge on one session at the new position. - Without a
share_key(normal Library playback) behaviour is unchanged.
- Open room names are saved to the
syncplay_roomssetting and restored on startup viaensure_room(), so rooms survive a restart. - The client stores its
{token, room}inlocalStorage; on load it callssnapshotto silently rejoin (withinMEMBER_TIMEOUT). The leave-on-reload beacon was removed so a refresh doesn't drop membership.
syncplay_enabled ("0"/"1"), syncplay_rooms (JSON list of persisted room names). A syncplay_enabled flag is also injected into the template context so the sidebar entry renders conditionally.
🇬🇧 English
Users
- Installation
- Getting Started
- Migration from AniWorld
- Configuration
- Web UI
- Download System
- Download History
- AutoSync
- Calendar
- Library
- Authentication
- Notifications
- Integrations
- SyncPlay
- Anime4K Upscaling
- Encoding
- Docker
- Supported Sites
Developers