12bfc9e5304164124b92a45268ed3240906b696f
21
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
12bfc9e530 |
Abspielen am Fernseher — mit Apples Player und unseren Spuren
Schritt sieben. Wiedergabe, Spurwahl, Titelzeile, „Intro überspringen". **Die schwierige Stelle war die Spurwahl.** Unsere Ton- und Untertitelwahl geht als `audioStreamIndex` an Jellyfin und verlangt bei einem Transcode einen **neuen Stream**. Apples eingebauter Spurwähler zeigt dagegen die Auswahlgruppen des **laufenden** Streams — beim Transcode genau eine. Er ist die richtige Oberfläche für die falsche Liste. Unsere Menüs sitzen deshalb über `transportBarCustomMenuItems` daneben; `customInfoViewControllers` wäre falsch, denn die Tonspur ist ein Bedienelement der Wiedergabe und kein Begleitmaterial. **Der Neuaufbau wird angesagt, nicht erklärt.** Jeder Eintrag, der Sekunden mit schwarzem Bild kostet, trägt das als Untertitel — vorher, nicht hinterher. Wer drückt, sieht den Haken sofort wandern, weil das Menü sich schliesst und darin kein Platz für einen Kreisel ist. Das schwarze Bild deckt eine Überlagerung in `contentOverlayView` zu, die hinter den Bedienelementen liegt und keinen Fokus nimmt — `customOverlayViewController` täte beides nicht und machte die Fernbedienung währenddessen tot. Und solange umgebaut wird, sind alle Einträge gesperrt: sonst räumt `restart()` eine Engine ab, die eine andere gerade baut. **Die Regel dahinter ist jetzt prüfbar.** „Wann kostet ein Spurwechsel einen Neuaufbau" lag bisher mitten im Vollzug von `selectAudioStream` und liess sich nur an einem echten Server mit mehreren Tonspuren auslösen. Als `TrackMenu` steht sie neben den übrigen Entscheidungsregeln, mit sieben Tests — darunter der Fall, auf den der Kommentar dort seit jeher hinweist: stimmt die Spurzahl nicht überein, ist die Zuordnung über die Reihenfolge falsch, und es muss neu aufgebaut werden. **Zwei stille Fehler nebenbei.** Die Tonsitzung stand hinter `#if os(iOS)` und wäre auf tvOS nie gesetzt worden — verfügbar ist sie dort seit 9.0, man hätte es nur daran gemerkt, dass sich die Wiedergabe gegen anderen Ton nicht durchsetzt. Und `PlayerModel.downloads` ist jetzt optional statt eines leeren Koordinators: ein leerer bräuchte einen `ModelContainer`, und der ist das Einzige, wofür diese App SwiftData benutzt. **Belegt statt vermutet.** Im Simulator lief die Wiedergabe gegen die Attrappe, und die Titelzeile zeigte „Unbekannt". Statt an AVKit zu drehen, hat ein Protokolleintrag die Frage beantwortet: `externalMetadata` wird gesetzt, der Wert stimmt, und „Unbekannt" ist wörtlich das, was die Attrappe für unbekannte Kennungen liefert. Geblieben ist davon nur die Erkenntnis, dass die Beschriftung auf das Item warten muss — beim Austausch des Players gibt es noch keines. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
4323c47db6 |
Schneller anfangen, dann hinaufgehen
Es dauerte eine halbe Minute bis zum ersten Bild. Der Grund war nicht die Leitung, sondern der Server: „Auto\" begann mit acht Megabit, und das heisst 1080p von der ersten Sekunde an. So lange braucht er, um das zu rechnen. Jetzt wird mit drei Megabit begonnen — also 720p. Das laeuft in wenigen Sekunden an, sieht einen Augenblick lang weicher aus, und von dort geht es hinauf, sobald sich zeigt, dass die Leitung mehr traegt. Dabei steckt eine Falle, die den ganzen Gewinn aufgezehrt haette: der Server baut seine Leiter aus der Bestellung. Wer mit drei Megabit anfaengt, bekommt eine Leiter, deren oberste Sprosse drei Megabit ist — und der billige Hebel bewegt sich nur **innerhalb** dieser Leiter. Aus dem schnellen Start waere ein Film geworden, der den ganzen Abend in 720p bleibt. Hoeher geht es deshalb ueber einen Neuaufbau, und der braucht Regeln: nicht zu frueh, nicht nach einem Ruckler, nicht ohne Vorrat, nicht oefter als zweimal. Sie stehen in `UpgradeRule` — reiner Typ, dreizehn Tests, wie beim Absenken. Und deutlich zurueckhaltender als dort: ein zu spaetes Absenken bedeutet Ruckeln, ein zu spaetes Hinaufschalten ein etwas weicheres Bild. Das erste ist ein Fehler, das zweite eine Geschmacksfrage. Bei Live-TV wird nicht hinaufgegangen: dort kostet jeder Neuaufbau den Tuner, und ein schaerferes Bild ist diesen Einsatz nicht wert. `StallLedger` kann jetzt sagen, wann der letzte Ruckler war — das brauchte die Regel, und „noch nie gestockt\" ist etwas anderes als „vor langer Zeit\". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
c681d69fcc |
Fünf Fehler auf einem Weg — warum nie ein Untertitel kam
Sechs Reparaturversuche haben nichts bewirkt, weil jeder an einer Stelle
ansetzte, die gar nicht die Ursache war. Eine systematische Untersuchung —
45 Hypothesen, jede von drei Skeptikern gegengeprüft — hat fünf voneinander
unabhängige Defekte auf demselben Weg gefunden. Jeder einzelne genügt.
**1. Es wurde eine Playlist bestellt, die keine Untertitelspur tragen kann.**
Jellyfins TranscodingUrl lautet `main.m3u8` — eine Media Playlist, also eine
Liste von Schnipseln. `#EXT-X-MEDIA`, die Zeile, mit der ein Server eine
Untertitelspur ankündigt, darf nach RFC 8216 nur in einer Master Playlist
stehen. Der Wechsel main→master hing aber an der Leiter, und die wird beim
Umpacken und bei jeder von Hand gewählten Stufe nicht bestellt. Ein Matroska
geht immer über das Umpacken — die halbe Sammlung konnte strukturell keine
Untertitel haben.
Der Kommentar daneben war überholt und hat den Fehler verdeckt: er stammte
aus der Zeit, als Umpacken eine fortlaufende MP4 war. Seit es über HLS läuft,
ist die Master-Liste genau richtig. Pfadwechsel und Leiter sind jetzt zwei
Fragen, nicht eine.
**2. Der lokale Server etikettierte jeden Schnipsel als video/mp4.** Auch die
WebVTT-Schnipsel der Untertitelspur. So angekündigt gehen sie bei
AVFoundation an den falschen Parser, finden keine Box, und es entsteht
lautlos keine Einblendung — die Spur steht im Blatt, ist ausgewählt, bleibt
leer. Der Vorrat behält jetzt die Sorte der Gegenstelle; die Endung ist nur
noch der Notnagel.
**3. Nach dem ersten Untertitel wählte niemand mehr etwas aus.**
`selectedSubtitleTrack` wurde beim Neuaufbau nie zurückgesetzt, und beide
Wähler prüfen genau darauf. Eine frische Engine hat aber nichts ausgewählt —
also blieb sie für den Rest des Films stumm, während das Symbol „ein" zeigte.
Auch das Nachfassen im Sekundentakt war damit stillgelegt. Die Bestellung
(`selectedSubtitleStream`) bleibt, die Merker fallen.
**4. Die Schutzmarke war nach 250 ms tot.** `restart()` setzte sie als
Allererstes — und wartete danach auf einen echten Netzaufruf mit fünf
Sekunden Zeitgrenze, bevor es den alten Ereignisstrom abbestellte. In diesem
Fenster meldete der alte Player weiter seine Stelle, und die Prüfung „sind
wir angekommen" traf sofort zu. Die Marke war gelöscht, bevor der neue Stream
bestellt war; danach setzte jede Meldung des anlaufenden Streams die Stelle
auf null, und der nächste Neuaufbau merkte sich den Filmanfang. Das war der
Sprung — und der Grund, warum mein Fix von
|
||
|
|
cff0c06745 |
Untertitel brachten den Vorratsspeicher auf 404
Untertitel stehen in der Abspielliste als eigene Spur unter EXT-X-MEDIA — eine Anweisung mit einer Adresse darin. Umgeschrieben habe ich bisher nur EXT-X-MAP, also blieb diese relativ stehen und zeigte danach auf den lokalen Server, wo es sie nicht gibt. Zu sehen war der Fehler erst, als jemand Untertitel einschaltete: HTTP 404, mitten im Film. Jetzt sagt der Umschreiber, **was** fuer eine Adresse er gerade in der Hand hat: eine Qualitaetsstufe, eine Nebenspur, ein Kopfstueck oder ein Schnipsel. Das ist noetig, weil sie Verschiedenes wollen — hinter einer Stufe liegt Bild und die soll vorgeladen werden, hinter einer Untertitel- spur nicht. Sonst lueden wir Untertitel statt Bild: der Film bliebe stehen, waehrend die Anzeige reichlich Vorrat meldet. Dazu wandern alle Adressen im lokalen Server auf die **vollstaendige** Form. Eine relative gilt je nach Liste gegen eine andere Grundadresse — die Untertitelspur liegt woanders als das Bild —, und wer das erst beim Abholen aufloest, loest es irgendwann gegen die falsche auf. Das waere der naechste Fehler derselben Art gewesen. Drei Tests halten die Unterscheidung fest, darunter die Liste mit Untertitelspur, die den 404 ausgeloest hat. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
0815432efd |
Untertitel lassen sich jetzt einstellen
Groesse in vier Stufen, Schrift und Hintergrund als fuenf feste Paare — Weiss, Weiss auf Schwarz, Schwarz auf Weiss, Gelb, Gelb auf Schwarz. Feste Paare statt zweier Regler, weil unabhaengige Achsen unlesbare Mischungen zulassen: Schwarz auf durchsichtig ueber einer Nachtszene ist eine gueltige Einstellung und eine wertlose. Die Uebersetzung nach AVFoundation ist eine Tabelle aus Schluesseln, bei der jeder Fehler lautlos ist — ein falscher Schluessel laesst den Initialisierer `nil` zurueckgeben, und dann gibt es gar keine Regel. Auf dem Bildschirm sieht „keine Regel\" genauso aus wie „Regel ohne Wirkung\". Zehn Tests decken das ab, darunter die drei Fallen: Alpha zuerst in der Farbe, Prozentwert statt Punktgroesse, und der Hintergrund auch fuer „durchsichtig\" ausdruecklich gesetzt statt weggelassen — weggelassen gilt sonst, was Jellyfins WebVTT selbst mitbringt, und das ist oft ein schwarzer Kasten. Als Protokollmethode, nicht als Feld an `PlaybackSource`: die wird einmal beim Oeffnen gelesen, und eine Einstellung, die erst beim naechsten Film wirkt, haelt jeder fuer kaputt. Angewandt wird an zwei Stellen — am laufenden Gegenstand und am frisch angelegten. Nur das erste zu tun hiesse, dass die naechste Folge wieder in der Vorgabe erscheint. VLC erbt die leere Vorgabe. Die Einstellungsseite sagt beides: dass es fuer den Standardplayer gilt, und dass eingebrannte Bilduntertitel von Blu-rays sich nicht gestalten lassen, weil es dort keinen Text gibt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
4b084bc926 |
Ein Sprung in den Vorrat laeuft jetzt sofort weiter
Drei Gruende, und der erste erklaert das ganze Aergernis. AVPlayer besteht nach einem Sprung darauf, erst seinen eigenen Puffer zu fuellen. Der stand auf sechzig Sekunden — richtig, solange er selbst vorladen sollte. Mit einem Speicher davor ist es das Gegenteil: springt man mitten in den Vorrat, liegt dahinter vielleicht noch eine halbe Minute, zu wenig fuer seine sechzig, und er wartet auf den Server, obwohl alles da ist. Der Abstand liegt jetzt bei uns; seiner darf klein sein. Der Vorrat holte ausserdem weiter Schnipsel hinter der Abspielstelle, wenn der Player vorausgesprungen war — und der eine, auf den gerade gewartet wurde, kam als letzter. Er macht jetzt dort weiter, wo gesprungen wurde. Und dieselbe Liste noch einmal zu holen setzte den Vorrat zurueck. Der Player holt sie staendig: nach einem Sprung, beim Stufenwechsel, von sich aus. Zurueckgesetzt wird nur noch bei einem echten Wechsel der Quelle. Dazu ein latenter Fehler, der beim Umpacken ein Schwarzbild gegeben haette: in EXT-X-MAP steckt die Adresse des Kopfstuecks einer fragmentierten MP4, ohne das kein einziger Schnipsel zu dekodieren ist. Sie blieb stehen und zeigte damit relativ auf den lokalen Server. Der alte Test hielt genau dieses Verhalten fest — er hat mitgeaendert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
bbd1c3b572 |
Der Vorrat steigt jetzt auch eine Ebene tiefer
Bei „Auto\" bestellen wir eine Liste von Stufen; die Schnipsel stehen eine Ebene darunter. Der Speicher erkannte das zwar, stieg dann aber aus und reichte die Liste mit relativen Adressen durch — die zeigten danach auf den lokalen Server, wo es sie nicht gibt. Es wurde nichts vorgeholt, und beinahe waere auch nichts mehr gelaufen. Jetzt wird auch die Stufenliste umgeschrieben. Holt der Player eine Stufe, kommt er wieder bei uns an, und dort beginnt das Vorauslaufen — bei der Stufe, fuer die er sich entschieden hat. Dazu eine Zeile im Qualitaetsblatt, die sagt, ob der Vorrat laeuft und wie weit er voraus ist. Von aussen ist das nicht zu sehen: laeuft er nicht, spielt es trotzdem, nur ohne Vorsprung — und genau so sieht auch ein Vorrat aus, der laeuft und nichts bekommt. Ohne diese Zeile sind die beiden Faelle nicht zu unterscheiden, und ich haette wieder geraten. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
134dc82927 |
Der Vorrat laeuft jetzt voraus, nicht der Player
AVPlayer entscheidet selbst, wie weit er puffert, und bei einer Abspielliste laesst Apple uns nicht dazwischen: auf eine Anfrage nach einem Schnipsel nimmt er von uns nur eine Weiterleitung an, keine Daten. Der Denkfehler war, seinen Puffer vergroessern zu wollen. Wir muessen aber gar nicht dazwischen — wir koennen die Gegenstelle sein. Ein winziger HTTP-Server auf 127.0.0.1 liefert dem Player die Abspielliste, umgeschrieben auf sich selbst, und holt die Schnipsel zuegiger von Jellyfin, als abgespielt wird. Jedes Segment, das der Player verlangt, liegt dann schon lokal. Faellt die Verbindung im Tunnel aus, merkt er es nicht. Das ist der Unterschied zwischen Puffer und Vorrat: den Puffer bestimmt Apple, den Vorrat bestimmen wir. Streng der Reihe nach, und mit begrenztem Vorsprung. Der Server erzeugt die Schnipsel gerade erst; wer Nummer 40 verlangt, waehrend er bei 12 ist, sieht fuer Jellyfin aus wie jemand, der gesprungen ist — und dann beginnt das Umrechnen von vorn, an der falschen Stelle. Der Lauscher haengt ausdruecklich an 127.0.0.1, nicht an allen Schnittstellen. Ein Server, der den Film ins Netz stellt, waere das Letzte, was eine Mediathek-App tun sollte. Kommt er nicht hoch, spielt es trotzdem — ueber die urspruengliche Adresse, ohne Vorrat. Ein fehlender Vorrat ist ein Verlust an Bequemlichkeit; keine Wiedergabe ist ein Verlust an allem. Das Lesen und Umschreiben der Abspielliste ist ein eigener Typ ohne Netz, mit sieben Tests. Eine falsch umgeschriebene Liste ergibt entweder ein schwarzes Bild oder — schlimmer — eine, die stellenweise am Speicher vorbeigeht und nur manchmal hakt. So etwas findet man im laufenden Film nicht. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
62691fc2bc |
Matroska geht jetzt an AVPlayer, nicht mehr an VLC
Ein Blu-ray-Rip liegt als Matroska vor, und AVFoundation oeffnet Matroska nicht — obwohl das Bild darin H.264 oder HEVC ist und der Ton AC3, also alles, was AVPlayer bestens kann. Im Weg stand allein die Verpackung. Dafuer gibt es jetzt einen dritten Weg neben Durchreichen und Neurechnen: umpacken. Dieselben Stroeme, Byte fuer Byte, in eine MP4. Kein Bild wird angefasst, es geht nichts an Qualitaet verloren, und den Server kostet es fast nichts. Entscheidend ist dabei `Protocol: http` statt `hls`. Eine Abspielliste laesst sich nicht vorausladen — AVPlayer holt Segment fuer Segment, und Apple laesst uns dazwischen nicht eingreifen. Eine fortlaufende Datei kommt ueber gewoehnliche Bereichsanfragen, und die kann man so weit vorausholen, wie man will. Der Vorratswunsch steht dort deshalb bei zehn Minuten statt bei einer. Der Gewinn ist gross: solche Titel bekommen Bild-in-Bild, AirPlay und die Systembedienung, die es bei VLC nicht gibt. Umgepackt wird nur, wenn **allein** die Schachtel im Weg steht. Stimmt auch nur ein Codec nicht — DTS, TrueHD, AV1 —, fehlt nicht der Zugang zur Datei, sondern das Koennen; dann bleibt es bei VLC. Sechs Tests halten die Grenze fest, in beide Richtungen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
983015b9ad |
Die Sprache lässt sich jetzt auch je Serie und je Mediathek sagen
Das Regelwerk stand schon und wurde abgefragt — es gab nur keinen Ort, an dem man etwas hineinschreiben konnte. Jetzt steht die Wahl auf der Serienseite und im Filter-Sheet der Mediathek, beide über denselben Auswahlknopf: zwei mit demselben Inhalt liefen unweigerlich auseinander. Bei der Mediathek in einem eigenen Abschnitt, nicht zwischen den Filtern. Ein Filter schränkt ein, was zu sehen ist; das hier bestimmt, was zu hören ist — dazwischengestellt läse es sich als „nur Titel mit deutscher Tonspur zeigen". Die Rangfolge Serie → Mediathek → allgemein ist dabei von den Einstellungen zu `AudioTrackChooser.resolve` gewandert. Sie ist eine Regel, keine Ablage, und im App-Ziel gibt es kein Unit-Test-Ziel — dort liesse sie sich nicht durchspielen. Sechs Tests decken sie jetzt ab, darunter der Fall, an dem man sonst lange sucht: eine leere Zeichenkette steht in den Einstellungen für „soll der Server entscheiden" und darf nicht als Sprache gelten, sonst schaltet sie die Ebene darüber stillschweigend ab. Nebenbei bekommt das Filter-Sheet die Mediathek selbst statt nur ihrer Art. Die Genres wurden vorher über irgendeine Mediathek dieser Art nachgeschlagen — bei zwei Filmmediatheken also über die falsche. Und der Puffer steht auf einer Minute statt auf 25 Sekunden. Es ist ein Mindestwunsch, keine Obergrenze; der harte Deckel liegt ohnehin woanders, nämlich darin, wie weit der Transcoder schon produziert hat. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
afb4a63655 |
Der Kern der Sprachwahl: Kürzel, die nicht lügen, und Abwägen statt Filtern
**`LanguageCode`** bringt Kürzel auf eine vergleichbare Form. Der Anlass ist ein stiller Fehler: verglichen wurden die ersten zwei Zeichen, also `ge` gegen `de` — und `ger` ist die ISO-639-2/B-Form von Deutsch, die ffmpeg in viele Dateien schreibt. Bei solchen Titeln wirkte die Spracheinstellung überhaupt nicht. `gsw` bleibt `gsw`. Schweizerdeutsch ist keine Schreibweise von Deutsch, und es gleichzusetzen hiesse, jemandem Schweizerdeutsch unterzuschieben, der Hochdeutsch wollte. Es gehört in die Rangliste, nicht in eine Gleichsetzung. **`AudioTrackChooser`** wägt ab, statt zu filtern: jede Spur bekommt Punkte, die höchste gewinnt. Damit gibt es immer eine Antwort, und Audiodeskription wird gemieden, ohne unmöglich zu werden — gibt es eine Sprache nur mit Audiodeskription, ist sie besser als Stille. Gemerkt wird nicht eine Nummer, sondern eine Beschreibung der Spur: Folgen derselben Serie haben unterschiedlich sortierte Spuren, und „Spur 2" bedeutet in der nächsten Folge etwas anderes. Siebzehn Tests, darunter der ZDF-Fall wörtlich — deutsche Spur mit Audiodeskription als Standard, englisch betitelte deutsche Spur daneben — und zwar in beide Richtungen: mit Meiden gewinnt die richtige, ohne Meiden gewinnt das Kürzel. Sonst stünde der Schalter nur dabei. Dazu trägt `MediaStream` jetzt `title`, `isDefault`, `isHearingImpaired` und `isOriginal`. Der Name aus der Datei ist das zweite Signal neben dem Kürzel und manchmal das einzige richtige. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
efed8717a3 |
„Auto" gibt nicht mehr auf, es versucht es kleiner
Der Fehlstart im Zug hat den eigentlichen Mangel gezeigt: die ganze neue Regelung misst **ab dem ersten Bild**. Kommt nie eines, misst sie nichts, greift nichts — und „Auto" hat sich als das erwiesen, was es wörtlich heisst: „nimm, was in den Einstellungen steht". Steht dort „Original", bittet die App über eine Zugverbindung um eine 4K-Datei und wartet vierzig Sekunden auf etwas, das nie kommen kann. Statt aufzugeben geht der Watchdog jetzt eine Sprosse tiefer und baut neu auf — bei Filmen über `restart()`, bei Sendern über den Tuner-Abbau. Gezählt gegen dasselbe Budget wie die Sicherheitsleine, sonst schickt eine tote Leitung die App endlos im Kreis. Scheitert auch das, nennt die Meldung jetzt den Grund und den Weg statt nur „keine Daten mehr": wer ohne Grenze angefragt hat, soll erfahren, dass es daran liegt und wo es sich ändern lässt. Und „Strom" heisst Stream. Zweiunddreissig Stellen, dazu das Gedächtnis ergänzt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
e324a474fd |
Watchdog heisst Watchdog, Engine heisst Engine
Eingedeutschte Fachbegriffe in den Kommentaren — „Wachhund" für Watchdog, „Motor" für Engine. Das liest sich albern und ist unklarer als das Original; niemand sagt Wachhund, wenn er einen Watchdog meint. Acht Stellen berichtigt. Gewöhnliche Sprache bleibt deutsch, der Stil der Kommentare ändert sich nicht. Dazu die Plex-Leiter messbar gemacht: `--leiter` baut dieselbe Anfrage zweimal, mit und ohne `autoAdjustQuality=1`, und zählt die Stufen. Die Zeile steht seit dem Umbau im Code und war nie geprüft — genau die Art ungeprüfter Annahme über Serververhalten, die bei Jellyfin vier Runden gekostet hat. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
a79d562642 |
Die Sicherheitsleine — der teure Weg, mit engen Regeln
Wenn der billige Hebel nichts ausrichtet, weil die Playlist nur eine Stufe hat oder schon die unterste läuft, bleibt nur der Neuaufbau mit kleinerer Bitrate: Transcoder anhalten, Motor abreißen, neu aushandeln, zurückspringen. Sekunden mit schwarzem Bild. **Ein Notnagel, keine Regelschleife.** Zu oft ausgelöst erzeugt er genau das Ruckeln, das er beheben soll. `DowngradeRule` ist deshalb eine Liste von Bedingungen, die alle gelten müssen, und keine Schwelle: nur bei „automatisch", nur wenn der billige Weg erschöpft ist, nur ab drei Rücklern in anderthalb Minuten oder einem einzelnen über fünfzehn Sekunden, nicht in den ersten zehn Sekunden, nicht binnen zehn Sekunden nach einem Sprung, höchstens alle drei Minuten, höchstens zweimal. Ein Sprung leert den Puffer **immer**. Ihn als Beleg für eine schlechte Leitung zu nehmen hieße, sich für das eigene Tun zu bestrafen. Das Urteil ist eine Aufzählung und kein `Bool`: „es hakt, aber wir tun nichts" ist ohne Grund von einem Fehler nicht zu unterscheiden. Vierzehn Tests schreiben jede Bedingung einzeln hin. Ist das Budget verbraucht, **wird es gesagt** statt lautlos weiter abgesenkt. Still bis zur untersten Sprosse zu gehen ist schlimmer, als einmal zuzugeben, dass die Leitung es nicht trägt. **Live-TV ist dabei**, wie gewünscht — aber mit Tuner-Abbau vorweg und Budget eins statt zwei. Er ist ein Stück Hardware, vier Stück gibt es, und ein doppelt belegter ist schlimmer als ein Stottern. Bekannte Grenze: `endLiveSession` meldet nicht, **ob** die Freigabe geklappt hat — das Protokoll gibt nichts zurück. Deshalb der eine Versuch und jeder Schritt im Protokoll, damit ein verlorener Tuner nachvollziehbar bleibt. Nebenbei aufgeräumt: die Wahl von Hand und die Sicherheitsleine teilen sich jetzt denselben Neuaufbau. Was sie unterscheidet, steht in `effectivePolicy` — die Wahl setzt `quality`, die Automatik `automaticCeiling`, und beides fließt dort zusammen. Die Automatik schreibt dabei nie `rememberedQuality` fort: eine schlechte Zugfahrt soll nicht dauerhaft das Wohnzimmer verschlechtern. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
52d6ebba43 |
„Automatisch" misst jetzt, statt die Einstellung durchzureichen
Bisher hieß `PlayerQuality.automatic` wörtlich: nimm die Zahl, die für WLAN oder Mobilfunk hinterlegt ist. Keine Messung, kein Nachregeln — die Entscheidung fiel einmal beim Start und galt bis zum Ende. `StreamHealth` beurteilt den Strom jetzt fortlaufend aus Proben im Sekundentakt. Ein reiner Typ: kein AVFoundation, keine Uhr, kein Netz. Damit lässt sich eine schlechte Zugfahrt als Zahlenreihe hinschreiben und nachsehen, was sie auslöst — zehn Tests tun genau das. **Welches Signal wann meldet**, ist der eigentliche Inhalt. Der Pufferverlauf ist der einzige echte Frühwarner: schrumpft er schneller, als die Zeit vergeht, kommt weniger herein als hinausgeht, und das sieht man zehn bis zwanzig Sekunden vor dem Rückler. `isLikelyToKeepUp` ist früh, aber sprunghaft — es zählt erst, wenn es anhält. Der Rücklerzähler meldet zu spät zum Regeln und bleibt trotzdem der Massstab, an dem sich hinterher ablesen lässt, ob es besser wurde. Die **gemessene** Bitrate steht bewusst nicht drin: auf einer kleinen Sprosse holt der Player wenig, ohne dass die Leitung schlecht wäre. Ausdrücklich kein Bandbreitentest. Der kostet Daten, ist beim Vorliegen veraltet und misst die Vergangenheit. Der Pufferverlauf ist die bessere Messung, weil er die einzige Größe misst, auf die es ankommt: lädt es schneller, als es läuft. Gehandelt wird über `preferredPeakBitRate` — der billige Hebel, der ohne Neuaufbau wirkt. Gezielt wird dabei auf eine Sprosse, **die es in `AVAsset.variants` wirklich gibt**: ein Wert zwischen zweien wählt dieselbe wie die untere und machte jede Anzeige darüber zur Lüge. Absichtlich unsymmetrisch — runter sofort, hinauf frühestens nach einer Minute Ruhe. Und getrennt von `quality`: die Automatik ruft **nicht** `select(quality:)`. Die Methode schreibt `rememberedQuality` fort und setzte eine manuelle Stufe — womit die Automatik sich selbst abschaltete und eine schlechte Zugfahrt dauerhaft das Wohnzimmer verschlechterte. Hat die Playlist nur eine Stufe, greift der Hebel ins Leere. Das ist kein Fehler, sondern der Fall, für den die Sicherheitsleine gebaut wird — bis dahin steht er wenigstens im Protokoll. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
903bfe5f63 |
Der Player weiß jetzt, wie es ihm geht
Bisher gab es über eine laufende Wiedergabe zwei Zahlen — gemessene Bitrate und verworfene Bilder —, und die standen nur zur Anzeige da. Die Frage „trägt die Leitung das gerade" war nicht zu beantworten, weder für den Nutzer noch für die App. **Die wichtigste neue Zahl ist `variantCount`.** Sie kommt aus `AVAsset.variants`, also vom Parser des Players, und beantwortet die Frage, ob es überhaupt etwas zum Umschalten gibt. Steht dort 1, greift jedes Nachregeln ins Leere — und genau das ist heute der Fall, weil die App bei beiden Servern eine Playlist mit einer Stufe bestellt. Dazu der Pufferstand aus `loadedTimeRanges`: der einzige echte Frühwarner. Schrumpft er schneller, als die Zeit vergeht, kommt weniger herein als hinausgeht — zehn bis zwanzig Sekunden, bevor das Bild steht. Alles andere meldet sich erst danach. **Verworfene Bilder werden jetzt über alle Protokolleinträge summiert.** Das Zugriffsprotokoll legt bei jedem Stufenwechsel einen neuen an, dessen Zähler bei null beginnt; wer nur `last` liest, hält eine Wiedergabe für makellos, die schon zwanzigmal Bilder verworfen hat. **Der Wachhund war blind für den häufigsten Fall.** Vierzig Sekunden am Stück ohne Daten brechen ab — aber er setzte sich bei jedem kurzen Erholen zurück, und zehn Rückler à dreißig Sekunden lösten ihn nie aus. `StallLedger` zählt daneben mit, wie viel Zeit im gleitenden Fenster fürs Warten draufging. Es bricht nichts ab, es stellt nur fest — und ist ein reiner Typ ohne Uhr und ohne AVFoundation, damit sich eine Folge von Rücklern als Zahlenreihe hinschreiben und das Urteil prüfen lässt. Sieben Tests, darunter genau der Fall, an dem der alte Wachhund vorbeisah. Gezählt wird nur der Weg vom Spielen ins Warten. Das anfängliche Puffern ist der Start, kein Rückler; würde es mitgezählt, hätte jede Wiedergabe eine Vorstrafe. Und zwei Kommentare sind berichtigt. Sie behaupteten, AVPlayer wechsle die Bitrate von selbst und das sei „die zugesagte automatische Qualitätsanpassung". Umschalten kann er nur zwischen Varianten, die in der Liste stehen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
dace5d1543 |
Einstellungen: sechs Bildschirme statt einer Liste — und was sie steuern
Deine Einstellungs-Bilder sind eine lange Liste. Ich habe sie durchgegangen
und getrennt: was etwas steuert, ist gebaut; was eine Einstellung für eine
Sache wäre, die es nicht gibt, ist nicht gebaut. Ein Schalter, der nichts
bewirkt, ist schlimmer als kein Schalter.
**Die Einstellungen sind jetzt eine Übersicht.** Vorher stand alles
untereinander auf einem Bildschirm — Serverangaben, Qualität, Wiedergabe, drei
Verweise, Downloads, Version, Abmelden. Das war beim Bauen bequem und beim
Suchen anstrengend. Jetzt führt jede Zeile in einen Bildschirm, der zu einer
Frage gehört: Server, Erscheinungsbild, Startseite, Wiedergabe, Ton &
Untertitel, Downloads, Diagnose.
**Neu, und jedes davon greift irgendwo:**
- Zurück- und Vorspulen getrennt einstellbar. Die Knöpfe im Player und die
Doppeltipp-Flächen tragen danach auch das passende Symbol.
- Startgeschwindigkeit — die Engine kann es längst, gefragt wurde nie.
- „Im Querformat sperren" abschaltbar. Für die allermeisten Filme richtig, für
den, der im Bett auf der Seite liegt, das Gegenteil.
- „Gewählte Qualität merken": die Stufe gilt dann auch für den nächsten Titel.
Aufbewahrt wird eine **Zahl**, keine Sprosse — welche Sprossen es gibt, hängt
von der Quelle ab, und eine 720p-Datei kennt keine Zwanzig-Megabit-Stufe.
Dann kommt die nächstgelegene, nicht „automatisch".
- „Bitrate im Qualitätsmenü zeigen".
- Gleichzeitige Downloads. Der Wert gab es schon in der Engine, nur ohne
Bedienung und ohne dass er einen Neustart überlebte.
- Aufmacher: wie viele Titel, und ob sie aus Angefangenem oder nur aus
Neuzugängen kommen.
- „Weiterschauen und Als Nächstes zusammenlegen" und „Gesehenes aus
Neuzugängen ausblenden" — beides in der Ansicht, nicht im Server: das sind
Fragen der Darstellung, und so gelten sie für Plex und Jellyfin gleich.
**Verkleinert herunterladen geht jetzt auch bei Jellyfin.** Da hatte ich zu
früh aufgegeben: ich hatte nur nach der Adresse zum *Abspielen* gesucht, und
die ist eine Abspielliste mit hunderten Schnipseln — als Datei unbrauchbar.
`/Videos/{id}/stream.mp4` gibt dieselbe Umrechnung als eine Datei heraus. Die
Größe bleibt eine Schätzung; eine Umrechnung, die erst beim Abruf beginnt,
kennt ihre eigene Länge nicht.
**Die Kontozeile ist jetzt eine Karte, die nach oben aufklappt**, kein
Systemmenü mehr. Ein Menü legt sich als eigene Ebene über alles und springt
dorthin, wo Platz ist; die Karte bleibt, wo sie ist, und lässt die Zeile mit
dem Namen stehen — man sieht also weiter, um wessen Konto es geht.
**Nicht gebaut, mit Grund:** Trailer im Karussell, Poster- und Sammlungsstil,
Untertitel-Aussehen, sekundäre Untertitel, lokale Intros, Modular Home,
App-Symbol. Für all das fehlt das, was die Einstellung steuern würde — es gibt
in Flixy weder Trailer noch Sammlungen, und der Player zeichnet Untertitel
nicht selbst.
263 Tests grün. **Nicht angesehen**: der Simulator-Tunnel ist weiterhin
abgestürzt und ließ sich nicht wieder aufbauen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
5b2b86b66b |
MediaKit: die Grundschicht, auf der Plex und Jellyfin dasselbe meinen
Erster Schritt zu Jellyfin, und er besteht vor allem aus Wegnehmen. `PlaybackDecider` nahm bisher Plex-Modelle entgegen und las daraus sechs Felder. Das band die Entscheidung an einen bestimmten Server, obwohl an der Frage „kann iOS das öffnen?" nichts serverspezifisch ist. Jetzt nimmt er ein `MediaProfile` — Container, Codecs, Bitrate, ob die Datei untersucht ist. Wie ernst die Kopplung war, zeigte der Player selbst: für die Offline-Wiedergabe baute er sich **erfundene** Plex-Werte zusammen — eine Media mit Kennung 0, ein Part mit dem Dateinamen als Schlüssel, zusammengesetzte Spurlisten —, nur damit die Signatur passte. Für eine Datei, die längst auf dem Gerät lag und mit Plex nichts mehr zu tun hatte. Dieser Hack ist weg. Neu ist das Target `MediaKit`: Typen und Rechnungen, die für jeden Server gelten, ohne eine einzige Abhängigkeit. Darin `MediaProfile` und die `Duration`-Brücke, die bisher in PlexKit stand — „weil es die unterste Schicht ist". Das stimmte, solange es nur einen Server gab. Damit kennen Wiedergabe und Downloads Plex nicht mehr. Zwei der Abhängigkeiten waren ohnehin tot; die dritte trug nur diese Brücke. Die wichtigste Testtabelle des Projekts ist mitgewandert und dabei kürzer geworden: dort steht jetzt, worauf es ankommt, statt vollständiger Modelle mit Kennungen und Dateipfaden, von denen niemand etwas las. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
f628819765 |
Player: eine gewählte Stufe wurde gar nicht umgerechnet
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> |
||
|
|
12bb49615b |
Player: die Qualitätsleiter des Servers, Bitrate im Blick, Doppeltipp
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> |
||
|
|
1ed64bf166 |
Phase 0: Projektgerüst, Design-Tokens und Navigation
Legt das Fundament für den Plex-Client an: - XcodeGen-Projekt nach dem Muster von Onyx (com.scarriffleservices, strict concurrency, Projektdatei nicht eingecheckt) - FlixyKit mit vier Modulen: PlexKit, FlixyDesign, FlixyPlayback, FlixyDownloads. macOS steht als Plattform mit drin, damit die Logik-Tests ohne Simulator laufen. - FlixyDesign: dunkle Farb-Tokens, Schriftskala, Poster- und Querformat-Kacheln, Aufmacher-Bild. Die Akzentfarbe fließt über die Umgebung und wird per WCAG-Rechnung lesbar gehalten. - PlexKit: Kopfzeilen-Identität, Transport-Protokoll, Fehlerarten und die Medienmodelle samt nachsichtigem Decoder. - FlixyPlayback: VideoPlayerEngine-Protokoll und PlaybackDecider — die eine Funktion, an der Phase 2 das Transcoding andockt. - Ausfahrbare Seitenleiste auf dem iPhone, Split-View auf dem iPad. - 45 Tests, alle grün. VLCKit 4.0.0-a24 ist vorab geprüft: baut über SPM unter Swift 6 mit strict concurrency. Die iOS-Slice ist mit 239 MB allerdings gewichtig, deshalb bekommt sie in Phase 1 ein eigenes Target. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |