Skip to content

更新日志

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 分钟),相关的弹幕统计、分段记录、生命周期事件等子行一并清理。
  • 按主播覆盖离线判定节奏

    主播、模板、平台编辑页新增了一张 离线检测(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 StreamsMulti-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后端通知字符串的语言环境。影响所有通知事件(直播、录制、分段、流水线、系统、凭据)。支持:enzh-CN,默认 en

详见配置说明

重要重构

为了让写入门干净地落地,顺便完成了几项下载子系统的重构:

  • ensure_output_dir 从引擎中上移到管理器。此前每个引擎(ffmpegstreamlink)都在自己的 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 故障排查列出了从源头避免挂载失效的安全清理方式。

Released under the MIT License.