# Flixy — nativer Plex-Client für iPhone & iPad ## Kontext Der Ordner `/Users/scarriffle/Projects_Coding/Flixy` ist leer. Es entsteht ein nativer Plex-Client, weil die aktuelle offizielle Plex-App gestalterisch nicht gefällt — gewünscht ist etwas, das näher an der alten Plex-Handy-App liegt, sich aber wertig anfühlt wie eine große Streaming-App: ein-/ausblendbare Seitenleiste für die Mediatheken, ein Hero-geführter Startbildschirm, durchgängig dunkel, mit einstellbarer Akzentfarbe. Zwei Subsysteme tragen das Projekt: eine **Wiedergabe-Schicht**, die zunächst nur DirectPlay macht, aber so gebaut ist, dass Server-Transcoding und adaptive Bitrate reine Ergänzungen sind — und eine **Download-Engine** für Offline-Nutzung von einzelnen Folgen bis zu ganzen Serien. Vorbild und Referenz im eigenen Bestand: **Nook** (`../ABS-Client`) — derselbe Aufbau für Audiobookshelf, inkl. Hintergrund-Downloads, Live Activities und DE/EN-Lokalisierung. **Onyx** (`../Onyx`) liefert das Muster für XcodeGen + lokale Packages. Der Name „Flixy" ist ein Platzhalter und soll später leicht änderbar sein. ## Entscheidungen (mit dem Nutzer abgestimmt) | Thema | Entscheidung | |---|---| | Plattformen | iPhone + iPad (kein macOS, kein tvOS) | | Mindest-iOS | 26.0 | | Anmeldung | plex.tv PIN-Flow, ein Server, ein Benutzer | | Player | Hybrid — AVPlayer wenn nativ möglich, sonst VLCKit | | Medien | Filme, Serien, Live-TV mit EPG (kein Musik/Fotos) | | Plex Pass | vorhanden | | Struktur | XcodeGen + lokale Swift Packages | | Persistenz | SwiftData | | Tests | Swift Testing für Logik, UI manuell im Simulator | | Sprachen | Deutsch + Englisch (String Catalogs) | | Design | nur Dark Mode, Layout A (Hero + Querformat-Reihen), Overlay-Drawer-Seitenleiste | | Akzentfarbe | kuratierte Palette + eigene Farbe mit Kontrastkorrektur | ## Architektur Vier lokale Swift Packages unter `Packages/`, das App-Target enthält nur noch SwiftUI-Views und Navigation. Abhängigkeiten fließen strikt in eine Richtung: ``` App ──► FlixyDesign (Farb-Tokens, Poster/Hero-Komponenten, Platzhalter — keine Abhängigkeiten) │ ├──────► PlexKit (HTTP, Auth, Modelle, Endpunkte — kein UI, kein SwiftUI) │ ├──────► FlixyPlayback ──► PlexKit │ └──────► FlixyDownloads ─► PlexKit ``` **PlexKit** — `PlexClient` (Actor) kapselt Header (`X-Plex-Token`, `X-Plex-Client-Identifier`, Produkt/Version), Verbindungsauswahl und Dekodierung. Endpunkte als kleine, einzeln testbare Typen; Modelle (`PlexItem`, `PlexMedia`, `PlexPart`, `PlexStream`, `PlexMarker`) als `Codable` gegen echte Server-Antworten, die als JSON-Fixtures im Test-Bundle liegen. **FlixyPlayback** — das Herzstück der Nachrüstbarkeit: ```swift protocol VideoPlayerEngine: AnyObject { func load(_ source: PlaybackSource) async throws func play(); func pause(); func seek(to: Duration) var state: AsyncStream { get } var tracks: [MediaTrack] { get } func select(_ track: MediaTrack) var supportsPictureInPicture: Bool { get } } ``` `AVPlayerEngine` und `VLCPlayerEngine` implementieren es. Darüber entscheidet `PlaybackDecider` anhand von Streams + Gerätefähigkeiten (+ ab Phase 2 Netzwerk) zwischen `.directPlay(engine:)` und `.transcode(url:maxBitrate:)`. **In Phase 1 gibt der Decider nur DirectPlay-Fälle zurück** — Phase 2 erweitert genau diese eine Funktion, ohne die View-Schicht anzufassen. Das ist die konkrete Form von „Transcoding vorbereiten". **FlixyDownloads** — `URLSession` mit Background-Konfiguration, ein `DownloadCoordinator`-Actor als Zustandsautomat (queued → downloading → paused → completed/failed), SwiftData-Modelle `DownloadItem` und `OfflineMetadata` (Titel, Beschreibung, Marker, Spuren, Cover-Pfad — alles, was die Detailseite und der Player offline brauchen). **Branding-Isolation** — App-Name, Bundle-ID, Icon und Standard-Akzentfarbe liegen ausschließlich in `project.yml` und `Branding.swift`. Umbenennen später = zwei Dateien. ## Phasen ### Phase 0 — Fundament - `project.yml` (XcodeGen), Packages-Gerüst, `.gitignore`, Git-Repo initialisieren - Dieses Dokument als Spec nach `docs/superpowers/specs/2026-09-11-flixy-design.md` ins Repo übernehmen und committen - App-Target: `.preferredColorScheme(.dark)`, String Catalogs DE/EN, `Branding.swift` - `FlixyDesign`: Farb-Tokens (Surface-Stufen, Text, Akzent als austauschbarer Wert), Typo-Skala, `PosterCard`, `LandscapeCard`, `HeroBanner`, Shimmer-Platzhalter - Test-Setup mit Swift Testing in allen Packages ### Phase 1 — Anmeldung, Bibliothek, DirectPlay *Ziel: App ist real benutzbar.* - **Auth:** `POST /api/v2/pins` → Browser-Auth → Polling → Token im Keychain. Server über `GET /api/v2/resources` auflisten; Verbindungen (lokal / plex.direct / Relay) parallel anpingen, schnellste erreichbare gewinnt und wird gemerkt. - **Seitenleiste:** Overlay-Drawer mit Abdunklung, per Wischgeste und Hamburger; auf dem iPad dauerhaft im Split-View, einklappbar. Einträge: Start, je Bibliothek, Downloads, Einstellungen. - **Startseite (Layout A):** Hero-Karussell oben, darunter Reihen aus `/hubs` — Weiterschauen, Als Nächstes, Zuletzt hinzugefügt je Bibliothek, Watchlist, Sammlungen. - **Bibliothek:** Grid und Liste umschaltbar, Filter (Genre, Jahr, Bewertung, Auflösung), Sortierung, „Nur Ungesehene"-Schalter, Paginierung über `X-Plex-Container-Start/Size`. - **Detailseiten:** Film und Serie → Staffel → Folge, Besetzung mit Porträts (tippbar), Ähnliche Titel, aufklappbare technische Infos (Codec, Bitrate, HDR, Tonspuren, Größe), Versionsauswahl bei mehreren Media-Einträgen. - **Suche:** Live-Ergebnisse über alle Bibliotheken, gruppiert, letzte Suchen gemerkt. - **Player:** `PlaybackDecider` + beide Engines, DirectPlay über `{Part.key}?X-Plex-Token=…`, eigenes Chrome (Scrubber, ±10 s, Titel/Folgenanzeige), PiP wenn AVPlayer aktiv ist. **Schlüsseldateien:** `Packages/PlexKit/Sources/{PlexClient,Auth,Endpoints,Models}`, `Packages/FlixyPlayback/Sources/{PlaybackDecider,AVPlayerEngine,VLCPlayerEngine}`, `App/Views/{Sidebar,Home,Library,Detail,Player}` ### Phase 2 — Transcoding, adaptive Bitrate, Spuren, Sync - `PlaybackDecider` um Transcode-Zweig erweitern: `/video/:/transcode/universal/decision` fragen, bei Bedarf `start.m3u8` mit Client-Profil und `maxVideoBitrate` bauen - Echtes ABR über den HLS-Stream, dazu Obergrenzen-Regler getrennt für WLAN und Mobilfunk - Untertitel- und Tonspurwahl (sidecar bei DirectPlay, eingebrannt bei Transcode), Untertitel-Styling, bevorzugte Sprache merken - Intro-/Abspann-Marker aus `includeMarkers=1`: Skip-Button, optionales Auto-Skip, Autoplay der nächsten Folge - Fortschritt an den Server melden (`/:/timeline`), gesehen/ungesehen markieren (`/:/scrobble`, `/:/unscrobble`), ungesendete Updates offline puffern und nachreichen ### Phase 3 — Download-Engine & Offline - Hintergrund-`URLSession`, Zustandsautomat, Wiederaufnahme nach App-Neustart - Einzeldownload (Film/Folge) und Batch (Staffel/Serie) mit Gesamtfortschritt und Abbruch - Download in Originalqualität über `?download=1` - Offline-Metadaten und Cover mit sichern, Offline-Wiedergabe aus der lokalen Datei - Downloads-Ansicht in der Seitenleiste: laufend, offline verfügbar, Belegung, pausieren/fortsetzen/löschen - Nur-WLAN-Schutz, Live Activity mit Fortschritt in Sperrbildschirm und Dynamic Island ### Phase 4 — Download-Intelligenz - Qualitätswahl pro Download (Original oder server-komprimiert) — **vorab ein Spike**: Plex-Sync-API (`/sync/items`) gegen HLS-Segment-Download abwägen - Einstellbares Speicherlimit mit Warnung - Automatisches Löschen nach dem Ansehen (global oder pro Serie) - Regel-Engine „halte N ungesehene Folgen offline" — lädt beim Weiterschauen nach ### Phase 5 — Feinschliff & Personalisierung - Akzentfarben-Einstellung: kuratierte Kacheln plus eigene Farbe mit automatischer Helligkeits-/Kontrastkorrektur gegen die dunklen Flächen - Startseiten-Reihen ein-/ausblenden und per Drag umsortieren - Watchlist verwalten, Sterne-Bewertung, Bibliotheks-Scan anstoßen (hinter Bestätigung) - Animationen, leere Zustände, Fehlerzustände, iPad-Feinschliff, Lokalisierungs-Durchgang ### Phase 6 — Live-TV & EPG - **Spike zuerst:** DVR-/EPG-Endpunkte sind bei Plex am schlechtesten dokumentiert — `/media/providers`, Sendergitter, `/livetv/sessions` - Sender-Liste, EPG-Raster mit Zeitachse, Umschalten auf laufende Sendung (immer transcodiert, baut auf Phase 2 auf) ### Später / bewusst nicht geplant Mehrere Server und Plex-Home-Profile (Datenschicht ist dafür vorbereitet), macOS, tvOS, Musik- und Fotobibliotheken, Chromecast, Trailer/Extras, Löschen von Serverdateien, Home-Screen-Widget, Siri/Spotlight. ## Risiken, die früh geprüft werden müssen 1. **VLCKit auf iOS 26 + Swift 6** — Verfügbarkeit über SPM, Concurrency-Verhalten, Binärgröße (~60–100 MB). Spike in Phase 0/1, bevor die Player-Abstraktion festgezurrt wird. 2. **Komprimierte Downloads** — die Plex-Sync-API ist umständlich und schlecht dokumentiert. Spike vor Phase 4; Original-Download in Phase 3 ist der sichere Boden. 3. **Sehr große Dateien im Hintergrund** — iOS drosselt Background-Transfers. Verhalten mit einem 40-GB-Remux real prüfen, `isDiscretionary` bewusst setzen. 4. **Live-TV** — kann sich als deutlich größer herausstellen als geplant; deshalb bewusst am Ende. ## Verifikation - **Automatisiert:** `swift test` je Package — PlexKit gegen aufgezeichnete Server-Antworten, `PlaybackDecider` gegen eine Matrix aus Codec/Container/Gerät (die wichtigste Testtabelle im Projekt), Download-Zustandsautomat inkl. Pausieren/Fortsetzen/Abbruch. - **Manuell je Phase im Simulator und auf dem Gerät:** - P1: Anmelden, Server finden, Bibliothek durchblättern, MP4 mit AVPlayer und MKV mit VLCKit abspielen - P2: Datei erzwungen transcodieren, Netzwerk drosseln (Network Link Conditioner) und Bitratenwechsel beobachten, Fortschritt auf einem zweiten Plex-Client gegenprüfen - P3: Serie herunterladen, App killen, Flugmodus, offline abspielen; Live Activity auf einem echten Gerät prüfen - P5: Akzentfarbe durchschalten und Kontraste gegenprüfen, DE/EN umschalten - **Am Ende jeder Phase:** ein lauffähiger Build, keine offenen Compiler-Warnungen, Screenshot-Durchlauf der Kernbildschirme auf iPhone und iPad.