Der zweite Server. Eigenes Target neben PlexKit, nicht darin — die beiden
teilen die neutrale Schicht und sonst nichts.
Die Anmeldung geht zuerst über **Quick Connect**: die App zeigt einen
sechsstelligen Code, den man in einem bereits angemeldeten Client
bestätigt. Damit gilt hier dieselbe Zusage wie bei Plex — das Passwort
erreicht Flixy nie. Benutzername und Passwort bleiben als Rückfall, falls
der Server Quick Connect abgeschaltet hat; dann hält die App das Passwort
für die Dauer einer Anfrage und speichert es nirgends.
Zwei Stellen, an denen sich eine Übersetzung verrechnet, und die deshalb
Tests haben:
Jellyfin zählt Zeiten in Ticks zu hundert Nanosekunden. Eine Null zu viel,
und aus 44 Minuten werden siebeneinhalb Stunden.
Und es meldet die **ungesehenen** Folgen, nicht die gesehenen. Ungeprüft
übernommen stünde bei einer fast fertigen Serie „9 von 24" statt „15 von
24".
Dass die Adressen den Token tragen, steht hier von Anfang an — bei Plex
hat genau das tagelang gekostet, weil AVPlayer keine Kopfzeilen mitschickt.
Zwei Fähigkeiten sind ausdrücklich nicht da: Sterne-Bewertungen kennt
Jellyfin nicht, nur Daumen hoch und runter. Und die Adresse einer
verkleinerten Fassung gibt es erst nach einer Anfrage — sie zu erfinden
hieße, eine Adresse anzubieten, die nicht antwortet.
Die Startseite ist nachsichtig gebaut: fällt „Als Nächstes" aus, bleibt
deshalb nicht die ganze Seite leer.
Die App bietet Jellyfin noch nicht an — das ist der nächste Schritt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Start, Suche, Mediathek, Detailseiten und Downloads arbeiten jetzt mit
`MediaItem` und dem `MediaBackend`-Vertrag. Damit sind sie fertig für
einen zweiten Server, ohne dass an ihnen noch etwas zu tun wäre.
Dabei sind ein paar Namen ehrlicher geworden: aus `grandparentTitle`
wurde `seriesTitle`, aus `viewedLeafCount` `playedEpisodeCount`. Und
`TagStrip` nahm bisher Plex-Marken mit Kennung und Rolle entgegen, um
davon nur den Namen anzuzeigen — Genres sind jetzt schlicht Zeichenketten.
`Arrangement` ist aus PlexKit heraus und generisch: dieselbe Rechnung
gilt für jede Liste, die jemand selbst zusammenstellt. Phase 3 baut die
konfigurierbare Seitenleiste darauf, statt sie ein zweites Mal — und ein
zweites Mal falsch — zu schreiben.
Die Fehlerübersetzung gibt es nur noch einmal. Es waren zwei, und sie
waren bereits auseinandergelaufen: derselbe Fehler bekam je nach
Bildschirm eine andere Erklärung.
Das Nachladen der Mediathek bleibt unangetastet: eine lange Liste, die
fünf Einträge vor dem Ende nachlädt. `MediaPage` heißt zwar Seite, ist
aber eine technische Portion — Blättern mit „weiter zu Seite 2" gehört
nicht in eine Mediathek, in der man stöbert. Der Kommentar dort sagt das
jetzt ausdrücklich, damit es niemand später „aufräumt".
Player, Live-TV und die Download-Engine sprechen noch unmittelbar mit
Plex. Sie folgen als Nächstes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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>
`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>
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>
Der Server antwortet auf eine angeforderte Umrechnung mit „direkte
Wiedergabe nicht verfügbar; Umwandlung ok" — Code 1001. Meine Prüfung
ließ nur 1000 durch, also ausschließlich DirectPlay. Damit blockierte die
App ausgerechnet den Fall, den sie selbst angefordert hatte: wer im
Player eine Stufe wählt, will umgerechnet bekommen, und die einzige
richtige Antwort darauf kam als Fehler an. Es spielte gar nichts mehr.
1000 heißt „direkt abspielbar", 1001 „direkt nicht, umrechnen schon",
2000 „Umwandlung in Ordnung" — alles Zusagen. Abgelehnt wird erst ab
3000, etwa bei fehlender Berechtigung oder überlastetem Transcoder.
Vier Tests halten die Codes fest, damit die Grenze nicht wieder zur
Auslegungssache wird.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Der Decider rechnet nur um, wenn die Datei die Grenze überschreitet. Für
den Normalfall ist das richtig — für eine ausdrückliche Wahl im Player
nicht: wer für eine 8-Mbit-Datei „12 Mbit" wählt, bekam DirectPlay, weil
die Datei ja darunter liegt. Gemeint ist aber „rechne es mir auf diese
Stufe um", nicht „gib mir die Datei, falls sie so vorliegt".
Die Richtlinie kann eine Umrechnung jetzt erzwingen, und die Wahl im
Player tut das.
Dazu verschluckte `select(quality:)` den eigentlichen Grund: lehnte der
Server ab und sagte im Klartext warum, wurde diese Meldung danach durch
das allgemeine „keine abspielbare Datei" ersetzt — der eine brauchbare
Hinweis war damit weg. Jetzt bleibt stehen, was der Server gesagt hat.
Und das Menü war zu viel. Vorn steht die kurze Liste — automatisch,
Original, je eine brauchbare Stufe pro Auflösung, nur Ton. Wer eine
bestimmte Bitrate erzwingen muss, klappt „Alle Stufen" auf; im Alltag
will man „eine Stufe kleiner" und nicht die Wahl zwischen zehn und zwölf
Megabit.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Die vier erfundenen Stufen weichen der Leiter, die der Server selbst
meldet: 64, 96, 208, 320, 720, 1500, 2000, 3000, 4000, 8000, 10000,
12000, 20000 kbps mit den Auflösungen, die Plex dafür vorsieht. Eine
eigene Leiter hieße, Stufen anzubieten, die der Transcoder nicht kennt —
er rundet dann still auf die nächste, und die Anzeige log.
Angeboten wird nur, was zur Quelle passt. Eine 720p-Datei auf 1080p
hochzurechnen bringt kein Bild dazu, und die Stufen sähen alle gleich aus.
Oben im Blatt steht, was tatsächlich über die Leitung kommt — gemessene
Bitrate, Auflösung, verworfene Bilder. Der Unterschied zur gewählten
Stufe ist keine Haarspalterei: die eine sagt, was erlaubt ist, die andere,
was ankommt. Ruckelt es trotz hoher Stufe, steht die Antwort dort.
„Nur Ton" gibt es, aber ehrlich beschriftet: Plex kennt für Videos keinen
reinen Tonstrom, also wird die unterste Sprosse geholt und das Bild
ausgeblendet.
Dazu Doppeltipp links und rechts für zehn Sekunden zurück und vor. Die
Reihenfolge der Gesten ist entscheidend — das einfache Tippen verschluckt
sonst das doppelte.
Die Kachel hängt nicht mehr in vier Ecken fest, sondern klebt links oder
rechts und ist in der Höhe frei. Vier Ecken sind schnell gebaut, aber im
Weg: steht sie über einem Knopf, bleiben drei Orte, die ebenso stören.
Größer wird sie durch Zusammen- und Auseinanderziehen; der Griff ist weg,
er saß zwangsläufig auf dem Bild und stritt sich mit dem Verschieben.
Und die Begriffe: „Motor" für engine sagt kein deutscher Entwickler.
Fachwörter, die im Deutschen englisch bleiben, bleiben jetzt englisch.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Drei Sachen aus dem Feldtest.
Der Griff zum Größerziehen war wirkungslos, und zwar durch meinen Fehler:
die Drehung lag auf dem ganzen Überzug statt auf dem Symbol. Bei zwei der
vier Ecken kippte damit die gesamte Trefferfläche weg, und man griff ins
Leere. Jetzt sitzt der Griff über `overlay(alignment:)`, nur der Pfeil
dreht sich, und das Ziehen hat Vorrang vor dem Verschieben der Kachel —
sonst gewinnt das Verschieben, das am selben Ort hängt.
Senderlogos sind Schriftzüge und fast nie quadratisch. Formatfüllend in
einem Quadrat fehlten links und rechts genau die Teile, an denen man den
Sender erkennt. Sie bekommen jetzt eine breitere Fläche und werden
eingepasst statt beschnitten — `FlixyArtwork` kann beides, und welches
gilt, entscheidet der Aufrufer.
Und im Player steht bei Live-TV, was gerade läuft. Der Name reist mit dem
Einschalten mit, denn nachzuschlagen ist er nicht — ein Sender hat keine
Metadaten in der Mediathek. Beim Aufdecken der Bedienelemente wird er
nachgeholt, aber nur, wenn die alte Sendung wirklich abgelaufen ist:
sonst stünde derselbe Name den ganzen Abend, und das wäre derselbe
Fehler, der eben erst im Programm behoben wurde.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Der Fehler steckte in der Abfrage, und er ist grob:
beginsAt> = jetzt − 1 Std.
endsAt< = jetzt + 6 Std.
Beide Grenzen schließen genau das aus, was man sehen will. Ein
Zweistünder, der um halb vier begann, war um Viertel nach fünf
unsichtbar — und angezeigt wurde stattdessen die nächste Sendung, die
durch den Filter passte. Deshalb wich die Anzeige von Plex' eigener ab.
Jetzt wird nur der **Anfang** eingegrenzt, mit sechs Stunden Vorlauf für
lange Sendungen, und keine Grenze auf das Ende. Zurück kommt, was den
Zeitraum berührt, statt was vollständig hineinpasst.
Gefiltert wird danach nur das Programm, nicht die Senderliste: ein Sender
ohne frische Daten soll „kein Programm" sagen, statt zu verschwinden.
Dazu zwei Wünsche aus dem Feldtest:
Die Kachel lässt sich in der Größe ziehen. Der Griff sitzt in der Ecke,
die zur Bildschirmmitte zeigt — am äußeren Rand läge er halb außerhalb
des Daumens. Gespeichert wird die begrenzte Breite, nicht die gezogene,
sonst bliebe eine einmal zu weit gezogene Kachel für immer zu groß.
Und eine Senderauswahl. Über zwanzig Sender zu zeichnen und ihr Programm
zu holen kostet Zeit, und die meisten sieht man nie an. Eine leere
Auswahl heißt weiterhin „alle" — sonst stünde man beim ersten Öffnen vor
einer leeren Liste.
Die Zeiten im Raster-Abzug liegen jetzt in der Zukunft. Mit den echten
Zeitstempeln von damals wäre er ab dem Tag nach der Aufnahme leer
gewesen, sobald auf Frische gefiltert wird.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Wer im Fernsehen etwas sieht und nebenher nachsehen will, was sonst
läuft, musste bisher abbrechen. Plex' eigene Handy-Apps können das auch
nicht — kein Grund, es hier genauso zu machen.
Der eigentliche Umbau ist nicht die Kachel, sondern der Besitz. Die
Wiedergabe hing an der Sichtbarkeit der Ansicht, und zwar dreifach: das
Modell war `@State` im Player, `onDisappear` hat den Motor abgeräumt, und
die Bildebene entstand in `makeUIView` neu. Zurückgehen hieß deshalb:
Motor tot, Transcoder aus, beim Live-TV der Tuner freigegeben.
Jetzt gehört das Modell der Wurzelansicht, die immer da ist. Verkleinern
und Vergrößern wechseln nur noch die Darstellung; beendet wird die
Wiedergabe an genau einer Stelle, und zwar weil jemand es will.
Die Bildebene hält das Modell selbst — dasselbe Muster, das VLC seit
jeher verwendet. Dieselbe `UIView` wandert zwischen Kachel und Vollbild,
statt neu zu entstehen; sonst setzte das Bild bei jedem Wechsel neu an.
Bild-in-Bild wandert mit und bekommt endlich einen Delegaten: bisher war
es eine Einbahnstraße, aus der man nicht in die App zurückkam.
Die Kachel schwebt und lässt sich in jede Ecke ziehen, statt als Leiste
unten zu kleben — eine Leiste nähme jeder Liste Platz weg und müsste in
jedem Bildschirm bedacht werden. Die Eckenberechnung steht in
MiniPlayerPlacement und ist geprüft: in einer View wäre eine Kachel, die
halb aus dem Bild ragt, erst auf dem Gerät aufgefallen.
Der Pfeil oben verkleinert, ein neuer Knopf daneben beendet wirklich.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Der Unterschied zwischen 400 und 200 war ein einziger Parameter:
…&protocol=hls&X-Plex-Token=… → 400
…&protocol=hls&X-Plex-Client-Profile-Name=Chrome&… → 200
Flixy hat immer nur `X-Plex-Client-Profile-Extra` geschickt. Das
**erweitert** ein Profil, es ersetzt keins — ohne Basis gibt es nichts zu
erweitern, und der Server kann keine Entscheidung bilden. Seine Antwort
darauf ist eine nackte 400 ohne jeden Hinweis.
Das betrifft alles, was durch den Transcoder geht: die Qualitätswahl im
Player, komprimierte Downloads und Live-TV. Bei Filmen fiel es nicht auf,
weil dort DirectPlay greift.
„Chrome" als Basis, weil dieses Profil auf jedem Server vorhanden ist und
genau das kann, was AVPlayer kann: HLS mit H.264 und AAC. Die Feinheiten
für iOS stehen weiterhin im Zusatz.
Gefunden wurde es, indem die Anfragen Angabe für Angabe aufgebaut wurden,
bis eine davon umschlug — nicht durch Nachdenken.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Zehn Runden lang habe ich die Antwort gelesen, ohne sie anzusehen:
<html><head><title>Bad Request</title></head><body><h1>400 Bad
Request</h1></body></html>
So antwortet Plex nicht. Plex schickt XML oder JSON. Das ist die
Standardseite eines Webservers. Dazu passt, dass ping und stop mit 404
antworten, obwohl es sie in jedem Plex gibt.
Die Anfragen erreichen Plex also womöglich gar nicht, sondern werden
davor abgewiesen — vom Router, vom Webserver des NAS oder einem Proxy.
Das erklärt, warum wirklich jede Variante scheitert, auch die minimalste,
und warum eine gewöhnliche Datei genauso betroffen ist.
Der Probe zeigt jetzt alle Wege zum Server und nimmt mit `--local` den
lokalen. Geht es dort, liegt es an dem, was von außen dazwischensteht,
und nicht an Flixy.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Die vollständige Sitzung zeigt es nebeneinander:
Tune-Antwort: …/sessions/df16c509…/B5080949-E767-…/index.m3u8
Sitzung selbst: …/sessions/df16c509…/2F3939E2-6196-…/index.m3u8
Dieselbe Adresse, aber mit verschiedenen Kennungen darin: die
Tune-Antwort setzt die Sitzungskennung ein, die Sitzung selbst die
Gerätekennung. Nur die zweite gibt es wirklich. Ich habe die erste
genommen — daher jedes Mal 404.
Gesehen habe ich das erst, als die Antwort nicht mehr bei 700 Zeichen
abgeschnitten war. Genau dort fing `Part` an.
Dieselbe Antwort trägt auch `TranscodeSession` — der Strom läuft bereits.
Es gab nie etwas umzurechnen, und der Transcoder hatte recht, jede dieser
Anfragen abzulehnen.
Die Abspieladresse kommt jetzt ausschließlich aus der Sitzung.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Der Server sagt transcoderVideo=1 — er kann umrechnen. Damit ist auch
diese Ausrede weg, und es liegt an meiner Anfrage.
Zwei Stellen, an denen ich es mir zu leicht gemacht habe. Der Titel für
die Gegenprobe kam aus den Startseiten-Reihen, und dort stehen auch
Einträge von plex.tv, deren Kennung kein Schlüssel dieses Servers ist.
Mit so einem Eintrag prüft die Gegenprobe gar nichts — sie kommt jetzt
aus einer echten Mediathek, und vorher wird nachgesehen, ob der Eintrag
überhaupt existiert.
Und die Antwort der Live-Sitzung war bei 700 Zeichen abgeschnitten,
ausgerechnet dort, wo `Part` anfängt — an der einzigen Stelle, die zählt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Der Befund wird deutlicher: ping und stop antworten mit 404, start.m3u8
und decision mit 400. Die einen Routen gibt es nicht, die anderen lehnen
ab. Und die Mediathek antwortet normal — an der Verbindung liegt es
nicht.
In jeder Antwort der Wurzel stehen Felder, deren Werte ich nie angesehen
habe: transcoderVideo, transcoderActiveVideoSessions,
transcoderVideoBitrates. Steht dort `transcoderVideo: false`, kann dieser
Server kein Video umrechnen — und dann suche ich seit Tagen nach einer
Fähigkeit, die es auf diesem Gerät nicht gibt.
Das ist dieselbe Art Annahme wie „der Transcoder tut es schon": etwas
Vorausgesetztes, das nie jemand nachgesehen hat. Nachsehen kostet eine
Zeile.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Jede Variante scheitert, auch „nur Pfad und Token". Damit ist es kein
Parameter — es ist der Endpunkt.
Also die Frage eine Ebene höher: `ping` und `stop` gehören zu derselben
Familie und brauchen nur eine Sitzungskennung. Antworten auch sie mit
400, ist nicht die Anfrage falsch, sondern der ganze Zweig nicht da.
Dazu zwei Gegenkontrollen von Dingen, die nachweislich gehen: der
Bild-Transcoder, der denselben `/:/`-Aufbau hat und aus dem die Plakate
kommen, und die Mediathek selbst. Wenn die antworten und der
Video-Transcoder nicht, liegt es nicht an Flixys Adressen.
Und der Probe nimmt jetzt `--channel 002`: bisher hat er immer Sender 001
genommen, auf dem gerade nichts läuft — ein Sender ohne Sendung sagt über
das Einschalten wenig aus. Ohne Angabe nimmt er den ersten, auf dem
etwas läuft.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Die Gegenprobe ist eindeutig:
Gegenprobe — Transcoder mit: Mogli: Legende des Dschungels
→ HTTP 400
Eine gewöhnliche Datei scheitert genauso. Damit ist die Suche der letzten
Tage an der falschen Stelle gelaufen: Transcoding hat in Flixy noch nie
funktioniert, es ist nur nie aufgefallen. Filme laufen über DirectPlay,
da wird der Transcoder gar nicht gefragt — Live-TV ist das Erste, was ihn
zwingend braucht.
Diese Annahme habe ich tagelang mitgeschleppt, ohne sie zu prüfen. Sie zu
prüfen hat zehn Zeilen gekostet.
Jetzt wird richtig halbiert, an einer Datei statt an einem Sender: von
„nur Pfad und Token" aufwärts, Angabe für Angabe, bis eine davon die 400
auslöst. Dazu derselbe Satz an den Entscheider, der in Plex' Format
antwortet statt mit HTML.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Das Abwarten brachte nichts: der Vorgang meldet auch nach zwölf Sekunden
keine Daten. Und jede Anfrage an den Transcoder endet mit einer nackten
400 — auch die an den Entscheider, der sich sonst erklärt.
Das ist ein Muster, das nicht nach Live-TV aussieht, sondern nach dem
Transcoder selbst. Dass er funktioniert, habe ich die ganze Zeit
vorausgesetzt und nie geprüft: Filme laufen über DirectPlay, da wird er
gar nicht gefragt. Live-TV ist das Erste, was ihn zwingend braucht.
Also die Gegenprobe, vor allem weiteren Suchen: eine gewöhnliche Datei
aus der Mediathek durch den Transcoder. Läuft sie durch, liegt es an
Live-TV. Läuft sie nicht durch, habe ich tagelang am falschen Ende
gesucht.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Die Antwort auf das Belegen sagt es deutlich:
"status":"inprogress", "currentSize":0, "percent":-1.0
Der Tuner fängt in diesem Moment erst an. Ich habe im selben Atemzug nach
dem Strom gefragt — nach etwas, das es noch nicht gab. Dass darauf 400
und 404 kommen, ist kein Rätsel, sondern die richtige Antwort auf eine zu
frühe Frage.
Der Vorgang hat eine eigene Adresse, unter der ihm zuzusehen ist:
/media/grabbers/operations/… Dort wird jetzt gewartet, bis `currentSize`
über null steht, höchstens zwölf Sekunden. Länger hieße, jemanden vor
einem schwarzen Bild sitzen zu lassen.
Dazu steht die Sitzung selbst im Bericht — dorthin zeigt der Pfad, den
der Transcoder bekommt, und wie sie nach dem Abstimmen aussieht, habe ich
noch nie gesehen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Auch streng kodiert und auf das Nötigste zusammengestrichen bleibt es bei
400. Damit sind Kodierung und überzählige Angaben ausgeschlossen — und
mir gehen die Vermutungen aus, was richtig ist.
Also zwei Stellen, die noch etwas sagen können. Der Entscheider bekommt
dieselben Angaben wie der Transcoder, antwortet aber in Plex' eigenem
Format statt mit HTML; seine Antwort steht jetzt im Wortlaut im Bericht.
Und die Antwort auf das Belegen selbst. Die habe ich mehrfach durchsucht
und dabei übersehen, was sonst darin steht — Behälter, Teil-Kennungen,
vielleicht eine bereits laufende Transcode-Sitzung. Sie steht jetzt
vollständig da, statt nur das, wonach ich gesucht habe.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Die gebaute Adresse anzusehen hat es in einem Blick beantwortet:
path=/livetv/sessions/0043d829-… ← meine
path=%2Flivetv%2Fsessions%2F0043d829-… ← Plex
Plex' Client schickt alle Parameter durch `qs.stringify`, und das kodiert
alles. `URLComponents` lässt `/`, `:`, `&`, `=`, `+` und Klammern in
Werten stehen. Beim Server kam damit ein Pfad mit echten Schrägstrichen
an — und ein Geräteprofil, das an seinen `&` in zwei Dutzend eigene
Angaben zerfiel. Beides beantwortet er mit einer nackten 400.
Eine Regel für alle Werte, keine Ausnahmen. Damit löst sich nebenbei die
Kodierfrage, die mich mehrere Anläufe gekostet hat: eine Kennung, die
schon `%3A` enthält, wird dabei zu `%253A` — und ist genau einmal
kodiert. Die Sonderbehandlung für `path` und das Profil entfällt, und mit
ihr die Schalter, mit denen ich zwischen falschen Formen hin- und
hergeschaltet habe.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Zwei Dinge, die ich mehrere Runden zu spät tue.
Die Adresse steht jetzt in der Spur, mit entferntem Token. Ohne sie
diskutiere ich über eine Anfrage, die niemand gesehen hat — und genau das
habe ich getan, während ich einzelne Parameter der Reihe nach verdächtigt
habe.
Und eine Form, die nur das Nötigste enthält: Pfad, Protokoll, hasMDE,
Indizes, Sitzung, Token, Gerätekennung. Eine nackte 400 sagt nicht,
welche Angabe stört. Drei Formen nebeneinander — voll, ohne Profil, nur
das Nötigste — sagen es.
Dazu zwei Kleinigkeiten: die Abspielliste wird ein zweites Mal nach zwei
Sekunden abgerufen, weil ein Empfänger ein Stück Hardware ist und einen
Moment braucht, bis er abgestimmt hat. Und wenn das Belegen klappt, hört
die Suche danach auf — die übrigen zwanzig Abfragen sind dann nur noch
Lärm im Bericht und Last für den Server.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`URLComponents` kodiert `&` und `=` innerhalb eines Abfragewerts nicht.
Das Geräteprofil steckt voll davon:
X-Plex-Client-Profile-Extra=add-transcode-target(type=videoProfile&
context=streaming&protocol=hls&container=mpegts&…)
Der Server hat das nicht als ein Profil gelesen, sondern als zwei Dutzend
eigene Angaben — darunter ein zweites `protocol=`. Eine widersprüchliche
Anfrage, und die Antwort darauf ist eine nackte 400 ohne Hinweis.
Der Fehler steckte in jeder Transcoder-Adresse, seit es sie gibt. Bei
Dateien fiel er nicht auf, weil dort meist DirectPlay greift und der
Transcoder gar nicht gefragt wird.
Kodiert wird der **ursprüngliche** Wert, nicht der bereits von
`URLComponents` behandelte: den ein zweites Mal zu kodieren macht aus
`%3D` ein `%253D` — derselbe Fehler, nur eine Ebene tiefer. Der Test
prüft beides: dass das Profil ein einziger Parameter bleibt und dass
`protocol` genau einmal vorkommt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Nachgelesen im Quelltext des Plex-Clients, Funktion `video`:
path: server.resolveUrl(item.get("key")), // /livetv/sessions/1268be73-…
hasMDE: 1, mediaIndex: …, partIndex: …,
protocol: "hls", …
Zwei Dinge fehlten. Umgerechnet wird die **Sitzung, die der Server beim
Belegen angelegt hat** — nicht die Abspielliste darin, nicht die Sendung
beim Programmführer, und schon gar nicht die Kennung, die ich selbst
erzeuge. Genau die hatte ich mehrfach eingesetzt und mich über 404
gewundert.
Und `hasMDE=1` — „ich verstehe die Entscheidungsantwort". Plex' Client
schickt das bei jeder Wiedergabe mit. Es fehlte bei allen bisherigen
Versuchen, auch bei denen, die sonst richtig waren.
Der Schlüssel wird suchend gelesen und dabei von der Abspielliste
unterschieden: beide beginnen mit /livetv/sessions/, aber nur eine endet
auf .m3u8.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Das Belegen des Tuners klappt, und heraus kommt:
/livetv/sessions/1b5bb998…/DBACCA6D…/index.m3u8?offset=-1.000000
Eine fertige HLS-Abspielliste. Ich habe sie an den Transcoder gegeben —
also darum gebeten, etwas umzurechnen, das schon fertig ist. Daher die
nackte 400. Sie wird jetzt unmittelbar abgerufen, mit Token und
Gerätekennung in der Adresse, weil der Systemplayer keine Kopfzeilen
mitschickt. Die eigene Abfrage der Sitzung bleibt dabei erhalten.
Dazu das Gegenstück, ebenfalls aus dem Quelltext des Plex-Clients:
`DELETE /media/subscriptions/{id}` gibt den Tuner wieder frei. Er ist ein
Stück Hardware — vier Stück gibt es, und ein nicht freigegebener fehlt
beim nächsten Einschalten. Das passiert jetzt beim Schließen des Players
und auch dann, wenn das Belegen zwar klappt, aber nichts Abspielbares
dabei herauskommt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Aus dem Quelltext von Plex' eigenem Web-Client:
const o = `/livetv/dvrs/${t}/channels/${a}/tune`,
l = {method:"POST", headers:{"X-Plex-Session-Identifier": …}}
Genau diesen Pfad hatte ich seit mehreren Runden richtig. Jedes Mal per
GET abgefragt, jedes Mal 404 — und ein 404 sieht aus wie „gibt es nicht"
statt wie „nicht so". Also habe ich den Pfad verworfen und weitergesucht,
wieder und wieder, an einer Stelle, die von Anfang an stimmte.
Die Antwort auf das POST trägt den Vorgang mitsamt Metadaten-Eintrag, und
dort steht die abspielbare Teil-Adresse — nicht im Raster und nicht bei
der Sendung. Sie wird suchend gelesen statt an fester Stelle: der Vorgang
liegt je nach Serverfassung unterschiedlich tief, und ein falscher Weg
hinein sähe wieder aus wie „nichts da".
Die Kennung wird wie in `encodeURIComponent` kodiert — insbesondere
bleibt der Bindestrich stehen. `.alphanumerics` hätte aus
`6228f7f2-5fc705fa` ein `6228f7f2%2D5fc705fa` gemacht, und der Server
fände den Sender nicht.
Dass die Antwort in einer öffentlichen Quelle stand, während ich zwei
Tage lang Adressen erfunden habe, ist die eigentliche Lehre dieser Runde.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Aus der Antwort von /media/grabbers/devices/1:
{"channelKey":"6228f7f2364e02fa5ec8e51c-5fc706063f45c6002eab58a3",
"deviceIdentifier":"300",
"lineupIdentifier":"002"}
Der Programmführer kennt den Sender als `6228f7f2…-5fc706…`, der Tuner
als `300`. Die Zuordnung im Empfänger übersetzt zwischen beidem — und
genau diese Übersetzung habe ich nie benutzt. Jede Anfrage an den
Empfänger ging in der Sprache des Programmführers hinaus, und darauf gibt
es nur eine Antwort: 404.
Die Nummer am Empfänger steht jetzt an erster Stelle im Suchraum und im
Bericht. Der Empfänger ist ein HDHomeRun FLEX QUATRO mit vier Tunern; er
kennt Sender ausschließlich unter dieser Nummer.
Nebenbei aus derselben Ausgabe: /livetv/sessions antwortet mit size 0.
Die Route gibt es also — sie ist eine Liste. /livetv/sessions/<uuid> gibt
es nur, wenn die Sitzung vorher angelegt wurde, und deshalb war jeder
Abruf dort ein 404 ohne Aussagekraft.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Der Transcoder antwortet auf jede Form der Adresse mit einer nackten
„400 Bad Request"-Seite — ohne Plex-Meldung, ohne Hinweis. Der Entscheider
bekommt dieselben Angaben und gibt eine Plex-Antwort zurück. Das ist die
letzte Stelle, an der dieser Server noch erklärt, woran es liegt.
Schlägt das Einschalten fehl, zeigt der Probe deshalb jetzt zusätzlich
die Antwort des Entscheiders — in beiden Kodierformen — sowie den
Empfänger, das Gerät und /livetv/sessions im Wortlaut.
Dazu kann der Probe beliebige Adressen abfragen:
`swift run FlixyProbe /livetv/dvrs/2`. Eine Schnittstelle ohne
Beschreibung erkundet man, indem man sie fragt.
Der Token wird aus jeder Ausgabe entfernt, falls der Server ihn
zurückspiegelt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Das Upload-Kontingent des Tages ist aufgebraucht — verbraucht von meinen
eigenen Vermutungen über eine Schnittstelle, die keine Oberfläche braucht.
Jede Runde ging als TestFlight-Build hinaus und kam als Screenshot zurück.
`swift run FlixyProbe` macht dasselbe in Sekunden: anmelden, Server
suchen, Senderliste und Programm lesen, das Einschalten durchprobieren —
mit derselben Logik wie die App. Der Token kommt über die übliche
Anmeldung und liegt im Schlüsselbund dieses Rechners; gedruckt werden
Pfade, Kennungen und Antworten, keine vollständigen Adressen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Der Bericht nennt zwei Zeilen, die alles erklären:
Media.protocol = livetv
ratingKey = plex%3A%2F%2Fepisode%2F6a9a1255715fa1c9375df709
Plex führt die Kennung selbst schon kodiert — auch im JSON, wo es dafür
keinen Grund gäbe. Als Abfragewert geschickt dekodiert der Server sie
einmal, bevor er sie als Pfad liest, und bekommt `plex://episode/…`. Das
ist kein gültiger Schlüssel, und die Antwort darauf war die nackte
„400 Bad Request"-Seite.
Für eine Datei in der Mediathek ist das kein Thema: `/library/metadata/123`
enthält nichts zu kodieren. Deshalb ist beides nötig, und welche Form
gilt, hängt daran, woher die Adresse stammt.
Ich habe in diesem Punkt inzwischen jede Variante einmal für die richtige
gehalten — entpackt, einfach, doppelt. Ein Test hält jetzt alle drei
Formen fest, damit die Frage nicht ein viertes Mal aufkommt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
„1 Fassung, keine Teil-Adresse" beendet nichts. Wenn dort kein
abspielbarer Pfad steht, zählt, was stattdessen dort steht — irgendwo muss
der Server sagen, wie diese Sendung abzuspielen ist. Der Bericht zeigt
den Medien-Block jetzt Feld für Feld.
Dazu zwei Dinge, die ich benutzt habe, ohne sie je gesehen zu haben: der
Raster-Schlüssel des Senders und seine übrigen Kennungen stehen jetzt im
Bericht. Und der Raster-Schlüssel wird als vollständige Adresse probiert
— bisher war immer nur sein letzter Abschnitt als Sitzungsschlüssel an
der Reihe.
Der Empfänger kommt ebenfalls dazu: /livetv/dvrs/2/channels antwortet auf
diesem Server gar nicht, aber /media/grabbers/devices/1/channels führt
278 Sender. Wenn der DVR keine kennt, kennt sie das Gerät.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Die gebaute Adresse ist sauber — nachgerechnet und ausgedruckt. Also
lehnt Plex selbst ab, und zwar mit Recht: eine Sendung beim
Cloud-Programmführer hat keine Datei, die sich umrechnen ließe. Die
nackte 400 war die Antwort auf eine unsinnige Bitte.
Deshalb wird die Sendung jetzt zuerst nachgeschlagen. Das Raster nennt
keine abspielbare Adresse; die Einzelabfrage beim Anbieter nennt
womöglich eine. Was dabei zurückkam, steht in jedem Fall in der Spur —
auch wenn nichts Abspielbares dabei war.
Dafür musste die Anfrage einen bereits kodierten Pfad durchreichen
können: `appending(path:)` kodiert das Prozent ein zweites Mal, und aus
`plex%3A%2F%2F` wird `plex%253A%252F%252F`.
Und der angebotene Endpunkt-Zettel des Programmanbieters ist eindeutig:
content, metadata, grid, timeline — nichts zum Einschalten. Der Empfänger
muss es also tun, und den kannte `tune` bisher gar nicht. Seine Adressen
stehen jetzt mit im Suchraum.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Die nackte „400 Bad Request"-Seite war keine Plex-Meldung, sondern der
Webserver: er hat die Anfrage selbst nicht verstanden. Grund war mein
eigener Fix von vorhin — ich habe `%3A%2F%2F` entpackt, und damit stand
`plex://episode/…` im Klartext in der Anfrage.
Beide naheliegenden Wege liegen daneben, und ich habe beide durchgemacht:
unverändert durchgereicht kodiert URLComponents ein zweites Mal, aus %3A
wird %253A. Vorher entpackt bricht sich der Server die Adresse. Richtig
ist genau einmal — dafür wird `path` von Hand eingesetzt, nachdem alles
andere kodiert ist.
Dazu aus der Spur gelernt: jede Form von /livetv/sessions/… antwortet auf
diesem Server mit 404. Den Tuner belegt er offenbar selbst, sobald die
Sendung umgerechnet werden soll. Deshalb steht die Adresse der Sendung
jetzt an erster Stelle, und die Sitzungen kommen nur noch dahinter.
Als dritte Form ein Versuch ohne das Geräteprofil: dessen Angaben stecken
voller `&` und `=`, die in einem Abfragewert mehrdeutig sind. Trägt es
ohne, liegt es daran, und das steht dann in der Spur. Ebenso, was der
Programmanbieter überhaupt an Endpunkten angibt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Wieder HTTP 400, und wieder ohne Begründung. Der Grund, dass das dreimal
passieren konnte: eine gebaute Adresse galt schon als Erfolg. Ob der
Server sie überhaupt annimmt, stellte sich erst im Player heraus — und
dort steht nur „HTTP 400 (unhandled)".
Jetzt ruft die App die Adresse einmal selbst ab, ohne Plex-Kopfzeilen,
genau so wie AVPlayer es tut. Nur was mit 200 antwortet, geht an den
Player. Was nicht, steht mit dem Wortlaut des Servers in der Spur.
Weil weder der Schlüssel für die Sitzung noch die anschließend
umzurechnende Adresse für diesen Teil der Plex-Schnittstelle beschrieben
sind, werden die Kombinationen der Reihe nach durchprobiert — Adresse der
Sitzung, Adresse aus der Antwort, Adresse des Teils darin, je mit
mediaIndex -1 und 0, höchstens acht Versuche. Antwortet keiner, wird der
Transcoder wieder angehalten, statt für nichts weiterzurechnen.
Die Spur steht in einem Blatt statt in einem Dialog: die Antwort des
Servers ist mehrzeilig, und ein Dialog schneidet sie ab — dann stünde
dort wieder nur, dass es nicht ging.
Dazu ein Nebeneffekt, der beim zweiten Sender zugeschlagen hätte: der
Player kannte die Sitzungskennung des Live-Stroms nicht und hat den Tuner
beim Schließen nicht freigegeben.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
HTTP 400 vom Transcoder. Die Kennung dieses Geräts reist bei jeder
Anfrage als Kopfzeile mit — aber die Adresse des Stroms ruft nicht die
App ab, sondern AVPlayer, und der schickt keine Kopfzeilen mit. Bei
Dateien fällt das nicht auf; bei Live-TV schon: der Server legt die
Sitzung unter der Gerätekennung an und kann die Abholung ohne sie keiner
Sitzung zuordnen.
Also trägt die Transcoder-Adresse die Kennung jetzt selbst. Das gilt für
alle Ströme, nicht nur für Live — dort fiel es nur zuerst auf.
Dazu: umgerechnet wird die Adresse der Sitzung, nicht die des Teils
darin. Der Transcoder sucht sich den Teil selbst, deshalb steht dort -1.
Und der Entscheider unterscheidet jetzt zwischen „nein" und „keine
Antwort". Ein Schweigen als Ablehnung zu lesen hieße, eine
funktionierende Sendung zu blocken, bloß weil er Live-Adressen womöglich
gar nicht kennt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Der übersprungene Schritt. Ich habe die Sendung direkt an den Transcoder
gegeben — ohne dass jemand den Tuner belegt hätte. Der Server fing an,
merkte, dass er nichts zu senden hat, und brach ab: „die Wiedergabe wurde
unterbrochen", ohne weitere Angabe.
Jetzt zwei Schritte. Erst eine Live-Sitzung eröffnen, dann die Adresse
dieser Sitzung umrechnen lassen — nicht die des Senders und nicht die der
Sendung. mediaIndex und partIndex sind dabei -1: ein laufender Sender hat
keine Fassungen zur Auswahl.
Unter welchem Schlüssel sich die Sitzung eröffnen lässt, unterscheidet
sich je nach Anbieter, deshalb werden die in Frage kommenden der Reihe
nach probiert — der Raster-Schlüssel zuerst, weil er den Sender genauso
bezeichnet wie die Liste, aus der er stammt.
Dazu zwei Stellen, an denen Gründe bisher verschwanden:
Vor dem Abspielen wird der Server gefragt, ob er den Strom überhaupt
zulässt. Er antwortet im Klartext; der Player meldet nur, dass es nicht
ging.
Und wenn der Motor scheitert, steht sein Grund ab jetzt auf dem
Bildschirm statt nur im Protokoll. „Die Wiedergabe wurde unterbrochen"
allein sagt weder, ob der Server ablehnt, noch ob der Motor das Format
nicht mag — und genau diese Unterscheidung hat diese Runde gekostet.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Drei Fehler, alle im eigenen Code.
Die Sendezeiten stehen beim Programmanbieter im Medien-Block, nicht am
Eintrag darüber. Ich habe sie oben gesucht, nicht gefunden und jede
Sendung verworfen — übrig blieb „Kein Programm verfügbar" auf jedem
Sender, und ohne Sendungen fiel auch die Senderliste auf die Tunerliste
zurück.
Die Senderspalte im Raster fehlte ganz. Die Zeitachse ist mehrere tausend
Punkte breit, und der HStack hat den Nachbarn dafür auf null gedrückt.
Jetzt hat die Spalte Vorrang, und die Achse liegt als Auflage über einer
durchsichtigen Fläche — eine Auflage zählt bei der Größenrechnung nicht
mit. Der Abstand der Achsenbeschriftung gehört in die Spaltenbreite
hinein statt daneben, sonst steht die ganze Achse acht Punkte neben dem
Programm darunter.
Und das Einschalten: die Adresse wurde berechnet und verworfen, dann
bekam der Player eine Senderkennung, zu der er einen Film in der
Mediathek suchte. Den gibt es nicht. Jetzt reicht die Anfrage die fertige
Adresse durch, der Player spielt sie ohne Umweg, und meldet weder
Fortschritt noch sucht er Metadaten. Scheitert es doch, steht ab jetzt
eine Meldung da statt gar nichts.
Geprüft wurde das Raster diesmal mit den Augen: ein Gerüst im Simulator
zeichnet es mit erfundenen Sendern. Dafür bekommt es die Bildadresse als
Abschluss übergeben, statt sie aus der Sitzung zu lesen.
Dazu: Downloads zeigen jetzt an, wenn sie auf WLAN warten. Die
Hintergrund-Sitzung wartet in dem Fall von sich aus — kein Fehler, nur
Stillstand, und das sah bisher aus wie ein hängender Download.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Der Abzug vom Server zeigt beides schwarz auf weiß.
Erstens: /media/grabbers/devices/1/channels antwortet mit 278 Sendern —
unter dem Sammelnamen `DeviceChannel`. Die Hülle kannte nur `Channel` und
hat die vollständige Liste als leer verworfen.
Zweitens: das Programmraster antwortet, aber nicht auf `type=1`. Die 1
steht bei Plex für Film, und im Programm laufen auch Folgen, Sport und
Nachrichten — der Filter hat das Raster geleert. Ohne ihn kommen die
Sendungen samt Sendername, Nummer, Logo und Kennung.
Das Raster hat jetzt Vorrang vor der Tunerliste, auch wenn beide
antworten: nur dort gehören Sender und Programm zusammen, es enthält
ohnehin nur die eingeschalteten Sender, und die Tunerliste führt alle 278.
Einschalten läuft über die Adresse der laufenden Sendung beim
Programmanbieter — Plex behandelt eine laufende Sendung wie einen Film.
Die Adresse wird einmal entpackt, bevor sie in die Anfrage geht: sonst
kodiert URLComponents ein zweites Mal, und aus %3A wird %253A.
Die Tests stehen jetzt auf den echten Antworten aus dem Abzug, nicht auf
erfundenen: DeviceChannel mit `name` und `key`, Rastereinträge mit
channelVcn, channelID und einer Folge neben einem Film.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Vier geratene Adressen, vier 404. Statt ein fünftes Mal zu raten, klopft
die App jetzt in einem Durchgang und gleichzeitig ein Dutzend Adressen ab:
was /media/providers an Endpunkten angibt, welche Unterverzeichnisse
Programmanbieter und Empfänger in ihrer eigenen Antwort führen, die
bekannten Formen anderer Serverfassungen, die Senderliste des Tuners
selbst — und zuletzt das Programmraster.
Dabei zwei Fehlerquellen mitbehoben, die jede einzeln wie „keine Sender"
ausgesehen hätten: der Sendername steht je nach Tunertyp in title, name,
channelTitle oder callSign, und eine Antwort mit dreißig namenlosen
Einträgen sieht aus wie eine Senderliste, ist aber keine.
Bleibt es leer, steht jetzt jede abgeklopfte Adresse mit ihrer Antwort in
der Ansicht. Das beantwortet „liegt es am Server oder an der App" ohne
eine weitere Runde Rückfragen.
Dazu drei Sachen bei Serien:
- Groß steht, worum es geht — bei einer Folge ihr Titel, bei einer
Staffel der Name der Serie. „Staffel 10" als Überschrift sagte nicht,
welche Serie gemeint ist.
- Die Einordnung steht darunter: Serienname bei Folgen, Staffelnummer bei
Staffeln.
- Von der Folge führt ein Weg zur Staffel und zur Serie. Wer über
„Weiterschauen" bei einer Folge landete, saß dort bisher fest.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Drei geratene Adressen für die Senderliste antworteten auf einem echten
Server alle mit 404. Statt ein viertes Mal zu raten, liest die App jetzt
zwei Dinge: was /media/providers an Endpunkten angibt, und das
Programmraster selbst — die Weboberfläche von Plex zeichnet ihren Guide
daraus, und jeder Rastereintrag trägt Sendername, Nummer, Logo und
Einschaltweg mit sich.
Damit kommen Sender und Programm aus einer Abfrage, und ihre Kennungen
passen zueinander. Getrennt geholt taten sie das nicht: das Programm
landete in keiner Zeile.
Dazu die Ansicht, für die das alles da ist — ein Programmraster mit
stehender Senderspalte, mitlaufender Zeitachse und einer Jetzt-Linie.
Antippen öffnet die Sendung; läuft sie gerade, schaltet ein Knopf um.
Die Geometrie steht in GuideLayout und ist geprüft: in einer View wäre
sie unsichtbar falsch. Die Trennlinien gehören in die Zeile statt
zwischen die Zeilen — Divider ist auf 3x-Schirmen keinen ganzen Punkt
hoch, und der Bruchteil summiert sich, bis die angetippte Zeile zum
Nachbarsender gehört.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Der zweite Abzug zeigt den Pfad, der 404 ergab:
/tv.plex.providers.epg.cloud:2/lineups/
lineup://tv.plex.providers.epg.cloud/6228f7f2364e02fa5ec8e51c
#Swisscom%20Blue%20TV%20%28Deutschschweiz%29%20%28521%20channels%29
/channels
`Lineup.id` heißt zwar Kennung, ist aber eine vollständige Adresse: mit
Schema, Doppelschrägstrich, Prozentzeichen und einem Anhang hinter der
Raute. Die habe ich roh in den Pfad gespleißt.
Gebraucht wird der nackte Teil in der Mitte —
`6228f7f2364e02fa5ec8e51c`. `PlexLineup.pathIdentifier` packt ihn aus,
vier Tests halten die Fälle fest.
Da die Pfadform zwischen Serverfassungen schwankt und dieser Teil der
API keine Beschreibung hat, werden drei Adressen der Reihe nach
probiert. Welche gegriffen hat, steht in der Diagnose — damit aus dem
Durchprobieren beim nächsten Mal Wissen wird. Der Strukturabzug deckt
jetzt alle drei ab.
173 Tests.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Der Strukturabzug vom Server hat es entschieden. ChannelMapping enthält
genau vier Felder — channelKey, deviceIdentifier, enabled,
lineupIdentifier — und keinen einzigen Namen. Es ist keine Senderliste,
sondern ein Verweis darauf: welche 28 von 521 Sendern der Swisscom-Liste
eingeschaltet sind.
Zweimal habe ich die Zuordnung für die Senderliste gehalten. Beim ersten
Mal blieb sie leer, beim zweiten hätten alle 28 Sender „—" geheißen und
dieselbe Kennung getragen — SwiftUI hätte bei doppelten Kennungen
ohnehin nur einen gezeigt.
Jetzt werden beide Quellen zusammengeführt: die Namen, Nummern und Logos
kommen von der Senderliste beim Programmanbieter, die Auswahl aus der
Zuordnung. Die Kennung des Anbieters steht als `epgIdentifier` direkt am
Empfänger — verlässlicher, als sie aus der Anbieterliste zu suchen.
Der Abgleich ist nachsichtig: je nach Anbieter entspricht der channelKey
der Kennung, dem Pfad oder der Sendernummer. Ein strenger Vergleich fände
in zwei von drei Fällen nichts, und „keiner passte zusammen" sieht aus
wie „keine Sender". Genau das unterscheidet die Meldung jetzt auch.
Der Strukturabzug in der Diagnose deckt jetzt beide Endpunkte ab — nach
einem Fehlschlag bleibt keine weitere Runde Raten übrig.
166 Tests, neun davon zu Zuordnung und Abgleich.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Die Sender bleiben leer, und ich habe jetzt zweimal geraten, wo sie
stehen könnten — beim ersten Mal ein eigener Endpunkt, beim zweiten die
Verschachtelung unter Device. Beides war eine Vermutung, weil dieser
Teil der Plex-API keine offizielle Beschreibung hat.
Statt ein drittes Mal zu raten: Einstellungen → Diagnose → „Live-TV-
Antwort ansehen" fragt den Server und zeigt den Aufbau der Antwort.
Schlüssel, Anzahl und Sendernamen, keine Werte und keine Zugangsdaten —
genug, um den richtigen Schlüssel zu finden.
Von Listen wird nur der erste Eintrag ausgeklappt; bei dreißig Sendern
wäre die vollständige Ausgabe unlesbar, und alle haben denselben Aufbau.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Senderliste
Plex liefert die Sender verschachtelt in der Antwort auf /livetv/dvrs
mit — jeder Empfänger bringt seine eigenen unter `Device` → `Channel`.
Mein getrennter Endpunkt /livetv/dvrs/{id}/channels war eine Vermutung
und lieferte nichts. Bei einem Server mit über dreißig eingerichteten
Sendern stand deshalb „Keine Sender".
Die verschachtelte Liste hat jetzt Vorrang, der Endpunkt bleibt als
Rückfalllinie. `ChannelMapping` wird als ältere Schreibweise ebenfalls
gelesen, und der Empfänger-Schlüssel kommt je nach Serverfassung als
Zahl oder als Zeichenkette.
Bleibt die Liste leer, sagt die Ansicht jetzt welcher Weg was ergeben
hat. In einem Bereich ohne offizielle Beschreibung ist das die Angabe,
mit der sich der nächste Fehler finden lässt.
Schriftzüge
Plex liefert bei vielen Filmen ein `clearLogo` — den Schriftzug in
seiner eigenen Type, freigestellt. Der tritt jetzt im Aufmacher an die
Stelle des gesetzten Titels.
Dabei ein Fallstrick: /photo/:/transcode liefert JPEG, und JPEG kennt
keine Transparenz. Über den Bild-Transcoder geladen wäre aus dem
freigestellten Logo ein schwarzer Kasten geworden. Es wird deshalb
unverändert geladen. Fehlt der Schriftzug oder scheitert das Laden,
bleibt es beim Titel.
161 Tests.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Picker-Werte leuchteten weiter in der Standardfarbe, während Schalter
und Symbole daneben schon die gewählte trugen. Zwei Ursachen, beide
behoben — welche davon im Einzelfall zog, ließ sich nicht nachstellen,
weil der Bildschirm hinter der Anmeldung liegt.
Erstens stand `.tint` im Scene-Body. Scene-Bodies werten Änderungen an
beobachteten Objekten nicht zuverlässig neu aus; nach einem Farbwechsel
blieb ein Teil der Oberfläche auf dem alten Wert stehen. Jetzt setzt
RootView Tönung und Akzent — eine View wertet zuverlässig neu aus.
Zweitens nahm der Vorgabewert der Umgebung blind die Standardfarbe.
Listen und Formulare zeichnen ihre Zeilen teilweise in einem eigenen
Darstellungskontext, in dem eigene Umgebungswerte verloren gehen; dort
erschien dann Mint statt der gewählten Farbe. Der Vorgabewert liest
jetzt die gespeicherte Auswahl.
Damit das nicht wieder auseinanderläuft, liegt die Speicherung an einer
Stelle: AccentOption.stored/store. Vorher hatte FlixyTheme eigene
Schlüssel — zwei Kopien derselben Namen driften auseinander, und zwar
unbemerkt.
Dazu setzen die Formulare die Tönung ausdrücklich, statt sich auf
Vererbung zu verlassen.
154 Tests, fünf davon zur Speicherung.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Alle Reihen zeigten Querformat-Kacheln. Für „Weiterschauen" ist das
richtig — man erkennt die Szene wieder, an der man aufgehört hat. Beim
Aussuchen sucht man dagegen nach Plakaten, und die sind hochkant.
Die Unterscheidung hängt an der Hub-Kennung, nicht am Titel: der kommt
in der Sprache des Servers, und die Startseite sähe sonst auf Englisch
anders aus als auf Deutsch, ohne dass jemand auf die Ursache käme.
`ondeck` zählt mit — ältere Serverfassungen nennen es so.
Bei Folgen in Hochkant-Reihen wird das Serienplakat genommen, nicht das
Szenenbild: ein 16:9-Bild hochkant beschnitten zeigt einen zufälligen
Ausschnitt und ist zum Wiedererkennen wertlos.
Plakate sind schmaler als Szenenbilder, damit bei gleicher Höhe mehr
nebeneinander passen — mehr Auswahl auf einen Blick ist der Zweck.
Vier Tests, darunter einer, der festhält, dass der übersetzte Titel die
Form nicht bestimmen darf.
149 Tests.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Player hing sich auf
Die Qualitätsauswahl war ein Menu. Schloss man es durch einen Tipp ins
Bild, blieb eine unsichtbare Ebene zurück, die alle weiteren Tipps
schluckte — die Bedienelemente kamen nicht mehr, pausieren ging nicht
mehr. Jetzt ein confirmationDialog, der sich selbst wegräumt. Nach dem
Schließen werden die Bedienelemente eingeblendet und die Ausblendfrist
neu gestartet.
Downloads galten als fertig, waren aber leer
Zwei Fehler übereinander. Erstens gilt ein URLSessionDownloadTask auch
bei 404 oder 500 als erfolgreich und legt die Fehlerseite als Datei ab.
Zweitens überschrieb ich danach die erwartete Größe mit der empfangenen
— womit die Anzeige auf hundert Prozent stand und der Beweis weg war.
873 MB waren in einer Sekunde „fertig", und beim Abspielen kam nur
„Wiedergabe wurde unterbrochen".
Jetzt wird der Statuscode geprüft, die erwartete Größe bleibt stehen,
und eine unplausible Datei wird verworfen statt verbucht: unter 100 KB
ist keine Videodatei, unter der Hälfte der angekündigten Größe ist eine
abgebrochene Übertragung. Sechs Tests dazu.
Stufenliste ergab keinen Sinn
Für einen 720p-Inhalt standen Original, Full HD und HD mit derselben
geschätzten Größe untereinander — das sah aus, als wäre die Schätzung
kaputt. Sie war es nicht, die Stufen waren es: hochrechnen bringt nur
eine größere Datei. Angeboten wird jetzt nur noch, was unter der
Auflösung der Datei liegt, und beim Original steht dabei, was es ist:
„Original · 720p · 873 MB".
Kopfleiste
Kein Hintergrund mehr statt eines weichgezeichneten — die Knöpfe
bringen ihre eigene Glasfläche mit. Der Titel ist weg: der Name der App
gehört auf den Homescreen, nicht über jeden Bildschirm.
145 Tests.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
iPad meldete sich nie an
Die Ursache ist unangenehm speziell: Wer im Browser schon bei Plex
angemeldet ist, wird von app.plex.tv/auth sofort auf watch.plex.tv
weitergeschickt — OHNE dass der Code verknüpft wird. Man sieht sich
angemeldet, die App wartet vergeblich, und es gibt keinerlei Hinweis.
Jetzt wird plex.tv/link mit eingetragenem Code geöffnet. Diese Seite
tut nur eine Sache: den Code bestätigen. Die Gerätekennung steckt
ohnehin schon im PIN.
Dazu ein Ausweg: bleibt es nach fünfzehn Sekunden ohne Token, erscheint
der Code im Anmeldebildschirm zum Abtippen. Schließt man das Fenster
ohne Erfolg, bleibt er stehen statt abzumelden — er gilt weiter.
Download fragte nicht nach der Qualität
Jetzt Original, Full HD, HD oder Sparsam, jeweils mit geschätzter
Größe. Die Größe ist das eigentliche Entscheidungskriterium — „Full HD"
allein sagt nicht, ob es aufs Telefon passt.
Die komprimierten Stufen laufen über den Universal-Transcoder und sind
weiterhin NICHT am Server geprüft; steht so im Quelltext.
Schwarzes Bild ohne Anzeige
Der Player kannte nur „spielt" und „spielt nicht". Wartete er auf
Daten, stand man vor einem Standbild ohne Hinweis. Es gibt jetzt einen
eigenen Pufferzustand mit Anzeige.
Aufmacher war beschnitten
Er stand auf 3:4, Plex liefert als `art` aber 16:9 — mit scaledToFill
ging links und rechts je ein Viertel verloren, ohne dass man es dem
Bild ansah. Die Seitenverhältnisse liegen jetzt an einer Stelle in
FlixyAspectRatio.
Schwarze Leiste über dem Aufmacher
.ultraThinMaterial statt undurchsichtigem Grund: das Bild läuft
unscharf hinter der Leiste weiter, statt an einer harten Kante zu
enden. Weichgezeichnet, nicht abgedunkelt.
139 Tests.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Download-Knopf tat nichts
DownloadService.plan gab still nil zurück, wenn es keinen Auftrag
bauen konnte, und download() reichte das als nil weiter. Der Knopf sah
aus, als wäre er kaputt, dabei war der Grund jedes Mal bekannt. Es
gibt jetzt einen eigenen Ablehnungsgrund mit vier unterschiedlichen
Begründungen — keine Verbindung, keine Datei, kein abrufbarer Teil,
keine Adresse. Eine Funktion, die kommentarlos nichts tut, ist selbst
der Fehler.
Live-TV galt als „nicht eingerichtet"
Plex legt die Empfängerliste unter dem Schlüssel `Dvr` ab, die Hülle
kannte aber nur Metadata, Directory und Hub. Sie las eine leere Liste
und zog daraus die falsche Schlussfolgerung — auf einem Server, auf
dem ein Empfänger eingerichtet war. Dasselbe galt für die
Anbieterliste unter `MediaProvider`, ohne die es keinen
Programmführer gibt.
Beim Umbau wäre beinahe die Fehlerdiagnose verloren gegangen: mit
`try?` über alle Schlüssel verschwände auch ein echter Dekodierfehler
und ergäbe stillschweigend eine leere Liste. Aus „ein Feld heißt jetzt
anders" würde „die Mediathek ist leer". Jetzt wird erst geprüft, ob
der Schlüssel da ist, und dann ohne try? dekodiert. Fünf Tests halten
beides fest.
Qualität im Player wählbar
Automatisch, Original, 1080p, 720p, 480p. Der Eingriff wird genau dann
gebraucht, wenn es ruckelt — dafür will niemand den Film verlassen.
Ein Wechsel baut den Strom neu auf (die Stufe steckt in der Adresse),
merkt sich die Stelle und fährt sie wieder an. Der alte Transcoder
wird vorher angehalten, sonst rechnet der Server zwei Ströme für einen
Zuschauer.
AVPlayer-Anzeige entfernt
Sie war eine Prüfhilfe aus Phase 1. Dieselbe Angabe steht weiterhin
unter Einstellungen → Diagnose, wo sie hingehört.
137 Tests.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>