プロジェクト · 2025 · 作者

Crispy-Tivi:クロスプラットフォーム IPTV・メディアストリーミング

M3U、Xtream Codes、EPG、VOD、ライブ TV 対応の Flutter アプリ。Chromecast、AirPlay、クラウド同期を内蔵。毎晩リビングで実際に使っているやつだ。

Screenshot of the Crispy-Tivi GitHub repo page showing the Flutter + Rust IPTV media app

これは毎晩リビングで流れているアプリだ。うちは IPTV でストリーミングしている。この空間のほとんどのアプリは2014年の UX を持つ放棄されたソフトか、怪しいサブスクリプションペイウォール後ろの有料製品か、プロバイダーの URL が変わっただけで一週間壊れるほど脆いかのどれかだ。その三つ全部に疲れた。

Crispy-Tivi は「耐えるんじゃなく実際に楽しんで使えるもの」が欲しくて作ったアプリだ。

なぜ作ったか

IPTV は本当にカオスなエコシステムだ。標準がない。プロバイダーは M3U プレイリスト、Xtream Codes API、またはその両方を提供し、警告なしにエンドポイントを変更する。EPG データは品質の異なる複数の XML 形式で来る。VOD カタログは2万エントリーだったり20だったりする。Android では問題ないクライアントがタブレットでは壊れ、デスクトップサポートはあったとしても大抵後付けだ。

存在するアプリはこの複雑さを固定コストとして扱う:チャンネル切り替えが遅い、検索なし、続きから再生なし、実際に意味をなすグルーピングなし。主要 IPTV プレイヤーの有料ティアはお金がかかってそれでも誰かの最初の Flutter プロジェクトみたいな感じがする。技術的な観察として言っているのであり、批判じゃない—最初の Flutter プロジェクトは Flutter を学ぶ方法だ。

欲しかったのは、自分がコントロールする IPTV ソース向けの Plex クオリティの UX で、クラウド依存ゼロ。インターネットが動いてプロバイダーが動いていれば、アプリは動くべきだ。それだけ。

どう動くか

アプリは Flutter で、Android、iOS、macOS、Windows、Linux で動くシングルコードベースを提供する。2025年にメディアアプリの明らかな選択に聞こえるし、そうだ—Flutter のレンダリングは本当に良くなっていて、パフォーマンスクリティカルなパスで Rust に落ちても頭がおかしくならないくらいプラットフォームチャンネルの話は成熟している。

Rust レイヤーが FFmpeg インテグレーションを扱う。HLS と DASH のパース、トランスコーディングヒント、バッファ管理—これらは Dart のガベージコレクターが最悪のタイミング、大抵ライブチャンネル切り替え中に、裏切るものだ。Flutter の FFI 経由の Rust は予測可能なメモリレイアウトとサプライズのない一時停止を与えてくれる。

プロトコルサポートは主な IPTV フォーマットをカバーする:M3U と M3U-plus プレイリスト(カスタム group-title パース付き)、Xtream Codes API(ログイン、ストリーム、VOD、シリーズ)、XMLTV EPG。キャスティングのために Chromecast と AirPlay をネイティブに実装した—プレイヤーがストリーム URL を渡してキャストセッションが残りを処理する。クラウド同期はお気に入り、視聴進捗、カスタムチャンネルグループを保存する;同期レイヤーはアイテムごとのラストライトウィンズ戦略でコンフリクト解決される、これは理論的には間違いで実際のユースケースでは問題ない。

EPG レンダラーが一番やっかいな部分だった。XMLTV ファイルは展開後に数百メガバイトになることがあり、ANR ダイアログが好きでなければメインスレッドでパースできない。EPG パイプラインは Dart アイソレートで動き、コンパクトなバイナリ形式にパースして、結果をチャンネル・タイムスロットタプルのストリームとして UI レイヤーに渡す。グリッドビューは積極的に仮想化する—常時メモリにあるのは見えているウィンドウと一画面分の先読みだけだ。

面白いところ

セルフホスターコミュニティは私がちゃんとした README を書く前にリポジトリを見つけた、これは嬉しくてちょっとカオスだった。最初の Issue の波はほぼ完全に M3U のエッジケースについてだった—文字エンコーディング、非標準の group-title エスケーピング、クライアントがリアルタイムでフィルタリングすることになっている1万チャンネルのプレイリスト。コンシューマーとしてこれらのサービスを何年も使ってきた中で蓄積したよりも多くのことを、2週間の Issue で現実世界の M3U データのカオスについて学んだ。

最も驚いた機能リクエストはシリーズサポートだった。IPTV を主にライブ TV プラス VOD ムービーだと思っていたが、大きなセグメントのユーザーはこの方法でエピソードコンテンツも取得していて、Xtream Codes のシリーズ API は VOD API とは本当に違う—異なるエンドポイント構造、異なるメタデータフィールド、異なる継続セマンティクス。適切に追加するにはコンテンツモデルを「ストリームまたはムービー」から「ストリーム、ムービー、またはエピソード付きシリーズ」に再考する必要があり、これはプレイヤー、検索インデックス、続きから再生トラッカーを通してカスケードした。

GitHub の8スターは多く聞こえない。でも、それらのスターのすべては本物の会話を伴っていた。このアプリを使っている人たちは自分のメディアスタックを動かしているディープインのセルフホスターで、非常に具体的な意見を持っている。それが最高のユーザーフィードバックだ。

変えたいこと

同期レイヤーはコードベースの最も古くて最悪な部分だ。ラストライトウィンズは良いデフォルトだが、同じユーザーがスマホとタブレットでアプリを持っていてそれぞれで違うものを見るときに壊れる。視聴進捗に適切な CRDT ベースの状態が欲しい—解決済みの問題で、まだ実装を移植していないだけだ。

Rust FFI 境界も希望より手動だ。オブジェクトのライフサイクルを境界をまたいで手動で管理している、これは正しいが監査が面倒だ。flutter_rust_bridge は最初のインテグレーションを書いてから大幅に良くなっていて、生成されたバインディングが今規約で強制している安全の不変量を処理するようにきちんと移行したい。

正直に言うと?UI はデザインのパスが必要だ。UX は良い—フローは動く、チャンネル切り替えレイテンシはしっかりしている、EPG グリッドは読みやすい。でも見た目のデザインは「エンジニアが作った」という感じで、これは完全な自覚を持って言っている。何が必要かはわかっている。まだ腰を据えてやっていないだけだ。