* 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
🛠️ 功能
- 提供 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
- 下载 Release: bbup-app
Linux 或 macOS
- 安装 uv
- 安装:
uv tool install biliup - 启动:
biliup server --auth - 访问 WebUI:
http://127.0.0.1:19159(默认只监听本机,远程访问见下方说明)
- 后台运行
nohup biliup server --auth &- 请查看参考
Termux
- 详见Wiki
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
- 确保 Node.js 版本 ≥ 18.17(Next.js 14 要求)
- 安装依赖:
npm i - 启动开发服务器:
npm run dev - 访问:
http://localhost:3000
Python
- 安装依赖
maturin dev npm run build- 启动 Biliup:
python3 -m biliup
Rust-cli
npm run build- 构建
cargo build --release --bin biliup - 开发启动 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, streamlinkprovides downloader. - Thanks
THMonster/danmaku.
💴捐赠
