Skip to content

SyncPlay Intern DE

TheMRX13 edited this page Jun 29, 2026 · 1 revision

SyncPlay – Intern

🌐 English · Deutsch

SyncPlay ist ein natives, server-autoritatives Watch-Together-Feature, komplett in der App gebaut — es spricht nicht das externe Syncplay-Protokoll und braucht keinen Drittanbieter-Server. Alle Clients sind Browser auf derselben Instanz.

Dateien

Datei Rolle
web/syncplay_rooms.py In-Memory-Raum-/Mitglieder-Dienst (das Herzstück). Reine Logik, unit-testbar.
web/transcoder.py HLS-Transcoder + geteilte/refcountete Sessions pro Raum.
web/app.py Alle /api/syncplay/*-Routen, Gast-Zugang, Raum-Persistenz.
web/static/syncplay_page.js Seiten-Controller: SSE-Client, Player-Einbettung, gesamte UI.
web/templates/syncplay.html Die /syncplay-Seite (Lobby, Raum, Picker, Modals).
web/static/syncplay.css Seitenspezifisches, responsives Styling.
web/static/player.js Wiederverwendeter Player; leitet den Raum-Token an die Stream-API.

Transport

  • Runter: Server-Sent Events — GET /api/syncplay/stream?token=… streamt JSON-Events.
  • Hoch: einfaches POST /api/syncplay/* für Aktionen.
  • Der Token ist ein Raum-Session-Token pro Mitglied (secrets.token_urlsafe). Er identifiziert das Mitglied und dient zugleich als Gast-Auth-Token.

Räume & Mitglieder

  • Registry nach Raumname; Mitglieder nach Token (_token_index mappt Token → Raum).
  • Host = das Mitglied, das den Raum erstellt hat; verlässt der Host, geht die Rolle an das am längsten anwesende verbleibende Mitglied.
  • Der Server hält den autoritativen Playstate: position + updated_at + paused. effective_position() läuft beim Abspielen weiter, sodass ein Mid-Stream-Joiner exakt die aktuelle Position erhält.
  • Ein Seek wird nicht an den Auslöser zurückgespiegelt (broadcast(..., exclude=token)) — der hat lokal schon gesprungen, ein Echo würde nur seinen Transcode neu starten.

Konstanten

Konstante Wert Bedeutung
MEMBER_TIMEOUT 30 s Mitglied ohne Heartbeat/Poll entfernen.
ROOM_GRACE 60 s (legacy) Leer-Raum-Gnadenfrist; Räume bleiben jetzt bestehen und werden nur beim expliziten Schließen entfernt.
CHAT_HISTORY 100 Begrenzter Chat-Verlauf für Late Joiner.
EVENT_BACKLOG 500 SSE-Queue-Limit pro Mitglied → sendet resync statt zu lecken.
SHARE_EPSILON 3 s Positionsfenster, in dem Zuschauer eine Transcode-Session teilen.
MAX_TRANSCODE_SESSIONS 8 Globales Limit gleichzeitiger ffmpeg-Sessions.

Routen

Alle unter /api/syncplay/. Gast-erreichbare Endpoints sind aus login_required ausgenommen und stattdessen über gültigen Raum-Token + syncplay_enabled abgesichert. Host-Aktionen prüfen token == room.host_token.

Methode Pfad Zweck
GET config enabled, eingeloggter username, can_manage.
POST join Erstellen/Beitreten; liefert token + Raum-snapshot.
GET stream?token= SSE-Event-Stream.
POST control play / pause / seek.
POST report Positions-Heartbeat (+ buffering, aktuelle file).
POST ready Ready-Umschalter.
POST chat Chat-Nachricht senden.
POST episode Host wählt Medium; mit countdown → synchrones Auto-Next.
GET snapshot?token= Zustand neu holen, um nach Reload wieder beizutreten.
POST leave Raum verlassen.
GET rooms Raum-Verzeichnis (Name, Anzahl, läuft, Host, Passwort/Lock).
POST close-room Raum per Name schließen (nur eingeloggter Owner).
POST kick / ban / transfer-host / close Host-Moderation.
POST host-lock / max / password Raum-Einstellungen (nur Host).
POST away / typing / reaction / track Präsenz & Social.

SSE-Events

members, chat, left, play, pause, seek, sync, waiting, buffering, all_ready, media, history, countdown, host, host_lock, denied, typing, reaction, track, kicked, closed, resync.

Gast-Zugang & Streaming

  • Gäste (nicht eingeloggt) erreichen Seite und API, weil diese Endpoints ausgenommen sind; Sicherheit kommt über Raum-Token + syncplay_enabled.
  • Fürs Video sind die nötigen /api/stream/*-Endpoints (check, start, index.m3u8, Segmente, status, stop, active) ausgenommen und per before_request abgesichert: eingeloggt ODER gültiger sp_guest-Session-Token (beim Join gesetzt).

Geteilte Transcode-Session

transcoder.start_or_join_session(file, start_pos, share_key):

  • share_key = "sp:" + raumname (serverseitig in api_stream_start aus dem vom Client gesendeten syncplay_token abgeleitet).
  • Nutzt die aktuelle Raum-Session wieder, wenn dieselbe Datei innerhalb von SHARE_EPSILON zur start_pos angefragt wird; sonst frische Session, _shared[share_key] zeigt darauf.
  • Sessions sind refcountet: stop_session zählt runter und killt ffmpeg erst bei 0. player.js macht beim Seek Stop-dann-Start, so bleiben die Refs balanciert; synchrone Seeks lassen alle Mitglieder auf eine Session an der neuen Position konvergieren.
  • Ohne share_key (normale Bibliotheks-Wiedergabe) bleibt das Verhalten unverändert.

Persistenz & Resume

  • Offene Raum-Namen werden in der Einstellung syncplay_rooms gespeichert und beim Start via ensure_room() wiederhergestellt, sodass Räume einen Neustart überstehen.
  • Der Client legt sein {token, room} im localStorage ab; beim Laden ruft er snapshot, um still wieder beizutreten (innerhalb von MEMBER_TIMEOUT). Das Leave-beim-Reload-Beacon wurde entfernt, damit ein Neuladen die Mitgliedschaft nicht beendet.

Einstellungs-Keys

syncplay_enabled ("0"/"1"), syncplay_rooms (JSON-Liste persistierter Raumnamen). Ein syncplay_enabled-Flag wird zudem in den Template-Context gegeben, damit der Sidebar-Eintrag bedingt rendert.

Clone this wiki locally