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>
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user