370f7f94f1c8430a424e97a1008a0dc76dccd19e
10
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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> |
||
|
|
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> |
||
|
|
bbe47b83be |
Live-TV: das Programm kam nie an, und die Einschalt-Adresse wurde weggeworfen
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> |
||
|
|
6150b018ec |
Vier Punkte: Player-Hänger, kaputte Downloads, Stufenliste, Kopfleiste
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> |
||
|
|
bf0a63a924 |
Vier Punkte aus dem Praxistest
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> |
||
|
|
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> |
||
|
|
5c0c1d651d |
Phase 6: Live-TV, plus Live Activity für Downloads
Live-TV - An keinem echten Server geprüft: die DVR- und EPG-Endpunkte sind der am schlechtesten dokumentierte Teil der Plex-API. Die Modelle sind deshalb durchgehend nachsichtig — fehlende Felder ergeben leere Listen, nicht Fehler, und die Senderkennung wird als Zahl, Zeichenkette oder uuid akzeptiert. - Ohne eingerichteten Empfänger erklärt die App das, statt einen Fehler zu zeigen. Das ist der häufigste Fall. - Senderliste mit laufender Sendung, Fortschrittsbalken und „danach". Der Programmführer wird getrennt geholt: fällt er aus, bleibt das Umschalten möglich. - Die Anbieterkennung wird aus /media/providers gelesen statt fest verdrahtet — sie wechselt je nach Server. Live Activity - Eigene Widget-Erweiterung; iOS zeichnet den Sperrbildschirm in einem getrennten Prozess, während die App meist gar nicht läuft. - Der Presenter drosselt auf 1,5 Sekunden. ActivityKit verwirft zu häufige Aktualisierungen kommentarlos, und der Fortschritt kommt mehrmals je Sekunde — ungebremst bliebe die Anzeige stehen. - @preconcurrency import: Activity und ActivityContent tragen keine Sendable-Zusage. Zwei Stolpersteine unterwegs: canImport(ActivityKit) ist auf macOS wahr, die Typen darin aber nicht verfügbar — es muss os(iOS) sein. Und eine von Hand geschriebene Info.plist ohne CFBundleIdentifier führt zu einer Erweiterung ganz ohne Kennung; der Fehler lautet dann „not prefixed with the parent app's bundle identifier" und zeigt auf die falsche Stelle. 126 Tests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
af23372997 |
Phase 4 und Teile von Phase 5
Download-Regeln - Automatisches Löschen nach dem Ansehen, mit Frist statt sofort: wer die letzte Szene noch einmal sucht, soll sie nicht schon verloren haben. „Nie" ist die Voreinstellung. - „Halte N ungesehene Folgen offline": wartende und laufende Downloads zählen mit, sonst würde bei jedem Durchlauf nachgelegt, bis die ganze Serie auf dem Gerät liegt. - Beides als reine Rechnung ohne Zugriff auf Datenbank oder Dateien — bei einer Regel, die Filme löscht, ist Prüfbarkeit keine Formsache. 13 Tests. Qualitätswahl beim Download - transcodedDownloadURL über den Universal-Transcoder mit protocol=http statt über die umständliche Sync-API. NOCH NICHT AM SERVER GEPRÜFT: offen ist, ob Plex eine Content-Length mitschickt (sonst kein Fortschritt und kein Fortsetzen) und ob die Datei durchsuchbar ist. Steht so im Quelltext; Original bleibt Voreinstellung. Erscheinungsbild - Akzentfarbe: zehn kuratierte Kacheln plus freie Hex-Eingabe. Bei einer eigenen Farbe greift die Kontrastkorrektur und sagt es auch — sonst wirkt sie wie ein Fehler. - Vorschaufläche mit Knopf, Symbol und Fortschrittsbalken. Serveraktionen - Gesehen-Status umschalten und Sterne setzen über ein Menü auf der Detailseite. - Suchlauf der Mediathek aus der Werkzeugleiste. 115 Tests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
62158303a2 |
Phase 3: Download-Engine und Offline-Wiedergabe
Engine - Hintergrund-URLSession: läuft weiter, wenn die App zu ist. Der ganze Zustand liegt in SwiftData, weil es nach einem Neustart kein anderes Gedächtnis gibt. - Pausieren über cancel(byProducingResumeData:) — nur so lässt sich weiterladen statt neu zu beginnen. Bei 40 GB der Unterschied zwischen brauchbar und unbrauchbar. - Zwei Übertragungen gleichzeitig, Folgen in Staffel-/Folgenreihenfolge. Alles parallel zu laden hieße: keine Folge wird fertig. - Speicherlimit und Geräteplatz werden vor dem Annehmen geprüft, ein Gigabyte bleibt als Puffer frei. - Verwaiste Dateien werden beim Start entfernt; sie entstehen, wenn die App genau beim Abschluss beendet wird. - Downloads liegen in Application Support, nicht in Caches (iOS räumt den eigenmächtig leer) und sind von der iCloud-Sicherung ausgenommen. Oberfläche - Download-Knopf auf Detailseiten mit vier Zuständen in einem Knopf. - „Alle Folgen laden" für Staffel und Serie; ein abgelehnter Titel stoppt den Stapel nicht. - Downloads-Ansicht mit Belegung, WLAN-Schalter, Pausieren, Fortsetzen, Löschen — nach Serie gruppiert. Offline - Der Player bevorzugt die lokale Datei, auch ohne erreichbaren Server. - Beim Herunterladen wird ein Abzug der Serverangaben mitgesichert: offline lässt sich nichts nachfragen, und der PlaybackDecider braucht Container und Codecs. Ein Fehler, den die Tests aufgedeckt haben: der Koordinator hielt nur den ModelContext, nicht den ModelContainer. Ein ModelContext hält seinen Container nicht am Leben — wird der anderswo freigegeben, stürzt jeder Zugriff auf einen Eintrag ab, und die Meldung zeigt auf ModelContext.reset statt auf die Ursache. Jetzt liegt die Zuständigkeit beim Koordinator. 102 Tests. 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> |