项目 · 2025 · 作者

Crispy-Tivi:跨平台 IPTV 和媒体流

一个支持 M3U、Xtream Codes、EPG、VOD 和直播电视的 Flutter 应用,内置 Chromecast、AirPlay 和云同步--因为 IPTV 生态里现有的 app 要么是有 2014 年代 UX 的废弃软件,要么贵还难用,我受够了。

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

这是每天晚上在我客厅里跑的 app。我家用 IPTV。这个领域里大多数 app 要么是有 2014 年代 UX 的废弃软件,要么是躲在可疑订阅付费墙后面的付费产品,要么脆弱到提供商 URL 一换整个东西就坏一周。这三种我都受够了。

Crispy-Tivi 是我因为想要一个自己真正乐于使用—而不是将就着用—的东西而构建的 app。

为什么它存在

IPTV 是一个真正混乱的生态。没有标准。提供商提供 M3U 播放列表、Xtream Codes API,或两者都有,而且他们不打招呼就改端点。EPG 数据有多种 XML 格式,质量参差不齐。VOD 目录可能有两万个条目,也可能只有二十个。在 Android 上看起来还好的客户端在平板上就崩了,桌面端支持通常是事后想到的,如果有的话。

现有的 app 把这种复杂度当作固定成本:慢频道切换、没有搜索、没有「继续观看」、分组毫无道理。IPTV 主要玩家的付费层要花钱,感觉还像某人的 Flutter 入门项目。我说这是技术观察,不是嘲讽—入门项目是学 Flutter 的方式。

我想要的是 Plex 级别的 UX,连接我控制的 IPTV 来源,零云依赖。如果我的网络通了、提供商通了,app 就应该工作。句号。

它是如何运作的

app 是 Flutter,给了我一份跑在 Android、iOS、macOS、Windows 和 Linux 上的单一代码库。在 2025 年这听起来是媒体 app 的显而易见选择,也确实如此—Flutter 的渲染现在是真的好,平台 channel 的故事足够成熟,我可以在不发疯的情况下用 Rust 处理性能关键路径。

Rust 层处理 FFmpeg 集成。HLS 和 DASH 解析、转码提示、缓冲区管理—这些是 Dart 的垃圾回收器最终会在最糟糕的时刻背叛你的东西,通常是在切换直播频道的时候。Rust 通过 Flutter 的 FFI 给了我可预测的内存布局和不会有意外暂停。

协议支持覆盖了主要 IPTV 格式:M3U 和 M3U-plus 播放列表(带自定义 group-title 解析)、Xtream Codes API(登录、流、VOD、剧集)和 XMLTV EPG。投屏方面,我原生实现了 Chromecast 和 AirPlay—播放器移交流 URL,投屏会话处理其余部分。云同步存储收藏、观看进度和自定义频道组;同步层用每个条目的最后写入胜出策略做冲突解决,理论上不对,实际中没问题。

EPG 渲染器是最棘手的部分。XMLTV 文件解压后可以有几百兆,你不能在主线程上解析,除非你喜欢 ANR 对话框。EPG 处理链路在 Dart isolate 里运行,解析成紧凑的二进制格式,把结果以频道-时间槽元组流的形式交给 UI 层。网格视图激进地虚拟化—任何时候内存里只有可见窗口和一屏的预加载。

有意思的地方

自托管社区在我还没来得及写一份像样的 README 之前就找到了这个 repo,这既让人受宠若惊,又有点混乱。第一波 issue 几乎完全是关于 M3U 边缘情况的—字符编码、非标准 group-title 转义、有一万个频道而客户端要实时过滤的播放列表。我在两周的 issue 里学到的关于真实世界 M3U 数据混乱程度的东西,比作为消费者使用这些服务多年积累的还要多。

最让我意外的功能请求是剧集支持。我主要把 IPTV 想成直播电视加 VOD 电影,但有很大一部分用户也用这种方式获取剧集内容,而 Xtream Codes 的剧集 API 和 VOD API 确实不同—不同的端点结构、不同的元数据字段、不同的续看语义。正确添加它意味着把内容模型从「流或电影」重新思考为「流、电影或有剧集的剧集」,这级联影响了播放器、搜索索引和「继续观看」追踪器。

GitHub 上 8 颗星听起来不多。但每颗星都伴随着一段真实的对话。使用这个 app 的人是运行着自己媒体栈的深度自托管玩家,有非常具体的意见。这是最好的那种用户反馈。

我会改什么

同步层是代码库里最老最糟糕的部分。最后写入胜出对默认情况来说没问题,但在同一个用户同时在手机和平板上用 app 看不同内容时就崩了。我想给观看进度用合适的基于 CRDT 的状态—这是个已解决的问题,我只是还没把实现移植过来。

Rust FFI 边界也比我希望的更手工。我手工管理跨边界的对象生命周期,这是正确的但审计起来很繁琐。flutter_rust_bridge 自从我写了初始集成之后已经显著改善,我想把它正确地迁移过去,让生成的绑定来处理我现在靠惯例强制执行的安全不变量。

说实话?UI 需要一次设计过关。UX 是好的—流程奏效,频道切换 latency 稳固,EPG 网格可读。但视觉设计是「工程师做的」,我说这话完全有自我意识。我知道需要什么,只是还没坐下来做。