Reine Umbenennung, kein Verhalten geändert — bewusst als eigener Commit,
damit „umbenannt" und „umgebaut" im Nachhinein auseinanderzuhalten sind.
712 mal `Flixy`, 778 mal `flixy`, 7 mal `FLIXY` in 169 Dateien, dazu 25
Pfade. Ziele: `Halo`, `HaloTV`, `HaloTopShelf`, `HaloUITests`, `HaloKit` mit
`HaloDesign`, `HaloDownloads`, `HaloPlayback` und `HaloProbe`. `MediaKit`,
`JellyfinKit` und `DownloadWidget` tragen den Namen nicht und bleiben.
`docs/superpowers/specs/` ist ausgenommen. Datierte Entwurfsdokumente sind
ein Protokoll getroffener Entscheidungen; sie nachträglich umzuschreiben
hiesse, die Vergangenheit zu glätten.
**Die Bundle-ID wandert mit** — ausdrücklich so gewollt, mit bekanntem Preis:
`com.scarriffleservices.halo` ist für iOS eine *andere* App. Die installierte
Fassung bleibt daneben stehen, mit Downloads und Anmeldung in ihrem Container;
Halo startet leer. Einmal neu anmelden, heruntergeladene Folgen einmal neu
laden. Dasselbe gilt für die App-Gruppe, die sich TV-App und Top Shelf teilen.
**Eine Folge davon, die beim Entscheiden noch nicht auf dem Tisch lag:** der
Aufräumschritt aus dem vorigen Commit kommt an die alten Plex-Token nicht mehr
heran. Der Schlüsselbund grenzt nach Team und Bundle-Kennung ab — was unter
`…flixy` liegt, ist für `…halo` unerreichbar. Erledigt wird es trotzdem, nur
anders: **die alte App löschen.** iOS räumt deren Schlüsselbund-Einträge beim
Deinstallieren mit ab. Der Code bleibt stehen, weil er für jeden Stand vor
dieser Umbenennung richtig ist und im Jahr einen Startvergleich kostet.
Die Projektdatei ist aus `project.yml` neu erzeugt, der alte Ordner gelöscht
statt umbenannt — sie steht ohnehin nicht im Repo.
Das Verzeichnis des Repos selbst heißt weiter `Flixy`; das umzubenennen ist
ein Handgriff ausserhalb des Projekts.
368 Tests grün. iOS und tvOS bauen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Während der Player auf Daten wartete, stand im mittleren Knopf das
Abspieldreieck und der Ladekreis lag dahinter. Das las sich als
„angehalten": man drückte auf Abspielen, und es geschah nichts — es lief
ja bereits, es kam nur nichts an.
Jetzt sitzt der Kreisel **im** Knopf, dort wo sonst das Symbol steht. Der
freistehende Kreisel in der Bildmitte bleibt für den Fall, dass die
Bedienelemente ausgeblendet sind. Zwei übereinander gibt es nicht mehr.
`togglePlayPause` zählt das Warten jetzt zum Laufen. Vorher führte ein
Tipp in diesem Moment zu `play()` auf etwas, das schon spielte.
Dazu auf dem Mac, wie im Browser auch:
- Ein Klick ins Bild hält an, statt erst die Bedienelemente zu holen. Die
kommen dort von allein, sobald sich der Zeiger regt.
- Leertaste hält an, Pfeil links und rechts springen.
Mit dem Finger bleibt es beim Alten: ohne Zeiger gibt es keine Bewegung
ohne Berührung, und ein Tipp, der sofort anhält, nähme den einzigen Weg
zu Scrubber, Tonspur und Qualität.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sie hingen an einem verschluckten catch. Beim Suchen nach dem verlorenen
Fortschritt war deshalb nirgends zu sehen, ob sie überhaupt ankommen —
ohne await zu senden ist richtig, ohne Hinsehen nicht.
Jetzt steht in der Konsole, was angenommen und was abgelehnt wurde.
Dazu ein Hinweis auf die Regel des Servers, die den eigentlichen Fall
erklärt: Jellyfin merkt sich eine Fortsetzposition nur zwischen fünf und
neunzig Prozent der Laufzeit. Eine halbe Minute von einem Film liegt
darunter, und der Server verwirft sie bewusst — "kurz reingeschaut" ist
kein Weiterschauen. Wer das nicht weiß, sucht den Fehler bei sich.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Beim Schließen wurde das Video abgeräumt, und dabei fiel currentTime auf
null — bevor die Stopp-Meldung hinausging. Jellyfin bekam "gestoppt bei
0:00" und löschte den Fortschritt. Der Film war zur Hälfte gesehen und
stand trotzdem nicht unter "Weiterschauen".
Jetzt wird die Stelle getrennt vom Element gehalten und die Meldung
gesendet, bevor abgeräumt wird. Nachgemessen: bei 45 Sekunden
geschlossen, 45 Sekunden gemeldet. Dazu PlayMethod und CanSeek, ohne die
Jellyfin die Meldung keiner Art von Wiedergabe zuordnet, und sofortige
Meldung bei Anhalten und Weiterlaufen — wer pausiert und weggeht, hatte
sonst bis zur nächsten Zehn-Sekunden-Marke nichts hinterlassen.
Und das eingefrorene Bild bei laufendem Ton nach einem
Anwendungswechsel: Der Browser hält das Entpacken an, während der Tab
verdeckt ist, und hls.js findet danach nicht von selbst in den Puffer
zurück. Jetzt wird beim Zurückkommen an derselben Stelle neu angestoßen,
mit einem winzigen Sprung — ohne den bleibt das zuletzt gezeigte Bild
stehen, obwohl längst wieder Segmente ankommen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ich habe nach dem Code gebaut und dabei das Wesentliche übersehen: In
PlayerView gibt es genau ein "background", und das ist die Abdunklung
über dem ganzen Bild. Die Knöpfe selbst sind blanke weiße Symbole.
Meine runden, hinterlegten Flächen waren eine Erfindung — und der Grund,
warum der Qualitätsknopf schief aussah: ein Wort passt in keinen Kreis.
Jetzt steht "Original" als das da, was es ist, nämlich Text.
Dazu die Spultasten als Kreispfeile mit der Sekundenzahl darin, wie
gobackward.10 und goforward.30 sie zeichnen, statt als "−10" und "+30"
in Textform. Und der Abspielpfeil in 52 Punkt, der Größe aus der App.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Der strukturelle Unterschied, den ich übersehen hatte: in der iPad-App
liegt unten nichts als der Regler über die volle Breite, darunter Zeit
links und Restzeit rechts. Bei mir standen Zeit, Regler und Restzeit
nebeneinander — der Regler wirkte dadurch wie ein Formularfeld zwischen
zwei Beschriftungen — und darunter lag noch eine Werkzeugleiste mit
Lautstärkeregler und Tempo-Auswahlfeld, die es in der App gar nicht gibt.
Alles Einstellbare sitzt jetzt oben rechts beisammen: Tonspur,
Untertitel, Qualität, Geschwindigkeit, Ton, Vollbild. Das Tempo als
Liste wie die anderen; ein Auswahlfeld mitten in der Bedienleiste war das
einzige Bauteil, das nach Formular aussah. Die Lautstärke über Mausrad
auf dem Tonknopf und über die Pfeiltasten.
Und ein Fehler, der genau das verhinderte, was gewünscht war: Die
Bedienfläche lag als Ganzes über dem Bild und schluckte jeden Klick.
"Ins Bild tippen hält an" funktionierte damit nur, solange die Bedienung
unsichtbar war — also fast nie. Jetzt nehmen nur ihre Knöpfe Klicks
entgegen. Nachgemessen: Klick pausiert, Pfeil rechts spult 30 Sekunden.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Mein Menü machte genau den Fehler, den der iPad-Code im Kommentar
ausschließt: alles in einer Liste. Dort steht, warum das falsch ist —
"die Tonspur stellt man einmal am Anfang um, die Untertitel mitten im
Film. Ein gemeinsames Blatt bedeutete jedes Mal Scrollen an der anderen
Liste vorbei."
Jetzt drei Knöpfe in der Kopfzeile, jeder mit seiner eigenen Liste
darunter, und jede Liste hängt sichtbar an ihrem Knopf. Die Tonspur
erscheint auch dann, wenn es nur eine gibt — welche läuft, will man
wissen, nicht nur ob man wechseln kann. Die Untertitel-Taste ist
gefüllt, solange welche laufen.
Die Qualität steht als Text im Knopf, nicht als Symbol. Auch das aus dem
iPad-Player übernommen: "Drei Schieberegler sagen nicht, was eingestellt
ist; '1080p' schon — und genau das ist die Frage, wegen der man hintippt."
Dazu eine gleichmäßige Abdunklung statt zweier Verläufe an den Rändern.
Die Verläufe ließen die Mitte frei — wo die Transportknöpfe stehen, die
damit auf hellen Szenen nichts hinter sich hatten.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sie standen bisher nur in den Einstellungen — also dort, wo man sie nicht
braucht. Man merkt beim Schauen, dass der Ton die falsche Sprache hat,
nicht vorher.
Jetzt ein Menü über der Bedienleiste, nicht als Blatt: man stellt hier
etwas ein, das man am Bild beurteilen will, und ein Blatt verdeckt genau
das. Die Auswahl kommt vom Server — welche Spuren es gibt, weiß man erst
nach dem Aushandeln, und seine Vorauswahl gilt beim ersten Lauf.
Anders als die App, die im Lauf umschaltet, muss der Browser neu
aushandeln: bei einem umgerechneten HLS-Strom bestimmt der Server beim
Aushandeln, was drin ist. Also wird neu geladen — und die Stelle
mitgenommen, sonst begänne jeder Sprachwechsel den Film von vorn.
Nachgemessen: bei 27:30 gewechselt, bei 27:30 weiter.
Dabei zwei Dinge mitgenommen, die sonst später weh getan hätten: der
Fehler-Horcher wird nur einmal angehängt (sonst meldete sich nach zwei
Wechseln ein einziger Fehler dreimal), und ein früherer HLS-Strom wird
abgeräumt, bevor der nächste anläuft.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Zwei Dinge.
Erstens: Symbole sind jetzt Vektoren, keine Schriftzeichen. Ein Lautsprecher
als Emoji wird auf jeder Plattform anders gezeichnet, trägt fremde Farben,
sitzt schief auf der Grundlinie und lässt sich weder in Strichstärke noch
Farbe anpassen — es sieht nach Platzhalter aus, weil es einer ist. Elf
Stellen umgestellt, die Pfade stehen an einer Stelle beisammen.
Zweitens, und das ist der Grund, warum nichts lief: Nennt der Server keine
eigene Adresse, fiel der Code auf "static=true" zurück — also auf die Datei,
wie sie auf der Platte liegt. Bei einem MKV mit DTS ist das für einen Browser
unlesbar, und er meldet bloß "Format nicht unterstützt". Der Rückfall tat
genau das Gegenteil dessen, was er sollte. Jetzt verlangt er master.m3u8,
also ausdrücklich einen HLS-Strom.
Damit so etwas nicht wieder im Dunkeln liegt, nennt die Fehleranzeige nun
auch, was der Server geantwortet hat und welche Adresse verwendet wurde —
der Zugangsschlüssel darin gekürzt.
Dazu: während des Ladens treten die Transportknöpfe zurück. Der Kreisel lag
vorher genau hinter dem größten von ihnen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Zwei berechtigte Beschwerden auf einmal.
Das war kein Player, sondern das nackte <video controls> des Browsers in
einem Blatt — ein eingebettetes Vorschaufenster. Jetzt Vollbild mit
eigener Bedienung: Titel und Folge oben, Spulen und Anhalten in der
Mitte, Fortschritt mit Restzeit unten, dazu Lautstärke, Tempo und
Vollbild. Die Bedienung blendet nach drei Sekunden Ruhe aus und kommt
bei jeder Bewegung zurück; steht das Bild, bleibt sie stehen — sie
auszublenden, während niemand spielt, versteckt den einzigen Knopf, der
weiterhilft. Tastatur: Leertaste, Pfeile, Escape.
Dunkle Knöpfe statt hell durchscheinender: über einer sonnigen Szene
verschwanden helle vollständig. Und die Sekundenzahl steht als Text, statt
im Spulpfeil zu stecken — übereinander war beides unlesbar.
Und bei einer Serie "spielt man nicht ab" zu antworten war Unsinn. Ich
hatte den Knopf entfernt, statt ihn richtig zu machen. Jetzt heißt er
"Weiterschauen" und startet die Folge, bei der man stehengeblieben ist —
über dieselbe Frage, die auch die Startseite stellt. Gibt es keine, wird
es die erste.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Es fehlte der erste Schritt. Die Stream-Adresse wurde selbst
zusammengebaut, und damit fehlten PlaySessionId, die ausgehandelte
MediaSourceId und die Adresse, die der Server eigentlich liefert. Im
Swift-Code steht seit jeher, warum das nicht geht: "eine selbst
zusammengebaute Adresse wäre eine Vermutung darüber."
Jetzt vier Schritte wie in der App: PlaybackInfo mit Geräteprofil, die
Quelle aus der Antwort wählen, die Adresse des Servers nehmen, die
Sitzungskennung mitführen.
Vor allem aber sind Fehler nicht mehr unsichtbar. Vorher endete jeder
Fehlschlag in einem stummen schwarzen Kasten — ein 401 sah aus wie ein
404 und wie ein Netzproblem. Geprüft: Ablehnung, fehlende Quelle und
nicht abspielbares Format nennen jetzt alle drei Ross und Reiter.
Serien und Staffeln haben keinen Abspielknopf mehr. Sie sind Ordner;
/Videos/{serienId} zeigt auf nichts. Wer über eine Serienkachel getestet
hat, konnte gar nichts starten.
Dazu Fortschrittsmeldung an /Sessions/Playing — ohne die erscheint der
Titel auf dem Server nie als "läuft gerade", und "Weiterschauen" bliebe
im Web für immer leer.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Phase 3 des Abgleichs. Dieselbe Rechnung wie Arrangement in der App,
samt ihrer wichtigsten Eigenschaft: unbekannte Reihen landen am Ende,
statt zu verschwinden. Sie zu verwerfen wäre die einfachere Regel — und
der Grund dafür, dass eine neu angelegte Mediathek nie aufgetaucht wäre.
Ausgeblendete stehen in den Einstellungen weiter unten in der Liste,
durchgestrichen. Ohne das könnte man sie nie wieder einblenden.
Knöpfe statt Ziehen: auf dem Zeigegerät ist Ziehen schöner, auf einem
Trackpad fummelig und mit der Tastatur gar nicht zu machen. Zwei Pfeile
können alle drei.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ein reiner Weg von A nach B sah aus, als würde ein Kasten verschoben.
Jetzt zwei Züge: die Leiste zieht sich erst in sich zusammen und zieht
dann erst um — wie ein Gummiband, das sich sammelt, bevor es schnellt.
Welche Richtung sich zuerst sammelt, hängt davon ab, wohin es geht. Wer
nach oben zieht, wird flach, also klappt zuerst die Höhe zusammen; wer
an die Seite zieht, wird schmal, also zuerst die Breite. Die
Zwischenstation steht deshalb noch am alten Ort, aber schon im Zielmaß
der einen Seite.
Nachgemessen: bei 220 ms ist die Breite von 2.27 auf 1.38 gefallen und
die Position unverändert — sie sammelt sich, ohne zu wandern. Bei 380 ms
ist sie unterwegs und zieht sich auf.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Phase 2 des Abgleichs. "Plakate oder Querformat" fliegt raus — das ist
keine Einstellung, sondern eine Entscheidung, die die App bewusst selbst
trifft: Angefangenes erkennt man am Szenenbild wieder, Neues sucht man
nach Plakaten aus. Ich hatte sie zur Wahl gemacht, ohne dass jemand
danach gefragt hätte.
Dafür die Abschnitte in der Reihenfolge der App — Darstellung,
Startseite, Server — und die eigene Akzentfarbe als Hex-Feld, mit
derselben Kontrastrechnung nach WCAG 2.1. Nachgerechnet: ein dunkles
Marineblau landet auf genau 4.5, dem Grenzwert, und eine Farbe, die ihn
ohnehin erreicht, bleibt unberührt. Gesagt wird es auch — eine Farbe
stillschweigend zu verändern wäre schlechter, als sie abzulehnen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Der erste Versuch klappte die Spalte nach oben weg und schob die Insel
seitwärts herein. Das sind zwei Leisten, von denen eine geht und eine
kommt — Mac und iPad lassen dieselbe umziehen, und man verfolgt sie
dabei mit den Augen.
Jetzt FLIP: alte Lage messen, umbauen, neue Lage messen, und das Element
vom alten in den neuen Zustand fahren lassen. Die Größe fährt mit, denn
die Insel ist oben breit und flach und an der Seite schmal und hoch; ein
Sprung in der Form mitten in einer ruhigen Bewegung fällt mehr auf als
gar keine Bewegung. Über CSS allein ginge das nicht — "right: auto" und
"bottom: auto" lassen sich nicht animieren, und genau die wechseln hier.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Nachgebaut nach der App: die Spalte klappt nach oben weg und kommt von
oben zurück, die Insel schiebt sich seitwärts hinaus und herein. Der
Inhalt rückt gleichzeitig nach — ohne das bliebe links ein Loch stehen,
bis die Leiste angekommen ist.
Dazwischen eine Pause von gut zwei Zehnteln: ohne sie tauscht der
Browser die Leiste in einem Bild aus, und vom Umzug ist nichts zu sehen.
Dazu gestaffelt einblendende Reihen und Kacheln, die sich beim
Überfahren leicht anheben. Wer "Bewegung reduzieren" eingeschaltet hat,
bekommt von alledem nichts.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Wie auf dem iPad: ein Knopf in der Leiste selbst — an der Seite oben in
der Ecke, oben ganz links. Ihn in die Einstellungen zu verbannen hieß,
für einen Handgriff zwei Ebenen tief zu gehen.
Dazu die Einstellungen, die bisher aus vier Zeilen bestanden: Aufmacher
(Quelle, Anzahl, Höhe), Reihen (Weiterschauen zusammenlegen, Gesehenes
ausblenden, Plakate oder Querformat), Kachelgröße, Akzentfarbe, Server.
Sie wirken auch — die Startseite baut sich danach auf.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Er war nun einmal der erste in "Weiterschauen", und genommen wurden
schlicht die ersten fünf der Liste. Jetzt wird gezogen — aber nicht bei
jedem Blick: der Zufall hängt an einem Zeitfenster von sechs Stunden.
Wer die App dreimal am Abend öffnet, findet sich zurecht; wer am
nächsten Morgen wiederkommt, sieht etwas anderes.
Titel ohne Hintergrundbild sind dabei nicht ausgeschlossen, sondern
ziehen ein schlechteres Los. Sie ganz auszusperren war zu hart — in
einer Mediathek, in der wenige Titel eines tragen, blieben immer
dieselben drei übrig.
Dieselbe Regel im Web, damit beide nicht auseinanderlaufen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Drei Dinge aus einer Runde Rückmeldungen.
Die Detailseiten forderten ihr Bild in 900×1200 an — hochkant — und
zeigten es in einem 16:9-Rahmen; zu sehen war ein Streifen aus der
Mitte. Derselbe Fehler war im Aufmacher schon einmal behoben worden, nur
hier nicht. Beide gehen jetzt über HeroArtwork, wo die richtigen Maße
schon stehen.
Im Web hingen die Karussellpunkte neben dem Aufmacher, das CSS zielte
aber hinein — sie waren unsichtbar. Jetzt sind sie da, anklickbar, und
es gibt Pfeile, Wischen und Pfeiltasten dazu. Wer selbst blättert, hält
das Weiterwechseln an: sonst springt der Aufmacher genau in der Sekunde,
in der man nach "Abspielen" greift.
Dazu Seitenleiste und Einstellungen fürs Web — Akzentfarbe aus derselben
Palette wie die App, Navigation oben oder seitlich, und ein Regler für
die Kachelgröße. Das Filmlogo ist um zehn Prozent gewachsen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Eine Datei, die gegen Jellyfin spricht: Anmeldung, Startseite mit
Aufmacher und Reihen, Mediathek als Raster, Wiedergabe über HLS. Die
Endpunkte sind aus JellyfinKit übernommen, nicht geraten — inklusive der
Anführungszeichen in der Authorization-Zeile, ohne die Jellyfin die Zeile
falsch zerlegt.
Gestaltet aus denselben Werten wie die App: Flächen, Radien, Abstände aus
FlixyDesign. Die Navigationsinsel schwebt über einem randlosen Aufmacher
und fährt beim Scrollen weg — dieselbe Entscheidung wie dort.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>