Commit Graph
249 Commits
Author SHA1 Message Date
ScarriffleandClaude Opus 5 8e0e8ec246 Der Vorrat gibt dem Player Vorrang und weicht, wenn er im Weg steht
Sobald das Umrechnen teuer wird — und das Einbrennen eines Untertitels ist
teuer —, bedient Jellyfin einen Schnipsel nach dem anderen. Der Vorrat hat
dabei gleichzeitig weitergeholt und dem Player die Leitung weggenommen:
zwei Anfragen machen die dringende langsamer, nicht schneller. Herauskam
„Es kommen keine Daten mehr\".

Jetzt ruht der Vorrat, solange eine Anfrage des Players offen ist.

Dazu ein Rueckzugsweg, der gefehlt hat. Der Speicher steht zwischen Player
und Server, und was dort schiefgeht, sieht von aussen aus wie eine tote
Leitung. Kommt nach zwoelf Sekunden nichts an, wird er weggenommen und es
laeuft ohne ihn noch einmal an — das kostet nur den Vorsprung und
unterscheidet die beiden Faelle, statt sie zu verwechseln. Erst danach die
kleinere Bitrate, erst danach das Aufgeben.

Und drei Zeilen ins Protokoll, die drei Runden Ratens erspart haetten: was
bestellt wurde, was der Player daraus meldet, und welche Spur eingeschaltet
wurde. „Nicht geliefert\" und „geliefert und nicht angezeigt\" sehen von
aussen gleich aus — im Protokoll nicht mehr.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-28 21:50:57 +02:00
ScarriffleandClaude Opus 5 5e33971452 AVPlayer hat die Untertitel wieder abgeschaltet
Der Server lieferte sie, die Spur lag im Stream, ausgewaehlt war sie auch —
und angezeigt wurde trotzdem nichts.

Der Grund ist eine Eigenschaft, die von Haus aus auf `true` steht:
`appliesMediaSelectionCriteriaAutomatically`. Der Player richtet sich damit
nach den Untertiteleinstellungen des Systems, und die stehen in aller Regel
auf „aus\". Er nahm also jede Wahl, die wir trafen, wieder zurueck —
lautlos, und erst nachdem wir sie gesetzt hatten.

Beim Ton aendert das Abschalten nichts: welche Spur klingt, ist laengst
beim Aushandeln entschieden, und bei einem umgerechneten Stream kommt
ohnehin nur eine an.

Dazu eine zweite Absicherung: bei einer Abspielliste laedt AVFoundation die
Nebenspuren nachtraeglich. Meldet sie beim ersten Mal noch keine, ginge der
bestellte Untertitel verloren — deshalb wird im Sekundentakt nachgefasst,
solange nichts ausgewaehlt ist.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-28 21:40:30 +02:00
ScarriffleandClaude Opus 5 cff0c06745 Untertitel brachten den Vorratsspeicher auf 404
Untertitel stehen in der Abspielliste als eigene Spur unter EXT-X-MEDIA —
eine Anweisung mit einer Adresse darin. Umgeschrieben habe ich bisher nur
EXT-X-MAP, also blieb diese relativ stehen und zeigte danach auf den
lokalen Server, wo es sie nicht gibt. Zu sehen war der Fehler erst, als
jemand Untertitel einschaltete: HTTP 404, mitten im Film.

Jetzt sagt der Umschreiber, **was** fuer eine Adresse er gerade in der Hand
hat: eine Qualitaetsstufe, eine Nebenspur, ein Kopfstueck oder ein
Schnipsel. Das ist noetig, weil sie Verschiedenes wollen — hinter einer
Stufe liegt Bild und die soll vorgeladen werden, hinter einer Untertitel-
spur nicht. Sonst lueden wir Untertitel statt Bild: der Film bliebe stehen,
waehrend die Anzeige reichlich Vorrat meldet.

Dazu wandern alle Adressen im lokalen Server auf die **vollstaendige**
Form. Eine relative gilt je nach Liste gegen eine andere Grundadresse — die
Untertitelspur liegt woanders als das Bild —, und wer das erst beim Abholen
aufloest, loest es irgendwann gegen die falsche auf. Das waere der naechste
Fehler derselben Art gewesen.

Drei Tests halten die Unterscheidung fest, darunter die Liste mit
Untertitelspur, die den 404 ausgeloest hat.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-28 21:36:35 +02:00
ScarriffleandClaude Opus 5 88ba1a540c Untertitel werden auch gezeigt, und die Stelle geht nicht mehr verloren
Zwei Fehler, die beide beim Umstellen im laufenden Film auftraten.

Der Server lieferte den gewaehlten Untertitel brav mit — nur sagte danach
niemand dem Player, dass er ihn **zeigen** soll. Gewaehlt wurde allein in
`applyPreferredLanguages()`, und das greift nur, wenn in den Einstellungen
eine Untertitelsprache steht. Wer keine eingestellt hatte, schaltete die
Untertitel ein und sah nichts. Jetzt wird die bestellte Spur nach dem
Neuaufbau angezeigt; bei einem umgerechneten Stream liegt genau eine drin,
naemlich die bestellte, also gibt es nichts zu verwechseln.

Und der Sprung an den Filmanfang, der „teilweise\" passierte: ein frisch
geoeffneter Stream meldet erst einmal null, eins, zwei — er springt erst
danach an die gemerkte Stelle. Wer in diesem Augenblick `currentTime`
liest, liest zwei Sekunden statt einundzwanzig Minuten. Genau das tut ein
zweiter Neuaufbau, wenn man zuegig noch etwas umstellt: er merkt sich die
falsche Stelle. Daher trat es nicht immer auf, sondern nur manchmal.

Waehrend auf die gemerkte Stelle gewartet wird, gelten die Meldungen des
anlaufenden Streams jetzt nicht mehr — dasselbe Prinzip wie beim Spulen,
wo die Nachzuegler der alten Stelle verworfen werden.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-28 21:30:26 +02:00
ScarriffleandClaude Opus 5 0815432efd Untertitel lassen sich jetzt einstellen
Groesse in vier Stufen, Schrift und Hintergrund als fuenf feste Paare —
Weiss, Weiss auf Schwarz, Schwarz auf Weiss, Gelb, Gelb auf Schwarz. Feste
Paare statt zweier Regler, weil unabhaengige Achsen unlesbare Mischungen
zulassen: Schwarz auf durchsichtig ueber einer Nachtszene ist eine
gueltige Einstellung und eine wertlose.

Die Uebersetzung nach AVFoundation ist eine Tabelle aus Schluesseln, bei
der jeder Fehler lautlos ist — ein falscher Schluessel laesst den
Initialisierer `nil` zurueckgeben, und dann gibt es gar keine Regel. Auf
dem Bildschirm sieht „keine Regel\" genauso aus wie „Regel ohne Wirkung\".
Zehn Tests decken das ab, darunter die drei Fallen: Alpha zuerst in der
Farbe, Prozentwert statt Punktgroesse, und der Hintergrund auch fuer
„durchsichtig\" ausdruecklich gesetzt statt weggelassen — weggelassen gilt
sonst, was Jellyfins WebVTT selbst mitbringt, und das ist oft ein
schwarzer Kasten.

Als Protokollmethode, nicht als Feld an `PlaybackSource`: die wird einmal
beim Oeffnen gelesen, und eine Einstellung, die erst beim naechsten Film
wirkt, haelt jeder fuer kaputt. Angewandt wird an zwei Stellen — am
laufenden Gegenstand und am frisch angelegten. Nur das erste zu tun hiesse,
dass die naechste Folge wieder in der Vorgabe erscheint.

VLC erbt die leere Vorgabe. Die Einstellungsseite sagt beides: dass es fuer
den Standardplayer gilt, und dass eingebrannte Bilduntertitel von Blu-rays
sich nicht gestalten lassen, weil es dort keinen Text gibt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-28 21:22:22 +02:00
ScarriffleandClaude Opus 5 a1ff5cb7bb Von der Kachel direkt abspielen, auch bei Serien
Wer weiss, was er sehen will, klickte bisher auf die Kachel, wartete auf
die Detailseite und drueckte dort „Abspielen\". Zwei Schritte fuer eine
Absicht. Jetzt gibt es oben rechts einen Knopf — auf dem Mac beim
Darueberfahren, auf iPad und iPhone ueber langes Druecken.

Bei einer Serie ist die Absicht nicht „spiel die Serie\", sondern „mach da
weiter, wo ich war\". Welche Folge das ist, entscheidet `EpisodeSelection`:
die aelteste ungesehene, fortgesetzt falls angefangen, und von vorn, wenn
alles gesehen ist. Ein reiner Typ mit achtzehn Tests — im App-Ziel gibt es
kein Unit-Test-Ziel, und „er hat die falsche Folge gestartet\" ist sonst ein
Befund, den man nicht nachstellen kann.

Ausdruecklich die aelteste ungesehene, nicht die am weitesten
fortgeschrittene: wer sich eine Folge aufgehoben hat, soll sie nicht
dadurch verlieren, dass er weiter vorn hineingeschaut hat.

Dazu `episodes(inSeries:)` im Vertrag — bei Jellyfin eine Anfrage statt
dreizehn, fuer alle anderen die Vorgabe ueber `children(of:)`. Nicht
`/Shows/NextUp`: der braucht eine Seriennummer, die eine Serie selbst nicht
hat, versteckt vollstaendig gesehene Serien und bringt eine eigene
Zeitgrenze mit. Welche Folge dran ist, soll eine Regel entscheiden, nicht
der Server.

Nebenbei behoben: der grosse Abspielknopf im Aufmacher scheiterte bisher
mit „keine abspielbare Datei\", sobald dort eine Serie stand — und Jellyfins
„Zuletzt hinzugefuegt\" liefert neue Folgen als ihre Serie.

Der Knopf liegt als Ueberzug auf dem NavigationLink, nicht darin: ein
Button im Label eines Links loest auf iOS nie aus, und `.plain` macht das
Versagen lautlos.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-28 21:18:39 +02:00
ScarriffleandClaude Opus 5 2a2afa8573 Der Aufmacher bleibt stehen, und Folgen bekommen ein Bild
Drei Sachen am Karussell.

Die Vorschlaege wechselten bei jedem Start, obwohl es dafuer laengst ein
Zeitfenster von sechs Stunden gab. Der Grund: das Los hing an der Position
in der Liste, nicht am Titel. Kam einer dazu, verschwand einer, oder
aenderte sich ein Fortschritt, bekamen alle dahinter ein anderes Los — und
„Weiterschauen\" waechst und schrumpft staendig. Jetzt haengt das Los an der
Kennung; wer dazukommt, stoert niemanden. Als eigener Typ mit elf Tests,
weil „es wechselt zu oft\" kein Befund ist, den man debuggen kann.

Folgen zeigten kein Bild. Die Kette Folge → Serie gab es zwar, aber sie
hoerte beim ersten Glied auf, das sich **zusammensetzen** liess — und ob es
das Bild dahinter gibt, stellt sich erst beim Abruf heraus. Die Reihenfolge
wandert deshalb ins Laden: Folge, Staffel, Serie, der Reihe nach, bis eine
Adresse antwortet.

Und auf dem Mac gibt es jetzt Pfeile links und rechts, sobald der Zeiger
ueber dem Aufmacher liegt, sowie Punkte, die unter dem Zeiger wachsen und
auf die man springen kann. Nur dort: auf dem iPad und dem iPhone wischt man
ohnehin, und zwei Knoepfe staenden nur im Bild.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-28 20:46:34 +02:00
ScarriffleandClaude Opus 5 4b084bc926 Ein Sprung in den Vorrat laeuft jetzt sofort weiter
Drei Gruende, und der erste erklaert das ganze Aergernis.

AVPlayer besteht nach einem Sprung darauf, erst seinen eigenen Puffer zu
fuellen. Der stand auf sechzig Sekunden — richtig, solange er selbst
vorladen sollte. Mit einem Speicher davor ist es das Gegenteil: springt man
mitten in den Vorrat, liegt dahinter vielleicht noch eine halbe Minute, zu
wenig fuer seine sechzig, und er wartet auf den Server, obwohl alles da
ist. Der Abstand liegt jetzt bei uns; seiner darf klein sein.

Der Vorrat holte ausserdem weiter Schnipsel hinter der Abspielstelle, wenn
der Player vorausgesprungen war — und der eine, auf den gerade gewartet
wurde, kam als letzter. Er macht jetzt dort weiter, wo gesprungen wurde.

Und dieselbe Liste noch einmal zu holen setzte den Vorrat zurueck. Der
Player holt sie staendig: nach einem Sprung, beim Stufenwechsel, von sich
aus. Zurueckgesetzt wird nur noch bei einem echten Wechsel der Quelle.

Dazu ein latenter Fehler, der beim Umpacken ein Schwarzbild gegeben haette:
in EXT-X-MAP steckt die Adresse des Kopfstuecks einer fragmentierten MP4,
ohne das kein einziger Schnipsel zu dekodieren ist. Sie blieb stehen und
zeigte damit relativ auf den lokalen Server. Der alte Test hielt genau
dieses Verhalten fest — er hat mitgeaendert.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-28 20:36:33 +02:00
ScarriffleandClaude Opus 5 bbd1c3b572 Der Vorrat steigt jetzt auch eine Ebene tiefer
Bei „Auto\" bestellen wir eine Liste von Stufen; die Schnipsel stehen eine
Ebene darunter. Der Speicher erkannte das zwar, stieg dann aber aus und
reichte die Liste mit relativen Adressen durch — die zeigten danach auf den
lokalen Server, wo es sie nicht gibt. Es wurde nichts vorgeholt, und
beinahe waere auch nichts mehr gelaufen.

Jetzt wird auch die Stufenliste umgeschrieben. Holt der Player eine Stufe,
kommt er wieder bei uns an, und dort beginnt das Vorauslaufen — bei der
Stufe, fuer die er sich entschieden hat.

Dazu eine Zeile im Qualitaetsblatt, die sagt, ob der Vorrat laeuft und wie
weit er voraus ist. Von aussen ist das nicht zu sehen: laeuft er nicht,
spielt es trotzdem, nur ohne Vorsprung — und genau so sieht auch ein
Vorrat aus, der laeuft und nichts bekommt. Ohne diese Zeile sind die beiden
Faelle nicht zu unterscheiden, und ich haette wieder geraten.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-28 20:29:20 +02:00
ScarriffleandClaude Opus 5 134dc82927 Der Vorrat laeuft jetzt voraus, nicht der Player
AVPlayer entscheidet selbst, wie weit er puffert, und bei einer Abspielliste
laesst Apple uns nicht dazwischen: auf eine Anfrage nach einem Schnipsel
nimmt er von uns nur eine Weiterleitung an, keine Daten. Der Denkfehler war,
seinen Puffer vergroessern zu wollen.

Wir muessen aber gar nicht dazwischen — wir koennen die Gegenstelle sein.
Ein winziger HTTP-Server auf 127.0.0.1 liefert dem Player die Abspielliste,
umgeschrieben auf sich selbst, und holt die Schnipsel zuegiger von Jellyfin,
als abgespielt wird. Jedes Segment, das der Player verlangt, liegt dann
schon lokal. Faellt die Verbindung im Tunnel aus, merkt er es nicht.

Das ist der Unterschied zwischen Puffer und Vorrat: den Puffer bestimmt
Apple, den Vorrat bestimmen wir.

Streng der Reihe nach, und mit begrenztem Vorsprung. Der Server erzeugt die
Schnipsel gerade erst; wer Nummer 40 verlangt, waehrend er bei 12 ist, sieht
fuer Jellyfin aus wie jemand, der gesprungen ist — und dann beginnt das
Umrechnen von vorn, an der falschen Stelle.

Der Lauscher haengt ausdruecklich an 127.0.0.1, nicht an allen
Schnittstellen. Ein Server, der den Film ins Netz stellt, waere das Letzte,
was eine Mediathek-App tun sollte.

Kommt er nicht hoch, spielt es trotzdem — ueber die urspruengliche Adresse,
ohne Vorrat. Ein fehlender Vorrat ist ein Verlust an Bequemlichkeit; keine
Wiedergabe ist ein Verlust an allem.

Das Lesen und Umschreiben der Abspielliste ist ein eigener Typ ohne Netz,
mit sieben Tests. Eine falsch umgeschriebene Liste ergibt entweder ein
schwarzes Bild oder — schlimmer — eine, die stellenweise am Speicher
vorbeigeht und nur manchmal hakt. So etwas findet man im laufenden Film
nicht.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-28 20:23:14 +02:00
ScarriffleandClaude Opus 5 46550fc30b Umgepackt wird als HLS, und ein Fehlschlag faellt auf VLC zurueck
Der Versuch mit einer fortlaufenden MP4 ist am Geraet gescheitert:
AVErrorServerIncorrectlyConfigured (-11850), sofort. Der Grund ist
einleuchtend, sobald man ihn kennt — AVPlayer verlangt bei einer
fortlaufenden Datei Bereichsanfragen, er will hineinspringen koennen. Eine
MP4, die ffmpeg gerade erst erzeugt, kann das nicht: sie hat weder Laenge
noch ein Inhaltsverzeichnis am Anfang.

Das war eine Annahme, die ich haette messen sollen, bevor ich darauf baue.

Vom Ziel bleibt damit das Umpacken, nicht das weite Vorausladen — und das
ist immer noch der groessere Teil: ein Matroska-Rip mit HEVC und AC3 landet
bei AVPlayer statt bei VLC, mit Bild-in-Bild und AirPlay, ohne dass ein Bild
neu gerechnet wird. `Container: mp4` statt `ts`, weil HLS als fragmentierte
MP4 auch HEVC traegt; in MPEG-TS waere dafuer eine Neukodierung noetig,
also genau das, was hier vermieden werden soll.

Dazu ein Rueckzugsweg, der vorher gefehlt hat. Ob der Server die Stroeme
wirklich kopieren kann, entscheidet sich erst beim Ausliefern, und manche
Antworten nimmt AVPlayer nicht an. Das vorher zu wissen ist nicht moeglich;
es hinterher aufzufangen schon. Scheitert das Umpacken, laeuft derselbe
Titel einmalig mit VLC an, statt den Nutzer vor eine schwarze Wand mit
einer Fehlernummer zu stellen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-28 20:11:49 +02:00
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 806eb381c7 Beim Durchreichen darf viel weiter vorgeladen werden
Es sind zwei verschiedene Lagen, und sie hatten bisher dieselbe Zahl.

Rechnet der Server um, existieren die Schnipsel noch gar nicht — ffmpeg
erzeugt sie gerade, und weiter voraus als bis dorthin kommt niemand. Eine
Minute ist dort reichlich; ein groesserer Wunsch liefe ins Leere.

Wird die Datei unveraendert durchgereicht, liegt sie fertig auf der Platte
und kommt ueber gewoehnliche Bereichsanfragen — dieselbe Lage wie bei einem
Video im Netz, das man beliebig weit vorausladen kann. Dort bei einer
Minute aufzuhoeren war schlicht zu schuechtern; jetzt sind es zehn.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-28 19:38:00 +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 67c679006f Untertitel kommen aus der Datei, und Fortsetzen geht wieder
Im Spurenblatt stand „CC" und sonst nichts — derselbe Fehler wie beim Ton:
das ist AVFoundations Name für den einen Untertitel, den ein umgerechneter
Stream mitbringt. Zur Auswahl steht jetzt, was in der Datei liegt, und die
Nummer geht als `subtitleStreamIndex` mit in die Anfrage.

`-1` heisst dabei ausdrücklich „keine". Das ist nicht dasselbe wie nichts zu
schicken: dann sucht der Server sich einen aus, und es steht plötzlich ein
Untertitel im Bild, den niemand bestellt hat.

Und der Klick ins Bild: Anhalten ging, Fortsetzen nicht. Der Abdunkler
hinter der Bedienung deckt das ganze Fenster ab und lag über dem Film —
solange die Bedienung zu sehen war, landete jeder Klick auf ihm. Auffallen
konnte das nur an einer Stelle: nach dem Pausieren bleibt die Bedienung
stehen, also traf der Klick, der wieder starten sollte, nie das Bild. Der
Abdunkler ist jetzt unantastbar; er dunkelt ab, mehr nicht.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-28 19:14:07 +02:00
ScarriffleandClaude Opus 5 60194c2d2a Die Bedienung blieb stehen, weil der Zeiger sich nie bewegt hat
Seit die Bedienung dauerhaft montiert ist, zeichnet der Scrubber darin im
Sekundentakt neu. `onContinuousHover` meldet nicht nur bei Bewegung, sondern
auch bei jedem Neuzeichnen — also setzte die Ausblendfrist sich endlos
selbst zurück. Die Bedienung blieb für immer stehen, und die Überblendung,
die dadurch nie stattfand, sah aus wie eine kaputte Animation. Sie war nie
das Problem; das war sie schon beim letzten Anlauf nicht.

Jetzt gilt der Zeiger nur als bewegt, wenn er an einer anderen Stelle ist
als zuletzt.

Und der Klick in den schwarzen Rand: ich habe zweimal ausgerechnet, wo das
Bild aufhört, und lag zweimal daneben. Jetzt rechnet das Layout. Die
Bildfläche ist auf ihr Seitenverhältnis zugeschnitten und hat ihren eigenen
Griff; der schwarze Rand ist schlicht der Hintergrund dahinter. Was am
Player ankommt, ist damit per Definition daneben gegangen — es muss niemand
mehr feststellen.

Damit fallen `videoRect`, `videoFrame`, `playerSize`, der benannte
Koordinatenraum und die Abfrage der `AVPlayerLayer` ersatzlos weg.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-28 18:01:27 +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 2dbe260901 Mediatheken stehen sofort da statt sich jedes Mal neu zu laden
Jeder Aufruf einer Mediathek war ein Serverabruf mit Ladekreis davor. Die
Plakate lagen längst auf der Platte; was fehlte, war die Liste, die sagt,
welche davon hierhergehören.

Jetzt liegt die erste Seite als Abzug daneben: beim Aufrufen steht sie
sofort da, der Server wird still nachgefragt, und man sieht in aller Regel
nichts davon. Antwortet er nicht, bleibt der Abzug stehen — eine Liste von
gestern ist im Zug brauchbarer als ein Fehlerbildschirm.

Bewusst nur die erste Seite. Stünden 240 gespeicherte Titel da und der
Server antwortet mit 60, müsste entweder die Liste vor den Augen schrumpfen
oder der Versatz fürs Nachladen wäre falsch.

Gespeichert wird ausserdem nur, was eine Kachel zeigt. Ein Titel aus einer
Listenabfrage bringt jede Spur jeder Datei mit — im Raster steht davon
nichts, und ausgepackt werden müsste es bei jedem Aufrufen, also genau da,
wo der Abzug Zeit sparen soll.

Die Mediathekenliste selbst wird nur als Rettungsanker gemerkt, nicht als
Beschleuniger: sie sofort zu zeigen hiesse, eine Sitzung für fertig zu
erklären, bevor feststeht, dass der Token noch gilt.

Abmelden räumt alles weg. Titel, Zusammenfassungen und Sehfortschritt sind
persönlich.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-28 15:59:15 +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 e61cb0403d Die Tonspur wird jetzt bestellt, nicht abgewartet
Der Befund, der die ganze Umsetzung bestimmt: `AudioStreamIndex` kam in
der PlaybackInfo-Anfrage **nicht vor**. Der Server wählte, wir nahmen was
kam — und bei einem umgerechneten Stream kommt danach nur noch diese eine
Tonspur an. `selectAudioTrack` schob seither ins Leere, weil im Player
eine Liste mit einem Eintrag stand.

Damit ist auch erklärt, warum die Spracheinstellung beim ZDF-Fall nichts
ausrichten konnte: sie lief auf einer Auswahl, die es gar nicht gab.

`PlaybackIntent` trägt die Spurnummer jetzt mit, und die Aushandlung
nennt sie ausdrücklich. Zwei Tests halten fest, dass sie ankommt — und
dass sie ohne Wunsch weggelassen wird, statt eine Null zu schicken.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-27 19:16:51 +02:00
ScarriffleandClaude Opus 5 afb4a63655 Der Kern der Sprachwahl: Kürzel, die nicht lügen, und Abwägen statt Filtern
**`LanguageCode`** bringt Kürzel auf eine vergleichbare Form. Der Anlass
ist ein stiller Fehler: verglichen wurden die ersten zwei Zeichen, also
`ge` gegen `de` — und `ger` ist die ISO-639-2/B-Form von Deutsch, die
ffmpeg in viele Dateien schreibt. Bei solchen Titeln wirkte die
Spracheinstellung überhaupt nicht.

`gsw` bleibt `gsw`. Schweizerdeutsch ist keine Schreibweise von Deutsch,
und es gleichzusetzen hiesse, jemandem Schweizerdeutsch unterzuschieben,
der Hochdeutsch wollte. Es gehört in die Rangliste, nicht in eine
Gleichsetzung.

**`AudioTrackChooser`** wägt ab, statt zu filtern: jede Spur bekommt
Punkte, die höchste gewinnt. Damit gibt es immer eine Antwort, und
Audiodeskription wird gemieden, ohne unmöglich zu werden — gibt es eine
Sprache nur mit Audiodeskription, ist sie besser als Stille.

Gemerkt wird nicht eine Nummer, sondern eine Beschreibung der Spur:
Folgen derselben Serie haben unterschiedlich sortierte Spuren, und
„Spur 2" bedeutet in der nächsten Folge etwas anderes.

Siebzehn Tests, darunter der ZDF-Fall wörtlich — deutsche Spur mit
Audiodeskription als Standard, englisch betitelte deutsche Spur daneben
— und zwar in beide Richtungen: mit Meiden gewinnt die richtige, ohne
Meiden gewinnt das Kürzel. Sonst stünde der Schalter nur dabei.

Dazu trägt `MediaStream` jetzt `title`, `isDefault`, `isHearingImpaired`
und `isOriginal`. Der Name aus der Datei ist das zweite Signal neben dem
Kürzel und manchmal das einzige richtige.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-27 19:15:08 +02:00
ScarriffleandClaude Opus 5 946de29893 Nachsehen, was in den Tonspuren wirklich steht
Die Sprachwahl vergleicht Kürzel, und Kürzel lügen — beim ZDF-Fall ist
die als deutsch ausgezeichnete Spur die mit Audiodeskription. Ob der
**Name** der Spur das rettet, sieht man nur, wenn man beides nebeneinander
legt. Alles andere wäre geraten, und davon hatten wir heute genug.

`--spuren <host> <suchbegriff>` sucht einen Titel und druckt zu jeder
Tonspur: Kürzel, Name aus der Datei, wie Jellyfin es zusammensetzt,
Codec, Kanäle und die Kennzeichen.

