Spotify ist über das MusicProvider-Interface angebunden (SpotifyMusicProvider,
ausgewählt per MusicProviderFactory). Pro Party hält SpotifyCredentials Access-/Refresh-Token,
Ablaufzeit, deviceId und lastPlaybackActive.
Spotify-Wiedergabe erfordert ein Premium-Konto des Hosts (Playback-API ist Premium-only).
OAuth-Flow#
- Host erstellt die Party (
POST /api/party) und geht zuGET /api/party/{id}/spotify/login. - Login leitet zur Spotify-Consent-Seite weiter (
stateträgt den Party-Bezug). - Spotify ruft
GET /api/spotify/callback?code=&state=(Web) bzw./api/spotify/ios/callback(iOS). - Das Backend tauscht den
codegegen Access-/Refresh-Token und bindet sie an diese Party. Der Refresh-Token wird zusätzlich inparty.spotify_refresh_tokengespeichert. - Web: Redirect auf
spotify.web.redirect.uri=/select-playlist?partyId=…. Dort wählt der Host eine Standard-Playlist (oder überspringt) und landet danach auf/dashboard. iOS: Redirect aufmusicvotingapp://callback?success=1, danach der native Playlist-Picker.
Tokens sind party-scoped: nicht zwischen Partys geteilt. Eine neue Party erfordert einen neuen Login.
Login übersteht Backend-Neustarts#
Access-Tokens liegen nur im Speicher (SpotifyCredentials), der Refresh-Token zusätzlich in der DB.
Nach einem Neustart (oder Quarkus-Dev-Reload) wird die Party aus der DB rekonstruiert und der
Refresh-Token zurückgeladen. ensureValidToken holt dann beim nächsten Aufruf ein neues Access-Token,
GET …/spotify/status meldet loggedIn: true. Rotiert Spotify den Refresh-Token, wird der neue
gespeichert.
Automatischer Token-Refresh#
Ein Access-Token läuft nach ~1 h ab. Damit eine Party länger als eine Stunde ohne Host-Eingriff läuft:
- Proaktiv: Läuft das Token in < 60 s ab, erneuert das Backend es vor der nächsten Anfrage.
- Reaktiv: Bei HTTP 401 von Spotify erneuert das Backend das Token einmal per Refresh-Token und wiederholt den Request.
- Fehlschlag: Schlägt der Refresh fehl (Token widerrufen), liefert das Backend 401 mit
"Spotify-Sitzung abgelaufen. Bitte neu anmelden."– der Host muss die Party neu starten.
Wiedergabe & Geräte-Registrierung#
- Die Startpage/TV lädt das Spotify Web Playback SDK (Token über
GET /api/party/{id}/spotify/token) und registriert ein Playback-Gerät viaPUT /api/party/{id}/spotify/deviceId. deviceActive(inGET /track/current) isttrue, sobald eine nicht-leeredeviceIdregistriert ist. Solangefalse, sind Host-Controls gesperrt.- Re-Registrierung setzt fort, statt zurückzuspulen:
restoreCurrentTrackOnDeviceberechnet die aktuelle Position (pausedPositionMsbzw.now() − playbackStartedAt) und spielt den aktuellen Track mitposition_msan dieser Stelle; danachtrack-changed-Event.
Zuverlässige Wiedergabe#
Drei Maßnahmen (Juli 2026) sorgen dafür, dass immer der angezeigte Song läuft:
- Resume setzt den aktuellen Song neu.
POST /track/resumeschicktPUT /me/player/playmit der URI des aktuellen Eintrags undposition_ms= pausierte Position (buildResumeBody). Ein nacktes Resume würde spielen, was das Gerät gerade geladen hat – nach einem Auto-Advance oft noch der vorherige Song. - Advance committet bei
2xx./track/nextsetztcurrently_playing_entry_idsofort, wenn Spotify den Play-Befehl annimmt. Ein Abgleich mitcurrently-playingwurde verworfen: Spotify meldet dort noch sekundenlang den vorherigen Song und hätte gesunde Wechsel blockiert. - Re-Assert nach ~700 ms. Direkt am Songende ist das SDK-Gerät kurz im Leerlauf und nimmt
playmit2xxan, ohne zu wechseln. Deshalb sendetreassertPlaydieselbe URI nach 700 ms noch einmal (mitposition_ms). Es wird nie ein Geräte-Transfer benutzt – der würde den alten Spotify-Kontext fortsetzen und den falschen Song spielen.
Diagnose-Logs#
Jede Start/Stop-Operation (START, NEXT, PLAY, PAUSE, RESUME) schreibt eine INFO-Zeile:
[playback NEXT] party=… | queue=[name | uri | id ;; …] | current=… | next=… | device=<uri> | is_playing=…
Beim Advance zusätzlich die aufgelöste Device-ID und ein Snapshot von /me/player/devices. Die Logs
sind best-effort und ändern nie das Ergebnis der Operation.
Auto-Refill (leere Queue)#
Würde die Queue leer, fügt das Backend einen Song hinzu (refillQueue, markiert als
autofilled). Der Player ruft dafür ~3 s vor Songende POST /track/prepare-next auf; /track/next
hat zusätzlich einen Fallback. Quellen in dieser Reihenfolge:
- Standard-Playlist des Hosts (
default_playlist_id), - ähnliche Songs – Top-Tracks der Künstler dieser Party (
/v1/artists/{id}/top-tracks, Marktspotify.market), - Top-Charts-Playlist (
spotify.topcharts.playlist.id, optional), - Suche (
/v1/search) nach dem aktuellen Künstler, dann eine breite Jahres-Suche.
/v1/recommendations und Spotifys eigene Editorial-Playlists (37i9dQZEV…) liefern für neue Apps
404 bzw. nichts – deshalb die Top-Tracks und die Suche als Rückfall. Eine Playlist als
Top-Charts-Quelle muss öffentlich und nutzereigen sein.
Auto-Refill-Songs sortieren unter allen Gast-Songs; Gäste können sie trotzdem liken.
Wichtig#
GET /track/currentruft nicht Spotifys „currently-playing“-API auf – der aktuelle Track und der Fortschritt kommen aus der DB (currently_playing_entry_id,playback_started_at,paused_position_ms).- Die Queue wird nie aus der Spotify-Playlist gelesen; die DB ist die Quelle der Wahrheit.