Bis hierher konnte man sich bei Jellyfin anmelden und stöbern, aber nichts abspielen. Der Player war die letzte große Stelle, die Plex unmittelbar kannte. Drei Mitglieder kommen dafür in den Vertrag: eine Wiedergabe vorbereiten, sie beenden, den Fortschritt melden. Die Entscheidung **ob** umgerechnet wird, fällt weiterhin vorher im `PlaybackDecider` — der Server führt sie nur aus. Genau diese Trennung fehlte: der Player hat Plex' Transcoder angesprochen und konnte deshalb mit nichts anderem reden. `MediaBackendError` bekommt einen siebten Fall: `refused(reason:)`. Der Server hat verstanden und sagt nein, in eigenen Worten. Bei Plex war genau dieser Satz die einzige brauchbare Spur, und er ging tagelang verloren, weil es dafür keinen Fall gab. Bei Jellyfin kommt die Adresse einer umgerechneten Fassung vom Server selbst (`PlaybackInfo` → `TranscodingUrl`), nicht von uns. Er weiß, welche Spuren er kopieren kann und welche er neu rechnen muss. Drei Dinge, die beim Hinsehen auffielen: - **`startTimeTicks` gehört nicht in die Anfrage.** Sonst entscheiden zwei Stellen über den Startpunkt: der Server ließe den Strom dort beginnen, und der Player spränge noch einmal um dieselbe Strecke vor. Bei einundzwanzig Minuten Fortschritt landet man bei zweiundvierzig. - **Der erste Bericht braucht einen eigenen Endpunkt.** Ohne ihn erscheint der Titel bei Jellyfin nirgends als „läuft gerade". Er fiel zunächst aus, weil er die Laufzeit verlangte — und die steht erst Sekunden nach dem Öffnen fest. Jetzt tut es für die erste Meldung die des Servers. - **In der Download-Adresse standen Titel und Fassung vertauscht.** Bei einem Film mit einer Fassung sind beide Kennungen dieselbe und nichts fällt auf; bei zweien schon. `MediaFile` sagt jetzt, zu welchem Titel sie gehört. Live-TV bleibt bei Plex und wird als Einziges noch unmittelbar durchgereicht. Ein Tuner ist ein Stück Hardware, und die fünfhundert Zeilen, die es gekostet hat, ihn zu bändigen, gehören in keinen gemeinsamen Vertrag. Nachgesehen, nicht nur gebaut: gegen die Attrappe läuft ein echtes Video, der Beginn wird gemeldet, der Fortschritt kommt in Ticks mit Sitzung und Fassung an. Was die Attrappe **nicht** beantworten kann, ist, ob ein echter Jellyfin das Geräteprofil annimmt und eine Umrechnung anbietet — das entscheidet erst ein richtiger Server. 257 Tests grün. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Flixy
Ein nativer Plex-Client für iPhone und iPad — dunkel, mit ausfahrbarer Seitenleiste und Offline-Downloads.
Der Name ist ein Platzhalter. Siehe Flixy/Branding.swift.
Stand
Alle sechs Phasen sind gebaut. Anmeldung, Blättern und Wiedergabe laufen am echten Server; der Rest ist unterschiedlich weit geprüft.
Der Fahrplan steht in
docs/superpowers/specs/2026-09-11-flixy-design.md.
| Phase | Inhalt | Stand |
|---|---|---|
| 0 | Projektgerüst, Design-Tokens, Navigation | ✅ |
| 1 | Anmeldung, Bibliothek, DirectPlay | ✅ am Server geprüft, iPhone und iPad |
| 2 | Transcoding, adaptive Bitrate, Spuren, Sync | ✅ Wiedergabe geprüft; Transcoding selbst noch nicht erzwungen |
| 3 | Download-Engine und Offline-Wiedergabe | ⚠️ Downloads starten, Offline-Wiedergabe noch nicht bestätigt |
| 4 | Download-Intelligenz | ⚠️ komprimierte Downloads ungeprüft |
| 5 | Feinschliff und Personalisierung | ✅ gebaut, teilweise geprüft |
| 6 | Live-TV mit EPG | ⚠️ Empfänger werden gefunden, Sender noch nicht bestätigt |
„Ungeprüft" heißt: die Logik ist von Tests abgedeckt, an einem echten Bestand aber noch nicht gesehen.
Was der Praxistest gelehrt hat
Drei Fehler, die kein Test gefunden hätte, weil sie in Annahmen über fremde Bibliotheken steckten — sie stehen als Warnung im jeweiligen Quelltext:
- VLCKit ruft seinen Delegaten vom eigenen Thread, nicht über die
Hauptwarteschlange.
MainActor.assumeIsolatedbrach die App beim Drücken auf Play hart ab. app.plex.tv/authverknüpft den Code nicht, wenn man im Browser schon bei Plex angemeldet ist — es springt auf watch.plex.tv weiter.plex.tv/linktut es zuverlässig.URLSessionDownloadTaskgilt auch bei 404 als erfolgreich und legt die Fehlerseite als Datei ab.
Aufbau
Die Xcode-Projektdatei wird aus project.yml erzeugt und ist nicht
eingecheckt — das erspart Konflikte in der pbxproj.
xcodegen generate
open Flixy.xcodeproj
Wichtig: Xcode vorher beenden.
xcodegen generateschreibtproject.pbxprojneu. Ein geöffnetes Xcode liest das nicht nach — es hält seinen eigenen Stand im Speicher und überschreibt die frisch erzeugte Datei beim nächsten Sichern wieder. Neue Dateien fehlen dann im Build, ohne dass irgendwo ein Fehler auftaucht.Das ist der Preis dafür, die Projektdatei aus Git herauszuhalten. Immer wenn Dateien dazukommen oder
project.ymlsich ändert: Xcode mit ⌘Q beenden,xcodegen generate, neu öffnen.Ob der Build aktuell ist, steht in der App unter Einstellungen → Diagnose als Build-Datum.
Der Code liegt zum größten Teil in lokalen Packages unter Packages/FlixyKit.
Das App-Target enthält nur Oberfläche und Navigation.
| Modul | Aufgabe | Hängt ab von |
|---|---|---|
PlexKit |
Anmeldung, Endpunkte, Modelle. Kein SwiftUI. | — |
FlixyDesign |
Farben, Typografie, wiederkehrende Bausteine. | — |
FlixyPlayback |
Wiedergabe-Abstraktion und die Entscheidung DirectPlay/Transcode. | PlexKit |
FlixyDownloads |
Warteschlange, Hintergrundübertragung, Offline-Bestand. | PlexKit |
Warum zwei Player
iOS spielt weder Matroska-Dateien noch DTS- oder TrueHD-Ton — und genau das
liegt in typischen Plex-Beständen. PlaybackDecider prüft deshalb pro Datei,
ob AVPlayer sie nativ öffnen kann (dann gibt es Bild-in-Bild und AirPlay
gratis), und schickt sie sonst an VLCKit.
Ab Phase 2 kommt der dritte Weg dazu: Server-Transcoding. Dafür ist genau eine
Funktion zu erweitern — PlaybackDecider.decide(media:part:policy:). Die
Oberfläche bleibt unberührt.
Tests
Die Logik-Tests laufen ohne Simulator, deshalb in Sekunden:
cd Packages/FlixyKit && swift test
Die wichtigste Testdatei ist PlaybackDeciderTests — sie hält fest, welche
Datei auf welchem Weg landet, und schlägt an, sobald jemand
DeviceCapabilities anfasst.
Die Antwort-Vorlagen unter Tests/PlexKitTests/Fixtures sind von Hand
geschrieben und gehören durch echte Mitschnitte ersetzt, sobald ein Server
erreichbar ist — siehe das README dort.
Die Oberfläche wird von Hand im Simulator geprüft.
Was noch am echten Server zu prüfen ist
- Komprimierte Downloads. Sie laufen über den Universal-Transcoder mit
protocol=httpstatt über die umständliche Sync-API. Offen: ob Plex eineContent-Lengthmitschickt (ohne sie gibt es keinen Fortschrittsbalken und kein Fortsetzen) und ob die Datei durchsuchbar ist. Original bleibt solange Voreinstellung. SiehetranscodedDownloadURLinPlexServer+Playback.swift. Ein Fehlschlag erscheint immerhin als solcher — die Plausibilitätsprüfung inDownloadCoordinator.implausibilityverwirft Fehlerseiten und abgebrochene Übertragungen, statt sie als fertig zu verbuchen. - Live-TV: die Senderliste. Dass die Empfänger unter
Dvrgefunden werden, ist geprüft. Ob/livetv/dvrs/{id}/channelsdie Sender liefert, nicht. Die Ansicht unterscheidet beide Fälle: „Kein Empfänger eingerichtet" gegen „Keine Sender" — daran ist ablesbar, wo es hakt. - App-Größe. Die iOS-Slice von VLCKit ist unstripped 239 MB. Was davon im fertigen Archiv übrig bleibt, ist noch nicht gemessen.
- Große Dateien im Hintergrund. Das Verhalten mit einem 40-GB-Remux — vor allem Pausieren und Fortsetzen — ist nur gegen Testdaten geprüft.
Voraussetzungen
- Xcode 26 oder neuer
- iOS 26.0 auf dem Zielgerät
- XcodeGen (
brew install xcodegen)
Live-TV untersuchen, ohne zu bauen
Die Live-TV-Schnittstelle von Plex ist nirgends beschrieben, und jede Vermutung darüber ging bisher als TestFlight-Build hinaus und kam als Screenshot zurück. Irgendwann war das Upload-Kontingent des Tages aufgebraucht — für eine Frage, die keine Oberfläche braucht.
cd Packages/FlixyKit && swift run FlixyProbe
Beim ersten Lauf zeigt das Werkzeug einen Code für plex.tv/link; danach liegt der Token im Schlüsselbund dieses Rechners. Es sucht den Server, liest Senderliste und Programm und probiert das Einschalten durch — mit derselben Logik wie die App, nur in Sekunden statt in einem halben Tag.
Ausgegeben werden Pfade, Kennungen und die Antworten des Servers. Keine vollständigen Adressen, also auch kein Token.