Dafür holt der Decoder jetzt auch `Title`, `IsHearingImpaired` und
`IsOriginal`. `Title` ist das zweite Signal neben dem Kürzel und
manchmal das einzige richtige: wo „eng" steht, kann im Namen „Deutsch"
stehen. Wir haben es bisher weggeworfen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-27 19:11:27 +02:00
ScarriffleandClaude Opus 5 41cd7331a9 Entwurf für Standardsprachen der Tonspur
Drei Ebenen (allgemein → Mediathek → Serie), eine globale Rangliste als
Boden darunter, und die Wahl als prüfbares Punktesystem.

Zwei Funde bestimmen den Entwurf:

**Jellyfin hat kein Kennzeichen für Audiodeskription.** `MediaStream`
führt `IsDefault`, `IsForced`, `IsHearingImpaired`, `IsOriginal` — nichts
für Sehschädigung; die `visual_impaired`-Disposition reicht der Server
nicht durch. Sie ist nur am Text zu erkennen, und deshalb muss die
Erkennung nachsichtig sein und sich überstimmen lassen.

**Der Sprachvergleich greift heute bei `ger` nicht.** Verglichen werden
die ersten zwei Zeichen, also `ge` gegen `de` — und `ger` ist die
ISO-639-2/B-Form von Deutsch, die ffmpeg in viele Dateien schreibt. Bei
solchen Titeln wirkt die Spracheinstellung lautlos gar nicht. Gemessen,
nicht vermutet.

Auf Serienebene wird nicht eine Sprache gespeichert, sondern die
Signatur der tatsächlich gewählten Spur. Nur so ist der ZDF-Fall lösbar,
bei dem die deutsche Spur die mit Audiodeskription ist und die als
englisch ausgezeichnete die richtige.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-27 18:59:46 +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 279d8e36ae Zwei Fehler um dieselbe Bildfläche
**Die Wiedergabe sprang von selbst wieder an.** Auf dem Mac pausiert ein
Klick ins Bild — und der Klick, mit dem man ein Fenster nach vorn holt,
kommt ebenfalls dort an. Man aktiviert das Fenster und drückt dabei auf
Play. Klicks innerhalb einer Viertelsekunde nach dem Aktivieren zählen
deshalb nicht: länger braucht niemand bis zum ersten gemeinten Klick,
kürzer erwischt den Fall nicht.

**Schwarzbild mit Ton nach dem Vergrössern.** Kachel und Vollbild
bestehen beim Wechsel kurz gleichzeitig, und beide holten sich dieselbe
Bildfläche in ihre Hülle. Eine Ansicht hat aber nur einen Vorgesetzten —
wer zuletzt aktualisiert, gewinnt, und das war nicht verlässlich die
richtige. Blieb die Fläche bei der Kachel, die gleich darauf verschwand,
lief im Vollbild der Ton weiter und das Bild blieb schwarz.

Die Hülle allein genügte nicht: sie verhindert, dass das Abräumen der
einen Ansicht der anderen etwas wegnimmt, entscheidet aber nicht, wer
die Fläche bekommen **soll**. Das sagt jetzt `isActive` — abgeleitet aus
`playback.presentation`, also aus derselben Quelle, die auch bestimmt,
welche Ansicht überhaupt zu sehen ist.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-27 18:44:20 +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 24d7423f9c Bei Jellyfin galt zuhause als unterwegs
Beinahe-Regression, gefunden bevor sie jemanden traf: `isLocalConnection`
kam aus `session.server?.connection.local`, und `server` ist ein
`PlexServer` — bei Jellyfin immer `nil`. Jede Jellyfin-Wiedergabe galt
damit als entfernt, auch zuhause am Kabel. Der neue Deckel hätte dort ein
67-Mbit-Remux ohne Not auf acht Megabit heruntergerechnet.

Jetzt wird gefragt statt geraten: `/System/Endpoint` beantwortet es mit
**demselben** Aufruf, mit dem Jellyfin auch selbst entscheidet, ob es eine
Bitratenleiter ausliefert. Ein Blick auf die Adresse hätte nicht genügt —
wer über einen Reverse Proxy geht, erreicht seinen Server auch von der
Couch aus unter einem öffentlichen Namen, und über ein VPN kommt eine
private Adresse von weit her.

Gefragt wird bei beiden Wegen, beim Anmelden und beim Wiederherstellen:
dasselbe Konto kann heute zuhause und morgen im Zug aufwachen.

Im Zweifel `false` — lieber einmal zu vorsichtig gedeckelt als eine
Wiedergabe, die über eine Fernverbindung gar nicht erst anläuft.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-27 18:37:16 +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 4abce1c8b9 Name und Passwort zuerst, und Blätter in Fenstergrösse
**Die Anmeldung.** Sobald ein Jellyfin-Server Quick Connect anbietet,
sprang der Weg von selbst in den Code-Fluss. Gut gemeint — dabei läuft
kein Passwort durch diese App —, aber am Alltag vorbei: der Code will in
einem zweiten Gerät bestätigt werden, und wer im Zug sitzt, hat das
nicht offen. Ein Passwortspeicher füllt dagegen beide Felder in einer
Sekunde. Der Code bleibt als Knopf darunter erreichbar.

**Die Blätter.** Wieder `#if os(macOS)`, das auf dem Mac nie greift, weil
dort der iOS-Bau läuft. Filter, Spurwahl und Qualitätsblatt bekamen
deshalb eine Blattkante in iPhone-Höhe — ein schmaler Streifen, der in
einem grossen Fenster unten im Nichts klebt. Jetzt zur Laufzeit
entschieden: auf dem Mac volle Höhe, sonst wie bisher.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-27 18:27:38 +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 c334fca665 Den grössten Titel messen, und die Quelle dazusagen
Die Reihe lieferte viermal dasselbe — 480p bei 1,1 Mbit, gleichgültig ob
mit `videoResolution` oder mit 20 Mbit. Das sieht nach einem Fehler in
der Anfrage aus und ist wahrscheinlich keiner: gemessen wurde „2 Fast 2
Furious", der erste Titel im Alphabet, und vermutlich ein 540p-Rip. Plex
rechnet nichts hoch.

Damit misst die Reihe die **Datei** statt die Einstellungen — und aus
einer Messung, die immer dasselbe sagt, liest man leicht das Falsche
heraus.

Die Probe nimmt jetzt den Titel mit der höchsten Bitrate aus einer
Stichprobe von sechzig und druckt die Quelle dazu: Auflösung, Bitrate,
Codec. Damit ist auf einen Blick zu sehen, ob eine kleine Antwort eine
kleine Quelle ist oder ein echter Deckel.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-27 18:16:40 +02:00
ScarriffleandClaude Opus 5 d2237c29cd Fragen statt starten — die Probe überfuhr den Server
Beim letzten Lauf scheiterte auch die Anfrage, die vorher funktioniert
hatte. Kein neuer Befund, sondern mein Messfehler: die Probe warf sechs
Transcode-Sitzungen in Folge an, und danach lehnt Plex alles mit nackten
400ern ab — auch das, was vorher ging.

Jede Anfrage an `start.m3u8` startet einen Transcoder. Für eine
Parameterreihe ist das das falsche Werkzeug. Der **Entscheider** sagt,
was der Server täte, ohne etwas zu tun: Auflösung, Bitrate, und ob
umgerechnet oder umgepackt wird. Die Reihe läuft jetzt darüber.

Die zwei verbleibenden echten Sitzungen bekommen Abstand voneinander.

Der Wert wird aus der Antwort gesucht statt geparst — Plex antwortet je
nach Kopfzeile als XML oder JSON, und es geht um eine Anzeige, nicht um
Daten, mit denen weitergerechnet wird.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-27 18:15:05 +02:00
ScarriffleandClaude Opus 5 b861ca0599 Prüfen, woran die kleine Plex-Auflösung hängt
Die Servereinstellungen sind es nicht: „Begrenzung der Stream-Bitrate
übers Internet" steht auf Original, keine Upload-Grenze, Hardware-
Transcoding an. Die Vermutung ist damit widerlegt.

Bleibt die eigene Anfrage. Bei „automatisch" schickt die App
`maxVideoBitrate` und `videoQuality`, aber **kein** `videoResolution` —
dort steht `nil`, weil die Stufe keine vorgibt. Verdacht: Plex nimmt
dann seine Voreinstellung für die Qualitätsstufe, und die ist 540p.

Wäre das so, bekäme jeder Plex-Nutzer bei „automatisch" über eine
Fernverbindung stillschweigend 960x540 bei 1,1 Mbit, auch auf einer
schnellen Leitung. Die Probe fährt deshalb vier Kombinationen ab und
hält jeden Transcoder einzeln wieder an.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-27 18:06:04 +02:00
ScarriffleandClaude Opus 5 7ecb16d4eb autoAdjustQuality bricht die Plex-Wiedergabe — wieder raus
Gemessen, nicht vermutet: zwei Anfragen an denselben Titel, getrennte
Sitzungskennungen, einzige Abweichung der Wert. Ohne ihn kommt eine
Playlist, mit ihm eine nackte **HTTP 400**.

Die Zeile hätte also nicht die Qualität verbessert, sondern die
Wiedergabe verhindert. Der Plan hat vor genau diesem Verhalten gewarnt —
nur habe ich es am Entscheider vermutet und ihn dort ausgespart, während
die Start-Adresse ihn ebenso ablehnt.

Plex' eigene Clients schicken den Wert; welche weitere Angabe sie dabei
mitführen, ist offen. Bis das gemessen ist, bleibt er draußen: eine
Leiter, die es vielleicht gibt, ist keinen Strom wert, der sicher nicht
läuft.

`TranscodeRequest.adaptive` bleibt samt seinen Tests bestehen. Das
Gerüst steht, es fehlt die richtige Beschwörungsformel.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-27 18:02:37 +02:00
ScarriffleandClaude Opus 5 165a82acd0 Der Plex-Vergleich verglich zwei Mal dasselbe
`askingForLadder()` behält die Sitzungskennung bei — richtig für die App,
weil sonst beim Aufräumen ein Transcoder stehen bliebe. Für eine
Gegenüberstellung ist es tödlich: die zweite Anfrage bekam die laufende
Sitzung der ersten zurück, und die Antwort war zwangsläufig identisch.
Im Wortlaut stand zweimal dieselbe Sitzungs-GUID — der Beweis, dass gar
nichts gemessen wurde.

Jetzt zwei eigenständige Anfragen mit getrennten Kennungen, beide
Kennungen werden gedruckt, und beide Transcoder werden hinterher
angehalten.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-27 17:49:36 +02:00
ScarriffleandClaude Opus 5 e324a474fd Watchdog heisst Watchdog, Engine heisst Engine
Eingedeutschte Fachbegriffe in den Kommentaren — „Wachhund" für
Watchdog, „Motor" für Engine. Das liest sich albern und ist unklarer als
das Original; niemand sagt Wachhund, wenn er einen Watchdog meint.

Acht Stellen berichtigt. Gewöhnliche Sprache bleibt deutsch, der Stil
der Kommentare ändert sich nicht.

Dazu die Plex-Leiter messbar gemacht: `--leiter` baut dieselbe Anfrage
zweimal, mit und ohne `autoAdjustQuality=1`, und zählt die Stufen. Die
Zeile steht seit dem Umbau im Code und war nie geprüft — genau die Art
ungeprüfter Annahme über Serververhalten, die bei Jellyfin vier Runden
gekostet hat.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-27 17:47:31 +02:00
ScarriffleandClaude Opus 5 314c1a3bfc Die Schnipsel wiegen, nicht die Playlist zählen
Berechtigter Einwand: eine Playlist zu bekommen beweist gar nichts. Sie
ist Text und kostet ein Kilobyte; die Datenrate steckt in den
Schnipseln. Die vorige Fassung zählte `#EXTINF`-Zeilen und hätte
„funktioniert" gemeldet, auch wenn der Server bei jeder Stufe denselben
Strom ausliefert.

Jetzt lädt die Probe je Stufe den **ersten Schnipsel** und rechnet aus,
was er an Datenrate bedeutet — inklusive der unveränderten Stufe als
Vergleichsmass. Werden sie kleiner, trägt die Idee. Bleiben sie gleich
gross, rechnet der Server trotz anderer Zahl dasselbe, und ein
selbstgebautes Menü brächte nichts.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-27 17:37:14 +02:00
ScarriffleandClaude Opus 5 f48eb55f35 Prüfen, ob wir uns die Leiter selbst bauen können
Der Wortlaut der Master-Playlist zeigt, woraus Jellyfins eigene Leiter
bestünde: dieselbe `main.m3u8`-Adresse, mehrfach, mit anderem
`VideoBitrate`. Eine Master-Playlist ist nur ein Menü.

Liefert der Server diese Unter-Playlists auch dann, wenn **wir** die
Zahl ändern, brauchen wir seine Zustimmung gar nicht — dann schreiben
wir das Menü selbst, und die Frage, warum er es verweigert, ist
gegenstandslos.

Die Probe versucht deshalb drei Stufen von Hand: halbe Bitrate, ein
Viertel, und ein Viertel zusammen mit `maxHeight=480`. Das letzte ist
der interessante Fall — Jellyfins eigene Leiter lässt die Auflösung
stehen und dreht nur an der Bitrate. Eine Stufe, die auch kleiner
rechnet, sieht bei wenig Bandbreite deutlich besser aus als dieselbe
Auflösung mit zu wenig Daten.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-27 17:34:00 +02:00
ScarriffleandClaude Opus 5 6e18fda4aa Die Probe zeigt Fassung und Wortlaut
Der Proxy ist gelöst — Jellyfin hält die Verbindung jetzt für
ausserhalb —, und es kommt trotzdem nur eine Stufe. Damit sind alle
Hürden widerlegt, die im Quelltext von `master` stehen. Also stimmt
etwas an der Annahme nicht, und am wahrscheinlichsten ist es die
Fassung: ich lese womöglich den Quelltext einer anderen Version als der,
die dort läuft.

Deshalb meldet die Probe jetzt `Jellyfin <Version>` — und druckt bei nur
einer Stufe die Master-Playlist **im Wortlaut**. Ab da hilft keine
Zusammenfassung mehr: was fehlt, sieht man nur im Original.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-27 17:30:25 +02:00
ScarriffleandClaude Opus 5 e4e6cc6cbd Die Probe liest zurück, was der Server wirklich eingetragen hat
Ein Eintrag unter „Bekannte Proxys", der gespeichert, aber nicht
übernommen wurde, sieht von außen genauso aus wie gar keiner — und bei
der Fehlersuche unterscheidet das alles.

`GET /System/Configuration/network` führt `KnownProxies`. Zusammen mit
`IsLocal` und `IsInNetwork` steht damit in drei Zeilen, woran es liegt:
kein Eintrag, der falsche Eintrag, oder fehlender Neustart.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-27 17:28:10 +02:00
ScarriffleandClaude Opus 5 539ea0aacb Die Probe zeigt beide Werte, nicht nur das Urteil
`IsLocal` und `IsInNetwork` beantworten verschiedene Fragen, und der
Unterschied benennt die Ursache: `IsLocal` heißt, die Anfrage kommt über
127.0.0.1 — dann steht der Proxy auf demselben Host, und ein Eintrag
unter „Bekannte Proxys" mit der LAN-Adresse geht ins Leere, weil Jellyfin
diese Adresse nie zu sehen bekommt.

Vorher stand nur „LOKAL" da, und damit blieb offen, ob der Eintrag fehlt
oder der falsche ist.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-27 16:25:29 +02:00
ScarriffleandClaude Opus 5 a79d562642 Die Sicherheitsleine — der teure Weg, mit engen Regeln
Wenn der billige Hebel nichts ausrichtet, weil die Playlist nur eine
Stufe hat oder schon die unterste läuft, bleibt nur der Neuaufbau mit
kleinerer Bitrate: Transcoder anhalten, Motor abreißen, neu aushandeln,
zurückspringen. Sekunden mit schwarzem Bild.

**Ein Notnagel, keine Regelschleife.** Zu oft ausgelöst erzeugt er genau
das Ruckeln, das er beheben soll. `DowngradeRule` ist deshalb eine Liste
von Bedingungen, die alle gelten müssen, und keine Schwelle: nur bei
„automatisch", nur wenn der billige Weg erschöpft ist, nur ab drei
Rücklern in anderthalb Minuten oder einem einzelnen über fünfzehn
Sekunden, nicht in den ersten zehn Sekunden, nicht binnen zehn Sekunden
nach einem Sprung, höchstens alle drei Minuten, höchstens zweimal.

Ein Sprung leert den Puffer **immer**. Ihn als Beleg für eine schlechte
Leitung zu nehmen hieße, sich für das eigene Tun zu bestrafen.

Das Urteil ist eine Aufzählung und kein `Bool`: „es hakt, aber wir tun
nichts" ist ohne Grund von einem Fehler nicht zu unterscheiden. Vierzehn
Tests schreiben jede Bedingung einzeln hin.

Ist das Budget verbraucht, **wird es gesagt** statt lautlos weiter
abgesenkt. Still bis zur untersten Sprosse zu gehen ist schlimmer, als
einmal zuzugeben, dass die Leitung es nicht trägt.

**Live-TV ist dabei**, wie gewünscht — aber mit Tuner-Abbau vorweg und
Budget eins statt zwei. Er ist ein Stück Hardware, vier Stück gibt es,
und ein doppelt belegter ist schlimmer als ein Stottern. Bekannte
Grenze: `endLiveSession` meldet nicht, **ob** die Freigabe geklappt hat
— das Protokoll gibt nichts zurück. Deshalb der eine Versuch und jeder
Schritt im Protokoll, damit ein verlorener Tuner nachvollziehbar bleibt.

Nebenbei aufgeräumt: die Wahl von Hand und die Sicherheitsleine teilen
sich jetzt denselben Neuaufbau. Was sie unterscheidet, steht in
`effectivePolicy` — die Wahl setzt `quality`, die Automatik
`automaticCeiling`, und beides fließt dort zusammen. Die Automatik
schreibt dabei nie `rememberedQuality` fort: eine schlechte Zugfahrt
soll nicht dauerhaft das Wohnzimmer verschlechtern.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-27 15:56:50 +02:00
ScarriffleandClaude Opus 5 d878289929 Der Server sagt jetzt selbst, ob er uns für lokal hält
Die Probe endete bisher mit einem Konjunktiv: „Kam dieser Aufruf von
zuhause, ist Punkt 1 die Erklärung." Das ist eine Vermutung an genau der
Stelle, an der die ganze Messung hängt.

`GET /System/Endpoint` beantwortet `IsInNetwork` mit **demselben**
Aufruf, mit dem auch die ABR-Sperre entscheidet:
`_networkManager.IsInLocalNetwork(HttpContext.GetNormalizedRemoteIP())`.
Damit steht als Erstes im Bericht, ob eine Leiter überhaupt möglich
wäre — und ob das Folgende eine Aussage über unterwegs enthält oder
keine.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-27 15:43:55 +02:00
ScarriffleandClaude Opus 5 52d6ebba43 „Automatisch" misst jetzt, statt die Einstellung durchzureichen
Bisher hieß `PlayerQuality.automatic` wörtlich: nimm die Zahl, die für
WLAN oder Mobilfunk hinterlegt ist. Keine Messung, kein Nachregeln — die
Entscheidung fiel einmal beim Start und galt bis zum Ende.

`StreamHealth` beurteilt den Strom jetzt fortlaufend aus Proben im
Sekundentakt. Ein reiner Typ: kein AVFoundation, keine Uhr, kein Netz.
Damit lässt sich eine schlechte Zugfahrt als Zahlenreihe hinschreiben
und nachsehen, was sie auslöst — zehn Tests tun genau das.

**Welches Signal wann meldet**, ist der eigentliche Inhalt. Der
Pufferverlauf ist der einzige echte Frühwarner: schrumpft er schneller,
als die Zeit vergeht, kommt weniger herein als hinausgeht, und das sieht
man zehn bis zwanzig Sekunden vor dem Rückler. `isLikelyToKeepUp` ist
früh, aber sprunghaft — es zählt erst, wenn es anhält. Der Rücklerzähler
meldet zu spät zum Regeln und bleibt trotzdem der Massstab, an dem sich
hinterher ablesen lässt, ob es besser wurde. Die **gemessene** Bitrate
steht bewusst nicht drin: auf einer kleinen Sprosse holt der Player
wenig, ohne dass die Leitung schlecht wäre.

