Der Umschalter stand in einem `withAnimation`, und trotzdem sprang die Leiste
von einer Stellung in die andere. `@AppStorage` schreibt in die
Voreinstellungen und meldet die Änderung erst danach zurück — sie kommt also
außerhalb der Animation an, und die Animation hat nichts, woran sie sich
festhalten könnte.
Jetzt führt ein Zustand dieser Ansicht, und gespeichert wird hinterher. Die
Bindung an die Leisten bringt die Animation gleich mit.
Nachgesehen, nicht vermutet: mit versuchsweise zwei Sekunden Dauer und einer
Serienaufnahme von außen ist der Zwischenzustand fotografiert — Seitenleiste
halb nach links draußen, Leiste oben im Einblenden, Inhalt auf dem Weg. Danach
zurück auf knappe 0,32 Sekunden.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ein Sicherheitsrand je Bildschirm wandert nicht in einen geschobenen Bildschirm
mit. Die Einstellungen schieben ihre Unterseiten über gewöhnliche Verweise, und
dort fehlte er: die Beschriftungen lagen hinter der Seitenleiste und wurden von
der Maske abgeschnitten — zu sehen waren nur die rechtsbündigen Werte.
Der Platz für die Leiste ist jetzt ein echter Rand am ganzen TabView. Der gilt
für alles, was darin aufgeht.
Dazu: der Aufmacher nimmt zweiundachtzig statt hundert Prozent der Höhe. Über
die ganze Höhe sah er aus wie die einzige Seite der App — von den Reihen war
ohne Scrollen nichts zu sehen. So lugt die erste gerade herein.
Und das Umschalten zwischen Seite und oben hat eine Bewegung: die Leiste kommt
von der Seite beziehungsweise von oben herein und geht dorthin zurück. Die
Überlagerung behält dabei ihre Ausrichtung — wechselte sie mit, sprang die
Leiste im Übergang an die neue Ecke, statt dorthin zu wandern.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Die Maske hörte an der Inhaltskante auf, die Leiste endet aber zwölf Punkte
davor — dazwischen stand nichts. Jetzt reicht das Bild bis an ihre Kante heran.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Die Seitenleiste lässt sich wieder bearbeiten: Ziehen zum Umsortieren, Haken
zum Ein- und Ausblenden. Das Anordnen macht `Arrangement` aus MediaKit — dieselbe
Rechnung samt Tests, die schon die Reihen der Startseite ordnet, mit ihren
kniffligen Fällen: eine Mediathek, die neu auftaucht, und eine, die aus der
gespeicherten Reihenfolge verschwindet. Gespeichert wird über eine feste
Kennung **ohne** den Titel, damit eine umbenannte Mediathek ihren Platz behält.
Die Suche ist auf dem iPhone kein eigener Reiter mehr. Die Leiste unten hat
fünf Plätze, und einer davon für etwas, das man in einer Mediathek sucht, ist
einer zu viel — sie sitzt jetzt über der Mediathek, neben Filter und Ansicht.
Der Umschalter zwischen Seite und oben ließ sich auf dem Mac kaum treffen: ein
Knopf aus einem Zeichen mit Rand hat ohne `contentShape` nur das Zeichen als
Ziel. Beide Umschalter haben jetzt eine Fläche.
Die Leiste oben trägt dieselbe Schriftgröße wie die Seitenleiste. In
Beschriftungsgröße war sie auf einem großen Bildschirm winzig.
Und eine Maske: der Aufmacher greift nach links unter die Seitenleiste, scrollt
dabei aber, während die Leiste steht — über und unter ihr blieb je ein Streifen
Bild stehen. Die Maske sitzt im festen Teil und lässt genau zwei Flächen übrig:
alles ab der Inhaltskante, und die Fläche der Leiste.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ein negativer Rand dehnt, was er umschließt, meldet nach außen aber die alte
Breite. Er stand ganz oben, also legten sich Verlauf und Schrift nur über den
alten Bereich — und die zwölf Punkte daneben blieben ohne Verlauf und damit
hell. Gemessen: dort 60 Helligkeit, im Inhaltsbereich 19, bei gleichem Bild.
Jetzt wird erst der Verlauf gelegt, dann gedehnt, dann die Schrift gesetzt. Der
Verlauf deckt bis zum Rand, die Schrift bleibt auf der gemeldeten Breite und
fängt von selbst dort an, wo auch die Reihen darunter anfangen.
Der Weg dorthin ging über drei Irrtümer, die alle widerlegt sind: nicht
`glassEffect` (der Streifen blieb bei undurchsichtiger Fläche), nicht der
Platzhalter (er blieb bei grünem Platzhalter), nicht der Scroll-Anzeiger (er
blieb ohne Bildlaufansicht). Die Bildlaufansicht in der Leiste ist trotzdem
draußen — sie wurde nicht gebraucht.
Dazu: der Aufmacher nimmt die volle Höhe statt achtundachtzig Prozent, der
Haarstrich der Leiste ist zurückhaltender, und auf dem iPhone ist der Aufmacher
das **Plakat** statt des Hintergrundbilds. Ein 16:9-Bild hochkant zeigt einen
Ausschnitt aus der Mitte; ein Plakat ist von vornherein hochkant und passt
ohne Beschnitt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Der Aufmacher nimmt jetzt achtundachtzig Prozent der sichtbaren Höhe, statt
sich seine Höhe aus 16:9 auszurechnen. Ein Aufmacher, der ein Drittel hoch ist,
sieht aus wie ein breites Vorschaubild; die zwölf Prozent Rest sind Absicht,
dort lugt die erste Reihe herein und sagt, dass es weitergeht. Auf schmalen
Geräten bleibt es beim Hochformat.
`FlixyArtwork` kann dafür den Rahmen ausfüllen, den es bekommt. Das
Seitenverhältnis bleibt trotzdem wichtig — danach werden die Bilder beim Server
angefordert.
Und `glassEffect` ist raus. Er zeichnet an seiner Kante einen Lichtsaum, und
über einem hellen Aufmacher wurde daraus ein heller Balken von mehreren Punkten
Breite neben der Leiste — kein Rand, sondern ein Streifen. Von einer Kante war
gleichzeitig nichts zu erkennen.
Von Hand sind es drei Lagen: ein trübes Material, ein Schleier gegen die
durchziehende Startseite, und ein Haarstrich, der oben links aufhellt und unten
rechts nachlässt. Dazu ein Schatten — was schwebt, wirft einen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Vier Dinge, die zusammengehören:
Die Spiegelung ist weg. `backgroundExtensionEffect` füllt die Fläche unter der
Leiste, indem er den Bildrand gespiegelt weiterzeichnet — und je größer die zu
füllende Fläche wurde, desto mehr übernahm der Spiegel das Bild. Auf dem iPhone
legte er sich ohne jede Seitenleiste über den Statusbalken. Jetzt läuft das
Bild selbst weiter, über einen negativen Rand.
Es endet dabei an der **Kante der Leiste**, nicht am Fensterrand. Der
Unterschied sind zwölf Punkte, und die waren deutlich zu sehen: neben der
schwebenden Leiste stand ein heller Streifen Bild, den nichts abdeckte.
Das Seitenverhältnis im Karussell musste weichen. Es legte den Aufmacher auf
die Breite des Inhaltsbereichs fest; weil er nach links unter die Leiste
greift, blieb rechts genau so viel schwarz übrig, wie er links gewann.
Und die Maße, in denen das Hintergrundbild angefordert wird, richten sich jetzt
nach dem Rahmen, in dem es landet. Vorher wurden 900×1200 geholt — hochkant —
und in einem 16:9-Rahmen gezeigt: zu sehen war ein Streifen aus der Mitte, oben
und unten fehlte je ein Drittel.
Dazu: auf schmalen Geräten ist der Aufmacher hochkant. In 16:9 blieben auf
einem iPhone keine zweihundertzwanzig Punkte Höhe, und Schriftzug, Zeile,
Beschreibung und zwei Knöpfe lagen übereinander.
Und ein Schleier unter dem Glas der Leiste. Reines Glas zeigt alles, was
dahinter vorbeizieht, und beim Scrollen zieht die ganze Startseite vorbei — aus
den ruhigen Beschriftungen wurde ein unruhiger Brei. Der Schleier nimmt der
Bewegung die Schärfe, ohne die Fläche zuzumauern.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sie auszublenden reichte nicht: solange `mode` auf `tabSidebar` steht, gehört
die Seitenleiste zur Darstellung, und das System stellte sie wieder her — mit
eigenem Pfeil und eigenem Knopf, hinter unserer. Auf dem Simulator fiel das
nicht auf, auf dem Mac standen beide da.
Jetzt wird erst die Form auf `tabBar` gestellt und dann die Tableiste
ausgeblendet. Damit gibt es keine Seitenleiste mehr, die zurückkommen könnte.
Der Test setzt die Stellung der Leiste über ein Startargument, statt zu nehmen,
was ein voriger Lauf hinterlassen hat — er fing sonst beim falschen Bild an.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Die System-Seitenleiste sitzt auf dem iPad als schwebende Fläche und auf dem
Mac angeklebt am Fensterrand. `preferredLayout = .overlap` ist die dafür
vorgesehene Bitte, und macOS verwirft sie stillschweigend — gemessen, nicht
vermutet.
Statt deshalb die ganze Navigation neu zu bauen, bleibt der `TabView` stehen
und verliert nur sein Kleid: `TabChromeSuppressor` blendet Seiten- und
Tableiste aus, gezeichnet wird darüber. Auswahl, ein eigener Navigationsstapel
je Eintrag, erhaltener Scrollstand beim Wechseln — all das macht weiter das
System. Dass das Ausblenden geht, war kein Ratschluss: derselbe Griff hatte die
Leiste vorher schon versuchsweise verschwinden lassen.
Neu sind `FlixySidebar` (schwebende Fläche mit eigenem Rand, Mediatheken
einklappbar, Konto unten) und `FlixyTopBar` (dieselben Einträge als Leiste
oben, Mediatheken als Menü, weil oben der Platz waagerecht begrenzt ist). Das
iPhone bleibt beim System: dort gibt es keine Seitenleiste, sondern die Leiste
unten, und die ist überall dieselbe.
Der Platz für die Leiste ist ein **Sicherheitsrand**, kein Abstand, und er
hängt je Bildschirm — weder `TabView` noch `NavigationStack` reichen ihn an
ihren Inhalt weiter. Damit bleibt eine Reihe fensterbreit und schiebt ihre
Kacheln beim Wischen hinter die Leiste, stellt aber im Ruhezustand keine
dorthin. Der Aufmacher darf über seinen Bereich hinaus; das `.clipped()` im
Karussell musste dafür weichen, es schnitt genau das ab.
Noch nicht dabei: „Bearbeiten" mit Ziehen und Ein-/Ausblenden. Die Einträge
stehen in fester Reihenfolge.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Beim Kreuzblenden sind für einen Moment beide Bilder halbdurchsichtig — und
dazwischen scheint der Untergrund durch. Das war das helle Aufblitzen des
vorigen Titels.
Jetzt bleibt das alte Bild voll deckend liegen, und das neue legt sich darüber,
während es hereinrutscht. Erst wenn oben nichts mehr durchscheint, verschwindet
unten etwas — die Fläche ist zu keinem Zeitpunkt durchsichtig.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Der Schriftzug hing weiter nicht an der Animation, und der Zwischenspeicher war
nicht schuld: er lag im Actor, und ein `await` kostet immer mindestens einen
Durchlauf. Das neue Bild erschien deshalb im ersten Bild ohne Schriftzug und im
zweiten mit — er sprang also getrennt herein, obwohl er längst da war.
Der Speicher liegt jetzt außerhalb des Actors und lässt sich beim Zeichnen
lesen. Das gilt für alle Bilder: auch eine zurückscrollende Kachel blitzt nicht
mehr kurz grau auf.
Zur Seitenleiste auf dem Mac, gemessen statt vermutet: `automatic` löst sich
laut Apples eigener Beschreibung auf iOS zu `overlap` auf (die Leiste schwebt
über dem Inhalt) und auf macOS zu `tile` (sie schiebt ihn zur Seite und klebt
am Fensterrand). Der Griff dorthin sitzt richtig — derselbe Weg hat die Leiste
auf Kommando ganz ausgeblendet —, aber `overlap` wird auf dem Mac
stillschweigend verworfen. Die Bitte bleibt trotzdem stehen; sie kostet nichts.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`AsyncImage` bringt seine eigene Einblendung mit und fängt erst an zu laden,
wenn es erscheint. Beim Überblenden des Aufmachers blendete sich der Schriftzug
des Films deshalb getrennt und später ein, während Titelzeile, Beschreibung und
Knöpfe längst mitgewandert waren — zwei Bewegungen, wo eine sein soll.
Jetzt lädt er über denselben Zwischenspeicher wie alle anderen Bilder, und das
Karussell holt ihn zusammen mit dem Hintergrundbild vor. Beim Erscheinen liegt
er bereit und macht mit, was der Aufmacher macht.
Dazu ein kleines Hilfsprogramm, das die Fensterkennung eines laufenden
Programms ausgibt, damit `screencapture -l` genau dessen Fenster fotografiert
und sonst nichts vom Bildschirm. Das System-Python bringt PyObjC nicht mehr
mit, und ohne Kennung gibt es keinen Weg dorthin.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Der gespiegelte Ausschnitt hinter der Seitenleiste war nicht das Problem —
JellyTV macht es genauso. Auffällig wurde er erst durch die Bewegung: beim
seitlichen Durchblättern wanderten das echte und das gespiegelte Stück
gegeneinander, und die Naht dazwischen war das auffälligste Ding auf dem
Bildschirm.
Jetzt rutscht das neue Bild 32 Punkte herein und blendet dabei über das alte.
Nichts wandert weit genug, als dass ein Versatz auffiele. Wischen blättert
weiter, nach vorn wie zurück; das nächste Bild wird geholt, solange das
aktuelle noch steht, sonst blendet der Aufmacher auf eine graue Fläche über.
Dazu ein Testziel, das die App mit der Attrappe in einen Zustand fährt und
fotografiert. Der Grund ist ein wiederkehrender Fehler: Aussagen über das
Aussehen waren Vermutungen, weil niemand hinsehen konnte — der Simulator kennt
keinen Befehl fürs Antippen, und dieses Xcode bringt kein Simulator-Fenster
mehr mit. Damit ist „sieht es so aus?" eine Frage, die sich beantworten lässt.
Die Startargument-Anmeldung an der Attrappe steht unter #if DEBUG und führt am
Schlüsselbund vorbei: kein echter Server, kein echtes Konto, kein echter Token.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Beide App-Ziele lesen denselben Ordner ein. XcodeGen nimmt nur die eigene
Info.plist aus den Quellen heraus — die des jeweils anderen Ziels bleibt drin
und landet unverarbeitet als Ressource im Bündel, mitsamt dem wörtlichen
$(PRODUCT_BUNDLE_IDENTIFIER).
Wer die Datei danach liest, liest die falsche: codesign stempelte
Identifier=$(PRODUCT_BUNDLE_IDENTIFIER) auf die App, und das iPad lehnte sie
als „not a valid bundle" ab. „Clean Build Folder" konnte daran nichts ändern —
der Rat war falsch, der Fehler kam aus project.yml.
Dazu: Bilder laden jetzt über ArtworkLoader statt AsyncImage. AsyncImage lädt
bei jedem Erscheinen neu und kennt keinen Zwischenspeicher; bei sechzig Kacheln
plus einem großen Aufmacher stauen sich die Anfragen hinter sechs Verbindungen.
Auf dem iPad fiel das nicht auf, auf dem Mac blieb der Aufmacher grau.
Und die beiden #if os(macOS) um die Breite der Seitenleiste liefen unter
Catalyst ins Leere — dort ist os(iOS) wahr.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Drei Fehler auf einmal, und alle drei meine.
**Die App kam gar nicht ins Netz.** Ein Catalyst-Programm läuft in der Sandbox
und braucht eine ausdrückliche Erlaubnis für ausgehende Verbindungen. Ohne sie
gibt es keine Fehlermeldung — der Ladekreis dreht sich einfach für immer, und
genau so sah es aus.
**In der Seitenleiste standen Schlüssel statt Wörtern** — „sidebar.downloads"
statt „Downloads". Ich hatte zwei Fassungen derselben Beschriftung gebaut, eine
mit `LocalizedStringKey` und eine mit `String` für Titel, die vom Server
kommen. Bei einem Literal darf Swift sich für beide entscheiden, und es
entschied sich nicht überall gleich. Jetzt gibt es eine Fassung; wer einen
fertigen Text hat, baut sein `Text` selbst.
**Das Fenster öffnete wieder in Notizzettelgröße.** Meine Größenangabe hing an
`os(macOS)` — unter Catalyst gilt aber `os(iOS)`, also griff sie nicht. Dort
hängt die Größe ohnehin an der Szene, nicht am Fenster.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Die Untergrenze allein war die halbe Arbeit. Eine Spalte, die sich nach ihrem
Inhalt richtet, lässt sich ohne Deckel über das halbe Fenster ziehen — und
genau das ging.
Beide Zahlen stehen jetzt an einer Stelle: 190 Punkt schmal genug, dass die
Leiste nicht bläht, 320 breit genug für lange Mediatheksnamen, und das ist
zugleich die Breite, die eine Seitenleiste auf dem iPad hat. Die Beschriftungen
tragen dieselben Grenzen, damit die Spalte nicht doch wieder an einem langen
Wort hängen bleibt.
Angefasst wird die Breite über den Inhalt der unteren Leiste — die sitzt in der
Seitenleiste, also nimmt diese ihre Angabe an. Einen unmittelbaren Griff an die
Spalte gibt es bei dieser Navigationsform nicht.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Drei Gründe, warum der Mac anders aussah, und keiner davon war Geschmack.
**Die Schrift.** macOS rendert dieselben Textstile deutlich kleiner als iOS:
`largeTitle` misst dort 26 Punkt statt 34, Fließtext 13 statt 17. Eine
Oberfläche, die auf dem iPad stimmt, wirkt damit zusammengeschrumpft. Auf dem
Mac stehen jetzt feste Größen — die von iOS. Das kostet dort Dynamic Type;
vertretbar, weil macOS keine systemweite Textgröße kennt und man ein Fenster
stattdessen größer ziehen kann.
**Der Aufmacher wächst mit.** Das war der zweite, größere Teil: das Bild ist so
breit wie das Fenster, die Schrift darauf war es nicht. Bei 1280 Punkt Breite
wirkt ein 34-Punkt-Titel verloren, bei 834 auf dem iPad genau richtig. Titel,
Schriftzug, Angaben und Abstände richten sich jetzt nach der Breite des
Aufmachers — mit dem iPad als Bezug und einem Deckel, damit ein Fenster über
die ganze Wand nicht in Plakatschrift endet. Die Beschreibung bekommt
außerdem eine Zeilenbreite: ein Satz über 1280 Punkt ist unlesbar, das Auge
verliert die Zeile.
**Die Seitenleiste.** Sie richtet sich nach ihrem Inhalt, und bei Wörtern wie
„Start" fällt sie auf eine Breite zusammen, in der schon der Kontoname darunter
nicht mehr Platz hatte — daher das „S…". Die Beschriftungen haben jetzt eine
Mindestbreite.
**Die Formulare.** Auf dem Mac nahm SwiftUI den Spalten-Stil: Beschriftungen
rechts ausgerichtet, keine abgesetzten Abschnitte. Jetzt derselbe gruppierte
Stil wie auf iOS.
Dazu: Scrollbalken sind weg. Der graue Streifen unter dem Aufmacher kam daher,
dass in den Systemeinstellungen „Scrollbalken immer einblenden" steht —
dagegen reicht `.hidden` nicht, `.never` schon. Und das Fenster öffnet mit
1280 × 820 statt in Notizzettelgröße, ohne graue Titelleiste.
263 Tests grün, iOS baut unverändert.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Und dein Argument dafür war das beste: auf dem Mac kann ich selbst hinsehen.
Kein Simulator-Tunnel, der mitten im Prüfen abstürzt — `screencapture` auf das
Fenster der App, und zwar nur auf ihres, nicht auf den Bildschirm.
**Ein eigenes Ziel, nicht ein gemeinsames.** Die Info.plist der iOS-App ist
voller iOS-Angaben: Startbildschirm, erlaubte Ausrichtungen, Live Activities.
Die alle bedingt zu machen hieße, dass jede spätere Änderung an der iOS-Seite
über eine Stelle geht, die auch den Mac betrifft. Zwei Ziele sind ein paar
Zeilen mehr und eine Sorge weniger.
**Was den Unterschied ausmacht, steht an einer Stelle.** Ein neuer
`PlatformShims` in FlixyDesign übersetzt, was auf beiden Seiten verschieden
heißt: schmale Titelzeile, halbhohes Blatt, Eingabefelder ohne
Großschreibung, die Plätze in der Werkzeugleiste, die Zwischenablage. Zwanzig
Ansichten benutzen ihn, statt zwanzigmal `#if os(iOS)` zu tragen — sonst
wandert die Fallunterscheidung in jede neue Datei und wird beim übernächsten
Bildschirm vergessen.
Wirklich verschieden sind nur fünf Stellen: die Bildfläche (`UIView` gegen
`NSView`), der AirPlay-Knopf, die Ausrichtungssperre (auf dem Mac gibt es
nichts zu sperren), der App-Delegat für Hintergrund-Downloads und das
Anmeldefenster, das ein anderes Fenster als Anker braucht.
**Das Karussell blättert jetzt mit einem einrastenden Streifen** statt mit
`TabView(.page)` — den Seitenstil gibt es auf dem Mac nicht. Das ist nebenbei
die bessere Lösung: das verschachtelte TabView hatte die Eigenschaft, beim
Wechsel kurz zwei Seiten gleichzeitig zu zeigen.
**Was auf dem Mac fehlt und warum:** VLC. Das Binärpaket ist auf iOS
festgelegt; sein xcframework enthält zwar Mac-Teile, aber das ist eine eigene
Runde. Solange fällt die Wiedergabe dort auf den Systemplayer zurück — MKV und
DTS gehen also noch nicht. Live Activities gibt es auf dem Mac nicht, Bild-in-
Bild startet von Hand statt beim Verlassen der App.
Gesehen: die App startet, der Anmeldebildschirm steht, beide Wege sind da.
Angemeldet habe ich mich auf dem Mac **nicht** — das ginge nur, indem ich
einen Token in deinen Schlüsselbund schreibe, und dann fragt macOS dich beim
ersten Zugriff nach dem Passwort. Das machst du mit einem Klick selbst.
iOS baut unverändert, 263 Tests grün.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
**Eine Taste zum Vergrößern** unten rechts auf der Kachel. Das Antippen der
Fläche vergrößert weiterhin, aber auf derselben Fläche liegen schon das Ziehen
und das Aufziehen — und was dabei danebengeht, sieht aus wie „reagiert nicht".
Ein Knopf geht nicht daneben.
**Ton und Untertitel getrennt.** Es sind zwei verschiedene Fragen: die Tonspur
stellt man einmal am Anfang um, die Untertitel mitten im Film. In einem
gemeinsamen Blatt scrollte man jedes Mal an der anderen Liste vorbei. Das
Untertitel-Symbol ist gefüllt, solange welche laufen — so sieht man es, ohne
das Blatt zu öffnen. Und eine Auswahl schließt das Blatt jetzt selbst.
**AirPlay.** Nicht nachgebaut, sondern die Systemstelle: sie kennt Geräte, die
eine App nie aufzählen könnte. Der Knopf erscheint nur beim Systemplayer — VLC
zeichnet selbst und kann kein AirPlay, und ein Knopf, der dort ins Leere
führte, wäre schlimmer als keiner.
**Die Qualität steht als Text da**, nicht als drei Schieberegler: „1080p",
„720p", „Auto". Bei „automatisch" steht dort, was gerade tatsächlich ankommt —
das weiß nur der Player, und ihn zu fragen ist ehrlicher, als „Auto"
hinzuschreiben und die Antwort schuldig zu bleiben.
Dazu die Einstellungen mit farbigen Symbolen und einer Kopfkarte je Bereich.
Das ist nicht nur Zierde: in einer Liste aus zehn gleich aussehenden Zeilen
findet das Auge nichts wieder, und „das mit dem blauen Zahnrad" ist eine
brauchbare Beschreibung. Die Farben sind fest und ausdrücklich **nicht** die
Akzentfarbe — trüge alles dieselbe, unterschiede sie nichts mehr.
Und der Aufmacher läuft jetzt unter der Seitenleiste weiter
(`backgroundExtensionEffect`): die Ränder werden gespiegelt und weich
fortgeschrieben, während Titel und Knöpfe stehen bleiben. Vorher endete das
Bild an der Kante der Leiste, und beim Weiterwandern schob sich das nächste an
einer harten Linie ins Bild.
263 Tests grün. Player-Knöpfe und Einstellungen angesehen; den Aufmacher hinter
der Leiste konnte ich an den einfarbigen Flächen der Attrappe nicht sicher
beurteilen — der Simulator ist dabei erneut abgestürzt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Der Simulator läuft wieder. Ich habe alles durchgeklickt, was ich dir in den
letzten beiden Runden ungeprüft gegeben habe. Es war nicht in Ordnung.
**Zwei Navigationsleisten übereinander.** Auf dem iPhone hat die Leiste unten
fünf Plätze; alles darüber schiebt das System in ein „Mehr"-Verzeichnis — und
dessen Einträge bekamen zwei Zurück-Pfeile, einen vom System und einen von
uns. Statt das zu flicken, gibt es jetzt die **Bibliothek**, die du ohnehin
wolltest: auf dem iPhone ein Eintrag, hinter dem alle Mediatheken stehen, auf
dem iPad weiter jede einzeln in der Seitenleiste. Damit bleibt die Leiste bei
fünf Einträgen, und das „Mehr" entfällt.
**Kacheln in der Mediathek öffneten nichts.** Kein Fehler, keine Meldung —
Antippen führte einfach ins Leere. Der Grund: in einem Stapel dürfen nicht
beide Arten von Verknüpfung nebeneinander vorkommen. Sobald ein Bildschirm
über eine **Ansichts**-Verknüpfung geschoben wurde, fanden die
**Wert**-Verknüpfungen darin ihr Ziel nicht mehr. Jetzt ist alles wertbasiert,
und das Ziel für Detailseiten steht **einmal je Stapel** an dessen Wurzel statt
in jedem Bildschirm einzeln.
**Der Lupenknopf in der Kopfzeile zeigte auf ein Ziel, das es nie gab.** Kein
Bildschirm hat je ein `SearchRoute`-Ziel angemeldet — der Knopf tat seit jeher
nichts. Er ist weg; die Suche hat ihren eigenen Platz in der Leiste.
**Der Download-Dialog zeigte Schlüssel statt Namen** — „quality.hd" statt
„720p". Die drei Übersetzungen fehlten schlicht.
**Die Kontokarte wuchs aus dem Bild heraus.** Die Leiste unten in der
Seitenleiste hat eine feste Höhe; meine aufklappende Karte schob die Zeile mit
dem Namen nach unten hinaus. Jetzt hängt sie als Popover darüber — die Zeile
bleibt stehen, wie bei JellyTV.
Verkleinerte Downloads bei Jellyfin laufen: Stufen erscheinen, die Adresse
stimmt, die Datei kommt an. Dass die Attrappe nur 28 kB liefert und die App
das als „keine Videodatei" zurückweist, ist richtig so — die Prüfung tut, was
sie soll.
Zwei Gruben in der Attrappe nebenbei gestopft: sie kannte `/stream.mp4` nicht
und meldete ihr Testbild als 360p, womit es gar keine kleineren Stufen geben
konnte.
263 Tests grün. Diesmal alles angesehen: iPhone und iPad, Einstellungen,
Bibliothek, Detailseite, Download-Dialog, Kontokarte.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Deine Einstellungs-Bilder sind eine lange Liste. Ich habe sie durchgegangen
und getrennt: was etwas steuert, ist gebaut; was eine Einstellung für eine
Sache wäre, die es nicht gibt, ist nicht gebaut. Ein Schalter, der nichts
bewirkt, ist schlimmer als kein Schalter.
**Die Einstellungen sind jetzt eine Übersicht.** Vorher stand alles
untereinander auf einem Bildschirm — Serverangaben, Qualität, Wiedergabe, drei
Verweise, Downloads, Version, Abmelden. Das war beim Bauen bequem und beim
Suchen anstrengend. Jetzt führt jede Zeile in einen Bildschirm, der zu einer
Frage gehört: Server, Erscheinungsbild, Startseite, Wiedergabe, Ton &
Untertitel, Downloads, Diagnose.
**Neu, und jedes davon greift irgendwo:**
- Zurück- und Vorspulen getrennt einstellbar. Die Knöpfe im Player und die
Doppeltipp-Flächen tragen danach auch das passende Symbol.
- Startgeschwindigkeit — die Engine kann es längst, gefragt wurde nie.
- „Im Querformat sperren" abschaltbar. Für die allermeisten Filme richtig, für
den, der im Bett auf der Seite liegt, das Gegenteil.
- „Gewählte Qualität merken": die Stufe gilt dann auch für den nächsten Titel.
Aufbewahrt wird eine **Zahl**, keine Sprosse — welche Sprossen es gibt, hängt
von der Quelle ab, und eine 720p-Datei kennt keine Zwanzig-Megabit-Stufe.
Dann kommt die nächstgelegene, nicht „automatisch".
- „Bitrate im Qualitätsmenü zeigen".
- Gleichzeitige Downloads. Der Wert gab es schon in der Engine, nur ohne
Bedienung und ohne dass er einen Neustart überlebte.
- Aufmacher: wie viele Titel, und ob sie aus Angefangenem oder nur aus
Neuzugängen kommen.
- „Weiterschauen und Als Nächstes zusammenlegen" und „Gesehenes aus
Neuzugängen ausblenden" — beides in der Ansicht, nicht im Server: das sind
Fragen der Darstellung, und so gelten sie für Plex und Jellyfin gleich.
**Verkleinert herunterladen geht jetzt auch bei Jellyfin.** Da hatte ich zu
früh aufgegeben: ich hatte nur nach der Adresse zum *Abspielen* gesucht, und
die ist eine Abspielliste mit hunderten Schnipseln — als Datei unbrauchbar.
`/Videos/{id}/stream.mp4` gibt dieselbe Umrechnung als eine Datei heraus. Die
Größe bleibt eine Schätzung; eine Umrechnung, die erst beim Abruf beginnt,
kennt ihre eigene Länge nicht.
**Die Kontozeile ist jetzt eine Karte, die nach oben aufklappt**, kein
Systemmenü mehr. Ein Menü legt sich als eigene Ebene über alles und springt
dorthin, wo Platz ist; die Karte bleibt, wo sie ist, und lässt die Zeile mit
dem Namen stehen — man sieht also weiter, um wessen Konto es geht.
**Nicht gebaut, mit Grund:** Trailer im Karussell, Poster- und Sammlungsstil,
Untertitel-Aussehen, sekundäre Untertitel, lokale Intros, Modular Home,
App-Symbol. Für all das fehlt das, was die Einstellung steuern würde — es gibt
in Flixy weder Trailer noch Sammlungen, und der Player zeichnet Untertitel
nicht selbst.
263 Tests grün. **Nicht angesehen**: der Simulator-Tunnel ist weiterhin
abgestürzt und ließ sich nicht wieder aufbauen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Zwei Dinge aus deinen Einstellungs-Bildern.
**Das Konto sitzt jetzt unten in der Seitenleiste**, nicht als gleichrangiger
Eintrag zwischen den Mediatheken. Dort sucht man es, und von dort führt der
Weg in die Einstellungen und zum Abmelden. Auch das ist eine Systemstelle —
`tabViewSidebarBottomBar` —, also dieselbe Zeile, an der sie bei JellyTV
steht. Auf dem iPhone gibt es sie nicht; dort bleibt es beim eigenen Eintrag.
Der Buchstabe auf der Scheibe ist kein Platzhalter für ein Bild, das noch
kommt: Jellyfin gibt kein Profilbild heraus, solange keines hinterlegt ist,
und Plex kennt den Kontonamen am Server gar nicht. Bei Plex steht deshalb der
Servername oben und darunter nichts — statt zweimal dasselbe.
**Ton- und Untertitelsprache lassen sich jetzt einstellen.** Beim Durchsehen
deiner Bilder ist mir aufgefallen, dass es diese Vorliebe längst gibt: der
Player liest sie bei jeder Wiedergabe aus und wählt die passende Spur. Nur
äußern konnte man sie nirgends — sie stand in den Voreinstellungen und blieb
dort für immer leer. Eine Vorliebe, die niemand äußern kann, ist keine.
Fünfzehn Sprachen, keine vollständige Liste: ein Rad mit zweihundert
Einträgen, um „Deutsch" zu finden, wäre keine Hilfe. Was fehlt, kommt später
mit einem Suchfeld dazu.
Gebaut und übersetzt, aber **nicht angesehen**: der Simulator-Tunnel ist
mitten im Prüfen abgestürzt und ließ sich nicht wieder aufbauen. Dass es die
Systemstelle gibt, beweist der Compiler; wie sie aussieht, habe ich diesmal
nicht gesehen.
258 Tests grün.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Auf deinem vierten Bild steht „Ziehe Objekte zur Seiten- oder Tableiste, um
sie anzupassen". Das ist kein Text, den jemand geschrieben hat, sondern einer
von Apple. JellyTV benutzt dort `TabView` mit `sidebarAdaptable` — und damit
ist die Frage, wie die das hinbekommen, beantwortet: gar nicht, das System
macht es.
Meine drei selbstgebauten Formen fliegen dafür raus: das ausfahrbare Blatt,
die geteilte Ansicht und die Reihe oben, die ich vor einer Stunde noch selbst
gebaut habe. Zusammen konnten sie weniger als das eine Bauteil und mussten
dreimal gepflegt werden. Was jetzt vom System kommt:
- die Seitenleiste aus Glas, mit dem Inhalt **dahinter** statt daneben
- der Knopf in ihrer Kopfzeile, der zwischen Seitenleiste und Leiste **oben**
umschaltet — genau der, den du auf deinen Bildern siehst
- „Bearbeiten" mit Ziehen, Ein- und Ausblenden, für beide Formen gleichzeitig
- auf dem iPhone die gewohnte Leiste unten statt eines Hamburger-Menüs
Die Einstellung „seitlich oder oben" entfällt damit: sie säße neben einem
Systemknopf, der dasselbe besser kann. Was du umstellst, merkt sich das
System selbst; wir geben ihm nur einen Ort zum Aufbewahren.
Die Mediatheken stehen in einem eigenen Abschnitt, der sich in der
Seitenleiste zuklappen lässt. Die Suche bekommt ihre eigene Rolle und wird
deshalb getrennt von den übrigen Einträgen dargestellt.
Angesehen auf iPad (Seitenleiste, Leiste oben, Bearbeiten-Dialog) und iPhone.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Drei Dinge aus dem letzten Durchgang.
**Die Startseite hatte zu wenig anzubieten.** Es gab eine Reihe „Zuletzt
hinzugefügt" für alles zusammen. Jetzt gibt es eine je Mediathek — „Neu in
Filme" und „Neu in Serien" sind zwei verschiedene Fragen, und wer nach einem
Film sucht, blättert in einer gemeinsamen Reihe an Folgen vorbei. Dazu
Favoriten, die es bei Jellyfin ohnehin gibt. Die drei Abfragen laufen
nebeneinander; nur die Mediatheken werden streng geholt, weil sie die Probe
darauf sind, ob der Server überhaupt antwortet.
**Die Seitenleiste ist jetzt echtes Glas**, nicht das alte Material: im
ausfahrbaren Blatt über `glassEffect`, in der geteilten Ansicht auf dem iPad
vom System selbst — dort lag mein eigener Hintergrund darüber und machte ihn
stumpf. Die zum Inhalt zeigende Kante ist gerundet, damit das Blatt sichtbar
über der Seite liegt statt daneben zu stehen.
**Die Navigation kann nach oben.** Eine Einstellung unter Erscheinungsbild,
seitlich oder oben. Oben stehen alle Einträge gleichzeitig da, als Glaskapseln
in einer waagerecht scrollenden Reihe — die Zahl der Mediatheken kommt vom
Server, und zusammengequetschte Beschriftungen wären die schlechtere Antwort
als eine Reihe, die weiterläuft.
Angesehen auf iPhone und iPad: beide Formen, das Glas in beiden Leisten, die
neuen Reihen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Die Kachel ließ sich nicht mehr öffnen, und das war kein Zufall: das
Antippen zeigte erst die Bedienelemente, und die zweite Berührung sollte
vergrößern — traf dabei aber den Abspielknopf, der genau in der Mitte lag.
Jede weitere Berührung landete wieder dort. Vergrößern war damit überhaupt
nicht möglich.
Jetzt gehört die Mitte dem Vergrößern, und zwar beim ersten Antippen. Die
zwei Knöpfe — Pause und Schließen — sitzen dauerhaft sichtbar in den oberen
Ecken, auf einer dunklen Scheibe, damit sie auf hellen Filmbildern nicht
verschwinden. Unten bleibt der Fortschrittsbalken.
Dass die Kachel „einfach weg" war, konnte ich nicht nachstellen; der eine
Fall, den ich reproduziert habe, war mein eigener Attrappenserver mit einer
Fortsetzungsstelle jenseits des Dateiendes. Zwei Dinge machen es trotzdem
unwahrscheinlicher: die Knöpfe sind auch dann zu sehen, wenn das Bild
schwarz bliebe — vorher war eine schwarze Kachel in einer fast schwarzen
Oberfläche praktisch unsichtbar —, und die Bildfläche hängt jetzt in einer
eigenen Hülle je Ansicht. Beim Wechsel zwischen Kachel und Vollbild gibt es
beide kurz gleichzeitig, und eine `UIView` hat nur einen Vorgesetzten: wer
sie zuletzt bekommt, gewinnt. Gab SwiftUI sie unmittelbar heraus, konnte das
Abräumen der einen der anderen die Bildfläche wegnehmen.
Dazu etwas, das mir beim Ausprobieren selbst passiert ist: der Aufmacher
wanderte weiter, während ich nach „Abspielen" griff, und ich startete den
falschen Film. Er hört jetzt bei der ersten Berührung auf zu wandern. Wer
die Hand darauf hat, fährt selbst.
Nachgesehen auf iPhone und iPad: verkleinern, antippen, wieder im Vollbild.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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>
Vier Dinge aus dem ersten Blick auf einen echten Jellyfin-Server.
**„Zuletzt hinzugefügt" waren nicht die Neuzugänge**, sondern eine
alphabetische Liste. Die Reihe kommt jetzt von `/Users/{id}/Items/Latest` —
dem Endpunkt, den Jellyfin dafür hat. Das behebt zwei Fehler auf einmal: die
Sortierung nach Anlegedatum kam alphabetisch zurück, und selbst wenn sie
gegriffen hätte, wäre sie falsch gewesen. Das Anlegedatum einer Serie ist der
Tag, an dem sie in die Mediathek kam; eine Serie, die seit zwei Jahren dort
steht und gestern eine neue Folge bekommen hat, wäre nie aufgetaucht. Die
Antwort ist dort ein nacktes Feld ohne `Items`-Hülle — dafür gibt es einen
Test, sonst bliebe die Startseite eines Tages stumm leer.
**Der Aufmacher war einer und blieb es.** Jetzt sind es bis zu fünf, die alle
sieben Sekunden weiterwandern und sich auch von Hand durchblättern lassen.
Der Zeitgeber hängt an der aktuellen Seite und fängt bei jedem Wechsel von
vorn an — sonst rutscht der Aufmacher genau dann weiter, wenn man sich einen
Titel ansieht. Wer Bewegung abgestellt hat, blättert selbst. Titel ohne
Hintergrundbild werden übersprungen, solange es andere gibt: eine graue
Fläche mit Text ist keine Bühne.
**Die Seitenleiste ist durchscheinend** statt undurchsichtig, und der
Schleier darunter dunkelt schwächer ab — ein voller Schleier hätte aus dem
Milchglas wieder eine Wand gemacht.
**Der Suchlauf ist aus der Mediathek verschwunden.** Er stand im
Dreipunktmenü, einen Tipp neben „Filter" und „Ansicht wechseln", und
beschäftigt den Server minutenlang. Er steht jetzt in den Einstellungen neben
der Mediathek, zu der er gehört.
Dazu eine Jellyfin-Attrappe unter Tools/: ein Server, der gerade genug kann,
dass sich die Oberfläche ansehen lässt. Damit ist diese Runde tatsächlich
angeschaut worden — Anmeldung per Code, Startseite, wandernder Aufmacher,
Seitenleiste, Einstellungen — ohne einen echten Server anzufassen.
Was weiter aussteht: die Seitenleiste oben statt seitlich, und ihre Einträge
selbst zusammenzustellen. Das ist die nächste Runde, nicht diese.
250 Tests grün.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Der Anmeldebildschirm kannte nur Plex. JellyfinKit war fertig, aber von der
App aus nicht erreichbar — „ich sehe nix von Jellyfin".
Quick Connect steht vorn: der Server nennt einen sechsstelligen Code, den man
in einem bereits angemeldeten Client bestätigt. Das Passwort erreicht Flixy
dabei nie, dieselbe Zusage wie bei Plex. Benutzername und Passwort sind der
Rückfall für Server, auf denen Quick Connect abgeschaltet ist; das Passwort
wird dann für die Dauer einer Anfrage gehalten und danach weggeworfen.
Die Adresse wird nicht erraten, sondern durchprobiert. „192.168.1.50" kann
alles Mögliche heißen, und die Reihenfolge ist der Kern von JellyfinAddress:
eine IP-Adresse im eigenen Netz spricht fast nie HTTPS, ein Domänenname fast
immer. Andersherum wartet man bei jedem Server ohne Zertifikat erst auf eine
Zeitüberschreitung.
Die Sitzung hält von hier an einen Server hinter dem neutralen Vertrag, nicht
mehr einen Plex-Server. Die Ansichten haben das nicht gemerkt — genau dafür
war MediaKit da. Dabei fällt ab, was Jellyfin kann und Plex nicht: Favoriten
stehen im Aktionsmenü, sobald der Server sie kennt, Sterne nur dort, wo es
sie gibt.
Was noch nicht geht und deshalb auch nicht behauptet wird: Wiedergabe und
Live-TV sprechen weiter unmittelbar mit Plex. Bei Jellyfin verschwindet der
Live-TV-Eintrag aus der Seitenleiste, und der Player sagt, dass die
Wiedergabe der nächste Schritt ist, statt „Server nicht erreichbar" zu
melden — er ist ja erreichbar.
Nebenbei zwei Stellen, die der Übersetzer aus Plex hinterlassen hat:
`catch is CancellationError` griff seit den getippten Fehlern nie mehr, weil
eine abgebrochene Netzanfrage als Übertragungsfehler zurückkommt; und das
Ersatz-Hintergrundbild einer Folge war toter Code, weil die linke Seite des
`??` gar nicht optional war.
TokenStore wandert nach MediaKit — Tokens sind nichts Plex-Eigenes. Die
Konten sind nach Server getrennt, damit ein späteres Umschalten nicht jedes
Mal eine neue Anmeldung kostet; „Abmelden" räumt trotzdem beide ab.
248 Tests grün.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Start, Suche, Mediathek, Detailseiten und Downloads arbeiten jetzt mit
`MediaItem` und dem `MediaBackend`-Vertrag. Damit sind sie fertig für
einen zweiten Server, ohne dass an ihnen noch etwas zu tun wäre.
Dabei sind ein paar Namen ehrlicher geworden: aus `grandparentTitle`
wurde `seriesTitle`, aus `viewedLeafCount` `playedEpisodeCount`. Und
`TagStrip` nahm bisher Plex-Marken mit Kennung und Rolle entgegen, um
davon nur den Namen anzuzeigen — Genres sind jetzt schlicht Zeichenketten.
`Arrangement` ist aus PlexKit heraus und generisch: dieselbe Rechnung
gilt für jede Liste, die jemand selbst zusammenstellt. Phase 3 baut die
konfigurierbare Seitenleiste darauf, statt sie ein zweites Mal — und ein
zweites Mal falsch — zu schreiben.
Die Fehlerübersetzung gibt es nur noch einmal. Es waren zwei, und sie
waren bereits auseinandergelaufen: derselbe Fehler bekam je nach
Bildschirm eine andere Erklärung.
Das Nachladen der Mediathek bleibt unangetastet: eine lange Liste, die
fünf Einträge vor dem Ende nachlädt. `MediaPage` heißt zwar Seite, ist
aber eine technische Portion — Blättern mit „weiter zu Seite 2" gehört
nicht in eine Mediathek, in der man stöbert. Der Kommentar dort sagt das
jetzt ausdrücklich, damit es niemand später „aufräumt".
Player, Live-TV und die Download-Engine sprechen noch unmittelbar mit
Plex. Sie folgen als Nächstes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Erster Schritt zu Jellyfin, und er besteht vor allem aus Wegnehmen.
`PlaybackDecider` nahm bisher Plex-Modelle entgegen und las daraus sechs
Felder. Das band die Entscheidung an einen bestimmten Server, obwohl an
der Frage „kann iOS das öffnen?" nichts serverspezifisch ist. Jetzt nimmt
er ein `MediaProfile` — Container, Codecs, Bitrate, ob die Datei
untersucht ist.
Wie ernst die Kopplung war, zeigte der Player selbst: für die
Offline-Wiedergabe baute er sich **erfundene** Plex-Werte zusammen — eine
Media mit Kennung 0, ein Part mit dem Dateinamen als Schlüssel,
zusammengesetzte Spurlisten —, nur damit die Signatur passte. Für eine
Datei, die längst auf dem Gerät lag und mit Plex nichts mehr zu tun
hatte. Dieser Hack ist weg.
Neu ist das Target `MediaKit`: Typen und Rechnungen, die für jeden Server
gelten, ohne eine einzige Abhängigkeit. Darin `MediaProfile` und die
`Duration`-Brücke, die bisher in PlexKit stand — „weil es die unterste
Schicht ist". Das stimmte, solange es nur einen Server gab.
Damit kennen Wiedergabe und Downloads Plex nicht mehr. Zwei der
Abhängigkeiten waren ohnehin tot; die dritte trug nur diese Brücke.
Die wichtigste Testtabelle des Projekts ist mitgewandert und dabei
kürzer geworden: dort steht jetzt, worauf es ankommt, statt vollständiger
Modelle mit Kennungen und Dateipfaden, von denen niemand etwas las.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ein frisch angeforderter Stream beginnt vorn, puffert und springt erst
danach an die Stelle, an der man war. Zehn Sekunden lang lief sichtbar
der Filmanfang — und der Eindruck, die Stelle sei weg und man müsse sie
wiederfinden.
Der Umstieg wird jetzt abgedeckt, solange der Player noch nicht dort ist,
und die Abdeckung nennt die Stelle ausdrücklich. Genau darum geht es:
nicht, dass es schneller wird, sondern dass niemand denkt, er habe etwas
verloren.
Aufgehoben wird sie, sobald der Player innerhalb von zwei Sekunden um die
Stelle liegt. Dazu eine Notbremse nach dreißig Sekunden — springt er nie
dorthin, bliebe sonst für immer eine schwarze Fläche über dem Bild.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`select(quality:)` stieg aus, wenn die Stufe bereits gewählt war. Nach
einem Fehlschlag stand sie aber schon fest — ein erneutes Antippen galt
als „keine Änderung", und es passierte nichts mehr. Man kam nur wieder
heraus, indem man eine andere Stufe nahm.
Steht die Wiedergabe auf „gescheitert", ist ein erneuter Versuch jetzt
erlaubt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Der Decider rechnet nur um, wenn die Datei die Grenze überschreitet. Für
den Normalfall ist das richtig — für eine ausdrückliche Wahl im Player
nicht: wer für eine 8-Mbit-Datei „12 Mbit" wählt, bekam DirectPlay, weil
die Datei ja darunter liegt. Gemeint ist aber „rechne es mir auf diese
Stufe um", nicht „gib mir die Datei, falls sie so vorliegt".
Die Richtlinie kann eine Umrechnung jetzt erzwingen, und die Wahl im
Player tut das.
Dazu verschluckte `select(quality:)` den eigentlichen Grund: lehnte der
Server ab und sagte im Klartext warum, wurde diese Meldung danach durch
das allgemeine „keine abspielbare Datei" ersetzt — der eine brauchbare
Hinweis war damit weg. Jetzt bleibt stehen, was der Server gesagt hat.
Und das Menü war zu viel. Vorn steht die kurze Liste — automatisch,
Original, je eine brauchbare Stufe pro Auflösung, nur Ton. Wer eine
bestimmte Bitrate erzwingen muss, klappt „Alle Stufen" auf; im Alltag
will man „eine Stufe kleiner" und nicht die Wahl zwischen zehn und zwölf
Megabit.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ein Überzug liegt in SwiftUI zwangsläufig obenauf. Ich hatte die
Doppeltipp-Flächen als `.overlay` über den ganzen Player gelegt, also
eine durchsichtige Fläche vor jeden Knopf — Pause, Scrubber, Qualität,
Kopfzeile, alles wirkungslos. Im Stapel entscheidet dagegen die
Reihenfolge, und dort liegen sie jetzt: über dem Bild, unter den
Bedienelementen.
Dazu kleiner. Volle Bildschirmhälften decken die Mitte ab, wo der
Pause-Knopf sitzt, und den oberen Rand mit der Kopfzeile — beim Zielen
auf einen Knopf träfe man zuerst die Fläche. Jetzt sind es zwei Streifen
von je dreißig Prozent Breite in der mittleren Höhe, mit Abstand zu oben
und unten.
Tippen außerhalb zeigt und verbirgt die Bedienelemente wie zuvor.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Die vier erfundenen Stufen weichen der Leiter, die der Server selbst
meldet: 64, 96, 208, 320, 720, 1500, 2000, 3000, 4000, 8000, 10000,
12000, 20000 kbps mit den Auflösungen, die Plex dafür vorsieht. Eine
eigene Leiter hieße, Stufen anzubieten, die der Transcoder nicht kennt —
er rundet dann still auf die nächste, und die Anzeige log.
Angeboten wird nur, was zur Quelle passt. Eine 720p-Datei auf 1080p
hochzurechnen bringt kein Bild dazu, und die Stufen sähen alle gleich aus.
Oben im Blatt steht, was tatsächlich über die Leitung kommt — gemessene
Bitrate, Auflösung, verworfene Bilder. Der Unterschied zur gewählten
Stufe ist keine Haarspalterei: die eine sagt, was erlaubt ist, die andere,
was ankommt. Ruckelt es trotz hoher Stufe, steht die Antwort dort.
„Nur Ton" gibt es, aber ehrlich beschriftet: Plex kennt für Videos keinen
reinen Tonstrom, also wird die unterste Sprosse geholt und das Bild
ausgeblendet.
Dazu Doppeltipp links und rechts für zehn Sekunden zurück und vor. Die
Reihenfolge der Gesten ist entscheidend — das einfache Tippen verschluckt
sonst das doppelte.
Die Kachel hängt nicht mehr in vier Ecken fest, sondern klebt links oder
rechts und ist in der Höhe frei. Vier Ecken sind schnell gebaut, aber im
Weg: steht sie über einem Knopf, bleiben drei Orte, die ebenso stören.
Größer wird sie durch Zusammen- und Auseinanderziehen; der Griff ist weg,
er saß zwangsläufig auf dem Bild und stritt sich mit dem Verschieben.
Und die Begriffe: „Motor" für engine sagt kein deutscher Entwickler.
Fachwörter, die im Deutschen englisch bleiben, bleiben jetzt englisch.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Der Ton, der nach dem Schließen der App weiterlief, und die neue
Wiedergabe, die stumm blieb, sind derselbe Fehler — und er ist meiner.
`stop()` machte drei Netzwerkaufrufe, bevor es den Motor abschaltete:
Fortschritt melden, Transcoder anhalten, Tuner freigeben. Beim Wechsel
habe ich das in den Hintergrund gelegt, damit man nach dem Antippen nicht
wartet. Damit lief der alte Motor die ganze Zeit weiter, und wenn er
endlich abräumte, nahm er der bereits laufenden neuen Wiedergabe die
Audiositzung weg. Bei Live-TV kam dazu, dass der Motor weiterspielte,
während die App längst im Hintergrund war — Hintergrundton ist erlaubt,
und niemand hatte ihn abgeschaltet.
Jetzt ist beides getrennt. `silence()` legt den Motor sofort still, ohne
Netzwerk und ohne Warten; `releaseOnServer()` meldet danach in Ruhe, dass
Schluss ist. Beim Wechsel wird zuerst stillgelegt, dann gebaut — nie
laufen zwei Motoren gleichzeitig.
Dazu eine Grenze für ewiges Laden: ein Live-Strom kann sterben, ohne dass
der Motor es merkt, und der Kreisel drehte sich dann bis zum nächsten
Antippen. Nach vierzig Sekunden ohne Daten wird abgebrochen, der Motor
stillgelegt und gesagt, was los ist — sonst bliebe bei Live-TV obendrein
der Tuner belegt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Drei Sachen aus dem Feldtest.
Der Griff zum Größerziehen war wirkungslos, und zwar durch meinen Fehler:
die Drehung lag auf dem ganzen Überzug statt auf dem Symbol. Bei zwei der
vier Ecken kippte damit die gesamte Trefferfläche weg, und man griff ins
Leere. Jetzt sitzt der Griff über `overlay(alignment:)`, nur der Pfeil
dreht sich, und das Ziehen hat Vorrang vor dem Verschieben der Kachel —
sonst gewinnt das Verschieben, das am selben Ort hängt.
Senderlogos sind Schriftzüge und fast nie quadratisch. Formatfüllend in
einem Quadrat fehlten links und rechts genau die Teile, an denen man den
Sender erkennt. Sie bekommen jetzt eine breitere Fläche und werden
eingepasst statt beschnitten — `FlixyArtwork` kann beides, und welches
gilt, entscheidet der Aufrufer.
Und im Player steht bei Live-TV, was gerade läuft. Der Name reist mit dem
Einschalten mit, denn nachzuschlagen ist er nicht — ein Sender hat keine
Metadaten in der Mediathek. Beim Aufdecken der Bedienelemente wird er
nachgeholt, aber nur, wenn die alte Sendung wirklich abgelaufen ist:
sonst stünde derselbe Name den ganzen Abend, und das wäre derselbe
Fehler, der eben erst im Programm behoben wurde.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Der Fehler steckte in der Abfrage, und er ist grob:
beginsAt> = jetzt − 1 Std.
endsAt< = jetzt + 6 Std.
Beide Grenzen schließen genau das aus, was man sehen will. Ein
Zweistünder, der um halb vier begann, war um Viertel nach fünf
unsichtbar — und angezeigt wurde stattdessen die nächste Sendung, die
durch den Filter passte. Deshalb wich die Anzeige von Plex' eigener ab.
Jetzt wird nur der **Anfang** eingegrenzt, mit sechs Stunden Vorlauf für
lange Sendungen, und keine Grenze auf das Ende. Zurück kommt, was den
Zeitraum berührt, statt was vollständig hineinpasst.
Gefiltert wird danach nur das Programm, nicht die Senderliste: ein Sender
ohne frische Daten soll „kein Programm" sagen, statt zu verschwinden.
Dazu zwei Wünsche aus dem Feldtest:
Die Kachel lässt sich in der Größe ziehen. Der Griff sitzt in der Ecke,
die zur Bildschirmmitte zeigt — am äußeren Rand läge er halb außerhalb
des Daumens. Gespeichert wird die begrenzte Breite, nicht die gezogene,
sonst bliebe eine einmal zu weit gezogene Kachel für immer zu groß.
Und eine Senderauswahl. Über zwanzig Sender zu zeichnen und ihr Programm
zu holen kostet Zeit, und die meisten sieht man nie an. Eine leere
Auswahl heißt weiterhin „alle" — sonst stünde man beim ersten Öffnen vor
einer leeren Liste.
Die Zeiten im Raster-Abzug liegen jetzt in der Zukunft. Mit den echten
Zeitstempeln von damals wäre er ab dem Tag nach der Aufnahme leer
gewesen, sobald auf Frische gefiltert wird.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Wer im Fernsehen etwas sieht und nebenher nachsehen will, was sonst
läuft, musste bisher abbrechen. Plex' eigene Handy-Apps können das auch
nicht — kein Grund, es hier genauso zu machen.
Der eigentliche Umbau ist nicht die Kachel, sondern der Besitz. Die
Wiedergabe hing an der Sichtbarkeit der Ansicht, und zwar dreifach: das
Modell war `@State` im Player, `onDisappear` hat den Motor abgeräumt, und
die Bildebene entstand in `makeUIView` neu. Zurückgehen hieß deshalb:
Motor tot, Transcoder aus, beim Live-TV der Tuner freigegeben.
Jetzt gehört das Modell der Wurzelansicht, die immer da ist. Verkleinern
und Vergrößern wechseln nur noch die Darstellung; beendet wird die
Wiedergabe an genau einer Stelle, und zwar weil jemand es will.
Die Bildebene hält das Modell selbst — dasselbe Muster, das VLC seit
jeher verwendet. Dieselbe `UIView` wandert zwischen Kachel und Vollbild,
statt neu zu entstehen; sonst setzte das Bild bei jedem Wechsel neu an.
Bild-in-Bild wandert mit und bekommt endlich einen Delegaten: bisher war
es eine Einbahnstraße, aus der man nicht in die App zurückkam.
Die Kachel schwebt und lässt sich in jede Ecke ziehen, statt als Leiste
unten zu kleben — eine Leiste nähme jeder Liste Platz weg und müsste in
jedem Bildschirm bedacht werden. Die Eckenberechnung steht in
MiniPlayerPlacement und ist geprüft: in einer View wäre eine Kachel, die
halb aus dem Bild ragt, erst auf dem Gerät aufgefallen.
Der Pfeil oben verkleinert, ein neuer Knopf daneben beendet wirklich.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Das Belegen des Tuners klappt, und heraus kommt:
/livetv/sessions/1b5bb998…/DBACCA6D…/index.m3u8?offset=-1.000000
Eine fertige HLS-Abspielliste. Ich habe sie an den Transcoder gegeben —
also darum gebeten, etwas umzurechnen, das schon fertig ist. Daher die
nackte 400. Sie wird jetzt unmittelbar abgerufen, mit Token und
Gerätekennung in der Adresse, weil der Systemplayer keine Kopfzeilen
mitschickt. Die eigene Abfrage der Sitzung bleibt dabei erhalten.
Dazu das Gegenstück, ebenfalls aus dem Quelltext des Plex-Clients:
`DELETE /media/subscriptions/{id}` gibt den Tuner wieder frei. Er ist ein
Stück Hardware — vier Stück gibt es, und ein nicht freigegebener fehlt
beim nächsten Einschalten. Das passiert jetzt beim Schließen des Players
und auch dann, wenn das Belegen zwar klappt, aber nichts Abspielbares
dabei herauskommt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Die gebaute Adresse ist sauber — nachgerechnet und ausgedruckt. Also
lehnt Plex selbst ab, und zwar mit Recht: eine Sendung beim
Cloud-Programmführer hat keine Datei, die sich umrechnen ließe. Die
nackte 400 war die Antwort auf eine unsinnige Bitte.
Deshalb wird die Sendung jetzt zuerst nachgeschlagen. Das Raster nennt
keine abspielbare Adresse; die Einzelabfrage beim Anbieter nennt
womöglich eine. Was dabei zurückkam, steht in jedem Fall in der Spur —
auch wenn nichts Abspielbares dabei war.
Dafür musste die Anfrage einen bereits kodierten Pfad durchreichen
können: `appending(path:)` kodiert das Prozent ein zweites Mal, und aus
`plex%3A%2F%2F` wird `plex%253A%252F%252F`.
Und der angebotene Endpunkt-Zettel des Programmanbieters ist eindeutig:
content, metadata, grid, timeline — nichts zum Einschalten. Der Empfänger
muss es also tun, und den kannte `tune` bisher gar nicht. Seine Adressen
stehen jetzt mit im Suchraum.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Wieder HTTP 400, und wieder ohne Begründung. Der Grund, dass das dreimal
passieren konnte: eine gebaute Adresse galt schon als Erfolg. Ob der
Server sie überhaupt annimmt, stellte sich erst im Player heraus — und
dort steht nur „HTTP 400 (unhandled)".
Jetzt ruft die App die Adresse einmal selbst ab, ohne Plex-Kopfzeilen,
genau so wie AVPlayer es tut. Nur was mit 200 antwortet, geht an den
Player. Was nicht, steht mit dem Wortlaut des Servers in der Spur.
Weil weder der Schlüssel für die Sitzung noch die anschließend
umzurechnende Adresse für diesen Teil der Plex-Schnittstelle beschrieben
sind, werden die Kombinationen der Reihe nach durchprobiert — Adresse der
Sitzung, Adresse aus der Antwort, Adresse des Teils darin, je mit
mediaIndex -1 und 0, höchstens acht Versuche. Antwortet keiner, wird der
Transcoder wieder angehalten, statt für nichts weiterzurechnen.
Die Spur steht in einem Blatt statt in einem Dialog: die Antwort des
Servers ist mehrzeilig, und ein Dialog schneidet sie ab — dann stünde
dort wieder nur, dass es nicht ging.
Dazu ein Nebeneffekt, der beim zweiten Sender zugeschlagen hätte: der
Player kannte die Sitzungskennung des Live-Stroms nicht und hat den Tuner
beim Schließen nicht freigegeben.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
HTTP 400 vom Transcoder. Die Kennung dieses Geräts reist bei jeder
Anfrage als Kopfzeile mit — aber die Adresse des Stroms ruft nicht die
App ab, sondern AVPlayer, und der schickt keine Kopfzeilen mit. Bei
Dateien fällt das nicht auf; bei Live-TV schon: der Server legt die
Sitzung unter der Gerätekennung an und kann die Abholung ohne sie keiner
Sitzung zuordnen.
Also trägt die Transcoder-Adresse die Kennung jetzt selbst. Das gilt für
alle Ströme, nicht nur für Live — dort fiel es nur zuerst auf.
Dazu: umgerechnet wird die Adresse der Sitzung, nicht die des Teils
darin. Der Transcoder sucht sich den Teil selbst, deshalb steht dort -1.
Und der Entscheider unterscheidet jetzt zwischen „nein" und „keine
Antwort". Ein Schweigen als Ablehnung zu lesen hieße, eine
funktionierende Sendung zu blocken, bloß weil er Live-Adressen womöglich
gar nicht kennt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Der übersprungene Schritt. Ich habe die Sendung direkt an den Transcoder
gegeben — ohne dass jemand den Tuner belegt hätte. Der Server fing an,
merkte, dass er nichts zu senden hat, und brach ab: „die Wiedergabe wurde
unterbrochen", ohne weitere Angabe.
Jetzt zwei Schritte. Erst eine Live-Sitzung eröffnen, dann die Adresse
dieser Sitzung umrechnen lassen — nicht die des Senders und nicht die der
Sendung. mediaIndex und partIndex sind dabei -1: ein laufender Sender hat
keine Fassungen zur Auswahl.
Unter welchem Schlüssel sich die Sitzung eröffnen lässt, unterscheidet
sich je nach Anbieter, deshalb werden die in Frage kommenden der Reihe
nach probiert — der Raster-Schlüssel zuerst, weil er den Sender genauso
bezeichnet wie die Liste, aus der er stammt.
Dazu zwei Stellen, an denen Gründe bisher verschwanden:
Vor dem Abspielen wird der Server gefragt, ob er den Strom überhaupt
zulässt. Er antwortet im Klartext; der Player meldet nur, dass es nicht
ging.
Und wenn der Motor scheitert, steht sein Grund ab jetzt auf dem
Bildschirm statt nur im Protokoll. „Die Wiedergabe wurde unterbrochen"
allein sagt weder, ob der Server ablehnt, noch ob der Motor das Format
nicht mag — und genau diese Unterscheidung hat diese Runde gekostet.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Drei Fehler, alle im eigenen Code.
Die Sendezeiten stehen beim Programmanbieter im Medien-Block, nicht am
Eintrag darüber. Ich habe sie oben gesucht, nicht gefunden und jede
Sendung verworfen — übrig blieb „Kein Programm verfügbar" auf jedem
Sender, und ohne Sendungen fiel auch die Senderliste auf die Tunerliste
zurück.
Die Senderspalte im Raster fehlte ganz. Die Zeitachse ist mehrere tausend
Punkte breit, und der HStack hat den Nachbarn dafür auf null gedrückt.
Jetzt hat die Spalte Vorrang, und die Achse liegt als Auflage über einer
durchsichtigen Fläche — eine Auflage zählt bei der Größenrechnung nicht
mit. Der Abstand der Achsenbeschriftung gehört in die Spaltenbreite
hinein statt daneben, sonst steht die ganze Achse acht Punkte neben dem
Programm darunter.
Und das Einschalten: die Adresse wurde berechnet und verworfen, dann
bekam der Player eine Senderkennung, zu der er einen Film in der
Mediathek suchte. Den gibt es nicht. Jetzt reicht die Anfrage die fertige
Adresse durch, der Player spielt sie ohne Umweg, und meldet weder
Fortschritt noch sucht er Metadaten. Scheitert es doch, steht ab jetzt
eine Meldung da statt gar nichts.
Geprüft wurde das Raster diesmal mit den Augen: ein Gerüst im Simulator
zeichnet es mit erfundenen Sendern. Dafür bekommt es die Bildadresse als
Abschluss übergeben, statt sie aus der Sitzung zu lesen.
Dazu: Downloads zeigen jetzt an, wenn sie auf WLAN warten. Die
Hintergrund-Sitzung wartet in dem Fall von sich aus — kein Fehler, nur
Stillstand, und das sah bisher aus wie ein hängender Download.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Der Abzug vom Server zeigt beides schwarz auf weiß.
Erstens: /media/grabbers/devices/1/channels antwortet mit 278 Sendern —
unter dem Sammelnamen `DeviceChannel`. Die Hülle kannte nur `Channel` und
hat die vollständige Liste als leer verworfen.
Zweitens: das Programmraster antwortet, aber nicht auf `type=1`. Die 1
steht bei Plex für Film, und im Programm laufen auch Folgen, Sport und
Nachrichten — der Filter hat das Raster geleert. Ohne ihn kommen die
Sendungen samt Sendername, Nummer, Logo und Kennung.
Das Raster hat jetzt Vorrang vor der Tunerliste, auch wenn beide
antworten: nur dort gehören Sender und Programm zusammen, es enthält
ohnehin nur die eingeschalteten Sender, und die Tunerliste führt alle 278.
Einschalten läuft über die Adresse der laufenden Sendung beim
Programmanbieter — Plex behandelt eine laufende Sendung wie einen Film.
Die Adresse wird einmal entpackt, bevor sie in die Anfrage geht: sonst
kodiert URLComponents ein zweites Mal, und aus %3A wird %253A.
Die Tests stehen jetzt auf den echten Antworten aus dem Abzug, nicht auf
erfundenen: DeviceChannel mit `name` und `key`, Rastereinträge mit
channelVcn, channelID und einer Folge neben einem Film.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Vier geratene Adressen, vier 404. Statt ein fünftes Mal zu raten, klopft
die App jetzt in einem Durchgang und gleichzeitig ein Dutzend Adressen ab:
was /media/providers an Endpunkten angibt, welche Unterverzeichnisse
Programmanbieter und Empfänger in ihrer eigenen Antwort führen, die
bekannten Formen anderer Serverfassungen, die Senderliste des Tuners
selbst — und zuletzt das Programmraster.
Dabei zwei Fehlerquellen mitbehoben, die jede einzeln wie „keine Sender"
ausgesehen hätten: der Sendername steht je nach Tunertyp in title, name,
channelTitle oder callSign, und eine Antwort mit dreißig namenlosen
Einträgen sieht aus wie eine Senderliste, ist aber keine.
Bleibt es leer, steht jetzt jede abgeklopfte Adresse mit ihrer Antwort in
der Ansicht. Das beantwortet „liegt es am Server oder an der App" ohne
eine weitere Runde Rückfragen.
Dazu drei Sachen bei Serien:
- Groß steht, worum es geht — bei einer Folge ihr Titel, bei einer
Staffel der Name der Serie. „Staffel 10" als Überschrift sagte nicht,
welche Serie gemeint ist.
- Die Einordnung steht darunter: Serienname bei Folgen, Staffelnummer bei
Staffeln.
- Von der Folge führt ein Weg zur Staffel und zur Serie. Wer über
„Weiterschauen" bei einer Folge landete, saß dort bisher fest.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Drei geratene Adressen für die Senderliste antworteten auf einem echten
Server alle mit 404. Statt ein viertes Mal zu raten, liest die App jetzt
zwei Dinge: was /media/providers an Endpunkten angibt, und das
Programmraster selbst — die Weboberfläche von Plex zeichnet ihren Guide
daraus, und jeder Rastereintrag trägt Sendername, Nummer, Logo und
Einschaltweg mit sich.
Damit kommen Sender und Programm aus einer Abfrage, und ihre Kennungen
passen zueinander. Getrennt geholt taten sie das nicht: das Programm
landete in keiner Zeile.
Dazu die Ansicht, für die das alles da ist — ein Programmraster mit
stehender Senderspalte, mitlaufender Zeitachse und einer Jetzt-Linie.
Antippen öffnet die Sendung; läuft sie gerade, schaltet ein Knopf um.
Die Geometrie steht in GuideLayout und ist geprüft: in einer View wäre
sie unsichtbar falsch. Die Trennlinien gehören in die Zeile statt
zwischen die Zeilen — Divider ist auf 3x-Schirmen keinen ganzen Punkt
hoch, und der Bruchteil summiert sich, bis die angetippte Zeile zum
Nachbarsender gehört.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Die Sender bleiben leer, und ich habe jetzt zweimal geraten, wo sie
stehen könnten — beim ersten Mal ein eigener Endpunkt, beim zweiten die
Verschachtelung unter Device. Beides war eine Vermutung, weil dieser
Teil der Plex-API keine offizielle Beschreibung hat.
Statt ein drittes Mal zu raten: Einstellungen → Diagnose → „Live-TV-
Antwort ansehen" fragt den Server und zeigt den Aufbau der Antwort.
Schlüssel, Anzahl und Sendernamen, keine Werte und keine Zugangsdaten —
genug, um den richtigen Schlüssel zu finden.
Von Listen wird nur der erste Eintrag ausgeklappt; bei dreißig Sendern
wäre die vollständige Ausgabe unlesbar, und alle haben denselben Aufbau.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Senderliste
Plex liefert die Sender verschachtelt in der Antwort auf /livetv/dvrs
mit — jeder Empfänger bringt seine eigenen unter `Device` → `Channel`.
Mein getrennter Endpunkt /livetv/dvrs/{id}/channels war eine Vermutung
und lieferte nichts. Bei einem Server mit über dreißig eingerichteten
Sendern stand deshalb „Keine Sender".
Die verschachtelte Liste hat jetzt Vorrang, der Endpunkt bleibt als
Rückfalllinie. `ChannelMapping` wird als ältere Schreibweise ebenfalls
gelesen, und der Empfänger-Schlüssel kommt je nach Serverfassung als
Zahl oder als Zeichenkette.
Bleibt die Liste leer, sagt die Ansicht jetzt welcher Weg was ergeben
hat. In einem Bereich ohne offizielle Beschreibung ist das die Angabe,
mit der sich der nächste Fehler finden lässt.
Schriftzüge
Plex liefert bei vielen Filmen ein `clearLogo` — den Schriftzug in
seiner eigenen Type, freigestellt. Der tritt jetzt im Aufmacher an die
Stelle des gesetzten Titels.
Dabei ein Fallstrick: /photo/:/transcode liefert JPEG, und JPEG kennt
keine Transparenz. Über den Bild-Transcoder geladen wäre aus dem
freigestellten Logo ein schwarzer Kasten geworden. Es wird deshalb
unverändert geladen. Fehlt der Schriftzug oder scheitert das Laden,
bleibt es beim Titel.
161 Tests.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>