Noah 48dc8fd65b fix(preview): 中转模式在有 RTT 的链路上频繁缓冲——旁路挪到解析点、掉队原地续播、缓冲分低延迟 2 s / 流畅 5 s 两档 (#1716)
* fix(preview): stream-gears FLV 在解析点旁路,不等写盘循环整 GOP 落盘

httpflv 的写盘循环把 tag 攒在 flv_tags_cache 里,等下一个关键帧到来才把整个 GOP
一起写出——预览的旁路点原来就挂在这个写出循环里,于是预览端每个 GOP 间隔(B 站
常见 2–4 s)收到一次整 GOP 的突发、其余时间一个字节都不来,且整体落后一个 GOP。
播放器拿到的缓冲恰好在下一个突发到达时耗尽,零余量:同机 0 ms 下勉强连上,链路
上稍有 RTT / 抖动就 waiting;GOP 长于 mpegts.js 的追帧阈值时更是每个 GOP 都跳一次
再停一次(实测 GOP 4.2 s 的房间同机也有 85% 时间处于 waiting)。

把旁路挪到 tag 解析完、进缓存之前:预览按 tag 实时收到,与 mesio 路径(在条目进
管线前 tee)一致。写盘循环一个字节不变;旁路仍只是 push,不 await、不返回错误。

实测(arrival-probe,40 s):同一斗鱼房间直连 CDN 到达连续(最大静默 0.24 s),
中转修前每 2.0 s 一个 ~940 KB 突发、20 个静默间隙全部 > 1 s。

Refs #1712 #1713

* feat(preview): 快照保留最近 6 s 的 GOP,新订阅者起播就有几秒缓冲

原来快照只含「当前 GOP」:新订阅者从零到一个 GOP 之间的缓冲起步,之后到达 = 消耗,
缓冲永远这么薄,链路上几十到几百毫秒的抖动就 waiting。浏览器直连 CDN 时 CDN 会先
给几秒的 GOP 缓存,中转得给同样的深度。

PreviewSink 里多一个已完成 GOP 的队列(按关键帧到达时刻计,保留 SNAPSHOT_WINDOW = 6 s
以内的),快照 = 文件头 + 序列头 + 队列里的 GOP + 当前 GOP,实际深度在 6 s 到 6 s +
一个 GOP 之间。字节上限 MAX_SNAPSHOT_BYTES = 16 MB(6 s × 20 Mbps),超过先丢最旧的
GOP;单个 GOP 超过 MAX_GOP_BYTES 作废时历史一并放掉(不再连续);序列头内容变了或
fMP4 换了 init 时历史也放掉(旧 GOP 与新参数不配),内容相同的重发(stream-gears
分段时重放 onMetaData / 序列头)不影响。热路径只多几次 usize 比较与一次 VecDeque
push,仍不 await、不返回错误;无订阅者时同样保留(第一个订阅者也要立刻有缓冲),
每路多占约 6 s 的码率(1080p 2–5 MB)。

`with_snapshot_window(Duration::ZERO)` 给只关心分块判定的测试用。

Refs #1712 #1713

* fix(preview): 客户端掉队时从最近关键帧原地续播,不再断开重连

广播缓冲被覆盖(Lagged)时原来直接结束响应,浏览器看到「连接已断开,2 秒后重连」
的加载态,再花一次 HTTP 往返 + 等下一个关键帧才恢复;带宽长期偏紧的链路上就是
反复「触发加载」。

现在 hub 多一个 `resubscribe(previous, timeout)`:复用原订阅的连接许可(不占第二
个名额),向写入端再要一份从最近关键帧起的快照与新接收端;响应在同一条连接里续
发这份快照——FLV 去掉文件头(`Subscription::snapshot_after_header`,解码器不能中途
再收到文件头;序列头照给,掉队期间可能换过),fMP4 的 init segment 照发(MSE 接受
中途再来一份)。mpegts.js 的 remuxer 会把批次之间的时间戳跳变收平,画面表现为跳
过一段而不是转圈;一次响应最多 MAX_RESYNCS_PER_RESPONSE = 20 次,再多说明客户端
长期跟不上,结束响应交给播放器。每次重对齐 warn 日志带 skipped 与累计次数。

Refs #1712 #1713

* perf(preview): 客户端追赶积压时把广播缓冲里攒着的分块合并写出

实时转发每收到一个分块就写一个 chunked 帧(FLV 一个 tag 几百字节到几十 KB)。
客户端跟得上时缓冲里本来就只有一个,无从合并;追赶积压(起播快照刚发完、链路
抖动之后)时缓冲里攒着几十上百个,逐个写出是几十上百次 socket 写与 chunked 帧。
`coalesce` 在 `recv()` 到手后用 `try_recv` 把已经在缓冲里的后续分块合并成一个
响应块(上限 COALESCE_BYTES = 64 KB),缓冲空着就原样透传不复制,不引入任何等待;
合并途中撞上 Lagged 时把跳过数交回主循环按掉队处理,不吞掉。

Refs #1712 #1713

* fix(ui): 中转模式的播放器维持约 5 s 缓冲,用倍速而不是跳变追帧

mpegts.js 原来对中转、直连一律 liveBufferLatencyMaxLatency 3 s / MinRemain 0.5 s:
缓冲一超过 3 s 就跳到末尾前 0.5 s,等于把服务端刚给的几秒快照直接扔掉,之后只剩
0.5 s 余量,链路一抖就 waiting;GOP 长于 3 s 的流更是每个 GOP 跳一次再停一次。

按取流方式分两套参数(`Players` 新 prop `transport`,LivePreviewPlayer 按实际 source
传):
- relay:liveSync 倍速追帧——落后 > 9 s 用 1.1× 追到 5 s 附近,≤ 5 s 恢复 1×;
  只在落后 > 20 s(长时间暂停后)才直接跳到末尾前 5 s。与服务端 6 s 的快照窗口配合,
  播放器起播就有约 5 s 缓冲并一直保持。
- direct:沿用原参数(CDN 自带 GOP 缓存,ForgQi 实测异地直连不卡,不动)。

fMP4 的 MediaSource 播放器(只在中转路径上)同样改成:起播对齐到末尾前 5 s、
落后 > 9 s 用 1.1× 追、> 20 s 才跳;另加空洞跳过——服务端掉队重对齐后分片时间戳
前跳,缓冲区出现空洞,停在空洞前沿时跳到下一段起点,不再原地转圈。

Refs #1712 #1713

* style(preview): cargo fmt 与 clippy(len_zero)

* feat(preview): /live 支持 snapshot_ms,起播快照按播放器要的缓冲深度回溯

新订阅者拿到多深的快照,播放器就有多深的起播缓冲,也就是多长的中转延迟。之前一律给整个 6 s
保留窗口,只适合「流畅」档;低延迟档要 2 s 就只该拿 2 s,而不是拿 6–10 s 再用 1.1 倍速追 40 s。

- PreviewHub::subscribe_with_depth(depth):从当前 GOP 往前取,取到第一个关键帧到达时刻早于
  「现在 − depth」的 GOP 为止(含),起播缓冲落在 depth 到 depth + 一个 GOP 之间;Some(ZERO)
  只给当前 GOP,None 给整个窗口。Subscription 记住 depth,Lagged 重新对齐沿用。
- GET /v1/streamers/{id}/live?snapshot_ms=2000;不带参数行为不变。「开始直播预览」日志多一个
  snapshot_ms 字段。
- 保留窗口 SNAPSHOT_WINDOW 仍是 6 s,服务端内存不变;热路径只在有订阅请求时多一次
  VecDeque::make_contiguous(历史只有几个 GOP)。
- 单测 +2:深度切分的边界(零 / 跨线 / 大于全部历史 / 空历史)、带深度订阅与重对齐沿用深度。

Refs #1713 #1716

* feat(ui): 中转预览分「低延迟 / 流畅」两档,默认低延迟 2 s,卡了自动升档

ForgQi 在 #1713 下问「中转有办法不延迟 5 s 也不卡顿吗」。主因(整 GOP 成坨下发)修掉后 5 s 是
过度配置:同一条模拟链路(60 ms RTT ± 16 ms、丢包重传停顿、20 Mbps)上四路真实房间扫
1 / 1.5 / 2 / 3 / 5 s 目标缓冲各播 180 s,三路 FLV 从 1 s 起就 0 次 waiting;只有 HLS 来的
fMP4 要盖住一个分片(B 站 1–3 s),3 s 起 0 次、4 s 余量最低约 1–2 s。

- app/lib/live-buffer.ts:缓冲控制统一到一处(原来 mpegts.js 的 liveSync 管 FLV、fMP4 播放器
  自己一套 chase):起播跳到「末尾 − target」,快照落地那几秒里超了再对齐(慢链路上几 MB 要
  几秒才到齐,每对齐一次多看 2 s、最多 15 s),稳态超 max 倍速 / 超 hard 跳 / 跳过重对齐留下的
  空洞,稳态里缓冲耗尽一次报 onStall。档位:low = FLV 2 s / fMP4·TS 4 s,smooth = 5 s
  (目标与原 0d41140 相同,倍速阈值 9 → 7)。mpegts.js 在中转模式下关掉自己的追帧,直连一行未动。
- /live 请求带 snapshot_ms = 目标缓冲,起播就是目标深度,不再从 6–10 s 追回来。
- 低延迟档在稳态里卡一次 ≥ 1 s 或 60 s 内两次,本次播放升到流畅档重连(更深的快照起播),
  角标「卡顿,已切到流畅档」;换房间或改偏好即复位。
- 弹层标题栏与监视器工具栏各一个「低延迟 / 流畅」开关,记 localStorage biliup.preview.latency,
  默认低延迟;直连模式下不显示。

实测(同一模拟链路,180 s,hub 最新时间戳 → 已渲染):低延迟档三路 FLV 延迟中位 2.1–2.4 s、
fMP4 2.8–3.7 s,四路 0 次 waiting;流畅档 5.1 s。每 18 s 停 2.5 s 的链路上低延迟档卡两次后
自动升档,之后 130 s 0 次。

Refs #1713 #1716
2026-09-23 06:20:47 +00:00
2026-09-23 00:33:34 +08:00
2025-07-16 03:43:05 +08:00
2023-12-08 23:37:40 +08:00
2023-12-31 23:56:40 +08:00
2024-01-17 22:15:17 +08:00
2026-09-23 00:33:34 +08:00
2026-09-23 00:33:34 +08:00
2024-01-27 07:46:15 +00:00
2019-07-28 17:54:29 +08:00
2021-01-20 02:54:06 +08:00

biliup

Python PyPI PyPI - Downloads License Telegram

GitHub Issues GitHub Stars GitHub Forks

🛠️ 功能

  • 提供 skill,让你的 Agent 成为 up 主: npx skills add biliup/biliup
  • 开箱即用,多种安装方式,提供可视化 WebUi 界面
  • 多主播录制/上传,24X7 无人值守运行,高自定义元信息
  • 录制同时抓取弹幕,输出 XML 弹幕文件
  • 正在录制的直播间可在 WebUI 内直接预览:复用正在写盘的那一路流(不另外拉流),带封面、实时弹幕与写盘码率曲线,支持多路同屏监视
  • 作为自动化流程中的命令行工具封装使用
  • 边录边传 0 落盘

论坛:BBS

📺 支持平台

内置 19 个直播平台解析,未匹配的地址会交给通用适配器(依赖本机的 yt-dlp / Streamlink)尝试处理。

平台 站点 弹幕
哔哩哔哩 live.bilibili.com、b23.tv ✅
抖音 douyin.com ✅
斗鱼 douyu.com ✅
虎牙 huya.com ✅
快手 kuaishou.com、chenzhongtech.com
AcFun acfun.cn
网易 CC cc.163.com
映客 inke.cn
猫耳 FM missevan.com
KilaKila / 红豆 FM live.kilakila.cn、hongdoufm.com
YY yy.com
TTingLive ttinglive.com
Twitch twitch.tv(直播与录像) ✅
YouTube youtube.com、youtu.be ✅
TwitCasting twitcasting.tv ✅
niconico nicovideo.jp
AfreecaTV afreecatv.com
Bigo Live bigo.tv
Picarto picarto.tv
通用适配器 其他 http(s) 地址

📜 更新日志

Important

Disclaimer / 免责声明

  • 本项目仅供个人学习研究,不保证稳定性,不提供技术支持
  • 使用本项目产生的一切后果由用户自行承担
  • 禁止商业用途,请遵守版权及平台规定
  • This project is for personal learning and research purposes only
  • No stability guarantee or technical support provided
  • Users are solely responsible for any consequences of using this project
  • Commercial use is strictly prohibited
  • Please respect copyright and platform ToS

📜 使用文档

B 站命令行投稿工具,支持短信登录、账号密码登录、扫码登录、浏览器登录以及网页Cookie登录,并将登录后返回的 cookie 和 token 保存在 cookies.json 中,可用于其他项目。

  • 下载 Release: biliupR
  • 获取命令帮助 biliup --help
  • 登录信息文件可用 -u/--user-cookie 指定,便于多账号切换

文档地址:https://biliup.github.io/biliup-rs

Upload video to bilibili.

Usage: biliup [OPTIONS] <COMMAND>

Commands:
  login     登录B站并保存登录信息
  renew     手动验证并刷新登录信息
  upload    上传视频
  append    是否要对某稿件追加视频
  show      打印视频详情
  comments  查看视频评论
  reply     回复视频评论,默认只打印将要回复的内容
  dump-flv  输出flv元数据
  download  下载视频
  server    启动web服务,默认端口19159
  list      列出所有已上传的视频
  help      Print this message or the help of the given subcommand(s)

Options:
  -p, --proxy <PROXY>              配置代理
  -u, --user-cookie <USER_COOKIE>  登录信息文件 [default: cookies.json]
      --rust-log <RUST_LOG>        [default: tower_http=debug,info]
  -h, --help                       Print help
  -V, --version                    Print version

启动录制服务

启动web服务,默认端口19159

Usage: biliup server [OPTIONS]

Options:
  -b, --bind <BIND>            Specify bind address [default: 127.0.0.1]
  -p, --port <PORT>            Port to use [default: 19159]
      --auth                   开启登录密码认证
      --secure-session-cookie  为会话 Cookie 附加 Secure 属性。仅当通过 HTTPS 反向代理访问 Web UI 时开启; 直接通过 HTTP 远程访问时开启会导致浏览器丢弃登录态
  -c, --config <FILE>          使用 biliup 1.0.7 风格配置文件启动录制
  -h, --help                   Print help

Important

自 #1660 起,--bind 的默认值由 0.0.0.0 改为 127.0.0.1,即默认只监听本机,局域网/公网无法直接访问。 如需从其他设备访问,请加上 --bind 0.0.0.0 --auth,详见下方「🔓 远程访问」一节。Docker 镜像已内置该参数,不受影响。

单独下载一场直播/视频,无需启动服务:

biliup download <URL> -o "./video/%Y-%m-%dT%H_%M_%S{title}" --split-time 1h

--split-size 与 --split-time 可按体积或时长自动分段,-o 支持 {title} 占位符与 strftime 时间格式。

🚀 快速开始

Windows

Linux 或 macOS

  1. 安装 uv
  2. 安装:uv tool install biliup
  3. 启动:biliup server --auth
  4. 访问 WebUI:http://127.0.0.1:19159(默认只监听本机,远程访问见下方说明)

Termux

Note

默认下载器 stream-gears 由 Rust 实现,无需外部依赖。若配置 ffmpeg 下载器或使用后处理,需要本机安装 ffmpeg;YouTube、niconico 等平台与通用适配器则依赖 yt-dlp 或 streamlink。Docker 镜像已内置 ffmpeg。

🔓 远程访问(监听 0.0.0.0)

默认的 127.0.0.1 只允许本机访问。需要从其他设备访问时,显式指定 --bind 0.0.0.0 并开启 --auth:

biliup server --bind 0.0.0.0 --auth

首次打开 http://your-ip:19159 会引导设置管理员密码(用户名固定为 biliup),之后即可正常登录使用。

Note

绑定非回环地址时必须同时开启 --auth,否则会拒绝启动,避免无认证的 Web API 被暴露:

refusing to expose the unauthenticated Web API on 0.0.0.0:19159; use a loopback bind address or enable --auth

Warning

将 Web UI 暴露到公网存在风险,建议仅在可信局域网内使用,或置于反向代理之后。 首次访问即可设置管理员密码,请在启动后立即完成初始化,避免被他人抢先占用。 若通过 HTTPS 反向代理访问,请加上 --secure-session-cookie;直接以 HTTP 远程访问时不要加,否则浏览器会丢弃登录态(见 #1669)。

Docker

镜像已内置 --bind 0.0.0.0 --auth,开箱即用,无需额外配置:

docker compose up -d

打开 http://your-ip:19159 设置管理员密码即可。录播与配置默认持久化在容器的 /opt 卷中。

Important

若要自定义 command,必须带上 --bind 0.0.0.0。容器内若监听 127.0.0.1,宿主机的端口映射无法转发进容器,Web UI 将完全无法访问。

📤 边录边传(sync-downloader)

把 downloader 设为 sync-downloader 后,ffmpeg 会把直播流 remux 成 Matroska 写到 stdout,按 UPOS 分片一边录一边上传,每录满 file_size(默认 2.5 GB,向上对齐到 10 MiB)就作为一 P 追加到同一稿件。前提:

  • 本机 PATH 中有 ffmpeg;HLS 流(B 站 bili_protocol = "hls_fmp4")若装有 streamlink 会用它拉流再交给 ffmpeg,没有则由 ffmpeg 直拉。
  • 主播必须绑定上传模板,且模板对应的 cookies 文件可用;uploader = "Noop" 或没有模板时不会录制,日志会给出 边录边传需要先为主播设定上传模板。
  • 上传并发固定 3 线程,不受 threads、segment_time 控制。
downloader = "sync-downloader"
uploader = "bili_web"
file_size = 2621440000          # 每 P 大小,可按上传带宽调小
# sync_save_dir = "/data/sync"  # 可选:额外保留每 P 的本地副本

[streamers."某主播"]
url = ["https://live.bilibili.com/1234"]
title = "{streamer}%Y-%m-%d 直播录像"
tid = 171
user_cookie = "cookies.json"

行为说明:

  • 录制期间每 P 会同时写入系统临时目录($TMPDIR/biliup-sync/worker-<id>/)作为兜底;预传完整且校验一致时直接 complete,否则(直播提前结束、上传落后)按实际长度从临时文件重传。投稿确认后临时文件删除,设置了 sync_save_dir 的副本保留并交给 postprocessor。
  • 停止/暂停/编辑主播只会结束当前分段,已录内容仍会上传并投稿;上传或投稿失败时保留有内容的分段文件并在日志中打印路径,可手动补传。
  • B 站直链约 1 小时过期。分段结束后拉不到数据时会立刻交回监控循环重新解析直链,并在同一稿件上继续追加分 P。
  • 排查问题时留意 ERROR ... 下载流程出错 日志:会带上具体原因(cookies 文件路径、preupload 拒绝等),而不是静默退回落盘录制。

🧑‍💻开发

架构概览

Rust后端 + 精简 Python 包 + Next.js前端的混合架构。

graph TB
    subgraph "🌐 前端层"
        UI[Next.js Web界面<br/>React + TypeScript<br/>Semi UI组件库]
    end

    subgraph "⚡ Rust后端服务"
        CLI[命令行与 Web API<br/>biliup-cli<br/>REST API / WebUI / 配置导入]
        CORE[核心库<br/>biliup<br/>直播解析 / 下载 / 上传]
        DANMAKU[弹幕库<br/>danmaku<br/>多平台协议 / XML输出]
        GEARS[Python绑定<br/>stream-gears<br/>python -m biliup 入口]
    end

    subgraph "🐍 Python包"
        PYENTRY[最小入口<br/>biliup.__main__<br/>调用 stream_gears.main_loop]
        PYUPLOAD[投稿库<br/>bili_webup / bili_webup_sync<br/>供外部项目调用]
    end

    subgraph "🗄️ 数据层"
        DB[(SQLite数据库<br/>配置存储<br/>任务状态 & 日志)]
        FILES[文件系统<br/>视频分段 / 弹幕XML<br/>缓存与临时文件]
    end

    subgraph "🌍 外部服务"
        BILI[Bilibili API<br/>视频上传服务]
        STREAMS[直播平台<br/>B站/斗鱼/虎牙/抖音/Twitch等]
    end

    UI --> CLI
    CLI --> CORE
    CLI --> DANMAKU
    CLI --> DB
    CLI --> FILES
    CORE --> STREAMS
    CORE --> BILI
    DANMAKU --> STREAMS
    DANMAKU --> FILES
    GEARS --> CLI
    PYENTRY --> GEARS
    PYUPLOAD --> BILI

    style UI fill:#e1f5fe
    style CLI fill:#f3e5f5
    style CORE fill:#f3e5f5
    style DANMAKU fill:#f3e5f5
    style GEARS fill:#f3e5f5
    style PYENTRY fill:#e8f5e8
    style PYUPLOAD fill:#e8f5e8
    style DB fill:#fff3e0
    style FILES fill:#fff3e0
    style BILI fill:#ffebee
    style STREAMS fill:#ffebee

目录结构

路径 说明
crates/biliup 核心库:直播解析、下载器、B 站投稿与凭据管理
crates/biliup-cli 命令行与 Web 服务:REST API、WebUI 托管、录制调度
crates/danmaku 弹幕客户端:多平台协议解析与 XML 输出
crates/stream-gears PyO3 绑定,暴露给 python -m biliup
app、public Next.js WebUI 源码;npm run build 产物输出到 out/ 并由后端内嵌
biliup 精简 Python 包:最小入口与可供外部调用的投稿库
tauri-app 桌面端外壳(实验性)

frontend

  1. 确保 Node.js 版本 ≥ 18.17(Next.js 14 要求)
  2. 安装依赖:npm i
  3. 启动开发服务器:npm run dev
  4. 访问:http://localhost:3000

Python

  1. 安装依赖 maturin dev
  2. npm run build
  3. 启动 Biliup:python3 -m biliup

Rust-cli

  1. npm run build
  2. 构建 cargo build --release --bin biliup
  3. 开发启动 BiliupR:cargo run

Note

biliup-cli 通过 rust-embed 内嵌前端产物目录 out/,因此在 cargo build / cargo test 之前必须先执行 npm run build,否则编译会因 out/ 不存在而失败。

测试

cargo test -p biliup -p biliup-cli -p danmaku

🤝Credits

  • Thanks ykdl, youtube-dl, streamlink provides downloader.
  • Thanks THMonster/danmaku.

💴捐赠

爱发电 »

⭐Stars

Star History Chart

S
Description
自动直播录制、投稿、twitch、ytb频道搬运工具。命令行投稿(B站)和视频下载工具,提供多种登录方式,支持多p。边录边传0落盘
https://biliup.github.io/biliup/
Readme MIT
6.7 MiB
0 Stars 1 Watchers 0 Forks
Languages
Rust 75.6%
TypeScript 16.6%
Python 4.8%
SCSS 2%
JavaScript 0.4%
Other 0.5%