ForgQi
03ed3f5ff2
Bumping version
2026-09-23 00:33:34 +08:00
Noah
072604eed5
feat: 直播预览阶段二——内置播放器复用录制流(卡片弹层 + 历史页监视器) ( #1712 )
...
* feat(downloader): 直播预览 hub 与写盘旁路(httpflv / hls / mesio)
每路录制任务一个 PreviewHub(寿命 = 整场 execute(),跨分段与断流重试),下载器开始拉流时
attach 一个写入端,在写盘点旁边把同一份媒体字节 push 给它:httpflv 在关键帧写出循环、
stream-gears/hls 在分片 chunk 循环、mesio 在条目进入修复管线前。fMP4 分片流与 HEVC 序列头
标为不可预览;ffmpeg / streamlink / yt-dlp / 边录边传明确不可预览并给出原因。
写入端热路径只做三件事:GOP 缓冲追加、try_recv 订阅请求、broadcast::send。没有 .await、
没有锁等待、没有 ?;订阅请求由写入端在下一次推送时回应,快照(文件头 + 序列头 + 当前 GOP)
与随后的实时分块严格衔接。GOP 快照上限 8 MB,广播缓冲 256 槽 × 64 KB 且无订阅者时不占用,
每路订阅者数由信号量限制;慢订阅者收到 Lagged 后由端点断开让其重连。
Part of #1708
* feat(api): GET /v1/streamers/{id}/live 与列表的 preview 字段
新增 chunked 视频流端点:先发关键帧对齐的快照再接实时分块,Content-Type 按容器为
video/x-flv 或 video/mp2t,Cache-Control: no-store。房间不存在 / 未录制 404,下载器不能
旁路 415(带原因),连接数超限 429(每路 4、进程 16),尚未开始拉流或正在重连 503 +
Retry-After。慢客户端 Lagged 或写入端换代时结束响应,由播放器重连。注册在 router() 内,
--auth 时与 /v1/streamers 同一道登录校验。
GET /v1/streamers 每项新增 preview: { available, format: "flv" | "mpegts" | null, reason },
未录制为 null。
Part of #1708
* feat(preview): fMP4 分片流也能预览——init segment 快照、按分片广播、解出 codecs
mesio 的 HLS 会话拿到 fMP4 分片时不再标为不可预览:init segment(ftyp + moov)作为文件头
进快照,每个 moof + mdat 分片作为一个关键帧分块广播,新订阅者拿到 init + 最近一个完整分片,
端点 Content-Type 为 video/mp4,列表字段 preview.format 为 "fmp4"。
从 init segment 的 moov/trak/…/stsd 解出 RFC 6381 编码串(avc1.PPCCLL、hvc1.…、mp4a.40.N、
av01 / vp09 / ac-3 / ec-3 / opus / flac)放进 preview.codecs,供前端 MediaSource.isTypeSupported()
校验后 addSourceBuffer;解析只在 attach 后收到 init 时做一次,不在逐分块路径上。
stream-gears 的 HLS 路径没有下载 #EXT-X-MAP 初始化分片,遇到非 TS 分片时标为不可预览并说明原因。
按 ForgQi 在 #1708 的要求把 fMP4 纳入阶段二。
Part of #1708
* feat(ui): 直播间卡片的「预览」弹层——复用录制流的内置播放器
Player.tsx 加 isLive / muted / autoplay / type / codecs 与 onEnded / onError:flv 与 mpegts 走
mpegts.js(直播模式:不攒缓冲、追帧、自动清理 SourceBuffer),fmp4 走一个轻量 MediaSource
播放器(fetch ReadableStream → appendBuffer 串行队列,首个缓冲对齐播放位置,落后 > 3 s 追到
末尾,定期 remove 30 s 之前的缓冲),都以 Artplayer customType 挂载保持 UI 一致;
type 未知时用一次 GET 读 Content-Type 再起播。MediaSource.isTypeSupported 不通过时报错并显示
封面 + 原因,不转圈。
StreamerCard 录制中加「预览」图标按钮(不可预览时禁用,tooltip 显示后端给的原因),封面可点
即打开预览;弹层(LivePreview.tsx)关闭即卸载播放器、断开连接。服务端断开(掉队 / 换直链)
自动重连 3 次,之后交给用户手动重试;不可恢复错误 Toast 提示一次。零新依赖。
Part of #1708
* feat(ui): 历史记录页「实时监视」Tab——多路同屏监视器
<LiveMonitor /> 是独立组件:所有正在录制的直播间以小窗网格展示,可预览且在同屏路数内的
直接播放(默认静音),超出的显示封面、点击替换最早开始的一路(先进先出),下载器不能旁路
的显示封面与原因。同屏路数默认 DEFAULT_MONITOR_TILES = 4(可选 1/2/4/6/9,localStorage 记住),
每路小窗占对应直播间的一个预览连接。正在播放的列表由「用户选择 + 当前可预览房间 + 路数」
纯推导,不在 effect 里 setState。
历史记录页改为两个 Tab:「录制文件」(原表格)与「实时监视」,keepDOM=false 切走即卸载全部
播放器。要把监视器挪到别的页面只需换挂载点。
Part of #1708
* test(preview): 让旁路测试不依赖调度时机
rolling 测试先把订阅请求 poll 进写入端队列再开始写盘,源数据按 2 ms 一块限速送入而不是瞬间灌完
(瞬间灌完时订阅者必然掉队,那是慢订阅者测试覆盖的场景);慢订阅者测试只断言
「收到 + 被告知跳过 = 全部」与写入端不等待,不再对读取任务在负载下的吞吐做假设。
* fix(ui): mpegts.js 的 MSE 解码失败给出可读提示
典型来源是 TS 的 PES 没按访问单元对齐(如 Twitch 的 HLS),mpegts.js 拆出的帧不完整、浏览器拒绝
解码;原文 "Failed to execute 'appendBuffer'…" 对用户没有信息量,改为说明原因并强调录制不受影响。
* fix(preview): fMP4 与 TS 的快照从关键帧起,而不是从分段边界起
B 站 hls_fmp4 的分段(约 1 s)各含多个 moof/mdat 分片、且分段边界不对齐关键帧(实测 10 次冷启动
只有 1 次首帧是 I 帧):写入端现在把每个分段按 moof/mdat 切开,读 init 里的视频轨 ID 与
trex 默认 flags、trun 的 first_sample_flags / 首样本 flags / tfhd 默认 flags,视频首帧为同步样本的
分片才作为 GOP 起点。TS 分片起点则嗅探第一个视频 PES 的 NAL 类型(IDR/SPS → 起点,只有普通片
→ 追加到上一 GOP,说不准 → 按起点处理)。FLV 路径本来就按关键帧 tag 切,未变。
单测覆盖:分片切分(前导 styp、非同步 / 同步 / trex 默认 / 无视频轨)、跨分段边界的快照、
TS 嗅探(跨包的 PES、音频 PES 在前、非 TS 输入);另有 #[ignore] 的 B 站真实抓样核对。
* feat(danmaku): 预览播放器的实时弹幕——录制中的弹幕客户端旁路一份到 SSE
danmaku crate 的 RecorderConfig 新增可选的 live_tx(tokio broadcast):每解出一条事件,在写 XML
之前 send 一份(不等待,无订阅者即丢弃),XML 录制路径不变。DownloadTask 按 stream.danmaku
是否存在建一条 256 槽的广播,传给 danmaku_client;新增 GET /v1/streamers/{id}/danmaku(SSE,
每条 event 为 danmaku / gift / super_chat / guard_buy,data 为 {kind, text, name, color, ts},
进场消息不进;掉队跳过不断开;进程上限 32 路,超出 429;无弹幕客户端的平台 415)。
/v1/streamers 的 preview 字段新增 danmaku: bool,前端据此启用 / 禁用弹幕开关。
Part of #1708
* feat(ui): 预览弹层装载实时弹幕(开关控制),监视器默认关
Player.tsx 新增 danmaku: { url, enabled }:装 artplayer-plugin-danmuku(新依赖,Artplayer 官方弹幕
插件,5.3.0),开关打开时用 EventSource 连 /v1/streamers/{id}/danmaku,每条事件 emit 进弹幕层
(普通弹幕滚动、礼物 / 醒目留言 / 上舰置顶描边),关闭时断开 SSE 并隐藏弹幕层,不重建播放器。
弹层标题栏加「弹幕」Switch,默认开、记在 localStorage(biliup.preview.danmaku);平台没有弹幕
客户端时禁用并 tooltip 说明。监视器工具栏加同名开关,默认关(biliup.monitor.danmaku),
对全部小窗生效。localStorage 偏好统一走 useSyncExternalStore(app/lib/use-local-pref.ts),
同屏路数也改用它。
Part of #1708
* fix(preview): FLV 广播缓冲扩到 1024 槽,装得下 CDN 的整 GOP 突发
斗鱼 CDN 把整个 GOP(~4 s、~320 个 tag、~2 MB)一次突发送达,httpflv 解析器 1 ms 内推完;
刚建立的订阅者还没读到第一个实时分块,256 槽的缓冲就已被覆盖,收到 Lagged 被断开,重连后
又赶上下一个突发——冷启动 10 次只有 1 次播起来。FLV 分块是单个 tag,小而多,缓冲按格式分开:
FLV 1024 槽(实测均值 ~10 KB → ~10 MB),TS / fMP4 沿用 256 槽(分块大而少,16 MB 上限)。
回归测试:请求在关键帧回应后紧跟 600 个 tag 同步推完,订阅者仍逐个读到。
Part of #1708
* fix(ui): 弹幕开关禁用时说明是平台不支持还是未开对应 *_danmaku 配置
* feat(danmaku): 多个直播间的实时弹幕可复用一条 SSE(GET /v1/danmaku?ids=…)
浏览器对同一主机的 HTTP/1.1 并发连接只有 6 个:监视器同屏 N 路视频已占 N 个,弹幕若每路再开
一条 SSE,N=4 时就把列表轮询也饿死。新增 /v1/danmaku?ids=1,3,9 把若干路合成一条,事件 JSON
带 id 供前端分发;没有弹幕 / 未录制的 id 静默跳过,一个都没有时 404;一条最多 16 路,占 1 个
连接许可。原 /v1/streamers/{id}/danmaku 保留,走同一实现(事件同样带 id)。
Part of #1708
* feat(ui): 弹幕连接由页面持有并复用,监视器路数按 HTTP 协议限制
浏览器对同一主机 HTTP/1.1 只有 6 个并发连接,监视器 4 路视频 + 每路一条弹幕 SSE 就把列表轮询
饿死;9 路时也只有 6 路能播(实测)。改为 DanmakuFeed:页面(弹层 / 监视器)持有一条
/v1/danmaku?ids=… 连接,各播放器按 id 订阅;构造无副作用、open() 后有订阅者才连,StrictMode
下重复构造无害。同屏路数选项在 HTTP/1.1 下为 1 / 2 / 4,页面经 h2 / h3 加载时放开 6 / 9。
小窗弹幕字号 15px,弹层 22px。
Part of #1708
* fix(ui): 解码失败后关闭弹层必须断开拉流连接
<video>.error 非空时 mpegts.js destroy() 里清 SourceBuffer 的 remove() 抛 InvalidStateError,
后面的 transmuxer.close() 跑不到,连接留在服务端占着该路的预览许可直到刷新页面——真实 Twitch
TS 房间(mpegts.js 放不了)连开 4 次弹层第 4 次就 429。销毁前先把 MediaSource 从元素上摘下
(load() 使其 closed,mpegts.js 对 closed 的 MediaSource 跳过清理),destroy 仍抛则直接关
内部 transmuxer 兜底。
Part of #1708
* fix(ui): mpegts.js 换用可中止的自定义 loader,弹层关闭一定断开拉流
上一提交把 MediaSource 从元素上摘下只避免了 destroy() 抛异常,连接仍未断开:mpegts.js 自带的
FetchStreamLoader 在 Chrome 上处于 buffering 状态时 abort() 不调 AbortController.abort(),
而是等下一个 read() 回来再 reader.cancel()——解码出错后读取链已断,那一次永远等不到,连接留在
服务端占着该路许可(真实 Twitch TS 房间:关闭弹层后用 4 条 curl 探,1 条 429;再开一次 2 条 429,
直到浏览器退出才释放)。改为 customLoader:每次 open() 一个 AbortController,abort() 直接调它,
其余与原 loader 一致(直播不需要 Range)。
Part of #1708
* fix(ui): 忽略 Artplayer 在实例销毁后的自动重连,杜绝孤儿拉流连接
真正的泄漏来源:Artplayer 收到 video:error 后每隔 1 s 重设 art.url 最多 5 次(RECONNECT_TIME_MAX),
不检查自己是否已 destroy。弹层在解码失败时立刻卸载播放器,1 s 后 Artplayer 仍在已销毁的实例上
重设 url → customType 再建一个 mpegts.js 实例、再拉一路流,没有任何人持有它——每次打开泄漏
最多 5 条连接,直到刷新页面。mpegts / fMP4 两个 customType 入口对 art.isDestroy 直接返回;
fMP4 入口同时复用上一次的 dispose(同一实例重设 url 时先清掉旧的 MediaSource)。
mock 上用循环发送的 Twitch 坏样本复现:修前每开一次多 1 条连接(1→2→3),修后开关 4 次始终 0。
Part of #1708
* fix(ui): 自动播放重试前确认播放器还活着
* fix(preview): 最后一个订阅者走后释放广播缓冲
tokio broadcast 的缓冲只在被新数据覆盖时丢旧分块,而没有订阅者时写入端不再 send——最后一个
订阅者走后,缓冲里最后 N 个分块就一直挂着(FLV 1024 槽约 10 MB / 路;7 路真实短测中 RSS 在
一轮预览后从 95 MB 停在 117 MB)。写入端发现订阅者归零且缓冲里有数据时换一条新通道,旧缓冲
整个释放;之后的 subscribe() 拿新通道,对订阅者透明。
Part of #1708
* feat(preview): 直播预览取流方式配置(relay / direct)与浏览器直连的能力判定、直链端点
后端与浏览器不在一台机器时,浏览器直接向 CDN 拉一路能省服务器出口带宽(ForgQi,#1712 review 2)。
全局配置新增 preview_transport:relay(默认,复用录制流经 biliup 中转)/ direct(浏览器直连 CDN),
JSON 配置里可选字段,不需要迁移。/v1/streamers 的 preview 新增 direct: { capable, reason },按
review 1 实测的平台表判定:B 站 / 抖音 / 虎牙 FLV 能直连;斗鱼一 token 一连接、直连会挤掉录制;
Twitch CDN 无 ACAO;HLS(TS / fMP4)本版本回落——mpegts.js 放不了 m3u8,直连需 hls.js;其它平台
未验证按不能。新增 GET /v1/streamers/{id}/live-url:当前录制中那条流的直链、容器、平台与过期时间
估计(B 站 expires / 抖音 expire 十进制 Unix 秒,虎牙 wsTime 十六进制;斗鱼的相对秒数不算),
Cache-Control: no-store,与 /live 同在登录校验之内。DownloadTask 随每次 check_stream 刷新直链。
Part of #1708
* feat(ui): 直播预览取流方式——全局设置下拉,direct 模式下浏览器直连 CDN、失败自动回落中转
空间配置 → 全局设置新增「直播预览取流方式(preview_transport)」:经 biliup 中转(默认)/ 浏览器直连
CDN,文案说明两者区别与适用场景。LivePreviewPlayer 按(房间, 配置, 能力)派生取流方式:direct 且
平台能直连时先 GET /v1/streamers/{id}/live-url 拿直链(https 页面下 http 直链换 https),mpegts.js
以 cors / 不带凭据拉 CDN;出错或断开时重取一次直链再播,仍失败回落中转;平台不能直连的房间在
direct 模式下直接走中转。播放器左上角角标:「直连 CDN」或「已回落中转:<原因>」。监视器小窗共用
同一组件。异步结果与回落都带 sourceKey,配置切换时不在 effect 里同步 setState。
Part of #1708
* fix(ui): 直连模式下弹层脚注说明取流方式
* feat(preview): 直连模式向平台另取一条直链,不复用录制那条;斗鱼由此也能直连
ForgQi(#1712):走直连就不要复用链接,直接获取新链接。/v1/streamers/{id}/live-url 改为对该房间的
平台插件再调一次 check_stream,把新拿到的 raw_stream_url(新 token)给浏览器;平台返回未开播或
出错时 503,前端回落中转。实测新 token 的斗鱼 / 虎牙直链各读 25 s 稳定,同时录制那条连接无重试
——review 1 观察到的「斗鱼一 token 一连接」「虎牙第二条连接只给 GOP 缓存即断」都是同一直链两条
连接的问题,另取一条就绕开了。能力表:B 站 / 抖音 / 斗鱼 / 虎牙 FLV 能直连;Twitch 无 ACAO、HLS 需
hls.js 仍回落。Monitor 新增 PluginFor 消息按房间 URL 取插件。
Part of #1708
* fix(ui): 直连播够 60 s 后再断按直链到期处理,重取直链继续直连
斗鱼 CDN 的 token 每 5 分钟切一次连接(录制那条同样如此,日志里每 5:01 一次 StreamEnded → 重连),
B 站直链 1 h 过期。之前直连只允许重取一次,第二次到期就回落中转;现在区分「刚起播就失败」
(重取一次仍失败 → 回落)与「播了一阵才断」(视为到期,照常再取)。
Part of #1708
* feat(preview): HLS 直连——直链是 m3u8 时走 hls.js(按需加载),B 站 hls_fmp4 等也能直连
ForgQi(#1712):HLS 直连也支持一下。新依赖 hls.js ^1.7(动态 import,只在直连模式打开 HLS 房间时
才下载那一个 chunk,首屏包不变)。live-url 的 format 按新直链后缀给 flv / hls;direct_capability
只看平台的跨域放行,不再因容器排除——B 站 .m4s 分片的 CDN 同样 ACAO: *。Player 新增 hls 分支:
跟到最后 3 个分片、后向缓冲 30 s、分片请求不带凭据;清单 / 分片 404、CORS、解码等致命错误报给
LivePreviewPlayer 走重取直链 / 回落中转,清单变为非直播视为断开。Twitch 仍因无 ACAO 回落。
Part of #1708
* feat(preview): live-url 同一房间 5 秒去抖,播放失败后的重取带 fresh=1 绕过
每次打开预览都向平台 API 取一条新直链(带 cookie 时消耗登录态配额,B 站接口有频控)。同一房间
5 s 内的重复请求(StrictMode 双调用、连点、同一房间多个小窗)复用上一次结果并标 cached: true,
不再打平台;前端播放失败后的重取传 ?fresh=1 一定拿新 token(抖音某节点 404、直链到期都靠它)。
缓存插入时顺手清掉一分钟前的条目,表不会长。
Part of #1708
2026-09-22 16:25:00 +00:00
Noah
20fe058b15
fix(ui): 修复 React Compiler lint 命中并恢复规则为 error ( #1711 )
...
* fix(ui): 主题与侧栏偏好改用 useSyncExternalStore 读取,去掉 effect 内 setState
react-hooks/set-state-in-effect 命中 4 处,都是「挂载后在 effect 里读浏览器状态再 setState」:
- app/(app)/layout.tsx:117 侧栏折叠偏好、:136 主题模式:两者都是 localStorage 里的值。
新增 useLocalStorageValue(key),用 useSyncExternalStore 订阅:服务端与水合期快照固定为
null(与 SSR 输出一致,不会水合不一致),水合完成后 React 自己用真实值重渲染一次;
写入走 setter 并通知本页订阅者。localStorage 写失败时退回内存值,本页内仍可切换,
与原先 try/catch 后照常 setState 的行为一致。
- app/lib/utils/index.tsx:66 useSystemTheme:matchMedia 是外部状态,改为 useSyncExternalStore
订阅 change 事件,服务端快照 light,与原来 useState('light') 再 effect 校正的结果一致。
- app/ui/ThemeButton.tsx:30:原来点击后先置 switchTrigger,再在 effect 里读标志位切主题、
setMode、清标志位,图标也在 effect 里 setState。改为点击事件里直接算出下一模式并
applyThemeMode + setMode,图标由 mode 派生,不再有中间 state 与 effect;切换结果相同,
少一轮渲染。setMode 的类型收窄为 (mode: string) => void,调用方只传字符串。
useTheme 未改:它的首次跳过逻辑仍然成立(首帧主题由 <head> 内联脚本设置)。
* fix(ui): 日志页、空间配置页、账号列表与模板表单去掉 effect 内同步 setState
react-hooks/set-state-in-effect 其余 5 处:
- app/(app)/logviewer/page.tsx:133:connectWebSocket() 开头同步 setIsLoading(true)/setLogs([])
再建连接,effect 与「刷新」按钮共用。拆开:effect 只负责建立 WebSocket、把消息写进
state、cleanup 时关闭(上一条连接不再靠 ref 手动 close);清空日志 + 进入加载态移到触发
重连的事件里(切 Tab 的 onChange、刷新按钮),刷新通过 connectSeq 计数器让 effect 重跑。
首次挂载时两个 state 本来就是初始值,其余时序与原来一致;顺带删掉不再需要的
exhaustive-deps 禁用注释。
- app/(app)/dashboard/page.tsx:101:校验失败后先切 Tab / 平台,再在 effect 里滚到出错字段并
把 pendingField 清空。改为 flushSync 同步提交切面板的 state,随后直接 scrollIntoView +
focus(React 文档推荐的「更新后读 DOM」写法),去掉 pendingField 这个中间 state。
onSubmitFail 由 Semi 在 Promise.catch 里调用,不在渲染期,flushSync 合法。
- app/lib/use-streamers.ts:26 useBiliUsers:data 为空时同步 setList([])。改为返回值直接派生
(无账号 → 空数组常量),effect 里只保留异步补全;data 为空数组时 Promise.all 解析为 []
仍会把上一批结果清掉,之后再有账号不会先闪旧数据。
- app/ui/TemplateFields.tsx:183:挂载后从 formApi.getValue('dtime') 算滚轮初始选中值。
Semi Form 在构造函数里就把 initValues 放进 store,首次渲染即可读到,改为 useState 的
惰性初始值(换算逻辑抽成 delayFromDtime),结果与 effect 版本相同。
- app/(app)/components/BackgroundSetter.tsx:71:hasBg / opacity 挂载后从 localStorage 读。
改为 useState 惰性初始值;两个值只在面板展开后渲染,SSR 首帧输出不受影响。
* fix(ui): 修复渲染期不纯写法:卡片实时时长、平台插件查找、time_range 转换
- react-hooks/purity app/ui/StreamerCard.tsx:101:渲染期调 Date.now() 算「已直播多久」。
新增共享时钟 useNowSec()(app/lib/use-dashboard.ts):模块级 store 由定时器每秒推进,
组件用 useSyncExternalStore 读取,首个订阅者接入时校准一次。时长拆成 LiveDuration 子组件,
只有录制中的卡片挂载它,其余卡片不随时钟重渲染。显示值语义不变(开播至今,同一
formatDuration),刷新节奏从「随 10 s 轮询重渲染时更新」变为每秒更新。timeAgo 内部也用
Date.now() 但未被规则命中(在工具函数里),本次不动。
- react-hooks/static-components app/ui/OverrideModal.tsx:297:JSX 里用 IIFE 调 platformSetting()
拿到组件再渲染,规则把它当成「渲染期创建的组件」。实际是按 url 从模块级常量表
SupportedPlatforms 取组件。改为先找出匹配的键,再按键从常量表取值,让「同一引用」这一点
在代码上可见;匹配顺序与结果不变。
- react-hooks/immutability app/ui/TemplateModal.tsx:121:把 props.entity.time_range 由 JSON
字串就地改写成 Date[]。改为 useMemo 生成一份本地副本作为 Form 的 initValues,解析失败
保持原样并 console.error(同原逻辑),不再修改上层传下来的对象。
* chore(lint): 恢复 React Compiler 系列规则为 eslint-plugin-react-hooks 的默认 error
#1710 升级 eslint-plugin-react-hooks 7 时把 set-state-in-effect / static-components /
purity / immutability 四条降为 warn,留给专门 PR 处理。命中的 12 处已在前三个 commit 修完,
这里删掉覆盖,回到插件 recommended 的 error 级别。
* fix(ui): useTheme 不再回写 localStorage,mode 只由 useLocalStorageValue 的 setter 持久化
mode 改由 useSyncExternalStore 从 localStorage 读取后,同一个键有了两个写入方。
StrictMode 下 effect 会在挂载时执行两次,useTheme 的「跳过首次」只能挡住第一次,
第二次会用水合期的默认值 auto 把用户刚保存的主题覆盖掉,再由 store 读回 auto——
`next dev` 里每次刷新主题都会丢(生产构建没有双次 effect,不受影响)。
去掉 useTheme 里的 setItem:切换主题时 setter 已经写过同一个值;系统配色变化时原来会
把 auto 写进去,去掉后键保持缺省,读出来同样按 auto 处理,行为一致。
* fix(ui): 登录页在手机宽度下卡片超出视口、两侧被裁切
review 意见(#1711 下 ForgQi):手机尺寸下登录页两侧被裁切。
原因:三个输入框写死 style={{ width: 440 }},卡片宽度由它们撑成 552px(440 + 56×2 内边距),
.main 居中放置、.frame 又是 overflow: hidden,窄于 552 的屏幕上卡片两侧直接被裁掉。
改法:去掉输入框的固定宽度,卡片定宽 552px 并允许 max-width: 100% 收窄到视口,
.inputs 改为 align-items: stretch 让字段撑满卡片内容区(Semi 的 .semi-input-wrapper 默认 width: 100%),
≤480px 时内边距收到 32px 24px。桌面 1440 浅 / 深色截图与修改前像素 0 差异;390px 下卡片 370px、
输入框 322px、无横向溢出,注册模式(三个输入框)同样。
2026-09-22 08:27:12 +00:00
Noah
e988af8268
chore(deps): 升级前端三方依赖至最新稳定版并适配 ( #1710 )
...
* chore(deps): 升级 Next.js 14→16、React 18→19 及 TypeScript/ESLint/sass 工具链
- next 14.2 → 16.3.5(Turbopack 成为默认打包器,`output: 'export'` 与 out/ 页面文件名不变)
- react / react-dom 18.3 → 19.3,@types/react(-dom) 同步到 19.3
- qrcode.react 3 → 4、react-json-tree 0.19 → 0.20:仅为满足 React 19 的 peerDependency,API 用法不变
- typescript 5.9 → 6.0.3;tsconfig `target` es5 → ES2017(TS 6 弃用 es5,TS 7 已移除),
`jsx` preserve → react-jsx(Next 16 强制),include 补 `.next/dev/types`
- next-env.d.ts 改为由 Next 生成、不再入库(官方文档要求,且 dev/build 会写入不同内容;
仓库 .gitignore 已有 `/*.ts` 规则覆盖)
- eslint 8 → 9.39.5,eslint-config-next 16,eslint-config-prettier 10:
Next 16 移除了 `next lint`,`.eslintrc.json` 迁移为 flat config `eslint.config.mjs`,
`npm run lint` 改为直接调用 `eslint .`;eslint-plugin-react-hooks 7 新增的
React Compiler 规则暂降为 warn(命中的都是既有写法,与本次升级无关)
- sass 1.97 → 1.104、@types/node 20 → 26(仅类型)
- next.config.js 关闭 Next 16.3 新增的 `agentRules`,避免 `next dev` 在仓库根目录生成 AGENTS.md
未升到最新的两项及原因:
- typescript 7.0.2 为原生实现、不再提供 JS API,eslint-config-next 依赖的 typescript-eslint
明确拒绝 TS 7(`>=4.8.4 <6.1.0`),故停在 6.0.3
- eslint 10 移除了 `context.getFilename()`,eslint-config-next 依赖的 eslint-plugin-react 7.37.5
尚未适配(peer `^9.7`),故停在 9.x 最后一版
* fix(ui): 适配 React 19 的类型变更
@types/react 19 带来的四类编译错误,逐个按最小改动处理,不涉及运行时逻辑:
- `useRef<T>()` 不再允许省略初始值 → `useRef<FormApi>(undefined)`(6 处,`.current` 类型不变)
- `useRef<HTMLDivElement>(null)` 现返回 `RefObject<HTMLDivElement | null>`,
logviewer 的 `LogContent` props 类型同步放宽
- 全局 `JSX` 命名空间被移除 → `React.JSX.Element`(AvatarCard)
- `ReactElement` 的 props 默认类型由 `any` 改为 `unknown`,
TemplateModal / OverrideModal 里 `cloneElement` 注入 onClick 的地方给 `isValidElement`
标注具体的 props 形状,替代原来的 `<any>`
- 顺带删掉 layout.tsx 里一条已无作用的 `eslint-disable-next-line react-hooks/exhaustive-deps`
(eslint-plugin-react-hooks 7 对该处不再报告,ESLint 会提示未使用的 disable 指令)
* chore(deps): 升级 Semi Design 2.89→2.103 及 swr/artplayer/mpegts.js,适配 ArrayField.add 新签名
- @douyinfe/semi-ui 2.89.1 → 2.103.0(semi-icons / semi-foundation 随之同步),同一 major,
用到的 Form / Tabs / Collapse / Modal / Table / Select 等组件 API 未变
- ArrayField 的 `add` 由 `() => void` 变为 `(index?: number) => void`(支持在指定位置插入),
原来直接写 `onClick={add}` 会把 MouseEvent 当 index 传入且不再通过类型检查,
7 处改为 `onClick={() => add()}`,行为仍是追加到末尾
- swr 2.3.8 → 2.5.1、artplayer 5.3.0 → 5.4.0、mpegts.js 1.8.0 → 1.8.2:均为 minor/patch,无 API 变更
* chore(deps): 移除无引用的 ncp
`rg ncp` 在 app/、脚本与配置里均无命中(仅 CHANGELOG 提到 #1265 曾把它移出 dependencies),
是早年从 Svelte 前端遗留下来的复制工具,当前构建链(next build → out/ → rust-embed)用不到。
* fix(ui): 站内 Link 关闭预取,消除 Next 16 预取样式触发的 preload 警告
Next 16 的 Link 进入视口即预取目标路由的 RSC 载荷,载荷里的 `HL` 样式提示会让 React 为
目标页的每个 CSS 分块插入 `<link rel="preload" as="style">`;页面停留数秒后 Chrome 会对每个
未使用的预加载报一条 "was preloaded using link preload but not used" 警告。侧栏 9 个链接常驻
视口,配合 Turbopack 的细粒度 CSS 分块,每页会稳定出现 20+ 条(Next 14 下为 0)。
站内导航共 6 处 Link,全部加 `prefetch={false}`:静态导出由本机 axum 直接服务,点击时再拉取
几 KB 的路由载荷,实测 9 个侧栏页面客户端切换均正常且耗时 <150 ms;之后 6 秒停留 0 警告。
本 commit 可独立丢弃:丢弃后功能不受影响,只是控制台会有上述预加载警告。
* fix(ui): 在根布局最先导入 Semi Design 的 React 19 适配器
React 19 从 `react-dom` 上移除了 `render` / `unmountComponentAtNode`,也不再从 `react-dom`
re-export `createRoot`。Semi 的 Toast / Notification / Spin 图标等命令式渲染走
`_utils/reactRender.js`,在 React 19 下既拿不到 legacy render 也拿不到 createRoot,
会打印 "[Semi UI] createRoot is not available" 并**静默不渲染**——表现为保存成功 / 失败的
Toast 与 Notification 全部消失、日志页 Spin 不显示。
按 Semi 官方文档(semi.design/zh-CN/ecosystem/react19)在根布局第一行导入
`@douyinfe/semi-ui/react19-adapter`,它把 `react-dom/client` 的 createRoot 注入
`semiGlobal.config`。验证:空间配置页保存成功 / 校验失败的 Toast 正常弹出,
日志页 console 0 error。
* fix(ui): 消除 React 19 开发模式下的水合属性与 Fragment props 警告
开发模式(StrictMode)逐页走查 13 个页面,React 19 报两类新错误,均为既有写法在新版本下
被更严格地校验,生产构建不受影响:
- 每页一条 "A tree hydrated but some attributes of the server rendered HTML didn't match":
差异只有 `<html>` / `<body>` 上由 <head> 内联脚本在水合前写入的 theme-mode 属性
(React 18 下同一原因表现为 "Extra attributes from the server: theme-mode")。
这是首屏防闪烁的有意行为,按 React 官方做法给这两个元素加 suppressHydrationWarning,
只屏蔽元素自身的属性差异,不影响子树校验。
- 直播管理页每张卡片一条 "Invalid prop `disabled` supplied to React.Fragment":
操作按钮以 Fragment 形式作为 Semi ButtonGroup 的唯一子元素,ButtonGroup 会对直接子元素
cloneElement 注入 disabled / size / type,React 19 起会对 Fragment 上的这些 props 报错。
改为返回带 key 的元素数组,DOM 结构与样式不变(子元素均非 Button 类型,不会插入分隔线)。
处理后开发模式 13 页 console 仅剩 mock 环境有意返回的 500。
2026-09-22 06:42:07 +00:00
Noah
5ce14df240
feat(ui): 录制中房间显示封面、头像与实时码率(直播预览阶段一) ( #1709 )
...
* feat(downloader): 录制写盘字节计数与实时速率,/v1/streamers 新增 live_bytes_per_sec
各下载器在写出点往 `ByteCounter`(Arc<AtomicU64>)累加,写盘路径上只多一次
`fetch_add`,不加锁、不 await、不改任何控制流:
- stream-gears/httpflv:`FlvFile::write_tag` 按 11 + 数据 + 4 字节计
- stream-gears/hls:`download_to_file` 每个 chunk 落盘后计
- mesio:条目进入修复管线前按 `FlvData::size()` / `HlsData::size()` 计
(writer 来自外部 crate,拿不到逐条写出量;修复只增删极少量 tag)
- ffmpeg / streamlink:子进程自己落盘,改为每秒读一次 `.part` 长度做差分
`DownloadTask` 持有 `RateMeter`:1 秒采样、10 秒滑动窗口取首尾差值。窗口明显长于
GOP,避免 httpflv 按关键帧批量落盘造成的抖动;采样任务随 `execute` 启停。
`GET /v1/streamers` 每项新增 `live_bytes_per_sec`(字节/秒):未录制、采样不足、
或下载器不经本进程写盘(边录边传、yt-dlp)时为 null。
Part of #1708
* feat(live): 解析主播头像、补斗鱼封面,新增录制中封面/头像的同源代理端点
`LiveStream` 新增 `avatar_url: Option<String>`:
- B 站:`getInfoByRoom` 同一次响应里的 `anchor_info.base_info.face`
- 抖音:房间对象 `owner.avatar_thumb.url_list[0]`(web / H5 两个接口都有)
- 斗鱼:betard 的 `owner_avatar`,缺失时退回 `avatar.{middle,big,small}`;
封面 `room_pic` 一并补上(此前写死空串)
- 虎牙:`gameLiveInfo` / `liveData` 的 `avatar180`,与 screenshot 一样升到 https
- 其余平台为 None,不改行为
`DownloadTask` 在内存里保存当前录制的封面与头像地址(每次 check_stream 刷新,
随任务消亡,不落库、不加迁移)。`GET /v1/streamers` 每项新增 `live_cover_url` /
`live_avatar_url`(未录制为 null)。
新增 `GET /v1/streamers/{id}/cover` 与 `/avatar`:页面直接引用图片 CDN 会带上 biliup
的 Referer 被 B 站等拒绝(403),改由服务端以直播间页面地址作 Referer 拉取后转发,
按图片 URL 做 5 分钟内存缓存(上限 64 条、单图 4MB)。只按主播 id 取 worker 里已有的
地址,不接受任意 URL;上游不是图片时不转发。路由注册在 `router()` 内,`--auth` 时
与其它 /v1 接口同一道登录校验。
Part of #1708
* feat(ui): 录制中卡片显示直播间封面、主播头像与实时写盘速率
`StreamerCard`(主页「进行中」与直播管理网格共用)在状态为录制中时:
- 顶部加 16:9 封面缩略图,懒加载,图片走 `/v1/streamers/{id}/cover` 同源代理;
加载中显示占位底色,加载失败整块消失,卡片回到原有样式
- 名称前加 22px 头像圆图(`/v1/streamers/{id}/avatar`),失败则不占位
- 时长右侧加写盘速率,按 B/s、KB/s、MB/s 自动换单位;后端未采样(null)显示「—」
图片地址带原始 URL 的短哈希作查询参数,换一场直播封面变了就不会沿用旧缓存。
未开播 / 暂停的卡片不变。直播管理列表视图新增「码率」列,主播名前带头像。
不引入新依赖:头像与封面用 next/image(项目已配置 unoptimized),速率格式化与
代理地址拼接放在 `use-dashboard.ts`。
Part of #1708
* perf(live): B 站封面/头像经代理时改取 CDN 缩略图
实测 B 站 `user_cover` 原图可达 11520×8640、4 MB,卡片上只显示成 16:9 缩略图和 22px
头像,原样转发既慢又占缓存。对 `*.hdslb.com` 的地址借 CDN 的 `@宽w_高h_1c` 后缀取
640×360 / 128×128 的裁切图(同一张图从 4 MB 降到约 25 KB);已带后缀或带 query 的
地址、以及其它平台的地址原样使用。
Part of #1708
* refactor(downloader): ffmpeg / streamlink 写盘速率改为解析 stderr 进度行,不再读盘
review 意见:每秒 stat 一次 `.part` 长度会额外占磁盘 IO。改为让子进程自己报告:
- ffmpeg 加 `-progress pipe:2 -nostats`,把 key=value 进度块打到 stderr(不受
`-loglevel quiet` 影响),`total_size=N` 就是本进程累计写出字节
- streamlink 加 `--progress force`,stderr 不是终端时也按周期打
`[download] Written 12.34 MiB to <file> (5s @ 2.50 MiB/s)`
都在既有的 `spawn_log` stderr 读取协程里顺带解析(`SubprocessProgress`),进度行降为
debug 日志不再刷 info;累计值只加增量、回退时重置基准。解析不出来只是没有速率,
不影响录制。删掉 `FileSizeProbe`。
Part of #1708
* feat(live): 主播头像开播时下载一次存到 data/avatar/,接口直接读本地文件
review 意见:头像几乎不变,不必每次经代理拉取。
- 新增 `common/live_image.rs`:图片抓取(带 Referer、大小与类型校验、B 站缩略图后缀)
从 `api/live_media.rs` 挪到这里共用;新增 `AvatarStore`,按主播 id 存
`data/avatar/<id>.<ext>` + `<id>.json`(记录来源地址与 Content-Type),先写临时文件
再改名,同一时刻只允许一个下载写盘
- 开播进入录制流程时后台下载一次;后续 check_stream 拿到的地址变了才重新下载;
下载失败保留旧文件、只记日志
- `GET /v1/streamers/{id}/avatar` 改为读本地文件(`Cache-Control: max-age=3600`);
正在录制而本地还没有时现场下载一次再返回,失败退回旧文件;不在录制时只读本地,
没有就 404。服务重启后凭 `<id>.json` 直接复用,不重复下载
- 封面仍走内存缓存的代理(每场直播都会换,落盘意义不大)
Part of #1708
* fix(downloader): streamlink 进度行打在 stdout,stdout / stderr 都解析
实测 `--output` 到文件时 streamlink 把控制台输出(含 `[download] Written …`)
打在 stdout 而不是 stderr,只解析 stderr 会得到恒为 0 的速率。两条流共用一个
进度跟踪器,避免重复累加。
Part of #1708
2026-09-22 06:34:11 +00:00
Noah
45ab54ba90
fix(ui): 修复新 UI 侧栏被顶出与平台设置页导航重复 ( #1706 )
...
* fix(ui): 去掉 html/body 的 overflow-x: hidden,恢复侧栏与页头吸顶
#1704 引入的 bg-global.css 给 html、body 加了 overflow-x: hidden,任何非 visible
的 overflow 都会把 body 变成滚动容器,侧栏(layout.module.scss .sider)和
PageHeader 的 position: sticky 于是相对 body 而不是视口定位——主内容一长,
整页滚动就把左侧导航和「保存」按钮一起顶出视口。
- 删除 html/body 的 overflow-x: hidden,并在两处样式注明这条约束
- 两个 position: fixed 的背景层改用 inset: 0 铺满视口,不再依赖 100vw,
也不会撑出横向滚动条
问题 1 of #1705(侧边导航被顶出)。
* fix(ui): 空间配置「平台设置」只保留左侧平台列表一套导航
#1704 把平台设置改成「左列平台列表 + 右栏 Collapse」后,右栏的手风琴仍然
为每个平台渲染一条带平台名的折叠栏,与左列重复(#1705 第二点)。
- 新增 app/ui/plugins/PlatformPanel.tsx:各平台插件的外壳,传入 bare 时只
输出字段,否则保持原来的 Collapse.Panel(配置覆写弹窗仍在用)
- 10 个平台插件把 Collapse.Panel 换成 PlatformPanel,并透传 bare
- dashboard 右栏不再用 Collapse,改为每个平台一个 <section hidden>:所有
字段始终挂载,只切换 hidden,保证 PUT /v1/configuration 整表覆盖时其他
平台的参数与凭据不会因为字段卸载而丢失
- 左列平台列表在页头下方吸顶(position: sticky),右栏面板很长时列表不再
随内容滚出视口;PageHeader 把自身高度写入 --page-header-height 供其取用;
Tabs 关闭 tabPaneMotion 并放开 .semi-tabs-pane 的 overflow,否则 pane 会
成为 sticky 的滚动容器。移动端横排 chip 行保持随内容滚动
- 左列按钮补 aria-pressed,nav 补 aria-label
Fixes #1705
* feat(ui): 空间配置保存校验失败时自动定位到第一个出错字段
平台设置的字段全部挂载、只切换 hidden,未选中的 Tab / 平台面板里的校验
错误用户看不到,表现为点「保存」没有任何反应。
- Form 加 onSubmitFail:把 Semi 的嵌套 errors 拍平成字段路径,按 DOM 顺序
找到第一个出错字段,切到它所在的 Tab(Tabs 改为受控)与平台面板,再
scrollIntoView 并聚焦,同时 Toast 提示未通过的项数
- TabPane 内容加 data-tab、平台 section 加 data-platform,供定位使用;
不改插件接口
Refs #1705
* refactor(ui): 改用应用壳布局解决侧栏被顶出,移除 sticky 补救与 tabPaneMotion 取舍
按 review 意见(#1706)不再靠 position: sticky 解决问题一,改成简单的应用壳:
- layout.module.scss:.app 改为 height: 100vh(100dvh)+ overflow: hidden 的 flex 容器,
页面本身不滚动;.sider 是固定宽度的普通子元素(撑满壳高,去掉 sticky/top/100vh);
.main 加 min-height: 0 + overflow: auto,成为唯一的滚动容器。侧栏、页脚服务灯
自然留在视口内,PageHeader 原有的 position: sticky 相对 .main 吸顶,无需改动
- 去掉为让平台列表在 Tab 面板内 sticky 而加的补救:.platformNav 的 sticky /
--page-header-height / max-height、PageHeader 的 ResizeObserver、Tabs 的
tabPaneMotion={false} 以及 .semi-tabs-pane 的 overflow 覆盖,Tab 切换动画恢复。
平台列表改回随内容滚动
- bg-global.css:文档不再滚动,html/body 不需要 overflow 规则,注释改为说明现状;
两个 fixed 背景层保持 inset: 0
验证:tsc 0 错误、next build 通过;13 页 × 浅色 / 深色 / 折叠 / 移动端 390 / 平板 760,
主区滚到底后 document.scrollHeight == innerHeight(文档不滚)、aside top 0、页脚
bottom == innerHeight、header top 0、main 无横向溢出;Semi Select 下拉(主区预滚 1.2k px
及打开后继续滚动)、Popconfirm、配置覆写 Modal 及其内部 Select、Toast 位置全部正确;
切遍 10 个平台后保存 PUT 73/73 键(3 次);校验失败自动定位三场景通过(2 轮)。
Refs #1705
* refactor(ui): 平台设置去掉外层嵌套卡片,改两栏独立滚动布局
按 review 第二条意见(#1706):平台列表不用 sticky,也不随内容滚走——去掉包在
「平台列表 + 面板」外面的嵌套卡片,让两栏并排、各自独立滚动。
- dashboard/page.tsx:删掉 dc.content / rootConfigPanel / main / content 四层包裹和
平台设置里的 framePlatformConfig 卡片与重复的「平台设置」标题;页面主体改为
.page(占满页头以下的剩余高度)→ Form → Tabs,每个 Tab 面板在自己内部滚动,
页面本身不滚,页头「保存」始终可见。去掉底部两个 Space 占位
- dashboard.module.scss:重写。删除 #1704 之前遗留的整段 .rootConfigPanel 嵌套规则
(其中 frameInside / group / lineStory 等类早已无人引用);Form / Tabs /
semi-tabs-content / semi-tabs-pane(含开启动画时的 motion-overlay)改为
flex: 1 + min-height: 0 的列容器,把高度让给当前面板,并补回
.semi-tabs-pane-inactive 的 display: none(本模块选择器特异性更高)
- 平台设置:.platformLayout 两栏,.platformNav 160px、align-self: flex-start、
max-height: 100% + overflow-y: auto(矮视口时列表自己滚),.platformBody
flex: 1 + overflow-y: auto。右栏再长列表也不动
- 全局设置 / 开发者选项一致处理:frameDownload / frameUpload / frameDeveloper 不再是
带边框和 50px 内边距的卡片,改为分区标题 + 分隔线的普通分区,面板 .pane 自身滚动
- 移动端(≤680px):两栏改为上下堆叠,平台列表是横向滑动的标签行,固定在面板顶部
不随字段滚走
- 自定义背景开启时给两个滚动面板垫一层 75% 的 bg-1 半透明底色,保证文字可读
验证:tsc 0 错误、next build 通过;空间配置页在 1440 / 760 / 390 × 浅 / 深 / 折叠 /
520px 矮视口下:文档与 <main> 均不滚动,页头与「保存」始终在视口内,右栏滚到底
(哔哩哔哩面板 1223px)时平台列表位置不变,矮视口下列表自身可滚;全局设置面板独立
滚动;切遍 10 个平台后保存 PUT 73/73 键(4 次);校验失败自动定位三场景通过(3 轮,
scrollIntoView 在嵌套滚动容器内正常);Select 下拉在面板预滚 1k px 及滚动中跟随触发器,
Popconfirm / Modal / Toast 位置正确;其余 12 页布局探针与上一提交一致。
Refs #1705
* fix(ui): 更新日志页内容不再覆盖导航顶栏
应用壳布局下页头真正吸顶后暴露出来的问题(review 第三条,#1706):更新日志页把
PageHeader 又包在一层 position: sticky; z-index: 1 的 Semi Layout.Header 里,
形成一个层级只有 1 的 stacking context,PageHeader 自己的 z-index: 10 只在这层
内部生效;而版本卡片流 .feed 又设了 position: relative; z-index: 1,DOM 顺序在
后,滚动时就画在页头之上。
- changelog/page.tsx:去掉外层 Header,与其它页一样直接渲染 PageHeader
- changelog.module.scss:删掉 .feed / .center 的 position: relative; z-index: 1
验证:10 个带页头的页面滚到底后对页头内 6 个点做 elementFromPoint 命中测试,
全部命中页头自身(修前 /changelog 有 4 个点命中版本卡片头);/history 播放器
Modal 居中且在最上层;/logviewer、/status 等无层叠。
Refs #1705
2026-09-22 04:20:48 +00:00
ForgQi
ea3298340f
Bump workspace version to 1.2.6 and update docs
...
Update workspace package version from 1.2.5 to 1.2.6 in Cargo.toml and synchronize package versions in Cargo.lock (biliup, biliup-cli, danmaku, stream-gears). Also add a README bullet noting streaming-upload without local save (边录边传 0 落盘).
2026-09-21 23:07:55 +08:00
Noah and Aluneu
f06efbb3b9
feat(ui): 前端整体重设计与统一(接手 #1653) ( #1704 )
...
* feat(ui): 前端整体重设计与统一(接手 #1653)
来自 Aluneu 的 PR #1653(head bdcfbea),在 master 4e156ac 上重新落地。
相对原 PR 的差异:
- 剔除 logo / favicon / logo.svg 品牌资产变更,侧栏与登录页仍用 public/logo.png
- 解决与 master 的三处冲突:upload-manager add/edit 的 studioEntity 补回 #1699 的 tid_v2;
global.tsx 下载插件说明补上 mesio(9d02856)
- README.md 相关提交在原 PR 合并上游时已归零,此处不含
主要内容:
- PageHeader / SectionTitle / data-card 统一页头与卡片容器,status.tsx 作为状态视觉的唯一来源
- 侧栏自绘重写:5 组 9 项、服务状态指示、折叠偏好持久化、移动端抽屉
- 首页从外链 iframe 改为本地数据的控制台(KPI / 进行中 / 未开播 / 24h 事件流)
- 直播管理:搜索、平台筛选、网格/列表双视图、批量暂停/删除;接口失败不再被当作空列表
- 新增 /changelog 页,运行时拉取官方 CHANGELOG,带 localStorage 离线缓存
- 全局自定义背景:仅本地图片,canvas 重绘为 JPEG data URL,2MB 上限
- 修复:根 layout 与 (app)/layout 双重 <html>/<body>、主题水合闪烁、扫码登录无保护取值崩溃、
弹窗内 width:1000 横向溢出、移动端抽屉被遮罩拦截
- 后处理 {cmd,value} ↔ HookStep 转换抽成 lib/postprocessor.ts
- 12 个平台插件文案精简
* fix(ui): 空间配置平台列表改为遍历插件注册表
dashboard 里硬编码的 PLATFORM_LIST 让新增平台插件要改第三个地方,漏了就在 UI 里静默消失。
把顺序与文案登记到 app/ui/plugins/index.ts 的 PlatformPanels,dashboard 只负责渲染。
* fix(ui): 控制台断连判定改为全部接口失败才触发,并放宽轮询频率
- /v1/status 单独失败时原先把整个控制台替换成「无法连接后端」,主播卡片全部消失;
现在只有四个接口全部失败才判定断连,单个接口失败只显示对应提示条
- 主播状态 4s → 10s;streamer-info(全表)、videos(扫目录)、status(序列化整份配置)
30s → 60s;侧栏与直播管理页共用同一组常量
- 事件流的平台名复用 status.tsx 的 platformName,删掉 use-dashboard 里的重复实现
- 首页「文档」链接对齐 README 里的 biliup.github.io 地址
* fix(ui): 直播状态文案与配色在卡片和列表视图保持一致
- 列表视图的「未开播」原是红底白字,与卡片的灰色徽章不一致;改为同一套三色
- 补回 Pending →「检测中」(原页面就有,重设计后被吞成「未开播」)
- OutOfSchedule 不再归到绿色录制组,绿色只留给 Working
- 删除无人引用的 statusMeta / platformTag
* fix(ui): 编辑投稿模板页在分区列表加载失败时给出原因而不是空白
/bili/archive/pre 需要可用的 B 站 cookie,没有账号时后端返回 500;原先 isError 为真但
error 为 undefined,页面渲染成一个空 div。现在保留页头并显示服务端返回的 message。
* fix(ui): 更新日志页不再把过时的最后一条标成「最新」
仓库里的 CHANGELOG.md 停在 1.2.1,而运行版本已是 1.2.5。页面现在读取 /v1/status 的版本号:
运行版本比日志最新一条还新时去掉「最新」标签并在顶部如实说明;日志里有当前版本时标「当前运行」。
* chore(ui): 清理重设计遗留的重复代码与死代码
- 直播管理页网格 / 列表两份完全相同的操作按钮合并为一处
- 去掉源码里面向评审工具的注释(「相对 PR 版本的增强」「保留 PR 逻辑」等)
- useGlobalBackground 删除 URL 模式残留的 validateBg / isRemoteUrl,遮罩透明度钳制与滑块范围对齐(0.05–0.8)
- 删除无人引用的 (app)/page.module.css 与 BiliupStatus 类型
- 移除 react-use(useWindowSize 已由 useIsMobile 替代,代码里已无引用)
* fix(ui): 全局设置里 sync_save_dir 输入框随下载插件下拉框即时显示
原先用 useFormApi().getValue() 做条件渲染,useFormApi 不订阅表单值变化,
选了 sync-downloader 之后输入框要先保存再刷新才出现。改用 useFormState 订阅 values。
* chore(ui): 删除原 PR 已移除的两个无人引用样式文件
app/page.module.css 与 app/styles/Home.module.css 是脚手架遗留,#1653 已删除;
接手时从合并树导入文件没有带上删除,这里补齐。
* fix(ui): 按 review 意见重审设置页文案,优先清晰无歧义
回应 #1704 的 review:设置页文案不再追求字数少,改为直观、清晰、无歧义。
逐条对照 master 原文与 Rust 实现重写,凡是精简后丢失默认值、单位、取值格式、
生效条件、依赖关系或风险提示的条目一律补回,用换行分点、加粗标出风险项。
- 12 个平台面板 + 全局「下载插件」说明:恢复多行结构,补回默认值 / 单位 / 格式 /
生效条件(如 douyin_true_origin 需画质为原画且协议为 flv、twitch_disable_ads /
twitch_cookie 仅 streamlink / ffmpeg 生效、youtube_cookie 填的是文件路径、
日期格式 YYYYMMDD、大小格式 100M/5G、编码多选用 | 分隔)
- 与实现对齐并修正原文:bilibili 弹幕不再限定非 stream-gears、且随录像分段;
bili_cookie 与 bili_cookie_file 同时填写时以文本为准;youtube_prefer_vcodec /
acodec 是编码而非封装格式;streamlink 现可直接下载 FLV 直链;
youtube_enable_download_playback「只想录直播的可以开启」笔误改为「关闭」;
bili_qn 占位符改为 25000(原画),与实现默认值一致
- 空间配置页头描述改为准确描述三个 Tab 并提示需点「保存」
- 链接颜色改用 var(--semi-color-link)(原 rgb(var(...)) 为无效 CSS)
不涉及任何逻辑改动。
---------
Co-authored-by: Aluneu <19215190873@163.com >
2026-09-21 13:46:15 +00:00
Noah
4e156ac683
fix(db): 启动时对齐被改写过的迁移校验和,1.2.4 的老库不再无法启动 ( #1703 )
...
* fix(db): 启动时对齐被改写过的迁移校验和,老库不再无法启动
1.2.5 就地修改了已经发布的迁移 2(#1684 把 SET tags = null 改成 SET tags = '[]')。
sqlx 会用文件内容的 SHA-384 校验每条已应用的迁移,于是 1.1.8 ~ 1.2.4 期间建立的
数据库升级后一律以 VersionMismatch 整体失败:进程退出码 1,容器反复重启。
在跑迁移之前加一步对齐:只有当 _sqlx_migrations 里记的校验和恰好等于登记在册的
历史摘要时,才在同一个事务里补跑新旧内容的差异(幂等 UPDATE)并改写校验和。
其余任何失配仍按原样报错,不会把用户自己改过的迁移悄悄放行。
* test(db): 钉死已发布迁移的校验和,防止再次就地改写
这次故障的根因是改了一条已经发布过的迁移。加一条断言把迁移 1..5 的 SHA-384 写死:
再有人就地改动已发布的迁移,CI 当场失败,而不是等用户升级时容器起不来。
2026-09-21 02:48:33 +00:00
ForgQi
9d02856a50
Integrate mesio (rust-srec) downloader
...
Add an in-process mesio downloader using the rust-srec mesio-engine. Introduces crates/biliup-cli/src/server/core/downloader/mesio.rs implementing FLV/HLS pull, timestamp fixes, keyframe indexing, and size/duration-based segmentation with pipeline_common, flv_fix and hls_fix. Wire Mesio into downloader enum/runtime and add UI options and public config docs. Update crates/biliup-cli/Cargo.toml and workspace Cargo.toml/Cargo.lock to include new dependencies and bump version to 1.2.5. Include tests for protocol selection, pipeline behavior and an e2e FLV download. Small helper changes: config enum docs and segment handling.
2026-09-21 00:24:52 +08:00
lplalbert and lplalbert
5ec63167d7
fix(douyu): remove incorrect video_loop check that skips live streams ( #1700 )
...
The Douyu API now returns videoLoop=1 for normal live streams,
causing the video_loop != 0 check to incorrectly filter them out
as looped/replay content. Remove this check and rely solely on
show_status to determine if the room is live.
Co-authored-by: lplalbert <“lplalber111@gmail.com ”>
2026-09-20 14:52:24 +00:00
Noah and ransxd
dfb933f066
fix(douyu): tolerate empty PlayResponse data and use server Date for tt ( #1698 )
...
Douyu getH5PlayV1 validates tt against server time; local clock skew
returns error -9 with data:"". PlayResponse could not deserialize the
empty-string data, so the -9/-5 branches never ran. Parse Date from
getEncryption for tt (cache offset for reuse) and deserialize data via
Value so real error/msg surfaces.
Fixes #1680
Co-authored-by: ransxd <ransxd@users.noreply.github.com >
2026-09-14 16:48:13 +00:00
Noah
970dfe8b37
feat(upload): support first-class tid_v2 partition id ( #1699 )
...
Add optional tid_v2 across Studio, config/DB, CLI, Web UI, stream-gears,
and Python uploaders so new Bilibili partition IDs can be set alongside
legacy tid. Fixes #1662 ; related to #1562 .
2026-09-14 16:19:53 +00:00
Noah and ransxd
2295ae1eba
fix(streamlink): httpstream prefix for FLV and Error backoff ( #1682 ) ( #1695 )
...
Prefix already-resolved progressive HTTP(S) media URLs (e.g. Huya/Douyu
.flv CDN links) with httpstream:// so Streamlink's http plugin matches
them, while leaving HLS/DASH and plugin webpage URLs unchanged.
When a download returns Error (or Err) while the room is still Live,
keep exponential retry backoff instead of resetting retry_count to 0,
which previously caused "Retrying download in 0ns..." spam. Immediate
retry remains only for successful segment completion / clean stream end.
Co-authored-by: ransxd <ransxd@users.noreply.github.com >
2026-09-14 16:18:15 +00:00
Noah
f85e66b6c2
fix(record): 文件名含点时不再截断扩展名或死循环 ( #1687 )
...
* fix(record): 文件名含点时不再截断扩展名或死循环
Path::with_extension 会把 Mr.Beast 变成 Mr.flv,冲突检测永远命中已有文件,generate_filename 的 loop 占死 tokio 线程且无法 stop。
* test(record): 合并 path_with_suffix 测试到已有 tests 模块
util.rs 已有 mod tests,新增第二个会 E0428。
2026-09-14 02:57:48 +00:00
Noah
1b426721d6
fix(upload): 定时发布把延迟秒数换成 Unix 时间戳 ( #1686 )
...
Web UI 存的是延迟秒数,B 站接口要 10 位时间戳。Python 版会做 time()+dtime,Rust 投稿路径漏了换算,定时稿被拒后边录边传首 P 会停摆。
2026-09-13 15:40:17 +00:00
Noah
40234133e3
fix(webui): 保存配置覆写时保留投稿模板关联 ( #1685 )
...
覆写弹窗的 entity 白名单漏了 upload_streamers_id,PUT 整行覆盖后外键变 NULL,后续录像不再投稿并走默认 rm。
2026-09-13 15:40:03 +00:00
Noah
9445db636b
fix(danmaku): 虎牙弹幕从用户结构体读取昵称和正文 ( #1689 )
...
MessageNotice 的 tag 2 是 lSid 不是用户名。旧解析把 lSid 当字符串读,lSid 非 0 时连正文都对不齐,弹幕几乎全部丢弃。
2026-09-13 15:36:15 +00:00
Noah
8437b028c3
fix(db): 迁移 2 将空 tags 写成 [] 而不是 NULL ( #1684 )
...
uploadstreamers.tags 是 JSON NOT NULL。SET tags = null 会让从 Python 升级且 tags 为空字符串的实例卡在迁移 2。
2026-09-13 15:33:12 +00:00
Noah
1a56242696
fix(log): 下载器命令行日志脱敏 Cookie 和 OAuth ( #1688 )
...
* fix(log): 下载器命令行日志脱敏 Cookie 和 OAuth
ffmpeg/streamlink/yt-dlp/ytarchive 会把 Cookie 头和 auth-token 打进 ds_update.log,该文件可经 Web 日志页与 /static 下载,排障贴日志即泄露登录态。
* fix(log): 头部值整段脱敏并合并测试到已有 tests 模块
Cookie/Authorization 的值吃到下一个引号,OAuth token 不再漏出;新增 --xxx-password 形式 argv 的脱敏;sid 不再当密钥以免误伤斗鱼直链。util.rs 已有 mod tests,第二个会 E0428。
2026-09-13 15:31:43 +00:00
Noah and ransxd
86bcaffd2a
fix(sync): AUTO 测速超时与 file_size 覆写占位 null ( #1693 )
...
- AUTO 线路探测为列表请求与单线路测速增加 15s 硬超时,失败只跳过该线路
- POST 探测体按服务端 probe.post(默认 0.1MB)发送,避免固定 10MB 拖慢选线
- ConfigPatch 序列化跳过未设置的 file_size,避免落库/回传把全局值清成 None
- 迁移清理历史 override 中的 file_size: null 占位
- 同步保留边录边传管线健壮性修复(取消保留分段、错误提示、分段顺序等)
Co-authored-by: ransxd <ransxd@users.noreply.github.com >
2026-09-13 15:29:12 +00:00
Noah
788682398a
fix(docker): FFmpeg 固定到 BtbN 月末 autobuild ( #1691 )
...
autobuild-2026-08-29 已被上游删除,Docker 构建 404。BtbN 只长期保留每月最后一天的构建,改为固定 autobuild-2026-08-31-13-27 并更新 SHA-256。
2026-09-12 18:39:59 +00:00
Noah
906e0f6fdb
docs: 依据代码实现重写 README,迁移弃用域名并修正失效信息 ( #1681 )
...
* docs: 依据代码实现重写 README,修正失效信息并补充平台与开发说明
保持原有结构、标题与徽章排版不变,按当前代码逐项校对内容。
修正与实现不符之处:
- 登录信息文件名写作 cookie.json,实际 CLI 默认值为 cookies.json
(crates/biliup-cli/src/cli.rs 及其单测均为 cookies.json)
- Windows 下载链接硬编码 bbup-app_0.1.0_x64,随发版即失效,改为
指向 Release 页
- Python 徽章链接 http://www.python.org/download 返回 404,改为
https://www.python.org/downloads/
- Node.js 要求由 ≥ 18 修正为 ≥ 18.17(Next.js 14 的实际下限)
新增内容:
- 支持平台表:依据 builtin_plugins() 的 19 个平台适配器与通用兜底
适配器,站点域名取自各适配器正则;弹幕列依据 danmaku
create_platform() 支持的 7 个平台
- download 子命令用法示例与分段参数说明
- 外部依赖说明:默认 stream-gears 为纯 Rust 实现无需外部程序,
ffmpeg 下载器与后处理需 ffmpeg,YouTube/niconico 及通用适配器
需 yt-dlp 或 streamlink
- 目录结构表,以及 cargo 构建前必须先 npm run build 的说明
(biliup-cli 经 rust-embed 内嵌 out/,缺失会导致编译失败)
- 测试命令与 Docker /opt 持久化、首次访问尽快初始化管理员的提示
校验:CLI 帮助文本与 target/debug/biliup --help、server --help 实际
输出逐字一致;远程访问拒绝启动的报错取自实际运行输出;mermaid
架构图经 mermaid-cli 渲染通过;README 全部外链已联网检查。
* docs: 迁移已弃用的 biliup.rs 文档域名,移除失效的 winget 安装方式
README:
- 顶部 logo 原引用 docs.biliup.rs/home.png,该域名已弃用且无法访问,
改为仓库内 public/logo.png 的 raw 链接。README 同时用作 PyPI 长描述
(pyproject.toml readme = "README.md"),故使用绝对地址而非相对路径
- 「使用文档」链接改为新域名
https://biliup.github.io/biliup/docs/guide/introduction/
SKILL.md:
- 删除 Windows winget 安装段落。winget 上不存在名为 biliup 的包,
该命令返回 No package found matching input criteria;现有的
ForgQi.biliup-app(0.6.7)、ForgQi.biliup-rs(0.2.4) 均已停止更新,
远落后于当前 1.2.4。Windows 用户仍可通过既有的 GitHub Releases
段落(含 PowerShell 脚本)安装
校验:README 全部 29 个链接联网复查,除 GitHub 对未登录请求的
/stargazers 统一返回 404(rust-lang/rust 同样如此,属反爬行为)外
均为 200;新 logo 地址返回 200、image/png、65525 字节。
2026-09-01 13:49:39 +00:00
Noah
c4dbf81c5d
fix(danmaku): B站数据包解析不再对畸形输入 panic,解码 panic 不再终结录制 ( #1678 )
...
decode_packet 只校验 packet_len 是否越界,未校验其不小于 16 字
节包头:远端发来 packet_len < 16 的畸形帧时,body 反向切片直接
panic;panic 发生在 tokio::spawn 的录制任务里,run() 的重连逻辑
只处理 Err,任务被杀死后没有任何重启机制,弹幕录制静默永久停
止,XML 文件也不会 finalize。
修复:
- packet_len < 16 视为不可信帧,丢弃剩余数据并保留已解析的包,
同时消除 packet_len=0 时 offset 不前进的死循环隐患
- 压缩包递归解码增加深度上限(真实协议仅一层嵌套),恶意多层
嵌套不再导致栈溢出
- 客户端在 WebSocket 与 TCP 两条路径上用 catch_unwind 包裹
decode_message:任何平台解码器 panic 都降级为可跳过的解码错
误,连接与录制继续存活
新增 5 项回归测试:零长度帧、短长度帧、合法帧后跟畸形头、单层
zlib 嵌套仍可解码、超深嵌套被丢弃。
2026-08-30 15:03:59 +00:00
Noah
01a33213b8
ci: 修复发布与 CI 工作流的权限、死代码及供应链问题 ( #1679 )
...
release.yml:
- Windows 归档条件比较 matrix.os 与 windows-2019 永远为假,实际
依赖 MSYS 的 .exe 兜底才能工作,改为按 runner.os 判断
- publish 归档循环中 exe 变量不重置,目录顺序变化时会污染后续平台
- 替换已归档的 actions-rs/toolchain、actions-rs/cargo,改用
dtolnay/rust-toolchain 与 cross;checkout@v2(node12 已下线)升 v4
- 弃用的 ::set-output 改为 GITHUB_OUTPUT
- 顶层收紧为 contents: read,仅 publish 任务授予写权限
ci.yml:
- tag 触发从 '*' 收紧为 v[0-9]+.[0-9]+.[0-9]+,任意 tag 不再能
触发 PyPI 正式发布
- setup-node@v3 升 v4
docker-publish.yml:
- 顶层 packages: write 会授予 pull_request 触发的 test 任务,收紧
为顶层只读、build/merge 任务单独授予
- 移除 buildx 已弃用的 install: true;docker-compose v1 命令改为
compose v2 子命令
docs-publish.yml:补 paths 过滤与显式权限。
Termux-publish.yml:setup-node@v3 升 v4。
新增 test.yml:PR 与 master push 运行
cargo test -p biliup -p biliup-cli -p danmaku,合并前有真实测试门禁。
Dockerfile:FFmpeg 从滚动的 latest tag 改为固定 autobuild 版本并
强制 SHA-256 校验,下载被篡改或损坏时构建失败。
docker-compose.yml:占位符 <HOST_PORT> 等使解析必然失败,改为
带默认值的环境变量;移除已废弃的 version 字段。
新增 .dockerignore:排除 .git、构建产物及本地数据库/凭据/日志,
避免进入构建上下文。
2026-08-30 15:00:16 +00:00
Noah
8a19c05c06
fix(webui): 注册提交前执行表单校验,确认密码规则不再被绕过 ( #1677 )
...
登录页的提交按钮直接调用 handleSubmit 发起 fetch,从不触发
Semi Form 的规则校验:确认密码字段的一致性 validator 从未执行,
注册时确认密码留空或与密码不一致都能成功提交。
现在提交前先 formApi.validate():校验失败时终止提交,错误信息
展示在对应字段下方;同时为确认密码补充 required 规则,留空时
给出明确提示而不是笼统的“两次密码输入不一致”。
2026-08-30 14:56:56 +00:00
Noah
631e1160b1
fix(flv): 使用 write_all 保证 FLV 标签与 JSON 输出完整写入 ( #1676 )
...
flv_writer 中三处使用 io::Write::write 写入数据:write 允许部分
写入且把已写字节数作为 Ok 返回,短写时 previous_tag_size 等
字段会静默缺字节,产出的 FLV 标签结构损坏,播放器与解析器
在该点之后无法继续解析。
将 write_tag 的 previous_tag_size 写入、write_previous_tag_size
与 to_json 的换行写入全部改为 write_all,公开签名保持不变。
新增短写 Writer 回归测试,验证 4 字节 previous_tag_size 在部分
写入场景下仍被完整写出。
2026-08-30 14:54:09 +00:00
Noah
2c3592f117
fix(bilibili): 稿件分页查询复用登录客户端,不再每页新建 Client ( #1675 )
...
archives() 之前在分页循环中每请求一页都重新 build 一个
reqwest::Client 和 cookie jar:连接无法复用,代理配置丢失导致
查询绕过 --proxy 直连;且 Jar::add_cookie_str 会把 "; " 拼接的
整串 Cookie 当作单条 Set-Cookie 解析,实际只有第一个 Cookie
生效,其余键值被当作 Cookie 属性丢弃。
改为复用登录时构建的 self.client:cookie store 已含全部登录
Cookie,UA 与原实现一致,并保留代理配置与连接池,同时按原
行为为该请求保留 60 秒总超时。
2026-08-30 14:52:47 +00:00
Noah
5c6c720fdc
fix(upload): 测速时单条线路失败不再中断整体测速 ( #1674 )
...
Probe::probe 对每条线路的 ping 使用 ? 传播错误,任意一条线路
DNS/连接/读取失败都会让整个测速立即返回错误,即使后续线路
完全可用,调用方也会因 unwrap_or_default 直接退回默认线路。
现在单条线路网络错误或异常状态码只记录警告并跳过该线路,
继续测速其余候选;全部线路失败时返回统一错误。
2026-08-30 14:51:09 +00:00
Noah and Noah
616432d190
feat(sync): 恢复边录边传下载器的流式上传管线 ( #1673 )
...
Rust 重写后 sync-downloader 被静默映射为 stream-gears 落盘下载,
边录边传完全失效(#1612)。本次恢复 ffmpeg stdout 到 UPOS 流式
分片上传、逐段投稿/追加分 P 的完整链路,并修复可靠性问题:
- UPOS 拆分为上传 parts 与显式 complete 两阶段,parts 按
partNumber 排序并校验连续,complete 带状态检查与重试
- 录制同时写临时文件(有界通道背压);短流/预传失败时按实际
长度回退为文件重传,投稿成功后清理临时文件
- SyncSession 跨拉流重连保留 aid,分 P 严格按录制顺序提交,
首投结果不确定时停止自动重试避免重复建稿
- 上传/投稿错误向上传播;外层重试只在分 P 推进后重置退避
- 修复 www.douyin.com/live/{id} 被解析成 web_rid=live 的问题
Co-authored-by: Noah <17682131+ransxd@users.noreply.github.com >
2026-08-30 14:50:37 +00:00
yjywybb
051f5c7eb0
fix(upload): preserve filename-invalid characters in submission titles ( #1671 )
2026-08-25 07:50:23 +00:00
ForgQi
03b7a84f55
Allow remote Web UI bootstrap with auth
...
This change updates the server default bind to 127.0.0.1 for safer local-only access while documenting how to expose the Web UI via --bind 0.0.0.0 --auth for remote or Docker use. It also removes the previous restriction that blocked first-time admin bootstrap on non-loopback addresses, allowing authenticated remote initialization and bumping the workspace version to 1.2.4.
2026-08-24 01:13:29 +08:00
Noah and Noah
c351c39771
fix(server): 会话 Cookie 不再强制 Secure,修复 HTTP 远程访问登录后无法保持登录态 ( #1669 )
...
Co-authored-by: Noah <17682131+ransxd@users.noreply.github.com >
2026-08-23 15:20:11 +00:00
Noah and Noah
6cef8afe22
fix(stream-gears): 登录函数释放 GIL 后再执行阻塞网络请求 ( #1667 )
...
Co-authored-by: Noah <17682131+ransxd@users.noreply.github.com >
2026-08-22 19:25:48 +00:00
ForgQi
eb8699b203
Add recording policy and time-range checks
...
This change adds a recording policy layer that enforces time-window eligibility and excluded title keywords before probing and before recording. It updates the streamer API to surface reasons like OutOfSchedule and TitleExcluded, clamps ffmpeg/segment durations to the configured window, and exposes the last rejection reason from the worker state for the frontend.
2026-08-22 19:15:48 +08:00
Noah and Noah
1affe97d87
fix(webup): Data 声明 desc_v2 字段,避免提交时被 asdict 静默丢弃 ( #1666 )
...
Co-authored-by: Noah <17682131+ransxd@users.noreply.github.com >
2026-08-22 08:52:05 +00:00
cls3389
8fe338b5f5
fix(douyin): 下播后回放房间仍被误判为直播中 ( #1663 )
...
抖音主播下播后,直播间跳转 reflow 回放页,接口仍返回 status=2
和可拉取的回放流,导致 monitor 循环误判开播并反复录制垫片流。
两处独立防线:
- get_room_info: finish_time > 0(抖音已写入直播结束时间)判定未开播
- select_stream_url: 流 URL 含 rtm_expr_tag=reflow_room_info
(回放流标记)时拒绝作为直播流返回
Fixes #1532
2026-08-22 08:39:50 +00:00
Noah and Noah
3750ced583
fix(hls): respect playlist refresh lifecycle ( #1665 )
...
Co-authored-by: Noah <17682131+ransxd@users.noreply.github.com >
2026-08-22 08:37:05 +00:00
Noah and ransxd
e1c4b6887e
fix(downloader): 纯视频流分段时避免序列头 expect 导致的 panic ( #1664 )
...
分段重建逻辑此前对 on_meta_data / aac_sequence_header / h264_sequence_header 使用 expect。纯视频流(无音频,aac_sequence_header 恒为 None)在首次分段时必然 panic,导致 7x24 录制任务中断。改为缺失时跳过并告警,不再中断录制。
新增回归测试 pure_video_stream_segments_without_panic:构造仅含 onMetaData 与 H264 序列头、无音频标签的 FLV 流并触发分段;修复前该测试会 panic(aac_sequence_header does not exist),修复后通过。
Co-authored-by: ransxd <ransxd@users.noreply.github.com >
2026-08-22 08:35:19 +00:00
Mostly_Harmless
b438d72415
feat(web): 完善账号管理、稿件查询与安全边界 ( #1660 )
...
启动 Web 服务时自动登记 CLI 指定的 B 站凭据,并为账号登记、刷新和删除统一补上并发去重、原子写入及引用检查。
同时收紧默认监听、管理员初始化、认证路由、静态文件和日志 WebSocket,恢复 server 的 ds_update.log,并避免凭据及上游响应进入日志。
新增按账号查询远程稿件的 API 与 Web 页面,补齐严格分页和响应校验。
关联 biliup/biliup#1624
关联 biliup/biliup#1658
关联 biliup/biliup#724
2026-08-16 14:05:20 +00:00
vector0902 and rootuc
ccb016be4f
fix(upload): include lock file path in rate-limit lock error message ( #1661 )
...
Co-authored-by: rootuc <rootuc@notunixchan>
2026-08-15 06:40:18 +00:00
PingouinFerreux
0c3bbe2ec1
fix: switch star history chart to a working provider ( #1659 )
...
The star history chart in the README is no longer loading because the upstream API it depended on stopped working. Point the chart and its link to the star-history service hosted at star-history.dera.page, which uses a different data source and requires no API key, so the chart renders again for everyone.
This keeps the same query and hash parameters as before, so only the domain in the two URLs changes.
2026-08-14 07:59:58 +00:00
dreammu
3e8b55c2c4
fix(upload): update upload lines ( #1656 )
2026-08-07 09:28:12 +00:00
Micuks
adf6a1c03b
fix(hls): 选取最高画质的流,而非固定取 variants[0] ( #1649 )
...
HLS 下载器一直取 master playlist 里的第一个变体。Twitch 会把各档
转码清晰度排在源流前面,所以经常录到较低画质的转码流,而不是源流。
改为选取带 RESOLUTION、且码率最高的变体,并跳过 I-frame(trick-play)
流。若没有变体带分辨率,则回退到码率最高的非 I-frame 变体,最后回退
到第一个变体,保证在异常 playlist 下行为不退化。
2026-07-14 14:35:35 +00:00
ForgQi
03c36e1da7
Add batch live-check support + fixes
...
Introduce batch live-check infrastructure and implementations. Add BatchCheckRequest and LivePlugin::supports_batch_check/batch_check API; Monitor uses BatchLiveCache and batch_verdict to prefilter rooms (platform-level checks + caching and fast rotation). RoomsActor gains PlatformUrls. Implement batch_check for Picarto (explore pagination) and Twitch (GQL batching with auth retry). Other fixes: Douyu auth-failure detection and key invalidation + retry, Inke optional response handling, streamlink runtime tweaks for yt-dlp/Twitch ffmpeg, small test additions and import refactors.
2026-07-14 21:33:23 +08:00
ForgQi
083313257f
Improve downloader reliability and add caching
...
- Add process-level caching for expensive API calls: login tokens (Afreecatv, Douyin), encryption keys (Douyu), room IDs (Douyu, Huya)
- Implement Huya mini-app API option (huya_mobile_api) with WUP protocol support for getCdnTokenInfoEx
- Add cdn_fallback for Huya with health checks and protocol negotiation
- Improve Bilibili stream selection with protocol fallback (hls_fmp4→stream) and qn-based grouping
- Fix Twitch auth token invalidation handling and improve cookie file management
- Refactor YouTube entry selection with proper playlist recursion and date filtering
- Add retry logic for network failures (Douyu betard) and improve error recovery
- Share WBI signer state across Bilibili check_stream calls to respect 2-hour key cache interval
- Always pass --download-archive for YouTube (including live) to prevent post-stream duplicates
- Add field aliases (inke: live_name, kilakila: backPic) and fix Niconico suffix (flv→ts)
2026-07-14 00:32:29 +08:00
ForgQi
18c5bf086e
Define AT_HWCAP2 for ppc64le; enable tokio
...
Set CFLAGS_powerpc64le_unknown_linux_gnu=-DAT_HWCAP2=26 in CI to work around aws-lc-sys (via rustls) calling getauxval(AT_HWCAP2) on manylinux2014 where glibc 2.17 headers lack AT_HWCAP2; glibc still provides getauxval so the value is safe and only used for ppc64le cc invocations. Also enable the tokio "io-std" feature for the biliup-cli crate to opt into the io-std I/O implementation.
2026-07-11 03:53:51 +08:00
ForgQi
ddc29f63b4
Update deps & fix server upload/download
...
Bump dependencies (Cargo.toml + Cargo.lock) to newer crate versions and adapt code to API changes. Refactor server-side upload/download logic, the upload_streamer model, and related API endpoints to improve streaming handling, buffer/error propagation and compatibility with updated crates (e.g. tokio-tungstenite/futures/etc.). These changes align the codebase with the upgraded dependency set and fix streaming/endpoint issues introduced by those upgrades.
2026-07-11 01:57:48 +08:00
ForgQi
58dc75b6ca
Build free-threaded wheels for Python 3.14t ( #1648 )
...
maturin 1.14 dropped support for free-threaded Python 3.13
("Free-threaded Python interpreter is only supported on 3.14 and
later"), which broke the linux/musllinux/macos free-threaded wheel
steps since maturin-action always uses the latest maturin. Switch
the free-threaded builds from python3.13t to python3.14t, matching
what maturin generate-ci now emits.
2026-07-10 17:08:08 +00:00
Micuks
33649fb568
fix server submit_api web routing ( #1645 )
2026-07-10 15:11:39 +00:00