Ausdrücklich kein Bandbreitentest. Der kostet Daten, ist beim Vorliegen
veraltet und misst die Vergangenheit. Der Pufferverlauf ist die bessere
Messung, weil er die einzige Größe misst, auf die es ankommt: lädt es
schneller, als es läuft.

Gehandelt wird über `preferredPeakBitRate` — der billige Hebel, der ohne
Neuaufbau wirkt. Gezielt wird dabei auf eine Sprosse, **die es in
`AVAsset.variants` wirklich gibt**: ein Wert zwischen zweien wählt
dieselbe wie die untere und machte jede Anzeige darüber zur Lüge.
Absichtlich unsymmetrisch — runter sofort, hinauf frühestens nach einer
Minute Ruhe.

Und getrennt von `quality`: die Automatik ruft **nicht** `select(quality:)`.
Die Methode schreibt `rememberedQuality` fort und setzte eine manuelle
Stufe — womit die Automatik sich selbst abschaltete und eine schlechte
Zugfahrt dauerhaft das Wohnzimmer verschlechterte.

Hat die Playlist nur eine Stufe, greift der Hebel ins Leere. Das ist kein
Fehler, sondern der Fall, für den die Sicherheitsleine gebaut wird — bis
dahin steht er wenigstens im Protokoll.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-27 15:40:48 +02:00
ScarriffleandClaude Opus 5 cdff53fa0d Eine Leiter bestellen statt einer einzelnen Stufe
Der Kern des Ruckelproblems: die App bestellte bei beiden Servern eine
HLS-Liste mit genau einer Variante. AVPlayer kann nur zwischen Stufen
wechseln, die in der Liste stehen — bei einer gibt es nichts zu wechseln,
und wenn die Leitung einbricht, kann nichts nachgeben.

**Jellyfin.** `enableAdaptiveBitrateStreaming` wird gesetzt, und zwar
ersetzend: die Voreinstellung im Server ist `false`, und bei zwei
gleichnamigen Werten gilt beim Server der erste — dieselbe Falle wie bei
`maxHeight` eine Funktion weiter oben. Dazu wird `main.m3u8` zu
`master.m3u8`, falls der Server die Schnipselliste nennt; nur auf der
Stufenliste kann der Parameter wirken.

Am Geräteprofil ändert sich **nichts**. Mehrere `TranscodingProfiles`
wären der naheliegende Fehlgriff — die beschreiben Formate, nicht
Bitraten. `MaxStreamingBitrate` bleibt die oberste Sprosse; was darunter
liegt, erfindet der Server.

**Plex.** `autoAdjustQuality=1`, als Eigenschaft der Anfrage statt als
feste Zeile: `queryItems` speist auch die Adresse zum Herunterladen, und
dort ergäbe eine Leiter keinen Sinn. Bewusst **nicht** am Entscheider —
der antwortet auf Unbekanntes mit nackten 400ern, wofür es hier eigens
`transcoderBisect` gibt. Er beantwortet ohnehin nur „darf das gespielt
werden". Stünde der Wert dort und nähme der Server ihn übel, wäre nicht
die Leiter kaputt, sondern die Wiedergabe.

Beides nur bei „automatisch". Wer im Blatt ausdrücklich „720p · 3 Mbps"
antippt, bekommt das und nicht „höchstens 3, meistens weniger" unter
unverändertem Etikett.

**Was das nicht kann, und das gehört dazugesagt:** Jellyfin verweigert
die Leiter im lokalen Netz grundsätzlich — „Within the local network this
will likely do more harm than good." Ein schwaches WLAN zählt dort mit,
und vom Client aus ist das nicht zu überstimmen. Ebenso fällt sie weg,
wenn Bild oder Ton nur kopiert statt gerechnet werden. Ob am Ende
wirklich mehrere Stufen ankommen, steht deshalb nicht im Code, sondern in
`PlaybackStatistics.variantCount`.

Elf neue Tests, darunter der doppelte Parameter, die andere
Schreibweise, und dass die Sitzungskennung beim Umbau dieselbe bleibt —
sonst bliebe beim Aufräumen ein Transcoder stehen.

Die Probe zeigt jetzt außerdem, ob kopiert statt gerechnet wird. Das ist
die zweite Hürde, und sie ist von zuhause aus nicht sichtbar.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-27 15:26:57 +02:00
ScarriffleandClaude Opus 5 2d3c4adbba Die Probe räumt auf und sagt, woran es liegt
Sie hat am Server eine Transcode-Sitzung angelegt und nicht wieder
beendet — ein Messwerkzeug, das Arbeit hinterlässt, ist ein schlechtes.
Jetzt läuft `endPlayback` am Ende.

Und sie erklärt das Ergebnis, statt es nur hinzustellen. Kommt nur eine
Stufe zurück, nennt sie die Hürden aus Jellyfins eigenem Quelltext
(`DynamicHlsHelper.EnableAdaptiveBitrateStreaming`) — allen voran die
erste und überraschendste: im lokalen Netz liefert Jellyfin
**grundsätzlich** keine Leiter. Wörtlich dort: „Within the local network
this will likely do more harm than good." Ein schwaches WLAN ist für
Jellyfin auch nur lokales Netz.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-27 15:21:36 +02:00
ScarriffleandClaude Opus 5 ab597ee2f0 Stillstand ohne Absicht ist kein Pausieren
`timeControlStatus` meldet zwei verschiedene Dinge als `.paused`: das
Anhalten durch den Nutzer und den Fall, dass `play()` gerufen wurde, der
Player aber nicht losläuft — weil die Quelle nicht bereit ist oder weil
nichts ankommt. Die Engine hat beides als „pausiert" weitergegeben.

Sichtbar war das als Player mit Abspieldreieck und Scrubber auf 0:00,
während in Wahrheit auf Daten gewartet wurde. Wer dann tippte, drückte
auf Play bei etwas, das längst laufen sollte.

Die Engine führt jetzt mit, ob überhaupt jemand spielen wollte.
Stillstand mit Absicht ist Pause, Stillstand ohne Absicht ist Warten.

Dazu die Anzeigeregel, wie gewünscht: **der Kreisel gilt immer**, mit
oder ohne eingeblendete Bedienung. Er ist keine Verzierung der Knöpfe,
sondern die Auskunft „es kommt gleich weiter" — und die braucht man
gerade dann, wenn nichts eingeblendet ist und man vor einem stehenden
Bild sitzt. Das Play-Pause-Symbol erscheint dagegen nur mit der übrigen
Bedienung, und an seiner Stelle bleibt beim Laden eine Lücke, damit der
Kreisel hindurchscheint statt zwei Anzeigen übereinanderzulegen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-27 15:14:10 +02:00