Files
Flaro/docs/superpowers/specs/2026-09-11-flixy-design.md
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

10 KiB
Raw Permalink Blame History

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:

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.