更新日志
v0.3.0
本次更新围绕四个独立主题:(1) 录制会话稳定性增强——为短暂网络抖动引入静默期、清理空会话、优化时间线展示,并支持按主播覆盖离线判定节奏;(2) 输出根写入门(output-root write gate)——修复了一类"rust-srec 遇到文件系统问题(磁盘满、Docker 绑定挂载失效)必须重启容器才能恢复"的故障;(3) 主播详情页新增检查历史条——一眼看清近期每次轮询的结果,悬停可查看 rust-srec 究竟挑了哪一路流;(4) 触达并发下载上限时的体验改善——主播卡片新增"排队中"徽章、高优先级主播获得真正优先权、上限被占满时不会再"整体卡死"。同时引入了后端本地化的基础设施,为 rclone 流水线步骤新增带宽与吞吐量控制,可直接在 UI 上对上传进行限速,让 Mesio CLI 可以更干净地运行而不生成日志文件,并修复了一批 Huya 与小红书的平台问题。
主播检查历史条
一眼看清近期的检查情况
主播详情页现在以一行彩色小竖条的形式展示最近 60 次监视轮询:绿色代表当时在直播,灰色代表离线,琥珀色代表被过滤规则(例如排程时段)跳过,红色代表出错。无需翻日志即可判断监视是否正常运行、近期是否出现过异常。
悬停任意一条即可看详情
工具提示展示该次检查的时间、耗时,以及平台返回的所有候选清晰度,其中被实际选中用于录制的那一路会以对勾标记突出显示。当遇到"为什么没选更高的清晰度?"这类疑问,或想确认是否选中了某个特定码率 / 编码时非常实用。对于未直播的轮询(被过滤、出错),工具提示会说明原因。
新数据实时滚入
仪表盘开启时,每次轮询完成后新条会实时滚入,无需刷新页面。连接正常时标题栏会显示绿色脉动的 LIVE 指示;连接断开后会改为"X 秒前最后一次检查"提示,便于判断屏幕上的数据是否为最新。
录制会话稳定性增强
断流静默期:短暂断开不会再立即结束会话
当上游 CDN 切换、网络抖动或主播端简短重连时,下载会干净地结束。此前任意一次断开都会立即结束会话——下次检测到 LIVE 时会创建一张新会话卡片,连续断开几次就会在仪表盘上堆出多张零字节卡片。
现在断开会进入一个简短的等待期(默认与"离线检测"配置相同,约一分钟)。如果在窗口内重新检测到直播,录制会无缝继续,复用同一会话——不会产生新卡片。如果窗口内没有重新出现直播,会话才会被认定为结束。
HTTP 404 不再被视作主播下线的权威信号
Douyu 等平台在直播刚刚恢复时,新签发的流地址需要几秒才能在 CDN 边缘节点同步——这段时间对该地址的请求会返回 404。HLS 流也存在类似的瞬时 404 场景(滑动窗口剔除、签名 URL 过期、节点失同步)。
现在 404 不再单独作为下线判定的依据。真正的下线信号通过两条更精确的渠道:连续多次网络失败(数量与窗口由"离线检测"配置决定,与静默期共用同一组参数),或 HLS 的
#EXT-X-ENDLIST标签(平台明确告知流已结束)。HLS 直播正常结束时立即关闭会话
当 HLS 流播放列表带有
#EXT-X-ENDLIST标签(平台明确表示直播已结束)时,会话会立即结束,不会再走断流静默期。后处理流水线(合并、上传等)也因此能更早开始。零字节"幽灵会话"清理
小于
min_segment_size_bytes阈值的录制片段会被自动丢弃(避免保留无用的几秒钟记录)。此前对应的会话行仍会留在数据库里,在仪表盘上显示为零字节卡片——现在通过两层机制清理:- API 默认过滤——会话列表 API 默认隐藏零字节的已结束会话;活跃(尚未结束)的会话始终保留。需要诊断时可通过
?include_empty=true调出来,或直接通过会话 ID 访问。 - 后台定期清理——空会话行在结束 5 分钟后会被自动从数据库删除(默认扫描间隔 30 分钟),相关的弹幕统计、分段记录、生命周期事件等子行一并清理。
- API 默认过滤——会话列表 API 默认隐藏零字节的已结束会话;活跃(尚未结束)的会话始终保留。需要诊断时可通过
按主播覆盖离线判定节奏
主播、模板、平台编辑页新增了一张 离线检测(Offline Check) 卡片,可以为单个主播(或使用某个模板/平台的所有主播)覆盖全局的"离线判定"节奏。任何字段留空表示沿用上一级配置。适合那些需要更长静默期的主播,或希望某个账号比全局默认更快被判定离线的场景。
并发下载:可见性与更智能的调度
当同时直播的主播数量超过 max_concurrent_downloads 配置时,rust-srec 现在会清楚告诉你哪些主播正在等待空闲槽位,并使用更智能的规则决定下一个空槽分配给谁。
主播卡片新增"排队中"徽章
当主播正在直播但暂时排队等待空闲下载槽位时,卡片不再固定显示红色"直播中"徽章却没有任何进度——而是显示琥珀色 排队中 徽章(高优先级主播则显示更深的玫红色调)。悬停可看到工具提示,包含"已达到并发限制"说明和"已等待 X 时间"计时器,一眼即可分辨哪些主播在录制、哪些还在排队。
即使排队期间刷新仪表盘,徽章也会保留——队列状态会包含在初次连接时收到的快照中。
高优先级主播真正享有优先权
此前当专用高优先级池和普通池都已占满时,高优先级主播只能按先来后到的顺序排在更早调用者之后。现在每当一个槽位释放,优先级最高的等待者会获得它——不论是哪个池子腾出来的位置。
触达上限不会再"整体卡死"
此前一旦并发上限被占满,监视事件循环会被阻塞直到有槽位释放。这意味着在饱和期间主播下线的事件会被卡住,主播会一直停留在"直播中"状态直到上限解除。现在监视事件循环与槽位获取已解耦,所有主播的上下线和恢复事件都能实时处理,即使下载在排队也不受影响。
未真正开始录制时不会预连弹幕
当录制处于排队状态时,弹幕(聊天)采集也会跟着等待——不会提前打开平台连接。这避免了在尚未实际录制的流上消耗平台连接配额。
长时间排队后过期的 URL 会自动刷新
Douyin、Huya 等平台的流地址包含带签名的令牌,可能在几分钟内过期。当排队中的录制等待超过 排队刷新阈值(默认 60 秒,可在全局配置的"并发与性能"中调节,无需重启)时,rust-srec 会在启动引擎前自动重新检查主播以获取新的 URL 和请求头。把阈值设为 0 表示每次排队等待都刷新;调高则可在流通常较稳定时减少平台请求。
录制时段会在等待之后依然生效
如果主播的录制时段在排队期间关闭,排队中的录制会被干净地取消,而不会在槽位空出的瞬间在时段外开始录制。
优雅关闭时不会启动新的录制
此前排队中的录制可能在某个录制刚结束、
stop_all释放槽位的瞬间抢到位置——意味着系统正在关闭过程中却又起了一个新录制。现在关闭过程中所有排队任务都会被拒绝,整个系统能干净地退出。重复的直播事件不再产生重复的录制
如果同一个主播的直播事件在短时间内被分发了两次(例如真实的直播事件与从静默期恢复的合成事件竞争),只有一条录制流水线会运行——重复的会被识别并跳过。
健康检查会指出并发上限是否成为瓶颈
/health端点的download_manager组件现在会在"所有槽位被占满且至少有一个主播在排队等待"时报告 Degraded 状态——也就是max_concurrent_downloads配置实际正在拖慢系统。仅占满但无人排队仍然算作 Healthy(满负荷是正常运行,不是故障),因此这个信号不会在每次黄金时段都触发。适合用于监控仪表盘和资源不足的告警。
Rclone 带宽与吞吐量控制
rclone 流水线步骤现在以专门的表单字段暴露 rclone 的带宽和并发参数,无需再记 rclone 的命令行标志语法即可对上传进行限速。
直接在 UI 上限制上传带宽
在 rclone 步骤的 高级 → 吞吐量 卡片中,新增的 带宽限制(Bandwidth Limit) 字段支持简单值(如
10M,上下行各限 10 MiB/s)、上下行不对称(如10M:100k,上行 10 MiB/s、下行 100 KiB/s),甚至完整时间表(如08:00,512k 23:00,off,按时段动态调速)。输入框下方直接给出示例,无需翻 rclone 文档。调节并发与远端 API 速率限制
同一卡片还提供 Transfers(并发文件数)、Checkers(并发完整性校验)、TPS Limit / TPS Burst(远端 API 每秒事务数及突发——当对象存储有速率限制时尤其有用)、Multi-Thread Streams、Multi-Thread Cutoff(多线程拷贝触发的文件大小阈值)等专门字段。留空即"使用 rclone 的默认值",只需填你想改动的那几项。
现有预设照旧工作;"额外参数"仍然胜出
此前保存的预设无需迁移即可正常加载。如果你已经在 额外参数(Extra Arguments) 列表里填入过
--bwlimit 5M之类的内容,它会继续生效——并且仍然优先于专门的吞吐量字段,不会因为这次更新而悄悄改变行为。
Mesio CLI
运行 Mesio 时可以不生成日志文件
Mesio 现在支持
--disable-log-file,适合一次性运行、脚本调用,或只想在控制台查看消息的临时目录。使用该选项时,Mesio 不会创建mesio.log。重定向命令时日志更清爽
当输出被重定向到文件或其他命令时,Mesio 会避免加入控制台颜色,让保存下来的日志保持普通文本,读起来更清楚。
平台修复
Huya:整改提示不再让主播卡在离线状态 (#557)
当 Huya 显示"该主播涉嫌违规,正在整改中"这类临时性的平台审核提示时,rust-srec 此前会将其当作永久封禁处理并停止轮询——意味着主播恢复直播后录制仍长时间黑屏。现在该提示会被当作普通的离线状态处理,轮询按正常节奏继续。
Huya:部分房间的 CDN 选择更准确 (#513 / #514)
此前在某些房间下,rust-srec 会选到 Huya 自己评分最差的那一路 CDN,少数房间还会有一路本来可用的 CDN 被整体丢弃。现在的选择行为与 Huya 自家网页播放器一致,首选 / 黑名单 CDN 配置也能按预期生效。
小红书:直播结束后能干净地切换到离线 (#510)
此前小红书直播结束后,监视会在每次轮询时报硬错误,而不是把主播标记为离线,导致仪表盘上一直显示"录制中"。现在结束的直播会正常切换到离线状态。
前端
平台已删除主播改用"账号未找到"徽章 (#519)
那些被平台标记为已删除、被封号或 URL 输错的主播,此前与磁盘满等通用错误一样使用红色 监视已停止 徽章。现在它们会显示一个独立的橙色 账号未找到 徽章,悬停说明可能的原因——一眼就能分辨出真正需要处理的主播。
Twitch、YouTube 品牌图标已恢复 (#545)
上游图标库更新移除了 Twitch、YouTube 等品牌 logo,现在已经在平台图标映射中重新加回——仪表盘的外观与之前一致。
会话详情页"时间线"Tab 计数修正——徽章此前只统计标题变更,忽略了会话生命周期事件。现在两者合计显示,与 Tab 内实际渲染的条目数一致。
会话时间线翻译更准确——简体中文界面:
原因:已完成→原因:下载断开(下载干净断开但是否真的下线尚未确定)通过备份计时器确认。→等待恢复超时后确认。(更准确表达"等待期内没有重新检测到直播")- 新增
主播离线、连续失败、弹幕流已关闭等翻译,用于会话结束原因展示。
亮点
新增输出根写入门,提升录制文件系统故障的弹性 (#508)
当录制磁盘写满或目标挂载不可写时,rust-srec 现在会在文件系统边界上暂停录制,把状态通过
/health暴露出来,发出一条包含可操作恢复说明的 critical 级通知;当文件系统恢复可写时会自动恢复——对于常见的磁盘满场景,无需重启。对于"通过宿主机清理破坏了 Docker 绑定挂载"的情况(例如通过宝塔面板的"移至回收站"操作挂载目录),写入门无法自动恢复(这是 Linux VFS 的限制,与 rust-srec 无关),但它现在会在一次监视周期内检测到问题、停止把日志淹没的级联重试风暴,并以明确的恢复说明提示用户重启容器。替换了 #508 中可见的 40+ 次级联失败风暴,只留下一个干净的
Degraded状态和一条通知。新的Docker 故障排查指南列出了如何避开挂载失效陷阱的安全清理方式。在 ffmpeg 和 streamlink 引擎中新增运行时 ENOSPC 检测
引擎的 stderr 读取任务现在会监控
"No space left on device"/ errno-28/ 退出码 228,并向下载管理器发出SegmentEvent::DiskFull事件,由管理器路由给写入门。这对"录制进行中磁盘才写满、今天的日期目录已存在"的常见场景至关重要——这种情况下启动前的ensure_output_dir钩子无法捕获故障。启用
StreamerState::OutOfSpace的运行时写入该状态此前存在于领域模型中,但从未在运行时被写入。现在当写入门阻塞某个主播时,状态会点亮为
OutOfSpace;写入门恢复时会自动清除。在主播列表中以停止状态徽章显示。基于
rust-i18n的后端通知本地化新增
rust-srec/locales/{en,zh-CN}.yml文件,新增RUST_SREC_LOCALE环境变量。所有通知事件均已本地化(英文和简体中文)——包括直播上下线、录制生命周期、分段、流水线任务、系统告警和凭据事件。推送到外部接收端(Telegram、Gotify、Discord、Webhook、邮件、Web Push)的通知会自动遵循该语言设置。新增
output_path_inaccessible通知事件与前端订阅与已有的
out_of_space磁盘预警不同:此事件仅在写入门实际阻塞录制时触发。优先级为 Critical。每次Healthy → Degraded切换只发出一次(而不是每次失败都发),通过每个启用的通知渠道推送一次。在订阅管理器中以独特的深红色显示。新增一次性启动探测,针对已配置的输出根
容器启动时,在完成主播加载之后、调度器启动之前,写入门会对每个已配置的根执行一次有界的 5 秒探测(通过
spawn_blocking+ 超时),以便在第一秒就暴露已经坏掉的挂载点,而不是等到第一次监视周期触发下载尝试才发现。
新增环境变量
| 变量 | 用途 |
|---|---|
RUST_SREC_OUTPUT_ROOTS | 以逗号分隔的绝对路径列表,作为写入门的输出根边界。未设置时,写入门会基于 OUTPUT_DIR 通过 2 段启发式推导一个根。 |
RUST_SREC_LOCALE | 后端通知字符串的语言环境。影响所有通知事件(直播、录制、分段、流水线、系统、凭据)。支持:en、zh-CN,默认 en。 |
详见配置说明。
重要重构
为了让写入门干净地落地,顺便完成了几项下载子系统的重构:
ensure_output_dir从引擎中上移到管理器。此前每个引擎(ffmpeg、streamlink)都在自己的start()中调用ensure_output_dir,同时各自做错误包装。现在统一改为在DownloadManager::prepare_output_dir预启动钩子中调用一次,写入门也在同一位置介入。Mesio 和未来新增的引擎都能免费受益。修复了遗留的
EngineStartError::from(crate::Error)bug。旧实现把所有 I/O 故障都归类为DownloadFailureKind::Other,丢失了std::io::ErrorKind信息。新实现会沿错误源链向下走,找到第一个std::io::Error并按其类型分类——重试决策和熔断器现在可以为所有 I/O 路径看到正确的失败类别。set_circuit_breaker_blocked重命名为set_infra_blocked(reason)(位于monitor/service.rs)。新签名接受一个InfraBlockReason枚举,包含熔断器阻塞(原有行为)和输出根阻塞(新增)两个变体。两者走同一条持久化路径,审计记录集中在一处。这是一次公开 API 重命名,未保留废弃别名。扩展
reset_errors(仅文档修正——实际的重置路径通过StreamerManager::clear_error_state已经正确)。DownloadManager.output_root_gate字段改用OnceLock,在容器初始化时一次性晚绑定,之后读取无锁。这是必要的:服务容器的两个 builder 中有一个在DownloadManager之后才构造NotificationService。Rclone 处理器迁移到类型化的
RcloneConfig结构体。处理器此前通过对泛型serde_json::Value拨字段来解析配置;现在使用#[derive(Deserialize)]结构体,与 crate 中其他所有处理器一致。行为完全不变——只是消除了一堆.get(…).and_then(…)调用,并为新增的吞吐量字段提供了自描述的归处。
兼容性
- 启动时会自动执行两项新的数据库迁移(检查历史条所需的迁移,以及
global_config表新增的排队刷新阈值字段),无需任何操作。 GET /sessions默认行为有所变化:零字节且已结束的会话不再返回(仪表盘上的"幽灵卡片"由此消失)。需要看到全部记录时,加上?include_empty=true即可。GET /sessions/:id不受影响。set_circuit_breaker_blocked重命名为set_infra_blocked(reason)——若有外部代码调用 monitor service(尚未发现),需要同步更新。DownloadManagerEvent::DownloadRejected事件新增了kind: DownloadRejectedKind字段。通过 WebSocket 或广播 API 订阅事件流的外部程序会在 JSON 载荷中看到该字段;忽略它是安全的。- 下载 WebSocket 数据流新增两种事件类型:
DOWNLOAD_QUEUED(主播正在等待空闲槽位)和DOWNLOAD_DEQUEUED(排队中的尝试在开始前被取消)。初次连接收到的快照也新增了queued数组。外部订阅者会在 enum 中看到这两个新成员;忽略它们是安全的。
备注
- 挂载失效场景无法在容器内部自动恢复。重新绑定 Docker 挂载需要
CAP_SYS_ADMIN以及对宿主机 mount namespace 的访问权限,非特权容器没有这些能力。写入门负责检测并提示用户重启;真正的自动恢复是部署侧的问题。Docker 故障排查列出了从源头避免挂载失效的安全清理方式。