Crispy-Tivi: Cross-Platform-IPTV und Media-Streaming
Eine Flutter-App für M3U, Xtream Codes, EPG, VOD und Live-TV - mit Chromecast, AirPlay und Cloud-Sync von Haus aus.
Das ist die App, die bei mir jeden Abend im Wohnzimmer läuft. Mein Haushalt streamt IPTV. Die meisten Apps in diesem Bereich sind entweder Abandonware mit 2014er-UX, kostenpflichtige Produkte hinter zwielichtigen Abo-Paywalls oder so fragil, dass eine Anbieter-URL-Änderung das ganze Ding für eine Woche kaputtmacht. Ich hatte genug von allen dreien.
Crispy-Tivi ist die App, die ich gebaut habe, weil ich etwas wollte, das ich wirklich gerne benutze - nicht nur toleriere.
Warum es das gibt
IPTV ist ein wirklich chaotisches Ökosystem. Es gibt keinen Standard. Anbieter servieren M3U-Playlists, Xtream-Codes-APIs oder beides, und sie ändern ihre Endpoints ohne Vorwarnung. EPG-Daten kommen in mehreren XML-Formaten mit unterschiedlicher Qualität. VOD-Kataloge können zwanzigtausend Einträge haben oder zwanzig. Clients, die auf Android gut aussehen, brechen auf Tablets zusammen, und Desktop-Support ist meistens ein Afterthought, wenn er überhaupt existiert.
Die Apps, die es gibt, behandeln diese Komplexität als feste Kosten: langsames Kanal-Switching, keine Suche, kein Continue-Watching, kein Grouping, das wirklich Sinn macht. Der bezahlte Tier der großen IPTV-Player kostet Geld und fühlt sich trotzdem wie jemandes erstes Flutter-Projekt an. Das sage ich als technische Beobachtung, nicht als Dig - erste Flutter-Projekte sind, wie man Flutter lernt.
Was ich wollte, war Plex-Qualität-UX für eine IPTV-Quelle, die ich kontrolliere, mit null Cloud-Abhängigkeit. Wenn mein Internet oben ist und mein Anbieter oben ist, sollte die App funktionieren. Punkt.
Wie es funktioniert
Die App ist Flutter, was mir eine einzelne Codebase gibt, die auf Android, iOS, macOS, Windows und Linux läuft. Das klingt wie die offensichtliche Wahl für eine Media-App im Jahr 2025, und das ist sie - Flutters Rendering ist inzwischen wirklich gut, und die Platform-Channel-Story ist ausgereift genug, dass ich für Performance-kritische Pfade in Rust abtauchen kann, ohne den Verstand zu verlieren.
Die Rust-Schicht handhabt die FFmpeg-Integration. HLS- und DASH-Parsing, Transcoding-Hints, Buffer-Management - das sind Dinge, bei denen Darts Garbage-Collector einen im falschen Moment im Stich lassen wird, meistens während eines Live-Kanal-Switches. Rust über Flutters FFI gibt mir vorhersehbares Memory-Layout ohne Überraschungspausen.
Protocol-Support deckt die wichtigsten IPTV-Formate ab: M3U und M3U-plus Playlists (mit Custom-Group-Title-Parsing), Xtream-Codes-API (Login, Stream, VOD, Series) und XMLTV-EPG. Fürs Casting implementierte ich Chromecast und AirPlay nativ - der Player übergibt eine Stream-URL und die Cast-Session übernimmt den Rest. Cloud-Sync speichert Favoriten, Watch-Progress und Custom-Channel-Groups; die Sync-Schicht ist conflict-resolved mit einer Last-Write-Wins-Strategie pro Item, was theoretisch falsch ist und in der Praxis für diesen Use-Case funktioniert.
Der EPG-Renderer war das kniffligste Stück. XMLTV-Dateien können mehrere hundert Megabyte unkomprimiert sein, und man kann das nicht auf dem Main-Thread parsen, es sei denn, man liebt ANR-Dialoge. Die EPG-Pipeline läuft in einem Dart-Isolate, parst in ein kompaktes Binärformat und übergibt das Ergebnis an die UI-Schicht als Stream von Channel-Time-Slot-Tuples. Die Grid-View virtualisiert aggressiv - nur das sichtbare Fenster und ein Bildschirm Lookahead sind jeweils im Memory.
Was interessant ist
Die Self-Hoster-Community fand das Repo, bevor ich auch nur ein ordentliches README geschrieben hatte, was schmeichelhaft und mäßig chaotisch war. Die erste Welle von Issues drehte sich fast ausschließlich um M3U-Edge-Cases - Zeichenkodierung, nicht-standardmäßiges Group-Title-Escaping, Playlists mit zehntausend Kanälen, bei denen der Client in Echtzeit filtern sollte. Ich habe in zwei Wochen Issues mehr über das Chaos realer M3U-Daten gelernt als in Jahren, diese Services als Consumer zu nutzen.
Der Feature-Request, der mich am meisten überraschte, war Series-Support. Ich hatte an IPTV primär als Live-TV plus VOD-Movies gedacht, aber ein großes Segment der Nutzer bezieht auch episodischen Content so, und die Xtream-Codes-Series-API ist wirklich anders als die VOD-API - andere Endpoint-Struktur, andere Metadaten-Felder, andere Continuation-Semantik. Sie ordentlich hinzuzufügen bedeutete, das Content-Modell von “Stream oder Movie” zu “Stream, Movie oder Series-mit-Episodes” zu überdenken, was durch den Player, den Suchindex und den Continue-Watching-Tracker kaskadierte.
Acht Sterne auf GitHub klingt nicht nach viel. Aber jeder dieser Sterne kam mit einem echten Gespräch. Die Leute, die diese App benutzen, sind tief drin - Self-Hoster, die ihre eigenen Media-Stacks betreiben und extrem spezifische Meinungen haben. Das ist das beste User-Feedback überhaupt.
Was ich ändern würde
Die Sync-Schicht ist der älteste und schlechteste Teil der Codebase. Last-Write-Wins ist ein vernünftiger Default, aber er fällt auseinander, wenn derselbe Nutzer die App auf einem Telefon und einem Tablet hat und auf beiden verschiedene Dinge schaut. Ich will ordentlichen CRDT-basierten State für Watch-Progress - das ist ein gelöstes Problem und ich habe die Implementierung noch nicht portiert.
Die Rust-FFI-Grenze ist auch manueller als mir lieb ist. Ich manage den Object-Lifecycle über die Grenze manuell, was korrekt aber tedious zu auditieren ist. flutter_rust_bridge hat sich seit meiner ursprünglichen Integration deutlich verbessert, und ich möchte ordentlich dazu migrieren, damit die generierten Bindings die Safety-Invarianten handhaben, die ich aktuell per Konvention durchsetze.
Und ehrlich gesagt? Die UI braucht einen Design-Pass. Die UX ist gut - der Flow funktioniert, die Kanal-Switching-Latenz ist solide, das EPG-Grid ist lesbar. Aber das visuelle Design ist “Engineer hat das gebaut”, was ich mit voller Selbstwahrnehmung sage. Ich weiß, was es braucht. Ich habe mich noch nicht hingesetzt, um es zu tun.