Commit Graph
66 Commits
Author SHA1 Message Date
ScarriffleandClaude Opus 5 62691fc2bc Matroska geht jetzt an AVPlayer, nicht mehr an VLC
Ein Blu-ray-Rip liegt als Matroska vor, und AVFoundation oeffnet Matroska
nicht — obwohl das Bild darin H.264 oder HEVC ist und der Ton AC3, also
alles, was AVPlayer bestens kann. Im Weg stand allein die Verpackung.

Dafuer gibt es jetzt einen dritten Weg neben Durchreichen und Neurechnen:
umpacken. Dieselben Stroeme, Byte fuer Byte, in eine MP4. Kein Bild wird
angefasst, es geht nichts an Qualitaet verloren, und den Server kostet es
fast nichts.

Entscheidend ist dabei `Protocol: http` statt `hls`. Eine Abspielliste
laesst sich nicht vorausladen — AVPlayer holt Segment fuer Segment, und
Apple laesst uns dazwischen nicht eingreifen. Eine fortlaufende Datei kommt
ueber gewoehnliche Bereichsanfragen, und die kann man so weit vorausholen,
wie man will. Der Vorratswunsch steht dort deshalb bei zehn Minuten statt
bei einer.

Der Gewinn ist gross: solche Titel bekommen Bild-in-Bild, AirPlay und die
Systembedienung, die es bei VLC nicht gibt.

Umgepackt wird nur, wenn **allein** die Schachtel im Weg steht. Stimmt auch
nur ein Codec nicht — DTS, TrueHD, AV1 —, fehlt nicht der Zugang zur Datei,
sondern das Koennen; dann bleibt es bei VLC. Sechs Tests halten die Grenze
fest, in beide Richtungen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-28 20:00:44 +02:00
ScarriffleandClaude Opus 5 f48e018fa3 Der Regler springt sofort, und man sieht, bis wohin geladen ist
Zwei Dinge, die beide dasselbe Missverständnis erzeugt haben.

Der Regler blieb beim Spulen liegen, wo er war, und rückte erst nach, wenn
das erste Bild von der neuen Stelle da war — über das Netz Sekunden später.
Das liest sich als „der Sprung ist nicht angekommen", und man spult noch
einmal. Der Sprung ist aber angekommen, in dem Moment, in dem man ihn
auslöst; alles Weitere ist Warten auf Daten, und dafür gibt es den Kreisel.
Die Stelle steht jetzt sofort. Nachzügler von der alten Stelle werden
verworfen, sonst zöge die letzte Meldung den Regler wieder zurück.

Und bis wohin geladen ist, stand nirgends. Dafür ist der `Slider` gewichen:
er hat genau eine Spur, und die gehört dem Fortschritt. Etwas dahinter zu
zeichnen und auf gleiche Innenabstände zu hoffen ginge so lange gut, bis
Apple sie ändert. Der neue Regler zeichnet drei Schichten aus derselben
Rechnung — Grund, Geladen, Gespielt — und hat einen Griff, den ein Finger
trifft.

Der Vorrat wird dabei abgelegt statt ausgerechnet. Eine berechnete
Eigenschaft wird nur gelesen, wenn ohnehin neu gezeichnet wird, und das
geschieht, weil die Abspielstelle weiterläuft. Im pausierten Zustand läuft
sie nicht — und ausgerechnet da will man zusehen, wie der Vorrat wächst.
Die Schleife, die ihn nachführt, lief bisher ausserdem nur bei
„automatisch": wer eine Stufe von Hand gewählt hatte, hätte nie gesehen,
wie weit geladen ist.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-28 19:28:44 +02:00
ScarriffleandClaude Opus 5 983015b9ad Die Sprache lässt sich jetzt auch je Serie und je Mediathek sagen
Das Regelwerk stand schon und wurde abgefragt — es gab nur keinen Ort, an
dem man etwas hineinschreiben konnte. Jetzt steht die Wahl auf der
Serienseite und im Filter-Sheet der Mediathek, beide über denselben
Auswahlknopf: zwei mit demselben Inhalt liefen unweigerlich auseinander.

Bei der Mediathek in einem eigenen Abschnitt, nicht zwischen den Filtern.
Ein Filter schränkt ein, was zu sehen ist; das hier bestimmt, was zu hören
ist — dazwischengestellt läse es sich als „nur Titel mit deutscher Tonspur
zeigen".

Die Rangfolge Serie → Mediathek → allgemein ist dabei von den Einstellungen
zu `AudioTrackChooser.resolve` gewandert. Sie ist eine Regel, keine Ablage,
und im App-Ziel gibt es kein Unit-Test-Ziel — dort liesse sie sich nicht
durchspielen. Sechs Tests decken sie jetzt ab, darunter der Fall, an dem
man sonst lange sucht: eine leere Zeichenkette steht in den Einstellungen
für „soll der Server entscheiden" und darf nicht als Sprache gelten, sonst
schaltet sie die Ebene darüber stillschweigend ab.

Nebenbei bekommt das Filter-Sheet die Mediathek selbst statt nur ihrer Art.
Die Genres wurden vorher über irgendeine Mediathek dieser Art nachgeschlagen
— bei zwei Filmmediatheken also über die falsche.

Und der Puffer steht auf einer Minute statt auf 25 Sekunden. Es ist ein
Mindestwunsch, keine Obergrenze; der harte Deckel liegt ohnehin woanders,
nämlich darin, wie weit der Transcoder schon produziert hat.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-28 17:12:17 +02:00
ScarriffleandClaude Opus 5 596a143545 Die Tonspur wird aus der Datei gewählt, nicht aus dem Stream
Im Spurenblatt stand eine einzige Zeile: „Unbekannt". Das ist der Name, den
AVFoundation einer namenlosen Spur gibt — und bei einem umgerechneten
Stream liefert der Server genau eine. Der Player hatte nichts zur Auswahl,
weil die Wahl längst gefallen war, bevor er gefragt wurde.

Gewählt wird jetzt aus den Spuren, die **in der Datei** stehen, und zwar
bevor der Stream ausgehandelt wird. Die Nummer geht als `audioStreamIndex`
mit hinaus; wer mitten im Film umstellt, bekommt den Stream neu — ausser
bei unveränderter Wiedergabe, wo der Player ohne Unterbrechung umschaltet.

Die Abwägung selbst macht `AudioTrackChooser`: gemerkte Spur, dann
Wunschsprache, dann Rangliste, dann Standardspur — und Audiodeskription
mit deutlichem Abzug statt Ausschluss, damit eine Sprache, die es nur so
gibt, immer noch besser ist als Stille.

Drei Ebenen in `LanguagePreferences`: Serie, Mediathek, allgemein. Gefragt
wird von unten nach oben. Die Mediathek wird nur nachgeschlagen, wenn
überhaupt jemand eine Sprache je Mediathek gesetzt hat — sonst kostete jede
Wiedergabe eine Anfrage für eine Ebene, die niemand benutzt.

Wer von Hand umstellt, dessen Wahl wird für die Serie gemerkt, ohne
Nachfrage. Der Dialog „für diese Serie merken?" ist genau die Art, die man
nach dem dritten Mal wegklickt. Sichtbar ist es trotzdem: das Spurenblatt
zeigt es und lässt es löschen.

Dazu die Rangliste in den Einstellungen, ziehbar, und der Schalter
„Audiodeskription meiden".

Und die Player-Oberfläche blendet endlich als Ganzes über. Die Kopfzeile
hatte gar keine Überblendung — sie sprang herein, während die Bedienung
darunter verblasste. Jetzt steht die Animation an einer Stelle statt an
sechs, und sie läuft über Deckkraft statt über Einfügen und Entfernen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-28 16:31:38 +02:00
ScarriffleandClaude Opus 5 18958aadbc Der Klick in den schwarzen Rand trifft jetzt den schwarzen Rand
Er hat weiterhin angehalten statt ein- und auszublenden, und der Grund
steckte in der Rechnung: der schwarze Rand wurde aus der Fenstergrösse
ausgerechnet, das Bild liegt aber in einer Fläche mit `ignoresSafeArea()`.
Auf dem Mac ist der Einzug der Fensterleiste genau der Unterschied
zwischen beiden — die Rechnung lag um diesen Betrag daneben.

Jetzt wird die Bildfläche gemessen statt angenommen, und beide Zahlen
stehen in einem gemeinsamen, benannten Koordinatenraum. Vor allem aber
wird zuerst `AVPlayerLayer.videoRect` gefragt: der Player weiss selbst,
wo Bild steht. Die eigene Rechnung bleibt für den Moment vor dem ersten
Bild und für VLC, das keine solche Ebene hat.

Dazu zwei Worte: „Nachschub" heisst jetzt „Nachladen", und „Blatt" heisst
wieder „Sheet" — das ist ein Fachbegriff und bleibt englisch.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-28 06:49:15 +02:00
ScarriffleandClaude Opus 5 f47c6385e3 Auflösungen heissen jetzt, wie Menschen sie nennen
Auf der Detailseite stand „1080" — die Zeichenkette des Servers in
Grossbuchstaben. Weder eine Zahl mit Bedeutung noch ein Name, den jemand
benutzt. Jetzt steht dort „Full HD", „4K", „HD", „SD".

Die Einstufung geht neu über die **Breite**, nicht über die Höhe. Ein
Kinofilm in 2,39:1 misst 1920 × 804; nach der Höhe war das bisher „720p",
und zwar ausgerechnet bei den grossen Filmen. Die Höhe bleibt als Notnagel
für Server, die keine Breite melden, und der Name des Servers als letzter.

Nebenbei fällt damit der 4K-Filter bei Jellyfin nicht mehr durch, wenn ein
breitwandiges 4K nur 1608 Zeilen hat, und die doppelte Einstufung in
PlexMedia verschwindet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-28 06:33:26 +02:00
ScarriffleandClaude Opus 5 10caa5fdd1 Platzhalter statt Ratespiel, und zwei Griffe im Player
**Das Raster sagt jetzt, ob noch etwas kommt.** Eine Mediathek lädt
seitenweise nach, und wer unten ankam, sah ein Raster, das aufhört — ohne
unterscheiden zu können, ob das alles war oder gerade nachgeladen wird.
Beides sah gleich aus.

Beide Server nennen die Gesamtzahl (Jellyfin `TotalRecordCount`, Plex
`totalSize`), und wir lesen sie längst. Also stehen die nächsten Kacheln
schon da, in der Form, die sie haben werden — gedeckelt auf ein Dutzend,
weil zweihundert Platzhalter niemandem helfen. Ist wirklich alles da,
steht es am Ende ausdrücklich: „Alle 252 Titel geladen".

Gegen die Attrappe mit 252 Titeln durchgeblättert und nachgesehen: die
Zeile steht da, und alle 252 sind angekommen.

**Herein sanft, hinaus zügig.** Das Einblenden der Bedienung begleitet
eine Bewegung des Zeigers und soll wirken, als käme sie der Hand
entgegen; zwei Zehntel wirkten wie ein Aufblitzen. Das Ausblenden
dagegen ist ein Aufräumen, und dabei muss niemand zusehen.

**Ein Klick ins Schwarze blendet um, statt anzuhalten.** Ein Film füllt
das Fenster fast nie ganz aus — die Balken daneben gehören nicht zum
Film, und dort zu klicken ist die natürliche Geste für „zeig mir die
Bedienung". Wo das Bild aufhört, wird aus `presentationSize` gerechnet,
also aus dem, was wirklich ankommt. Fehlen die Maße, verhält es sich wie
bisher.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-27 18:50:26 +02:00
ScarriffleandClaude Opus 5 348ec96282 Eine misslungene Seite beendete das Nachladen für immer
Der Grund für „es sind wieder nicht alle Filme drin": im Fehlerfall stand
hier `page = nil`. Eine einzige misslungene Anfrage auf einer wackeligen
Leitung, und die Mediathek blieb endgültig bei sechzig Titeln stehen —
lautlos, ohne dass irgendetwas darauf hindeutete. Im Zug passiert das
zuverlässig.

Der Kommentar daneben begründete das Schweigen, und das war auch richtig:
ein Fehlerblatt mitten im Scrollen wäre störend. Nur hat er das
eigentliche Problem verdeckt — nicht die fehlende Meldung war schlimm,
sondern dass es danach keinen Weg zurück gab.

Jetzt bleibt die Seite erhalten, und unten erscheint eine Zeile zum
Weiterladen. Von selbst wird nach einem Fehlschlag **nicht** mehr
nachgefasst: sonst liefe bei jedem Scrollen eine Anfrage in dieselbe tote
Leitung.

Dazu der Knopf zum Neuladen in der Leiste, wie gewünscht. Ziehen von oben
gibt es auf dem Mac nicht, und in einem Raster ist es ohnehin kaum zu
treffen — wer den Verdacht hat, dass etwas fehlt, braucht einen Knopf
und keine Geste.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-27 18:40:53 +02:00
ScarriffleandClaude Opus 5 fd11a9eebe „Auto" misst jetzt die Verbindung, statt die Einstellung abzulesen
Der Denkfehler, und er war meiner: `automatic` hiess wörtlich „nimm, was
in den Einstellungen steht". Stand dort „Original", bat die App über eine
Zugverbindung um eine 4K-Datei und wartete vierzig Sekunden auf etwas,
das nie kommen konnte. Das ist keine Automatik, das ist eine Durchreiche
mit einem irreführenden Namen.

**Die Einstellung ist jetzt die Obergrenze, nicht das Ziel** — „bis
hierhin, wenn die Leitung es trägt". Im eigenen Netz bleibt alles wie
bisher: dort gibt es keine Grenze, weil die Leitung das Original trägt
und ein Deckel das Bild ohne Grund verschlechterte. Die Unterscheidung
steht in `isLocalConnection` und ist nicht geraten.

Unterwegs beginnt „Auto" bei acht Megabit — genug für ordentliches
1080p, wenig genug, dass es fast überall anläuft — und tastet sich von
dort hinauf, solange es gut geht, bis höchstens zur eingestellten Decke.
Hinunter geht es sofort, sobald es hakt.

**Und die Auflösung geht mit.** Bitrate allein genügt nicht: 4K mit zwei
Megabit sieht schlechter aus als 720p mit zwei Megabit — dieselbe
Datenmenge, auf viermal so viele Bildpunkte verteilt. Wer die Bitrate
senkt, muss die Auflösung mitnehmen, sonst spart er Daten und
verschenkt das Bild.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-27 18:32:33 +02:00
ScarriffleandClaude Opus 5 efed8717a3 „Auto" gibt nicht mehr auf, es versucht es kleiner
Der Fehlstart im Zug hat den eigentlichen Mangel gezeigt: die ganze neue
Regelung misst **ab dem ersten Bild**. Kommt nie eines, misst sie nichts,
greift nichts — und „Auto" hat sich als das erwiesen, was es wörtlich
heisst: „nimm, was in den Einstellungen steht". Steht dort „Original",
bittet die App über eine Zugverbindung um eine 4K-Datei und wartet
vierzig Sekunden auf etwas, das nie kommen kann.

Statt aufzugeben geht der Watchdog jetzt eine Sprosse tiefer und baut
neu auf — bei Filmen über `restart()`, bei Sendern über den Tuner-Abbau.
Gezählt gegen dasselbe Budget wie die Sicherheitsleine, sonst schickt
eine tote Leitung die App endlos im Kreis.

Scheitert auch das, nennt die Meldung jetzt den Grund und den Weg statt
nur „keine Daten mehr": wer ohne Grenze angefragt hat, soll erfahren,
dass es daran liegt und wo es sich ändern lässt.

Und „Strom" heisst Stream. Zweiunddreissig Stellen, dazu das
Gedächtnis ergänzt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-27 18:30:10 +02:00
ScarriffleandClaude Opus 5 8ff74ed4fa Der Hinweis bei „ohne Grenze" — aber nur mit Beleg
Das letzte offene Stück aus dem Plan. Du wolltest die WLAN-Voreinstellung
nicht verschoben haben, sondern einen Hinweis — und einen, der sich auf
etwas stützt.

Ohne Grenze kommt die Entscheidung oft bei direkter Wiedergabe heraus,
und dann gibt es keine Stufe, auf die irgendetwas ausweichen könnte.
Beim Testserver heisst „Original" konkret: 67 Mbit, 4K, HEVC. Über eine
Fernverbindung trägt das keine Leitung.

Der Hinweis erscheint deshalb nur, wenn beides zutrifft: die Einstellung
steht auf „ohne Grenze" **und** die letzte Wiedergabe hat tatsächlich
gehakt — gemessen am gleitenden Fenster, nicht geraten. Eine Warnung,
die immer dasteht, liest nach dem zweiten Mal niemand mehr.

Der Befund überlebt die Wiedergabe, weil der Player längst zu ist, wenn
jemand in die Einstellungen schaut.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-27 18:19:48 +02:00
ScarriffleandClaude Opus 5 903bfe5f63 Der Player weiß jetzt, wie es ihm geht
Bisher gab es über eine laufende Wiedergabe zwei Zahlen — gemessene
Bitrate und verworfene Bilder —, und die standen nur zur Anzeige da. Die
Frage „trägt die Leitung das gerade" war nicht zu beantworten, weder für
den Nutzer noch für die App.

**Die wichtigste neue Zahl ist `variantCount`.** Sie kommt aus
`AVAsset.variants`, also vom Parser des Players, und beantwortet die
Frage, ob es überhaupt etwas zum Umschalten gibt. Steht dort 1, greift
jedes Nachregeln ins Leere — und genau das ist heute der Fall, weil die
App bei beiden Servern eine Playlist mit einer Stufe bestellt.

Dazu der Pufferstand aus `loadedTimeRanges`: der einzige echte
Frühwarner. Schrumpft er schneller, als die Zeit vergeht, kommt weniger
herein als hinausgeht — zehn bis zwanzig Sekunden, bevor das Bild steht.
Alles andere meldet sich erst danach.

**Verworfene Bilder werden jetzt über alle Protokolleinträge summiert.**
Das Zugriffsprotokoll legt bei jedem Stufenwechsel einen neuen an, dessen
Zähler bei null beginnt; wer nur `last` liest, hält eine Wiedergabe für
makellos, die schon zwanzigmal Bilder verworfen hat.

**Der Wachhund war blind für den häufigsten Fall.** Vierzig Sekunden am
Stück ohne Daten brechen ab — aber er setzte sich bei jedem kurzen
Erholen zurück, und zehn Rückler à dreißig Sekunden lösten ihn nie aus.
`StallLedger` zählt daneben mit, wie viel Zeit im gleitenden Fenster
fürs Warten draufging. Es bricht nichts ab, es stellt nur fest — und ist
ein reiner Typ ohne Uhr und ohne AVFoundation, damit sich eine Folge von
Rücklern als Zahlenreihe hinschreiben und das Urteil prüfen lässt.
Sieben Tests, darunter genau der Fall, an dem der alte Wachhund
vorbeisah.

Gezählt wird nur der Weg vom Spielen ins Warten. Das anfängliche Puffern
ist der Start, kein Rückler; würde es mitgezählt, hätte jede Wiedergabe
eine Vorstrafe.

Und zwei Kommentare sind berichtigt. Sie behaupteten, AVPlayer wechsle
die Bitrate von selbst und das sei „die zugesagte automatische
Qualitätsanpassung". Umschalten kann er nur zwischen Varianten, die in
der Liste stehen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-27 14:59:29 +02:00
ScarriffleandClaude Opus 5 00db1bbab9 Der Folgen-Schlüssel hieß beim Auslesen anders als beim Nachschlagen
`Text("episode.number \(episode)")` trug Xcode beim Auslesen als
`episode.number %@` in den Katalog ein, während die Laufzeit
`episode.number %lld` nachschlägt. Zwischen zwei verschiedenen
Schlüsseln findet man nie einen — und auf dem Bildschirm stand weiter
der Schlüssel selbst.

Jetzt über `String(localized:)` wie die Staffel zwei Blöcke darüber.
Damit ist der Platzhalter eindeutig, und die übersetzte Tabelle im
gebauten Bündel führt genau die beiden Schlüssel, die der Code sucht.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 11:10:38 +02:00
ScarriffleandClaude Opus 5 fc812f5a25 Zwei Schlüssel ohne Platzhalter, zwei Kreisel übereinander
**Die Schlüssel.** Im Quelltext steht `"season.number \(season)"`, der
Schlüssel heißt damit `season.number %lld`. Im Katalog stand er ohne
Platzhalter — also traf er nie, und die App zeigte den Schlüssel selbst:
„The 50 · season.number 1 · episode.number 1". Beide umbenannt. Ein
Überbleibsel `episode.number %@` ohne eine einzige Übersetzung ist
mitgegangen, und aus „Episode" wurde „Folge", wie überall sonst im Haus.

**Die Kreisel.** Die Abdeckung beim Wiederherstellen der Stelle ist eine
ganze Seite, kein Schleier — nur lag das Übrige weiter darüber. Zu sehen
waren Bedienelemente mitten im Satz, ein Kreisel in der Abdeckung und ein
zweiter im Abspielknopf, den man in dem Moment ohnehin nicht drücken
soll. Jetzt ist die Abdeckung ausschließlich. Die Kopfzeile bleibt: sie
ist der Weg zurück.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 21:52:08 +02:00
ScarriffleandClaude Opus 5 8a63f815cd Das Qualitätsblatt hat die App angehalten
Kein Hänger, sondern ein Absturz, der nicht durchkam. Der Stapel des
eingefrorenen Prozesses endet in `_assertionFailure` beim Lesen eines
`@Environment`-Werts — mitten in `SheetBridge.present`, also während das
Blatt aufgebaut wird. Weil der Player in diesem Moment in einem
Layoutdurchlauf steckt, erschien nie eine Meldung: das Fenster stand
still.

`@Environment(PlaybackSettings.self)` ohne Fragezeichen ist keine Bitte,
sondern eine Behauptung — fehlt der Gegenstand, hält Swift die Anwendung
an. Und ein Blatt hängt an einem eigenen Darstellungsbaum. Auf dem iPad
kam die Umgebung des Players dort bisher an, auf dem Mac nicht.

Zwei Änderungen, beide nötig:

- Der Player reicht `PlaybackSettings` und den Akzent ausdrücklich in
  seine Blätter hinein, statt sich auf Vererbung zu verlassen.
- `QualitySheet` liest die Einstellungen als Optional. Ein Vergessen
  kostet künftig eine fehlende Zeile im Blatt statt der ganzen App.

Dazu ein Test, der genau diesen Weg geht: abspielen, Qualitätsknopf,
Blatt da? Er lief schon vor der Korrektur durch — der Unterschied
zwischen iPad und Mac ist mit einem Simulator nicht zu fassen. Er hält
trotzdem fest, dass der Weg überhaupt gegangen werden kann.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 21:45:09 +02:00
ScarriffleandClaude Opus 5 9a4b6b1e06 Ein Kreisel ist kein Pausenknopf
Während der Player auf Daten wartete, stand im mittleren Knopf das
Abspieldreieck und der Ladekreis lag dahinter. Das las sich als
„angehalten": man drückte auf Abspielen, und es geschah nichts — es lief
ja bereits, es kam nur nichts an.

Jetzt sitzt der Kreisel **im** Knopf, dort wo sonst das Symbol steht. Der
freistehende Kreisel in der Bildmitte bleibt für den Fall, dass die
Bedienelemente ausgeblendet sind. Zwei übereinander gibt es nicht mehr.

`togglePlayPause` zählt das Warten jetzt zum Laufen. Vorher führte ein
Tipp in diesem Moment zu `play()` auf etwas, das schon spielte.

Dazu auf dem Mac, wie im Browser auch:

- Ein Klick ins Bild hält an, statt erst die Bedienelemente zu holen. Die
  kommen dort von allein, sobald sich der Zeiger regt.
- Leertaste hält an, Pfeil links und rechts springen.

Mit dem Finger bleibt es beim Alten: ohne Zeiger gibt es keine Bewegung
ohne Berührung, und ein Tipp, der sofort anhält, nähme den einzigen Weg
zu Scrubber, Tonspur und Qualität.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 20:33:24 +02:00
ScarriffleandClaude Opus 5 df75fa98a5 Kachelgröße und Aufmacherhöhe gibt es jetzt auch in der App
Phase 1 des Einstellungs-Abgleichs. Beide standen bisher nur im Web,
obwohl sie nirgends hingehören, wo es die Sache nicht überall gibt.

Die Kachelgröße ist ein Faktor und keine Punktzahl: die Breiten von
Plakat und Querformat stehen in einem Verhältnis, das stimmen soll —
zwei getrennte Zahlen ließen sich gegeneinander verstellen, bis eine
Reihe zur anderen nicht mehr passt. Sie wirkt auf Startseite und
Mediathek, sonst sähe das eine anders aus als das andere.

Sie steht bei der Darstellung, nicht bei der Startseite: es geht ums
Aussehen, und sie gilt für beide Seiten. Die Aufmacherhöhe steht bei der
Startseite, direkt bei Quelle und Anzahl.

Voreingestellt ist genau das bisherige feste Verhalten — wer nichts
anfasst, merkt nichts. Gelesene Werte werden auf ihren Bereich begrenzt,
damit eine ältere Ablage keine Kachel über den halben Bildschirm ergibt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 17:02:50 +02:00
ScarriffleandClaude Opus 5 982ae83e76 Aufnehmen, was noch gar nicht läuft
Der dritte Modus neben Raster und Senderliste — und der einzige, den es
nicht überall gibt. Bei Plex fehlt schlicht der Schalter dorthin, denn
supportsRecordings fragt die eigene Fähigkeit ab.

Vormerken sitzt im Blatt, das eine angetippte Sendung ohnehin schon
öffnet, statt in einem zweiten Kontextmenü daneben. Trägt die Sendung
bereits einen Serien-Timer, steht dort das statt der Knöpfe: zweimal
vormerken ist kein Wunsch, sondern ein Versehen.

Aufnahmen führen über die Detailseite statt unmittelbar in den Player.
Eine Aufnahme ist auf dem Server ein gewöhnlicher Eintrag, und erst dort
steht, in welchen Fassungen sie vorliegt — von der Liste aus zu starten
hieße, das zu raten.

Zwölf Schlüssel in DE und EN. Die Übersetzungsdatei ist dabei auf das
Format zurückgeschrieben worden, das auch im letzten Commit steht;
inhaltlich ist sie HEAD plus diese zwölf, nachgerechnet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 06:26:34 +02:00
ScarriffleandClaude Opus 5 eebe9280cb Die Oberfläche weiß nicht mehr, dass es Plex gibt
Keine Datei unter Views/LiveTV importiert noch PlexKit. Sender, Programm
und Einschalten laufen über den neutralen Vertrag, und supportsLiveTV
fragt endlich die Fähigkeit ab statt "server != nil" — womit die Zusage
in JellyfinBackend.capabilities zum ersten Mal stimmt.

Der Player holt die laufende Sendung jetzt für genau einen Sender nach.
Vorher ging dafür das Raster über alle Sender hinaus, weil der Plex-Weg
keinen schmaleren kannte; im neutralen Vertrag ist die Senderliste ein
Parameter.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 06:20:30 +02:00
ScarriffleandClaude Opus 5 be4b961641 Der Serienname ist jetzt der Weg zur Serie
Unter dem Folgentitel stand „S2 E1" als Text, und wer zur Serie wollte, musste
darunter eine Knopfreihe finden. Jetzt steht dort „Serienname · Staffel 2 ·
Episode 1", und Serienname wie Staffel führen dorthin, wohin sie zeigen. Der
Name steht ohnehin da — dann soll er auch der Weg sein.

`HeroBanner` nimmt dafür wahlweise eine fertige Zeile entgegen statt eines
Textes. Gebaut wird sie beim Aufrufer, weil nur der weiß, wohin ein Teil führt;
die Gestaltung kennt keine Ziele.

Die Knopfreihe bleibt bei Staffeln, wo sie der einzige Weg nach oben ist, und
entfällt bei Folgen.

Beim Entfernen eines Downloads gibt es keine Rückfrage mehr. Sie stand da, weil
„löschen" nach etwas Endgültigem klingt — es ist aber nur die heruntergeladene
Kopie, der Titel bleibt auf dem Server. Wer nach links wischt und auf „Löschen"
tippt, hat zweimal gesagt, was er will.

Und das Umschalten sieht jetzt nach Umzug aus: die Seitenleiste klappt nach
oben weg, die Leiste oben schiebt sich seitwärts herein — und zurück genauso.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 22:09:31 +02:00
ScarriffleandClaude Opus 5 e2779f2519 Bearbeiten, Suche auf dem Handy, und zwei Knöpfe, die man treffen kann
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>
2026-09-13 20:33:33 +02:00
ScarriffleandClaude Opus 5 fbed83b10f Die System-Leiste kam auf dem Mac zurück
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>
2026-09-13 19:50:38 +02:00
ScarriffleandClaude Opus 5 fccec1217b Eigene Seitenleiste — überall dieselbe
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>
2026-09-13 19:17:27 +02:00
ScarriffleandClaude Opus 5 39d9138911 Catalyst: ohne Netz, ohne Übersetzung, ohne Fenstergröße
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>
2026-09-13 16:22:42 +02:00
ScarriffleandClaude Opus 5 21c5a8b46b Player: vier Knöpfe, die vorher fehlten oder nichts sagten
**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>
2026-09-13 15:36:24 +02:00
ScarriffleandClaude Opus 5 95723588c2 Nachgesehen — und dabei vier Fehler gefunden, zwei davon meine neuen
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>
2026-09-13 15:22:18 +02:00
ScarriffleandClaude Opus 5 dace5d1543 Einstellungen: sechs Bildschirme statt einer Liste — und was sie steuern
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>
2026-09-13 14:56:08 +02:00
ScarriffleandClaude Opus 5 3f6c6d8816 Konto unten in der Seitenleiste — und eine Einstellung, die es nicht gab
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>
2026-09-13 14:45:16 +02:00
ScarriffleandClaude Opus 5 5cebfed36b Navigation: Glas, oben oder seitlich, und mehr Reihen auf der Startseite
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>
2026-09-13 14:31:09 +02:00
ScarriffleandClaude Opus 5 fb418c842d Wiedergabe: der Player spricht nicht mehr mit Plex, sondern mit dem Vertrag
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>
2026-09-13 14:00:02 +02:00
ScarriffleandClaude Opus 5 370f7f94f1 Startseite: der Aufmacher wechselt, die Neuzugänge stimmen
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>
2026-09-13 13:36:57 +02:00
ScarriffleandClaude Opus 5 5696254034 Jellyfin: der Anmeldebildschirm bietet jetzt beides an
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>
2026-09-13 13:22:09 +02:00
ScarriffleandClaude Opus 5 fe2d730c00 Die Ansichten sprechen nicht mehr mit Plex
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>
2026-09-13 12:46:52 +02:00
ScarriffleandClaude Opus 5 33dfac352a Player: beim Umstellen sah man den Filmanfang statt seiner Stelle
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>
2026-09-12 22:21:51 +02:00
ScarriffleandClaude Opus 5 0bafec4838 Wiedergabe: „Umwandlung ok" war eine Zusage und wurde als Absage gelesen
Der Server antwortet auf eine angeforderte Umrechnung mit „direkte
Wiedergabe nicht verfügbar; Umwandlung ok" — Code 1001. Meine Prüfung
ließ nur 1000 durch, also ausschließlich DirectPlay. Damit blockierte die
App ausgerechnet den Fall, den sie selbst angefordert hatte: wer im
Player eine Stufe wählt, will umgerechnet bekommen, und die einzige
richtige Antwort darauf kam als Fehler an. Es spielte gar nichts mehr.

1000 heißt „direkt abspielbar", 1001 „direkt nicht, umrechnen schon",
2000 „Umwandlung in Ordnung" — alles Zusagen. Abgelehnt wird erst ab
3000, etwa bei fehlender Berechtigung oder überlastetem Transcoder.

Vier Tests halten die Codes fest, damit die Grenze nicht wieder zur
Auslegungssache wird.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 22:14:29 +02:00
ScarriffleandClaude Opus 5 f628819765 Player: eine gewählte Stufe wurde gar nicht umgerechnet
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>
2026-09-12 22:05:27 +02:00
ScarriffleandClaude Opus 5 b53fce65b8 Player: die Sprungflächen lagen über allen Knöpfen
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>
2026-09-12 21:56:18 +02:00
ScarriffleandClaude Opus 5 12bb49615b Player: die Qualitätsleiter des Servers, Bitrate im Blick, Doppeltipp
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>
2026-09-12 21:50:39 +02:00
ScarriffleandClaude Opus 5 c2ddc10871 Wiedergabe: der alte Motor lief weiter, während der neue startete
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>
2026-09-12 21:22:17 +02:00
ScarriffleandClaude Opus 5 c348ab0755 Kachel größer ziehen, Senderlogos vollständig, Sendung im Player
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>
2026-09-12 21:19:50 +02:00
ScarriffleandClaude Opus 5 af45bebceb Live-TV: das Programm zeigte die falsche Sendung als laufend
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>
2026-09-12 17:26:14 +02:00
ScarriffleandClaude Opus 5 26c909a2e2 Mini-Player: die Wiedergabe überlebt das Zurückgehen
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>
2026-09-12 17:09:22 +02:00
ScarriffleandClaude Opus 5 861ca805a5 Live-TV: erst den Tuner belegen, dann umrechnen lassen
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>
2026-09-12 13:51:05 +02:00
ScarriffleandClaude Opus 5 bbe47b83be Live-TV: das Programm kam nie an, und die Einschalt-Adresse wurde weggeworfen
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>
2026-09-12 13:34:48 +02:00
ScarriffleandClaude Opus 5 2362865ba9 Live-TV: die Sender waren da, zwei eigene Fehler haben sie verschluckt
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>
2026-09-12 13:08:15 +02:00
ScarriffleandClaude Opus 5 7e36f37506 Live-TV: die Senderliste wird gesucht, nicht geraten — und Serien richtig beschriftet
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>
2026-09-12 12:21:29 +02:00
ScarriffleandClaude Opus 5 591cba73a7 Live-TV: die Sender stehen im Programmraster, nicht in einer Senderliste
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>
2026-09-12 12:03:11 +02:00
GuidoandClaude Opus 5 90d5d96485 Live-TV: Zuordnung und Senderliste sind zweierlei
Der Strukturabzug vom Server hat es entschieden. ChannelMapping enthält
genau vier Felder — channelKey, deviceIdentifier, enabled,
lineupIdentifier — und keinen einzigen Namen. Es ist keine Senderliste,
sondern ein Verweis darauf: welche 28 von 521 Sendern der Swisscom-Liste
eingeschaltet sind.

Zweimal habe ich die Zuordnung für die Senderliste gehalten. Beim ersten
Mal blieb sie leer, beim zweiten hätten alle 28 Sender „—" geheißen und
dieselbe Kennung getragen — SwiftUI hätte bei doppelten Kennungen
ohnehin nur einen gezeigt.

Jetzt werden beide Quellen zusammengeführt: die Namen, Nummern und Logos
kommen von der Senderliste beim Programmanbieter, die Auswahl aus der
Zuordnung. Die Kennung des Anbieters steht als `epgIdentifier` direkt am
Empfänger — verlässlicher, als sie aus der Anbieterliste zu suchen.

Der Abgleich ist nachsichtig: je nach Anbieter entspricht der channelKey
der Kennung, dem Pfad oder der Sendernummer. Ein strenger Vergleich fände
in zwei von drei Fällen nichts, und „keiner passte zusammen" sieht aus
wie „keine Sender". Genau das unterscheidet die Meldung jetzt auch.

Der Strukturabzug in der Diagnose deckt jetzt beide Endpunkte ab — nach
einem Fehlschlag bleibt keine weitere Runde Raten übrig.

166 Tests, neun davon zu Zuordnung und Abgleich.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 10:28:38 +02:00
GuidoandClaude Opus 5 afccb81792 Diagnose zeigt den Aufbau der Live-TV-Antwort
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>
2026-09-12 10:13:41 +02:00
GuidoandClaude Opus 5 8a77ab6600 Ausfuhr-Erklärung zur Verschlüsselung hinterlegt
ITSAppUsesNonExemptEncryption: false in der Info.plist. Damit entfällt
das „Missing Compliance" bei jedem neuen Build in App Store Connect.

Die Erklärung trifft zu: die App nutzt ausschließlich Verschlüsselung
des Betriebssystems — HTTPS über URLSession und den Schlüsselbund für
die Tokens. Eigene Algorithmen gibt es keine; im Quelltext steht nur ein
`import Security`, und das für SecItemAdd und SecItemCopyMatching.

Als Vorbehalt im Quelltext vermerkt: VLCKit ist ein fremdes Binary mit
eigenen Netzwerkbibliotheken. Solange nur Dateien vom eigenen Server
laufen, bleibt es bei Standardverfahren — käme etwas mit eigener
Verschlüsselung dazu, gehört der Wert überprüft.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 22:14:28 +02:00