fb418c842dbc6dd93733f379ccfe152a672bcffa
8
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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> |
||
|
|
ac0c27c026 |
Fünf Punkte aus dem Praxistest
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> |
||
|
|
a2b3f901ed |
Absturz beim Abspielen, und die Annahme dahinter
Der Crashlog vom Gerät:
_dispatch_assert_queue_fail
_swift_task_checkIsolatedSwift
MainActor.assumeIsolated
VLCPlayerEngine.mediaPlayerStateChanged(_:)
VLCKit input_thread_Events
Im Code stand als Begründung für assumeIsolated, VLCKit stelle seine
Rückrufe über die Hauptwarteschlange zu. Das ist falsch — es ruft den
Delegaten von seinem eigenen Input-Thread. Die App brach deshalb hart
ab, sobald VLC den ersten Zustandswechsel meldete, also unmittelbar
beim Drücken auf Play.
Alle drei Delegat-Methoden springen jetzt mit Task { @MainActor in }
sauber hinüber. Die Reihenfolge bleibt erhalten: die Aufgaben werden aus
demselben Thread nacheinander eingereiht.
Dieselbe Annahme steckte im AVPlayer-Motor in den KVO-Beobachtern —
KVO meldet auf dem Thread, der die Änderung ausgelöst hat, und AVPlayer
ändert seinen Zustand auch aus internen Threads. Ebenfalls umgestellt.
Die beiden Rückrufe mit ausdrücklichem queue: .main behalten
assumeIsolated: dort ist die Annahme belegt statt geraten, und der
Zeitbeobachter feuert viermal je Sekunde.
Dazu die Anmeldung: Plex führt den Rücksprung über forwardUrl nicht
zuverlässig aus. Auf dem iPad landet der Browser nach dem Anmelden auf
watch.plex.tv und bleibt stehen — der Token liegt bereit, aber die App
wartete auf einen Rückruf, der nie kam. Das Fenster wird jetzt geöffnet,
ohne darauf zu warten; parallel wird im Sekundentakt beim Server
nachgefragt. Der Rücksprung ist damit eine Annehmlichkeit, keine
Voraussetzung.
132 Tests.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
222ac1c968 |
Vier Fehler aus dem ersten echten Gebrauch
Kein Video lief jemals an AVPlayerEngine.load() wartete auf asset.load(.duration) und die Spurenliste, BEVOR es zurückkehrte — und erst danach rief das Modell play(). AVFoundation muss für die Dauer aber so viel von der Datei holen, bis es den Index findet, und der sitzt bei MP4 wie MKV oft am Ende. Über eine langsame Leitung sind das Minuten, in denen nur ein Ladekreis zu sehen war, obwohl der Player längst hätte anfangen können. Jetzt kehrt load() zurück, sobald das Item hängt; Dauer und Spuren kommen nach und melden sich über den Ereignisstrom. Dazu AVURLAssetPreferPreciseDurationAndTimingKey: false — sonst durchsucht AVFoundation bei manchen Formaten die ganze Datei. Zweiter Fund an derselben Stelle: VLCMedia(url:) ist nullable. Ging es schief, blieb media still nil, play() tat nichts und es gab keinen Fehler — nur einen Ladekreis ohne Ende. Wird jetzt gemeldet. Kein Querformat im Player requestGeometryUpdate allein dreht nicht: das System dreht sofort zurück, solange die erlaubten Ausrichtungen es nicht hergeben. Die Erlaubnis kommt nur über den App-Delegaten, deshalb OrientationLock als Brücke. Player dreht ins Querformat, beim Verlassen zurück. iPad bleibt frei — dort ist Querformat beim Blättern der Normalfall. Uhrzeit über dem Film Die Statusleiste war nur ausgeblendet, solange die Bedienelemente weg waren. Jetzt immer. Sehr langer Start Zwei Ursachen. Erstens legte der Initialisierer der App die Hintergrund-URLSession an — die spricht über XPC mit nsurlsessiond und verzögerte den allerersten Bildaufbau. Jetzt erst bei Bedarf, und wer keine Downloads hat, braucht sie nie. Zweitens fragte jeder Start erst plex.tv nach der Serverliste und suchte die Verbindung neu. Die zuletzt funktionierende wird jetzt gemerkt (Server-Token im Schlüsselbund) und mit kurzer Zeitgrenze zuerst probiert; die vollständige Suche greift nur noch, wenn sie nicht mehr antwortet — etwa wenn man von zuhause weggeht. Dazu ein Hinweis im Player, wenn die Verbindung über die gedrosselte Plex-Vermittlung läuft. Sonst sucht man den Fehler in der App. 132 Tests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
1756ed11cc |
Anmeldung über die Plex-Seite statt über einen Code
Kein Code mehr zum Abtippen. Die Anmeldemaske kommt von Plex und öffnet sich über ASWebAuthenticationSession in einem Fenster innerhalb der App; nach dem Anmelden schickt Plex über forwardUrl auf ein eigenes Schema zurück, das Fenster schließt sich von selbst. Der PIN steckt weiterhin darunter — er ist der einzige Weg, auf dem Plex einen Token herausgibt. Sichtbar ist er nicht mehr. Was das bringt: - Kein Fernseher-Gefühl mehr. - Zwei-Faktor läuft mit, weil Plex die Seite selbst ausliefert. - Das Passwort erreicht Flixy nie. Steht auch so unter dem Knopf. - prefersEphemeralWebBrowserSession = false: wer in Safari schon bei Plex angemeldet ist, braucht nur zu bestätigen. Zwei Details, die sonst still scheitern: - Die Rückadresse wird strenger kodiert als die übrigen Werte. `:` und `/` lässt urlQueryAllowed durch, und für Plex endete der Wert dann beim ersten Schrägstrich — der Rücksprung käme nie. - Das Schema muss in CFBundleURLTypes stehen, sonst kennt iOS es nicht und das Fenster bliebe offen stehen. Als Schema die Bundle-Kennung statt eines kurzen Worts wie „flixy": ein kurzes könnte eine andere App beanspruchen und die Anmeldung abfangen. 132 Tests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
9a69df4635 |
Phase 1: Anmeldung, Bibliothek, Detailseiten, Hybrid-Player
Die Datenschicht: - plex.tv-Anmeldung über das PIN-Verfahren, Token im Schlüsselbund - Serversuche und Verbindungsauswahl nach Rang (lokal vor direkt vor Vermittlung), mit Abgleich der Server-Kennung gegen veraltete plex.direct-Adressen - PlexServer als einzige Stelle, an der die App Endpunkte kennt: Mediatheken, Hubs, seitenweises Blättern mit Filtern, Metadaten, Staffeln/Folgen, Suche Die Oberfläche: - Startseite mit Aufmacher und den Reihen vom Server - Mediathek als Raster oder Liste, mit Filter- und Sortierblatt - Detailseiten für Filme, Folgen, Serien und Staffeln inkl. Besetzung, Fassungsauswahl und aufklappbaren technischen Angaben - Suche mit gemerkten Anfragen - Vollbildplayer mit eigenem Chrome Der Player: - AVPlayerEngine und VLCPlayerEngine hinter einem gemeinsamen Protokoll - VLCKit liegt in einem eigenen Package (FlixyVLC), damit `swift test` in FlixyKit nicht das 920-MB-Binärpaket mitzieht Am echten plex.tv geprüft: der Anmeldecode kommt an. Dabei fiel auf, dass `strong=true` einen dreißigstelligen Code liefert, der sich bei plex.tv/link nicht eintippen lässt — jetzt der kurze. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |