我用 Rust + Tauri 2 复刻了 N_m3u8DL-CLI:一个 m3u8 多线程下载器

原项目.NET m3u8 downloader 是一个开源的命令行m3u8/HLS/dash下载器,支持普通AES-128-CBC解密,多线程,自定义请求头等。目前在github上有16k星。本文是基于 Rust + Tauri 2 对 N_m3u8DL-CLI(C# / .NET)的复刻实现一个简单易用的 m3u8 多线程下载器,自带可视化配置界面。

前言:为什么要折腾

前阵子逛 GitHub 发现了 N_m3u8DL-CLI 这个老牌 m3u8 下载器------C#/.NET 写的,功能相当能打:多线程下载、AES-128 自动解密、Master List 清晰度选择、ffmpeg 混流,还有一堆命令行参数。但它是 .NET Framework 的,作者自己也说进入维护阶段了,新功能都跑去了跨平台的 N_m3u8DL-RE。

我寻思着:这项目思路清晰、MIT 开源、还带完整文档,不如用 Rust + Tauri 2 把它复刻一遍?

理由:

  • 跨平台,移植方便:原版是 .NET Framework,只能在 Windows 上跑;而 Rust + Tauri 天然跨平台(Windows / Linux / macOS),核心逻辑用 Rust 写一遍,以后换平台直接编译就能用,几乎零移植成本,不用再到处找替代工具;
  • 好用,比命令行强 :原版是纯命令行,参数一大堆(--maxThreads --retryCount --headers......),虽然强大但劝退小白;套一层可视化界面,填个链接、点个按钮,进度条、速度、日志一目了然,日常用起来舒服多了;
  • 顺便还能学习 Tauri 2 的桌面开发,把 C# 那套下载逻辑用现代 Rust 重新写一遍。

说干就干,于是就有了这个项目(就叫它 N_m3u8DL-Tauri)。

这项目长啥样

一句话:一个自带可视化配置界面的 m3u8 多线程下载器,前端是原生 HTML/CSS/JS(零构建),后端是纯 Rust,两者通过 Tauri 2 的命令桥接。

复制代码
m3u8dl-tauri/
├── frontend/            # GUI:表单配置 + 进度条 + 彩色日志
├── bin/ffmpeg/          # 内置 ffmpeg(自动探测)
└── src-tauri/
    ├── src/
    │   ├── commands.rs  # 命令层
    │   └── core/        # 核心逻辑,与 GUI 完全解耦
    │       ├── hls.rs       # m3u8 解析
    │       ├── download.rs  # 多线程下载引擎
    │       ├── merge.rs     # 合并(二进制 / ffmpeg)
    │       └── ...
    └── tests/           # 端到端集成测试

我最满意的设计是 core/ 和 Tauri 完全解耦------核心逻辑就是个普通 Rust 库,能单独编译、单独测试,GUI 只是它的一个壳。这也让后面的测试变得非常爽。

复刻了哪些核心功能(对照原版)

原版功能 我的实现
m3u8 链接/文件解析 hls.rs 逐行状态机,对齐原版 Parser.cs 的逻辑
Master List 多清晰度 解析所有 #EXT-X-STREAM-INF,自动选最高带宽
AES-128-CBC 自动解密 支持 base64/data/file/http 密钥,默认 IV = 分片序号
多线程下载 tokio 信号量并发,线程数可配(默认 8)
分片重试 指数退避 + 随机抖动,404/400 快速失败
断点续传 已下载分片自动跳过
下载限速 全局令牌窗口
下载范围 按分片序号或时间段
自定义 Headers / 代理 表单直接填
自动合并 二进制拼接(ts/fMP4)或 ffmpeg 混流 mp4
完成后清理 一键开关

原版的一些边界功能(直播录制、优酷/气球云专有解密、外挂音轨)没做,但检测到直播流会明确报错,不会装死。

踩坑记:真·血泪史

写代码的过程比想象中曲折,记录几个印象深刻的坑,给同样踩 Tauri 2 的兄弟们提个醒:

1. 前端静默崩溃:window.TAURI 不存在

第一次跑起来,界面能显示,但点"开始下载"毫无反应 ,日志区一片空白。查了半天,根因是 tauri.conf.json 里没开 withGlobalTauri: true------Tauri 2 默认不注入 window.__TAURI__ 全局对象,我的 app.js 第一行解构就直接抛异常,整个脚本挂了,按钮监听压根没绑定上。修复很简单,但排查过程很抓狂。后来我给前端加了防御性检查,桥接缺失时在日志区显示红色错误提示,再也不怕静默崩溃了。

2. 进度条显示 "NaN GB"、分片 "undefined/undefined"

跑起来之后更离谱:进度条显示 NaN GB分片 undefined/undefined。原因是我后端 Progress 结构序列化出来是 snake_case(total_segments),前端却按 camelCase(totalSegments)读------全都读到 undefined。加一行 #[serde(rename_all = "camelCase")] 搞定。这种前后端字段名不一致的坑,新手真的容易踩。

3. 错误信息被"轮询竞态"吞掉

任务失败后,前端只显示"任务已结束",具体错误看不到。原因是:任务失败 → 设置错误状态 → 立即从注册表移除,三步一气呵成,而前端每 500ms 轮询一次,大概率完美错过那个短暂存在的错误状态。解决方案是给 AppState 加了归档机制:任务结束后保留最终快照,前端随时还能查到错误信息和完整日志。这个设计后来帮了大忙------用户反馈问题,一眼就能从日志定位。

4. 下载 ffmpeg 的曲折经历

为了验证 ffmpeg 混流路径,我需要一个真实的 ffmpeg.exe。gyan.dev 官方源 14KB/s 龟速、GitHub release 直接连不上、winget 又没装......最后灵机一动走了 npmmirror 的 ffmpeg-static 二进制镜像,5MB/s 秒下。这告诉我们:国内开发者,镜像站就是命。

5. 测试源不给力:不是程序的锅

拿公开测试源 mux.dev 测试,分片下载疯狂重试,一度以为代码有 bug。后来 curl 一测:分片 URL 完全正常(HTTP 200,5MB 大小),但大文件传输只有 3~12KB/s 且频繁断开------纯粹是网络环境到那个服务器太慢。程序的重试机制反而证明了自己在正确工作。顺手优化了默认线程数(16→8)和重试退避(加随机抖动打散重试风暴),慢网下体验好不少。

测试:7 个用例全部通过

既然是复刻,就得用测试证明"真的能用"。我写了 7 个测试,全部通过:

单元测试(4 个):基础解析、AES 默认 IV、Master List、URL 拼接。

端到端集成测试(3 个) ------这是最过瘾的部分,我在测试里起了一个本地迷你 HTTP 服务器,模拟真实下载场景:

  • 前 3 个分片用 AES-128 加密、后 3 个明文(还测了 KEY 状态切换),4 线程并发下载 → 解密 → 二进制合并,最后和原始数据逐字节比对
  • 用真实 ffmpeg 生成 3 个 MPEG-TS 分片 → 下载 → ffmpeg concat 混流 mp4(faststart)→ 再用 ffmpeg 反向探测输出文件可正常读取。

测试代码长这样(节选)------本地起服务器喂数据,全链路真跑:

rust 复制代码
#[tokio::test]
async fn test_e2e_encrypted_download_and_merge() {
    // 本地迷你 HTTP 服务提供 加密m3u8 + key + 分片
    let (port, server) = start_server(routes);
    let ctx = DownloadCtx::new(opts, "e2e".to_string()).unwrap();
    let result = m3u8dl_tauri_lib::core::download::run(ctx.clone()).await;
    // 输出与原始分片拼接逐字节比对
    assert_eq!(got, expect, "输出内容不符(解密或顺序有误)");
}

怎么跑

bash 复制代码
cd m3u8dl-tauri/src-tauri
cargo run        # 启动 GUI
cargo test       # 跑全部测试

ffmpeg 放 bin/ffmpeg/ 下会自动探测到,界面会显示"✔ 已找到 ffmpeg";找不到就自动回退为二进制合并,不影响使用。

写在最后

这个项目做下来,最大的收获是把 Tauri 2 的开发套路、前后端桥接、异步并发下载这些知识点全都实战了一遍,而且------所有功能都有测试兜底,重构起来心里有底。原版 N_m3u8DL-CLI 的设计(meta.json 中间产物、分片分组、二进制/ffmpeg 双合并路径)本身就很值得学习,复刻过程相当于把它的设计读薄了一层。

后续如果有时间,想补上直播录制、外挂音轨这些原版功能,或者把 master 清晰度选择做成 GUI 下拉而不是自动选最高。项目已开源在本地,欢迎交流指正。

关于博主:CSDN 猫哥

本文作者是 CSDN 猫哥。博客里记录了 Rust、Tauri、音视频处理相关的实战经验、踩坑笔记和一些小工具分享,欢迎关注交流:

如果你觉得这篇文章对你有帮助,或者对 m3u8 下载 / Tauri 开发有更好的想法,欢迎到博客留言交流,也可以提出 issue 一起改进这个项目。

相关推荐
月光有害1 小时前
理解 Spring 依赖注入:从构造器注入到集合与条件 Bean
java·后端·spring
对象存储与RustFS1 小时前
大文件传到 48 GiB 就断:S3 分片上传的参数怎么算,以及那些没人清的残片
后端·rust·开源
程序员雷欧1 小时前
LongAdder
开发语言·python
foggyprojects1 小时前
客户说“销售额”,系统怎样落到订单含税金额或开票含税销售额?
后端
鱼子星_1 小时前
【C++】反向迭代器:反向迭代器的底层认识与模拟实现
开发语言·c++·笔记·stl
名字还没想好☜1 小时前
Java 用 MethodHandle 替代反射:调用性能实测、invokeExact 的坑与缓存
java·开发语言·缓存·反射·methodhandle
SMF19191 小时前
【Linux】完美解决缩略图工具gm调用java.io.FileNotFoundException: gm问题
java·开发语言·python
念何架构之路1 小时前
路由注册:RouterGroup(routergroup.go)
开发语言·后端·golang
小当家.1051 小时前
LLM服务缓存与连接池管理:三层缓存架构与ChatClient池化
java·后端·spring·缓存·llm·agent