Files
Flaro/docs/superpowers/specs/2026-09-11-flixy-design.md
T
GuidoandClaude Opus 5 1ed64bf166 Phase 0: Projektgerüst, Design-Tokens und Navigation
Legt das Fundament für den Plex-Client an:

- XcodeGen-Projekt nach dem Muster von Onyx (com.scarriffleservices,
  strict concurrency, Projektdatei nicht eingecheckt)
- FlixyKit mit vier Modulen: PlexKit, FlixyDesign, FlixyPlayback,
  FlixyDownloads. macOS steht als Plattform mit drin, damit die
  Logik-Tests ohne Simulator laufen.
- FlixyDesign: dunkle Farb-Tokens, Schriftskala, Poster- und
  Querformat-Kacheln, Aufmacher-Bild. Die Akzentfarbe fließt über die
  Umgebung und wird per WCAG-Rechnung lesbar gehalten.
- PlexKit: Kopfzeilen-Identität, Transport-Protokoll, Fehlerarten und
  die Medienmodelle samt nachsichtigem Decoder.
- FlixyPlayback: VideoPlayerEngine-Protokoll und PlaybackDecider — die
  eine Funktion, an der Phase 2 das Transcoding andockt.
- Ausfahrbare Seitenleiste auf dem iPhone, Split-View auf dem iPad.
- 45 Tests, alle grün.

VLCKit 4.0.0-a24 ist vorab geprüft: baut über SPM unter Swift 6 mit
strict concurrency. Die iOS-Slice ist mit 239 MB allerdings gewichtig,
deshalb bekommt sie in Phase 1 ein eigenes Target.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 16:04:40 +02:00

136 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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<PlaybackState> { 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.