2fcf6821c8c8013a28cc214b22d184526dee5459
30
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
2fcf6821c8 |
Plex ist raus — und nahm zwei stille Fehler mit
Vollständig entfernt: das Paket `PlexKit` samt 108 Tests, der Web-Authenticator, die Anmeldung über plex.tv, die Serverauswahl und alles, was daran hing. 43 Dateien gelöscht, 68 Stellen in 28 weiteren bereinigt. **Zwei Dinge hätten beim blossen Streichen still das Falsche getan.** `playbackUnavailableMessage` deckte im Else-Zweig zwei Lagen gleichzeitig ab — „es ist Plex" und „niemand ist angemeldet". Wer die Serverart naiv herausstreicht, bekommt als nicht angemeldeter Nutzer „Jellyfin-Wiedergabe steht aus" statt „Server nicht erreichbar". Die Unterscheidung hängt jetzt daran, ob überhaupt ein Server da ist. Die Schlüsselbund-Einträge `plex.account` und `plex.server` wurden allein über den Authenticator aus dem gelöschten Paket entfernt. Danach stünde `plex.account` in keiner Zeile dieses Projekts mehr: wer sich nie abgemeldet hat, trüge ein gültiges Token am fremden Konto bis in alle Ewigkeit mit sich, ohne dass irgendein Weg es noch löschen könnte. `purgePlexLeftovers()` räumt beide Token und fünf Voreinstellungen einmalig ab — und fasst nichts an, wo nichts liegt, damit kein Jellyfin-Nutzer dafür seinen Mediathekenspeicher verliert. **Was Nutzer sehen.** Die Berechtigungsfrage fürs lokale Netz sagte wörtlich, die App brauche es, „um deinen Plex-Server zu finden" — das stand so im Systemdialog. Der Anmeldebildschirm hatte zwei Knöpfe, und der hervorgehobene war der für Plex; der verbliebene erbt die Hervorhebung, denn ein Knopf, der aussieht wie die zweite Wahl, lässt nach der ersten suchen. 14 verwaiste Textbausteine sind weg, vier umformuliert. Darunter einer, der schlicht nicht mehr stimmte: `signIn.privacy` versprach, das Passwort erreiche diese App nie. Beim Jellyfin-Rückfall auf das Passwort tut es das sehr wohl. Die Zusage, die weiterhin gilt, ist die über fremde Dienste — und nur die steht jetzt da. **Was ausdrücklich bleibt:** `MediaBackend`. Diese Abstraktion hat zwei Server gleichzeitig getragen, und der Steckplatz, den Plex räumt, ist genau der, auf den Halo kommt. `ServerKind` fällt dagegen weg — mit einem Fall ist es ein Bool, das `backend != nil` schon beantwortet. Nebenbei zog die README nach. Sie beschrieb noch einen „nativen Plex-Client" mit zwei Playern und VLCKit, beides seit Längerem nicht mehr wahr. 368 Tests grün (von 476 minus die 108 aus PlexKitTests, kein anderer). iOS und tvOS bauen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
a432592271 |
Beim Umpacken fällt die Spurwahl an — also wird sie dort getroffen
Dein Befund an „Notruf Hafenkante": keine Spuren, nichts auswählbar. Die
technischen Angaben derselben Folge sagen, warum:
Container MKV
Tonspur AAC - Stereo - Standard
Tonspur Deutsch - German - AAC - Stereo - Standard
Untertitel German - Standard - SUBRIP
Die Quelle ist Matroska. AVFoundation öffnet das nicht, also lief der
Download bisher über die zweite Engine — und die meldete die Spurliste leer.
Offline wählen ging damit nie.
**Umpacken allein hätte es nicht gelöst.** Jellyfin bildet in die neue Hülle
genau **eine** Tonspur ab und übernimmt den Untertitel nur, wenn man ihn
nennt. Ohne Angabe nimmt der Server seine eigene Vorgabe — und das ist bei
deutschen Aufnahmen regelmäßig die Spur mit Audiodeskription.
Also fällt die Wahl jetzt beim Herunterladen, mit derselben Abwägung wie beim
Abspielen (`AudioTrackChooser`, der für genau diesen Fall gebaut wurde: die
als deutsch ausgezeichnete Spur ist die mit Audiodeskription). Der Untertitel
geht als `subtitleMethod=Embed` mit — als **Spur**, nicht ins Bild gebrannt,
sonst liesse er sich nicht abschalten.
Die Mediathek bleibt bei der Abwägung aussen vor: sie nachzuschlagen kostet
eine Anfrage je Folge, und Serie und allgemeine Einstellung tragen den Wunsch
ohnehin.
Der Offline-Abzug behauptet nicht mehr, alle Sprachen seien dabei — beim
Umpacken bleibt je eine übrig, und alle zu notieren wäre ein Versprechen, das
die Datei nicht hält.
**Nebenbei richtiggestellt:** die technischen Angaben rechneten mit
`.directPlayOnly` und zeigten deshalb „Direkt (Systemplayer)" direkt über
„iOS öffnet den Container MKV nicht" — zwei Angaben, die sich widersprechen.
Das war früher richtig (ohne Serverhilfe übernahm die zweite Engine und hiess
auch so); jetzt wird mit Serverhilfe gerechnet.
NOCH NICHT AN EINEM ECHTEN SERVER GEPRÜFT.
iOS und tvOS bauen, 494 Tests grün.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
ef2596e2bb |
Was heruntergeladen wird, muss der Systemplayer öffnen können
**Vorbereitung dafür, dass VLC verschwindet.** Online ist der Fall längst
gelöst: steht nur die Schachtel im Weg, packt der Server um — dieselben
Ströme, eine MP4-Hülle. Offline gibt es diesen Ausweg nicht. Was einmal als
Matroska auf dem Gerät liegt, kann hinterher niemand mehr umpacken, und ohne
zweite Engine bliebe die Datei schwarz.
Also entscheidet sich das beim Herunterladen. Fragt jemand das Original an und
AVFoundation könnte die Datei nicht öffnen, wird `remuxDownloadURL` benutzt:
`/Videos/{id}/stream.mp4` mit `allowVideoStreamCopy` und
`allowAudioStreamCopy`. Der Server reicht durch, was passt, und rechnet nur
um, was nicht passt — das Bild bleibt das Original, nur DTS oder TrueHD
werden zu AC3 oder AAC.
Drei Dinge hängen daran und sind mitgezogen:
- Die **Größe** ist dann keine Zusage mehr: dieselben Ströme in einer anderen
Hülle wiegen anders. `expectedIsExact` fällt auf falsch, und es gilt wieder
die weiche Prüfung.
- Der **Container** im Offline-Abzug ist „mp4", nicht die alte Schachtel.
Sonst entschiede der Player anhand einer Datei, die es so nicht mehr gibt.
- Der **Tonkodierer** bleibt leer. Ob aus DTS dann AAC oder AC3 wird, sagt der
Server vorher nicht; eine Behauptung wäre schlechter als die Lücke, und die
Spuren liest der Player ohnehin aus der Datei.
**Die Frage „kann das der Systemplayer?" steht jetzt an einer Stelle.**
`DeviceCapabilities.opens(container:videoCodec:audioCodec:)` — dieselbe Liste,
nach der auch beim Abspielen entschieden wird. Was der Server nicht nennt,
gilt als passend: vorsichtshalber umzupacken hiesse, aus einer tadellosen MP4
eine neu gerechnete Datei zu machen. Fünf Tests.
NOCH NICHT AN EINEM ECHTEN SERVER GEPRÜFT: ob Jellyfin das Kopieren hier
tatsächlich wählt, verrät erst die Dateigröße des ersten Downloads.
140 Tests in FlixyPlayback, iOS und tvOS bauen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
19c9dc843c |
„Aus Weiterschauen entfernen" tut jetzt etwas — und man kann es sehen
Der Knopf war wirkungslos, und zwar aus zwei Gründen, die beide auf
dasselbe hinauslaufen: er war nie geprüft worden.
**Der Weg war falsch.** Die Methode las erst ein Benutzerdatenblatt über
`GET /Users/{id}/Items/{id}/UserData`, änderte es und schrieb es zurück.
Diese Stelle war die **einzige im ganzen Projekt**, die ein solches Blatt
eigens liest — überall sonst kommt es am Titel mit. Es gab also kein
zweites Beispiel, an dem der Weg je funktioniert hätte, und ein 404
beendete die Sache, bevor überhaupt etwas geschrieben wurde.
Jetzt wird das Blatt **gebaut**, nicht erfragt: „gesehen", „Favorit" und
die Zählung stehen am Titel, den der Aufrufer ohnehin in der Hand hat —
deshalb nimmt `removeFromContinueWatching` jetzt den Titel statt nur
seiner Kennung. Gelesen wird trotzdem noch, aber unverbindlich: gelingt
es, bleiben auch Felder erhalten, die diese App gar nicht kennt. Und
geschrieben wird über beide Wege — erst der gebündelte
`/UserItems/{id}/UserData`, bei einem 404 der ältere. Welcher gilt, hängt
an der Serverfassung.
**Und man konnte es nicht sehen.** Die Attrappe kannte die Route nicht;
ihr Auffangzweig antwortete auf jeden Schreibversuch mit Status 200, und
die Weiterschauen-Reihe war fest verdrahtet. Der Aufruf gelang also immer
und änderte nie etwas — genau das Bild eines kaputten Knopfes, nur aus
einem anderen Grund. Genau deshalb ist der Fehler durchgerutscht.
Sie führt jetzt echte Benutzerdatenblätter, beantwortet beide Wege und
liefert unter „Weiterschauen" nur noch, was eine gemerkte Stelle grösser
null hat. Unter `/__userdata` steht, was geschrieben wurde und wohin.
Gemessen im Simulator, nach einem Tipp auf den Eintrag:
/UserItems/m2/UserData
{"Played":false,"IsFavorite":false,"PlaybackPositionTicks":0,…}
Danach ist „Weiterschauen" leer und die Reihe verschwindet von der
Startseite.
**Zwei Dinge, die danach trotzdem auffallen können**, und beide sind
keine Fehler: Jellyfins „Als Nächstes" führt angefangene Folgen mit, eine
entfernte Folge kann dort wieder auftauchen. Und bei zusammengelegten
Reihen stehen auch Folgen ohne gemerkte Stelle — dort gibt es nichts zu
entfernen, und der Eintrag fehlt.
Plex bleibt unberührt: der Weg dort war in Ordnung und bekommt nur die
neue Form.
iOS und tvOS bauen, 469 Tests grün.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
ac62e8bf19 |
Der Aufmacher war weg, weil die Mediathek unterwegs verlorenging
Zwei Schalter, die im selben Bildschirm untereinander stehen, ergaben zusammen gar keinen Aufmacher mehr: „Gesehenes aus Neuzugängen ausblenden" und eine angehakte Mediathek. Der Grund steht in `HomeFeed.prepared`. Der Zweig für das Ausblenden baut die betroffenen Reihen neu — und baute sie **ohne `libraryID`** neu, weil der neue Parameter dort nicht mitgegeben war. Getroffen hat es dabei ausgerechnet die Reihen `latest.<mediathek>`, die als einzige überhaupt eine tragen. Danach warf der Filter des Aufmachers jede Reihe ohne Mediathek weg, und übrig blieb nichts. Dasselbe an zweiter Stelle: `combiningContinueRows` verlor `libraryID` **und** `moreKey` — Letzteres ein eigener, älterer Fehler, durch den bei zusammengelegten Weiterschauen-Reihen „Alle anzeigen" wegfiel. **Dazu eine Regel, die ich zurücknehme.** Gestern stand hier: trifft die Auswahl nichts, bleibt nichts. Das war als Ehrlichkeit gedacht und ist in der Praxis der schlechtere Fehler — ein Aufmacher, der lautlos verschwindet, wird überall gesucht, nur nicht in einer Einstellung zwei Bildschirme weiter. Trifft die Auswahl keine einzige Reihe, gilt sie jetzt als wirkungslos. Das deckt auch den Fall ab, dass eine gespeicherte Kennung nach einem Serverwechsel ins Leere zeigt. Und damit solche Kennungen nicht unsichtbar weiterwirken, werden sie beim Öffnen des Bildschirms gegen die vorhandenen Mediatheken abgeglichen — vorher standen sie in keiner Zeile und liessen sich nicht abwählen. Geprüft im Simulator: „Gesehenes ausblenden" an, „Filme" angehakt — der Aufmacher ist da und zeigt einen Film. Vorher: nichts. iOS und tvOS bauen, 469 Tests grün. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
3a6a6c4242 |
Intro und Abspann bei Jellyfin, und drei Dinge aus dem Gebrauch
**Intro überspringen gab es für Jellyfin nie.** Der Knopf, die Marken,
das automatische Überspringen, die Einstellungen dazu — alles stand seit
Langem im Player und auf dem Fernseher. Nur kam bei Jellyfin nie eine
Marke an: `JellyfinItem+Media.swift` setzte `markers: []`, weil Jellyfin
sie nicht am Titel liefert.
Seit 10.10 gibt es `/MediaSegments/{itemId}`, gefüllt von einem Anbieter
im Server wie dem Intro-Skipper. Die Anfrage läuft **neben** dem Abruf
des Titels, nicht danach — sonst wartete die Detailseite zweimal.
Zwei Dinge, die sonst eine Stunde kosten: `includeSegmentTypes` muss
**zweimal** stehen, nicht einmal mit Komma (mit Komma antwortet Jellyfin
mit null Treffern), und die Abfrage wirft nicht. Ein Server ohne Anbieter
antwortet leer, einer vor 10.10 mit einem 404 — und ein Film ohne Intro
ist kein Fehler, sondern der Normalfall.
Geprüft im Simulator: die Attrappe meldet jetzt Segmente, und im Player
steht „Intro überspringen".
---
**„Aus Weiterschauen entfernen".** Eine halbe Stunde gesehen und keine
Lust mehr — der Titel ist weder gesehen noch ungesehen, er soll nur nicht
ewig oben stehen. Beide Server können es, auf verschiedenen Wegen:
- **Plex** hat einen eigenen: `PUT /actions/removeFromContinueWatching`.
**Nicht** `/:/unscrobble` — das setzt zugleich auf ungesehen.
- **Jellyfin** hat keinen. Was es gibt, ist das Benutzerdatenblatt; die
Liste führt, was eine gemerkte Stelle grösser null hat. Also wird das
Blatt gelesen, bekommt eine Null und geht so, wie es war, zurück.
**Erst lesen, dann schreiben**, weil dort auch „gesehen", „Favorit" und
die Bewertung stehen — wer seinen Favoriten verliert, weil er einen
Film aus einer Liste genommen hat, sucht den Grund nie dort.
Der Eintrag erscheint nur, wenn es eine gemerkte Stelle gibt.
---
**Die Mediatheken für den Aufmacher taten zu wenig.** Die Regel „Reihen
ohne Mediathek bleiben immer drin" war freundlich gemeint und falsch: bei
„nur Filme" stand weiter eine Serie oben, weil sie aus „Weiterschauen"
kam. Gemessen und nicht vermutet — es lag nicht am Zeitfenster der
Auswahl, wie zuerst angenommen.
Jetzt gilt: ist etwas gewählt, speist sich der Aufmacher **nur** daraus.
Wer Weiterschauen oben haben will, wählt keine Mediathek. Der Hinweis
unter der Auswahl sagt das auch.
---
**Der „Bearbeiten"-Knopf stand über allem und tat dort nichts.** Er gilt
nur für die Reihen; jetzt steht er an deren Überschrift.
---
Die Attrappe kann drei Dinge mehr, sonst wäre nichts davon prüfbar:
Segmente, `parentId` bei den Neuzugängen (vorher stand in jeder Mediathek
dasselbe) und damit überhaupt eine Mediathekenauswahl, die man ansehen
kann.
iOS und tvOS bauen, 468 Tests grün.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
dc93d6e22e |
Der Aufmacher lässt sich auf Mediatheken einschränken
Mehrfachauswahl in den Einstellungen unter „Startseite": welche Mediatheken den Aufmacher speisen dürfen. Haken statt Auswahlliste — eine Liste könnte nur eine, und „keine" wäre dort ein Eintrag, den man versehentlich trifft. Dafür musste eine Reihe sagen können, woher sie kommt: `MediaRowGroup` trägt jetzt eine `libraryID`. Jellyfin setzt sie an den „Neu in …"-Reihen — die baut die App ohnehin je Mediathek. Zwei Regeln, und beide sind Entscheidungen, keine Nebenwirkungen: **Leer heisst alle.** Wer nichts ankreuzt, meint „mir ist es gleich", nicht „nimm nichts". Ein leerer Aufmacher wäre der Fehler, den niemand mit dieser Einstellung in Verbindung brächte. **Reihen ohne Mediathek bleiben immer drin.** Weiterschauen, Als Nächstes und Favoriten gehen quer durch alle, und aus welcher Mediathek eine angefangene Folge stammt, sagt der Server an keiner Stelle. Sie wegzuwerfen hiesse, jemandem das Angefangene zu nehmen, weil er „Musikvideos" abgewählt hat. Der Hinweis unter der Auswahl sagt das auch dem, der nicht in den Quelltext sieht. Eingeschränkt wird **nur der Aufmacher**, nicht die Startseite: die Reihe selbst bleibt stehen. Wer sie ganz loswerden will, blendet sie aus — das ist eine andere Frage und hat ihren eigenen Schalter. Fünf Tests an `HeroSelection.rows(_:limitedTo:)` halten die Regeln fest, darunter der Fall einer gespeicherten Kennung, die es nicht mehr gibt. **Plex bleibt aussen vor:** dort kommen die Reihen als fertige Hubs vom Server, und `librarySectionID` wird in `PlexHub` nicht gelesen. Die Auswahl schränkt dort also nichts ein. Nachzuholen, wenn Plex wieder dran ist. iOS und tvOS bauen, 468 Tests grün. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
e801502c7a |
Ein Regal über der App — und ein Weg aus dem Player heraus
**Das Regal.** Wer im tvOS-Menü über der App steht, sah bisher das Icon noch einmal gross. Jetzt stehen dort „Weiterschauen", „Als Nächstes" und die Neuzugänge als Plakate, mit Fortschrittsbalken; ein Druck spielt ab, ein Wisch nach unten öffnet die Detailseite. **Warum es dafür eine App-Gruppe braucht.** Das Regal zeichnet eine eigene Erweiterung, also ein anderer Prozess mit eigener Sandbox — und tvOS ruft sie auf, während die App **nicht** läuft, mit weniger als einer Sekunde Zeit. Kein Netzabruf, keine Anmeldung, kein Blättern. Also legt die App beim Laden der Startseite hin, was das Regal braucht, und sonst nichts: Titel, Bildadresse, Kennung, Fortschritt. Eine Datei, ein `JSONDecoder`, fertig. Die Bildadressen tragen den Zugangsschlüssel, weil Jellyfin ihn für Bilder verlangt und die Erweiterung ihn sonst nirgends herbekäme. Die Datei liegt in der App-Gruppe — erreichbar für die App und ihre Erweiterungen, für sonst niemanden. Der Schlüsselbund bleibt, wo er ist. **Und der Fehler, den du gefunden hast.** Solange der Stream noch ausgehandelt wurde, gab es keinen Weg zurück: schwarzes Bild, Kreisel, und der Menü-Knopf tat nichts. `AVPlayerViewController` fängt ihn nämlich selbst ab — aber erst, wenn er einen Player mit einem Item hat, und genau den gibt es in dem Moment noch nicht. Der Griff liegt jetzt eine Ebene darüber, wo es ihn vom ersten Bild an gibt. Doppelt ist er nicht: greift Apples Player zu, kommt der Befehl dort gar nicht mehr an. **Was am Gerät noch offen ist.** Die App-Gruppe muss im Entwicklerkonto eingetragen sein, sonst bleibt das Regal leer — im Simulator legt das System sie von selbst an, auf einem echten Apple TV nicht. Geprüft ist, dass die App die Datei schreibt (drei Reihen, richtige Adressen) und dass tvOS das Schema kennt. Dass das Regal beim Darüberstehen auch zeichnet, lässt sich ohne Fernbedienung nicht auslösen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
87f23afe90 |
Die Startseite am Fernseher — und die Ladeschicht nur einmal
Aufmacher, Reihen, Mediatheken, Raster. Der sichtbare Teil ist neu; der Teil darunter ist derselbe wie am Telefon, und das war die eigentliche Arbeit. **Herausgelöst statt kopiert.** `HomeFeed` und `LibraryFeed` tragen jetzt, was vorher in `HomeView` und `LibraryView` stand: laden, ordnen, den Aufmacher auslosen, den Zwischenspeicher abziehen, entdoppeln, nach einem Fehlschlag sperren. Das zu kopieren wäre der schlimmste Ausgang dieses Vorhabens gewesen — an mindestens drei dieser Stellen klebte schon je ein Fehler, der sich als „es sind nicht alle Filme drin" äusserte und sonst nichts. Einer davon, das Los des Aufmachers an der Position statt am Titel, wäre in einer Kopie ein zweites Mal zu finden gewesen. Mitgewandert sind `rowTitle`, `rowArtworkPath`, `posterArtworkPath`, `runtimeLabel` und `HeroArtwork`: reine Ableitungen, die in einer Ansichtsdatei lagen und deshalb nur das Telefon übersetzte. **Nachgeladen wird am Fokus, nicht am Erscheinen.** Die alte Regel hiess „die letzten fünf" und hing daran, dass eine Kachel sichtbar wird. Beim ersten Zeichnen erscheinen aber alle sechzig, auch die letzten fünf — das Nachladen lief sofort los statt beim Blättern. Auf einem Raster aus sechs Spalten sind fünf Einträge ausserdem nicht einmal eine Zeile. `PagingWindow` rechnet deshalb in Zeilen; sechs Tests halten es fest, darunter der Fall, der die alte Regel entlarvt. **Der Fokus setzt den Aufmacher.** Wer durch „Zuletzt hinzugefügt" wandert, sieht oben, was er gerade anschaut. Kein Wechsel im Sieben- sekundentakt wie am Telefon: vor einem Fernseher wäre ein Bild, das sich von allein austauscht, während man es liest, eine Zumutung. **Drei Dinge, die erst der Simulator zeigte.** Die Seiten müssen bildfüllend laufen, weil ein Aufmacher, der achtzig Punkte vor der Bildschirmkante aufhört, nach Fehler aussieht — also trägt `screenMargin` auf tvOS den Überscan mit. Die Karten brauchen einen Fokus-Rückruf, weil `\.isFocused` den nächsten fokussierbaren Vorfahren meint und das in einer Reihe aus Karten nicht verlässlich die Karte ist. Und der Bau scheiterte an „Unable to resolve module dependency: VLCKit" — der Abhängigkeits- Sucher prüft `canImport(FlixyVLC)` auch für ein Ziel, das es nie verlinkt. Ein `&& !os(tvOS)` genügt; das Paket selbst bleibt unverändert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
9c66727aca |
Mediatheken ohne bekannte Art verschwanden wortlos
`isSupported` war `movie || show`. Alles Übrige fiel heraus — auch eine Mediathek mit gemischtem Inhalt, die Jellyfin ohne `CollectionType` meldet. Wer Filme und Serien nicht getrennt hat, sah sie nirgends: nicht in der Seitenleiste, nicht auf der Startseite, und nirgends eine Meldung. Eine unbekannte Art ist kein Grund, etwas zu verschweigen. Musik, Fotos und Live-TV bleiben draußen, und das aus jeweils eigenem Grund — jetzt aufgezählt statt stillschweigend. Die Attrappe blättert außerdem richtig: vorher kam immer alles auf einmal und `TotalRecordCount` war die Länge der Antwort, womit eine App, die richtig blättert, genauso aussah wie eine, die es gar nicht versucht. Damit ist belegt, dass die Mediathek alle 252 Titel holt — in fünf Seiten, bis StartIndex 240. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
24765a5afc |
Live-TV bekommt einen neutralen Vertrag
Zwei Protokolle statt eines: LiveTVBackend für Sender, Programm und Einschalten, LiveTVRecordingBackend für Aufnahmen. Getrennt, weil Plex das eine kann und das andere nicht — ein as? sagt die Wahrheit, eine Methode, die bei Plex nur wirft, verspricht etwas und bricht es dann. Plex zieht um, ohne umgebaut zu werden. Der Suchlauf, der die Senderliste findet, bleibt Zeile für Zeile, wo er ist; davor liegt jetzt ein Actor, der sich Empfänger und Sender merkt. Nötig, weil PlexBackend ein struct ist und tune() den echten PlexChannel braucht — ohne Gedächtnis liefe der Suchlauf bei jedem Antippen neu. Jellyfin ist neu und deutlich kürzer, weil es dort nichts zu suchen gibt. Die Endpunkte stammen aus der OpenAPI-Spec des Servers. Beim Vormerken wird die Vorlage des Servers roh weitergereicht statt entschlüsselt und neu zusammengesetzt: welche Felder Pflicht sind, sagt die Beschreibung nicht, und ein verlorenes fiele erst an einer Aufnahme auf, die nicht startet. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
effdcd4532 |
MediaBackend: der Vertrag, den Plex und Jellyfin beide erfüllen
Ein Protokoll für das, was jeder Server können muss — blättern, Bilder liefern, Zustand melden. Bewusst schmal: alles, was nur einer kann, steht nicht darin, sondern als abfragbare Fähigkeit. Ein gemeinsamer Nenner, der alles umfasst, wäre keiner mehr. Deshalb `BackendCapability`. Plex kennt keine Favoriten, Jellyfin keine Plex-Empfänger — und eine Oberfläche, die Knöpfe für Dinge zeigt, die der Server nicht kann, ist schlimmer als eine, die sie weglässt. Plex meldet hier ausdrücklich keine Favoriten und wirft, wenn jemand es trotzdem versucht; stillschweigend nichts zu tun wäre die schlechtere Antwort. `MediaQuery` ersetzt `LibraryQuery` nach oben hin. Deren Sortierwerte waren Plex' Drahtformat („titleSort:asc"), was hieß: entweder lernt Jellyfin Plex' Schreibweise, oder jede Ansicht muss wissen, mit wem sie spricht. `PlexBackend` ist eine Hülle, keine Umschreibung — die Endpunkte bleiben, wo sie sind. Der Fehlerübersetzer nimmt den ungetypten Wurf, weil Swift getippte Fehler durch eine Closure hindurch nicht ableiten kann; der zweite `catch` ist dabei keine Zierde, sondern verhindert, dass ein unerwarteter Fehler durchfällt und die Signatur zur Lüge macht. Die App benutzt noch nichts davon. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
ba6be4ac79 |
MediaKit: die neutralen Datensätze und die Übersetzung aus Plex
`MediaItem` und die Typen darunter — die Form, in der die Oberfläche Titel sieht, unabhängig davon, welcher Server sie geliefert hat. Die Namen sind dort geblieben, wo sie ohnehin neutral waren: `title`, `summary`, `year`, `index`. Umbenannt wurde, was Plex-Jargon ist. `ratingKey` heißt `id`, `viewOffset` heißt `resumeMilliseconds`, und aus dem „Großelternteil" ist die Serie geworden, die von Anfang an gemeint war — ein Name, den man in jeder Ansicht im Kopf übersetzen musste. Zwei Dinge, die dabei ehrlicher werden: `isFavourite` gibt es, aber Plex meldet immer `false`. Ein Feld, das ein Server nicht füllt, ist besser als eine Fähigkeit, die es nur auf einem gibt und die sich nirgends setzen ließe. Dateigrößen sind jetzt `Int64`. Ein Remux überschreitet vier Gigabyte mühelos. Die Übersetzung steht in **einer** Datei und geht in **eine** Richtung. Die Drahtmodelle bleiben, wo sie sind — sie tragen Eigenheiten, die nur Plex hat, und die gehören nicht nach oben. Geprüft wird gegen echte Server-Antworten, nicht gegen erfundene Werte: genau dort stehen die Eigenheiten, an denen eine Übersetzung scheitert. Ein Test vergleicht außerdem beide Wege zum Wiedergabe-Profil — sagten sie Unterschiedliches, spielte ein Titel nach dem Umbau plötzlich über einen anderen Player. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
5b2b86b66b |
MediaKit: die Grundschicht, auf der Plex und Jellyfin dasselbe meinen
Erster Schritt zu Jellyfin, und er besteht vor allem aus Wegnehmen. `PlaybackDecider` nahm bisher Plex-Modelle entgegen und las daraus sechs Felder. Das band die Entscheidung an einen bestimmten Server, obwohl an der Frage „kann iOS das öffnen?" nichts serverspezifisch ist. Jetzt nimmt er ein `MediaProfile` — Container, Codecs, Bitrate, ob die Datei untersucht ist. Wie ernst die Kopplung war, zeigte der Player selbst: für die Offline-Wiedergabe baute er sich **erfundene** Plex-Werte zusammen — eine Media mit Kennung 0, ein Part mit dem Dateinamen als Schlüssel, zusammengesetzte Spurlisten —, nur damit die Signatur passte. Für eine Datei, die längst auf dem Gerät lag und mit Plex nichts mehr zu tun hatte. Dieser Hack ist weg. Neu ist das Target `MediaKit`: Typen und Rechnungen, die für jeden Server gelten, ohne eine einzige Abhängigkeit. Darin `MediaProfile` und die `Duration`-Brücke, die bisher in PlexKit stand — „weil es die unterste Schicht ist". Das stimmte, solange es nur einen Server gab. Damit kennen Wiedergabe und Downloads Plex nicht mehr. Zwei der Abhängigkeiten waren ohnehin tot; die dritte trug nur diese Brücke. Die wichtigste Testtabelle des Projekts ist mitgewandert und dabei kürzer geworden: dort steht jetzt, worauf es ankommt, statt vollständiger Modelle mit Kennungen und Dateipfaden, von denen niemand etwas las. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |