74643b8242522dd2e449269773da8a1ee3aea61a
8
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
7673c73757 |
Vier Sekunden, die ffprobe zu früh aufhörte zu lesen
Die Untertitel kamen vier Sekunden zu spät. Nicht an ihnen lag es: der
Server meldete dem Spieler einen Anfang, der vor dem lag, an dem der
Strom wirklich begann, und genau diese Differenz landet eins zu eins im
Versatz.
Die Schlüsselbildsuche las mit `-read_intervals` bis eine halbe Sekunde
hinter den Wunsch. ffprobe bricht am Ende eines Fensters auf Paketebene
ab — das letzte Schlüsselbild darin kam regelmässig gar nicht mehr
heraus, und die Suche gab das davor zurück. Bei Bildgruppen von vier
Sekunden sind das vier Sekunden.
Nachgemessen gegen die Stelle, an der ffmpeg tatsächlich anfängt, an
einer Datei mit Schlüsselbildern alle vier Sekunden:
Wunsch Suche (0,5 s) ffmpeg Abweichung
52,0 48 52,0 −4,0
73,3 68 72,0 −4,0
100,2 96 100,0 −4,0
Mit zehn Sekunden Nachlauf lag dieselbe Messung bei allen sieben Werten
auf 0,000. Was über den Wunsch hinausgelesen wird, verwirft die
Filterung ohnehin.
Die Prüfung vergleicht nicht gegen eine erwartete Zahl, sondern gegen
ffmpeg selbst — mit dem alten Wert fällt sie um.
Dazu der Filmteil der Abspannerkennung: `Abspannkante` als reine
Rechnung mit elf Prüfungen, `Bildabtaster` über ffmpeg, Marken auch an
Filmen, Knöpfe zum Suchen und Verwerfen auf der Filmseite. An einer
erzeugten Datei trifft die Kante auf vier Sekunden genau.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
1d70be0f99 |
Fünf Aufmacher je Titel, und der Abspann zählt als Schluss
**Der Aufmacher darf wechseln.** Bisher hing hinter jedem Titel genau ein Hintergrundbild, und das Karussell war nach einer Woche ein Standbild. Jetzt lassen sich bis zu fünf Aufnahmen aussuchen — im Bilderdialog unter einem vierten Reiter, gespeist aus denselben Hintergründen. Welches gerade gilt, entscheidet der Server aus Uhrzeit und Kennung: alle fünf Stunden ein anderes, überall dasselbe — im Web, in der App, nach einem Neustart. Ein Zufall je Abruf hiesse, dass das Bild beim Blättern springt; eine gespeicherte Wahl hiesse einen Schreibvorgang je Titel und Fenster. Die Kennung geht mit in die Rechnung, damit nicht alle Titel im selben Augenblick umschalten. Plakat und Hintergrund bleiben unberührt. Sie stehen hinter Kacheln und Kopfzeilen, und dort soll nichts wechseln. **Und der Abspann zählt als Schluss.** Eine Serie hängt sechs Minuten Abspann an eine Folge von fünfundvierzig; neunzig Prozent liegen dann mittendrin, und die Folge gilt erst als gesehen, wenn man ihn sich ganz ansieht. Die Schwelle herunterzudrehen hiesse, jeden anderen Titel zu früh abzuhaken. Der erkannte Abspann ist die genauere Antwort auf dieselbe Frage — er zieht die Schwelle nach vorn, nie nach hinten. Dazu der Verlauf über dem Aufmacher: er war immer noch zu dunkel. Jetzt trägt der Schattenwurf der Schrift, was vorher eine Decke über dem ganzen Bild tat. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
8cf2118723 |
Vorspann und Abspann — erkannt, abgelegt, überspringbar
Beides mit derselben Rechnung, nur in einem anderen Fenster: vorn die ersten fünfzehn Minuten, hinten die letzten zehn. Was `Tonabgleich` findet, hängt allein daran, welchen Ausschnitt man ihm gibt — eine zweite Erkennung für den Abspann wäre dieselbe Suche ein zweites Mal. Je Staffel verglichen, nicht je Serie: Serien wechseln ihren Vorspann zwischen Staffeln, und über alle hinweg fände man nichts oder das Falsche. Gefundenes über fünf Minuten wird verworfen — zwei Folgen, die dieselbe Datei tragen, teilen vierzig, und darüber stünde sonst ein Überspringen-Knopf. Der Lauf geht auf Zuruf, nicht bei jedem Scan: er liest von jeder Folge ein Stück Ton und dauert Minuten. Der Knopf dafür steht auf der Serienseite, bei den Verwalterknöpfen. Die Marken gehen im Folgenvertrag mit — als „intro" und „credits", die Wörter, die die Apps von Jellyfin her ohnehin kennen. In der App zeichnet sie der vorhandene Überspringen-Knopf, der bisher nie etwas zu tun hatte; im Web ist er neu, rechtsbündig über der Zeitleiste. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
84612335ec |
Sprachwahl in der App — bei Serien und bei Filmen
Auf der Serienseite stand ein Menü für den Ton, gefüllt mit zwanzig Sprachen, von denen die Serie zwei hatte. Für Untertitel gab es keines, und auf der Filmseite überhaupt nichts. Jetzt beides an beiden Orten, und zwar nur mit dem, was es wirklich gibt: Bei einer **Serie** über den neuen Endpunkt — die Schnittmenge über alle Folgen. Was dadurch wegfällt, steht als Satz darunter, sonst sähe eine gekürzte Liste aus wie die ganze Wahrheit. Bei einem **Film** aus den Spuren der Datei. Kein Serverweg nötig: dort ist die Datei der Titel, und ihre Spuren stehen in der Antwort, die die Seite ohnehin geholt hat. Ton und Untertitel teilen sich einen Typ. Sie unterscheiden sich in genau zwei Punkten — woher der Wert kommt und ob es „Keine" gibt; ein zweiter Typ wäre dieselbe Datei mit zwei geänderten Zeilen. „Keine" gibt es nur beim Untertitel: ein Film ohne Tonspur ist kein Wunsch, sondern ein Fehler. Die Untertitelsprache liegt je Titel, in einem Topf für Serien und Filme — eine Filmkennung kollidiert mit keiner Serie, und zwei Wörterbücher für dieselbe Frage liefen auseinander. Der Spieler liest sie jetzt: vorher kannte er nur die allgemeine, und eine Serie im Original mit deutschen Untertiteln liess sich nicht einstellen, ohne sie überall einzuschalten. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
1bcf6f12e5 |
Die App wartete auf eine fertige Datei, statt zu streamen
Gleichzeitig im Web und in der App auf Abspielen getippt: das Web lief,
die App lud dreissig Sekunden. Das war keine Ladezeit, sondern die Zeit,
die der Server braucht, um den ganzen Film einmal durchzuschreiben.
`preparePlayback` kannte nur `/files/{id}/download`. Muss die Datei
umgepackt werden, fragte es im Sekundentakt nach, bis die vollständige
MP4 auf der Platte lag — Grenze zehn Minuten. Der Endpunkt, den das Web
benutzt, ist am Server längst da; die App wusste nur nichts davon.
Jetzt zuerst `/files/{id}/stream`. Das erste Stück ist beim Umpacken in
deutlich unter einer Sekunde da. Der alte Weg bleibt als Rückfall: er ist
langsam, kommt aber ohne Sitzung aus, und eine späte Wiedergabe ist
besser als keine.
**Der Strom beginnt dort, wo man weiterschauen wollte**, und zählt
trotzdem ab null. Ohne das mitzurechnen stünde bei „Fortsetzen ab 1:32"
eine Null auf der Uhr — und genau die ginge als Fortschritt an den
Server. Also: die Zeit der Engine plus Vorlauf ist die Zeit des Films,
und der Spieler springt nicht noch einmal dorthin, wo der Server schon
steht.
**Ein Sprung aus dem Strom heraus baut ihn neu auf.** Die Abspielliste
wächst von der Einstiegsstelle vorwärts; davor und dahinter gibt es
nichts, und ein Sprung ins Leere liesse den Spieler mit einem Kreisel
stehen. Innerhalb des Erzeugten springt er weiterhin selbst — dafür eine
neue Sitzung zu starten hiesse, Sekunden zu warten, um fünf Sekunden
zurückzugehen.
**Und die Sitzung wird geschlossen.** `endPlayback` war eine leere
Methode; das passte, solange es keine Sitzung gab. Mit dem Strom läuft
sonst ein ffmpeg weiter und schreibt den Film zu Ende, während längst
niemand mehr zusieht.
Die Laufzeit kommt weiter aus den Metadaten und nicht aus der Engine:
eine wachsende Liste meldet anfangs ein paar Sekunden, und als Filmlänge
genommen stünde der Regler sofort am Ende.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
e30158b84c |
Eine Sekunde heisst nicht „Weiterschauen"
Die Schwelle aus den Einstellungen wirkt serverseitig, also sofort überall: `/home/continue` liest die gespeicherte Spalte. Eine Stelle blieb trotzdem widersprüchlich — die **Beschriftung**. Web und Apps entschieden „Weiterschauen" gegen „Abspielen" selbst, allein daran, ob eine Stelle über null steht. Nach einer Sekunde stand der Titel damit nicht mehr in der Reihe, hiess aber weiter „Weiterschauen", trug einen Fortschrittsbalken unter der Kachel und bot „Aus Weiterschauen entfernen" an. Der Server hatte diese Frage längst beantwortet. `ItemStateResponse` bringt sie jetzt mit; `MediaItem.isResumable` und `.resumeProgress` sind die einzigen Stellen, die sie noch stellen. Im Simulator nachgeprüft: nach einer Sekunde heisst der Knopf „Abspielen", „Von vorn" fehlt, und der Balken unter der Kachel ist weg. Kennt ein Server das Feld nicht — Jellyfin, oder ein älterer Flaro —, gilt wie früher die blosse Stelle. **Und das Profilbild steht in den Apps.** `FlaroAccount` führt es mit, `/auth/me` bringt es bei jedem Start frisch, und der Buchstabenkreis tritt zurück, sobald eines da ist. Jellyfin bleibt beim Buchstaben — dort gibt es kein Profilbild, und das ist keine Lücke, sondern der Stand der Dinge. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
10a5070e58 |
Eine Folge ist weder ein Film noch eine Serie
Die App meldete auf der Startseite „Dieser Titel ist auf dem Server nicht
mehr vorhanden" — über eine Folge, die sie selbst gerade anzeigte.
Gemessen im Quelltext: `item(id:)` probiert `/movies/{id}`, dann
`/series/{id}`, und wirft sonst `.notFound`. In „Weiterschauen" steht
eine Folge mit ihrer eigenen Kennung, und einen Endpunkt für eine
einzelne Folge gab es gar nicht. Jetzt gibt es `GET /api/v1/episodes/{id}`
— mit `SeriesId`, denn ohne die Serie fehlen der Folge Titel und Bild.
Im Simulator nachgestellt und nachgeprüft: die Seite steht.
**Zuletzt hinzugefügt.** Die App zeigte „Filme" und „Serien" — den Anfang
der Mediathek in der Reihenfolge der Datenbank, also immer denselben.
Jetzt je Mediathek eine Reihe „Neu in …", sortiert nach Zugang. Die
Kennungen beginnen nun mit `latest`, und daran hängen zwei Einstellungen,
die an einem Flaro-Server deshalb nichts taten: „Gesehenes in Neuzugängen
ausblenden" und „Aufmacher nur aus Neuzugängen".
**Geplante Titel.** `MediaItem` kennt jetzt `availableFrom`. Aufmacher und
Detailseite zeigen statt des Knopfes „Bald ist es soweit" mit dem Termin.
Die Kachel spielte trotzdem los — im Simulator kam der Spieler bis „Wird
vorbereitet …" —, also zählt ein geplanter Titel jetzt wie eine Sammlung:
nichts zu starten, der Tipp führt auf die Seite.
**Und das Bild im Aufmacher.** Bei einer angefangenen Folge stand dort ihr
Standbild statt des Bildes der Serie; wer der Serie eines ausgesucht
hatte, fand ein anderes vor. Die Kachel behält das Standbild — sie zeigt,
wo man steht —, der Aufmacher bekommt `SeriesBackdropImage`.
**Die Punkte im Karussell waren 26 × 3 Pixel.** Von drei Klicks auf
denselben Punkt traf nur der auf der Mittellinie; fünf Pixel daneben
geschah nichts. Der Knopf ist jetzt 27 Pixel hoch, sichtbar bleiben 3.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
172b38da06 |
Die Apps können jetzt beides: Jellyfin oder Flaro
`FlaroServerKit` steht neben `JellyfinKit`, nicht darin — genau auf dem Steckplatz, den Plex geräumt hat. Dass `MediaBackend` zwei Server zugleich trägt, war schon einmal bewiesen; die Naht hat gehalten, und keine einzige Ansicht musste dafür neu geschrieben werden. `ServerKind` ist zurück. Es wurde beim Entfernen von Plex gestrichen, weil ein Aufzählungstyp mit einem Fall ein umständliches Bool ist. Jetzt gibt es wieder zwei, und es ist wieder eine Unterscheidung — einer zur Zeit, nicht beide nebeneinander. **Flaro steht oben auf dem Anmeldebildschirm.** Nicht aus Eitelkeit: dort bringt ein Download alle Tonspuren mit und der Fortschritt wird ab der ersten Sekunde gemerkt. Wer Jellyfin benutzt, sucht gezielt danach — wer neu anfängt, soll nicht in der schwächeren Hälfte landen, weil ihr Knopf auffälliger war. Am Fernseher steht bei beiden der sechsstellige Code voran. --- **Der Zugang geht als Cookie, nicht in der Adresse.** Jellyfin hängt sein Token an jede Bildadresse, weil eine Bildansicht keine Kopfzeilen schickt; es steht damit in jedem Zugriffsprotokoll und jedem weitergegebenen Link. Flaro tut das nicht, und deshalb legt der Client sein Token selbst im gemeinsamen Keksspeicher ab — auch beim Wiederaufnehmen aus dem Schlüsselbund, wo gar keine Anmeldung stattfindet und der Server es nie setzen könnte. `URLSession` schickt es von allein mit; **AVFoundation nicht**. Der Systemplayer bekommt die Kekse jetzt ausdrücklich. Serverneutral: wo keine liegen, ist die Liste leer. **`BackendCapability.allTracksDownload`** statt einer Spurwahl, die ins Leere führt. Die Frage „welche Tonspur?" gibt es nur, weil Jellyfins Transcoder eine je Sitzung abbildet. Bei Flaro führen beide Antworten zum selben Ergebnis — also wird nicht gefragt, und das Umpacken mit einer einzigen Spur entfällt gleich mit. **Die Adressraterei wandert nach `MediaKit`.** Sechzig Zeilen sorgfältig begründeter Reihenfolge — IP-Adressen sprechen selten HTTPS, Domänennamen fast immer — unterscheiden sich zwischen beiden Servern nur im Standardport. Zweimal hingeschrieben wäre die eine Fassung beim nächsten Sonderfall nachgezogen worden und die andere nicht. Jellyfins Prüfungen laufen unverändert gegen die verschobene Fassung; das ist der Beweis. **Nebenbei zwei Altlasten:** `player.jellyfinPending` war unerreichbar — der Player zeigt diese Meldung nur, wenn gar kein Server da ist, dann aber nie die mit Jellyfin. Und `signIn.jellyfin.userName` hiess „Benutzername"; der Schlüssel heisst jetzt so neutral wie der Text. --- **Der Fehler, den erst der Simulator zeigte:** „Stelle wird wiederhergestellt", 0:00, für immer. Muss der Server umpacken, antwortet er auf den Download mit 202 und fängt an. AVPlayer bekommt darauf kein Video, sondern eine leere Antwort — und wartet, unbegrenzt, ohne Fehler. Die Weboberfläche fragt in einer Schleife nach; `preparePlayback` tat es nicht. Jetzt wartet es, mit Obergrenze und einer Begründung, wenn sie reisst. Mit Prüfung, die zwei Runden erzwingt. 413 Tests grün. Im Simulator gegen den echten Server nachgewiesen: Adresse eintippen, Server gefunden, angemeldet, Mediathek mit Plakaten, Neustart ohne erneute Anmeldung — und eine MKV abgespielt, die der Server dafür erst umpacken musste. Danach stand auf dem Server: Matrix, 2,0 s von 2,0 s, gesehen, ein Durchlauf. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |