Noah
486676db29
feat(ui): 直播预览阶段三——录制中房间实时码率折线图 ( #1714 )
...
* feat(api): GET /v1/live-rates 录制中房间的每秒写盘速率瘦端点
/v1/streamers 每次查全表、拼齐全部字段,10 s 一次的节奏不适合调快;码率曲线需要 1 s 粒度,
新加一个只读内存的瘦端点:扫一遍 worker,对每个 Working 的房间读一次阶段一 RateMeter 的窗口均值,
返回 [{id, bytes_per_sec, ts}]。与 /v1/streamers 的 live_bytes_per_sec 同源同值;不新增采样任务、
服务端不存历史、Cache-Control: no-store;注册在 router() 内,--auth 时同一道登录校验。
Part of #1708
* feat(ui): 实时码率数据层与 uPlot 折线组件
- app/lib/use-live-rates.ts:页面内唯一的一份采样源。有订阅者时每秒拉一次 /v1/live-rates,
全部卸载 / 标签页隐藏即停;历史放模块级环形缓冲(每房间最近 3 分钟),组件卸载不清,
切走再切回曲线还在;帧里没有的房间补 null 成断口。useSyncExternalStore 订阅,无新帧不重渲染。
- app/ui/RateChart.tsx:uPlot 折线,full(带时间轴 / Y 轴,单位按窗口最大值自动选 KB/s / MB/s,
Y 从 0 起)与 sparkline(无坐标轴,hover 读数)两种形态;实例在 effect 里建 / 销毁,渲染期不碰 ref,
StrictMode 双次 effect 不留多余 canvas;配色每次绘制时读 Semi CSS 变量,深浅色切换不重建。
无采样 / 未录制只显示「—」占位,不画空图。
- app/ui/LiveRateChart.tsx:next/dynamic 懒加载入口 + 当前 / 峰值 / 均值统计条;
uPlot 与其 CSS 打进独立 chunk(约 23 KB gzip),首屏不含。
- 新依赖 uplot ^1.6.32(本 PR 唯一新依赖)。
Part of #1708
* feat(ui): 三处接入码率折线——弹层可折叠折线、卡片与监视器 sparkline
- 卡片弹层底部新增「写盘速率 · 最近 3 分钟」可折叠区(默认展开,折叠态记 localStorage
biliup.preview.rateChart):标题行带当前 / 峰值 / 均值,展开时才轮询与加载 uPlot。
- 控制台 / 直播管理页录制中卡片:名称行下一条 60 s sparkline(28 px,hover 显示时刻与数值)。
- 历史记录页「实时监视」:工具栏新增「码率图」开关(默认关,记 biliup.monitor.rateChart),
开启后每路小窗下方一条 60 s sparkline;所有小窗共用同一次每秒请求。
Part of #1708
* style(api): live_rates 测试按 rustfmt 换行
* fix(ui): sparkline 的 hover 读数浮在折线上方,不再被容器裁掉
* docs(readme): 功能列表补充直播预览(封面 / 播放器 / 弹幕 / 码率曲线)
#1708 三个阶段至此完整,在 README 的功能列表里加一条说明。
Closes #1708
* feat(api): GET /v1/ws/live-rates 用 WebSocket 每秒推送码率帧
review 1(ForgQi):复用现有的 WebSocket 机制推送而不是每秒轮询。WebSocket 不占浏览器对同一主机
HTTP/1.1 的六个并发连接(监视器 4 路视频 + 1 条弹幕 SSE 已用掉五个,这正是当初不用 SSE 的原因),
与日志页 /v1/ws/logs 同款:Origin 与 Host 同源校验、进程内连接数上限(16)、Ping 回 Pong、Close 即止。
连上立刻发一帧,之后每秒一帧,形状与 GET /v1/live-rates 相同;HTTP 版保留给 WebSocket 连不上时回退与 curl 核对。
两个路由都在 router() 内,--auth 下同一道登录校验。单测用手写的最小 WebSocket 客户端走完握手 + 两帧,不引新依赖。
* feat(ui): 码率数据改走 WebSocket;卡片 sparkline 加开关并叠在封面上
review 1(ForgQi):
- use-live-rates.ts 首选一条 WebSocket /v1/ws/live-rates(服务端每秒推一帧,所有图表共用),
一帧都没收到就断开(反代没放行 upgrade、401)时回退到每秒 GET /v1/live-rates 轮询;中途断开退避重连,
标签页隐藏时断开、可见时重连;快照新增 transport: 'ws' | 'poll'。
- 控制台「进行中」区块头与直播管理页工具栏新增「码率图」开关(默认关,记 localStorage biliup.cards.rateChart,
两页共用);开启后录制中卡片的 sparkline 叠在封面底部(深色渐变垫底、读数条在图区内;点击仍冒泡到封面打开预览),
封面没有 / 加载失败时退到名称行下方。
* test(api): 码率 WebSocket 连接上限测试改用独立信号量
CI 上与同一二进制里并发跑的 WebSocket 推送测试争用进程级信号量(后者占着一个许可),
上限测试拿不满 16 个而失败;改为像 ws.rs 的日志连接测试那样传入独立的 Semaphore。
2026-09-23 03:44:43 +00: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
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 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
XuanRan_Nyan
caf106c3f8
Fix(Huya): refactor anticode generation using getCdnTokenInfoEx API ( #1575 )
...
* Fix(Huya): refactor anticode generation using getCdnTokenInfoEx API
* chore: cleanup
* chore: get random sHuYaUA
* chore: 代码复用
2025-12-21 13:03:52 +00:00
ForgQi
3698bcf28a
Add struct-patch support and improve config handling
...
Introduces the struct-patch crate for partial config updates and applies it to configuration models. Refactors config access patterns, adds PartialEq to several types, and updates LiveStreamer to use ConfigPatch for override_cfg. Also updates the CI workflow to use macos-15-intel, improves download task cancellation logic, and upgrades @douyinfe/semi-ui and related dependencies in the frontend.
2025-11-20 03:26:40 +08:00
ForgQi
92b5a30df5
Add streamer pause/resume feature and UI updates
...
Introduces a pause/resume button for streamers in the UI and backend, including API endpoint and status handling. Refactors streamer status display, updates related TypeScript interfaces, and improves modal forms. Also updates @douyinfe/semi-ui and related dependencies to v2.86.0, and bumps workspace version to 1.1.10. Minor logging and trait changes in Rust backend for improved clarity and error handling.
2025-09-29 03:18:21 +08:00
ForgQi
1e035df468
Refactor web build output and packaging paths
...
Updated Dockerfile to copy from /biliup/out instead of /biliup/biliup/web/public. Removed web public directory from PyInstaller and pyproject.toml includes. Adjusted build script in package.json to stop copying build output to web/public. These changes streamline the build and packaging process by consolidating output paths.
2025-09-19 03:34:01 +08:00
ForgQi
9d7318b25f
init tauri-app
...
init tauri-app
init rs
2025-07-16 03:43:05 +08:00
bec4f72cc6
feat: 移动端响应式增强 ( #1266 )
...
* feat: 弹出层支持响应式宽度
* feat: 优化折叠效果
* feat: 优化移动端折叠体验
* fix: ssr 编译报错
* fix: 修复 PC 切移动端时不会自动折叠的问题
* feat: 支持仅自己可见开关 (#1261 )
* feat: 支持仅自己可见开关
* fix: 私密视频投稿失败
* chore: 移除迁移脚本
* fix: 修复 is_only_self 覆盖界面原先额外字段配置问题
* fix: ncp and sass should not production dependencies (#1265 )
* feat: 抖音真原画 (#1267 )
* feat: 抖音真原画
* add webui option
* Update __init__.py
---------
Co-authored-by: ForgQi <34411314+ForgQi@users.noreply.github.com >
* feat: 优化移动端折叠体验
* chore: rebase master,重新提交 lock
---------
Co-authored-by: XuanRan_Nyan <31968665+xxxxuanran@users.noreply.github.com >
Co-authored-by: ForgQi <34411314+ForgQi@users.noreply.github.com >
2025-04-29 02:03:19 +00:00
Gweesin Chan
3a28b3b3b5
fix: ncp and sass should not production dependencies ( #1265 )
2025-04-27 01:58:05 +00:00
1938e58802
Add 添加实时日志展示 ( #1210 )
...
* fix
* 修复虎牙拉流失败异常
* 添加边录边传下载器日志记录
* 修复边录边传投稿标题概率异常
* Update biliup/plugins/huya.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com >
* 添加边录边传的说明链接
* 添加日志实时ws接口
* 避免日志文件过大读取异常
* 修复可能出现的Keyerror
* 添加 LogViewer 页面
* LogViewer 添加自动滚动
* 更换 Icon
* 添加下载按钮
* 修复投稿标题格式化异常
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com >
Co-authored-by: xxxxuanran <xxxxuanran@gmail.com >
2025-04-01 03:07:37 +00:00
ForgQi
94d4c679c5
chore: bump version to 0.4.81 and update dependencies to 2.72.1
2025-01-04 12:41:19 +08:00
XuanRan_Nyan
dfdd92ae60
fix: dtime ( #1121 )
...
* fix dtime
* change npm mirror
* 发布时间离当前时间必须≥4小时且≤15天
* 使用 artplayer 替换 西瓜播放器
* 自动切换播放器
* 控件修改为 ScrollList
* 移除 西瓜播放器
2024-12-31 16:45:21 +00:00
Monine Melolaris
4273edc6c7
Feat(webui): 深色模式添加自动跟随系统,添加格式化配置文件。 ( #1109 )
...
* Style(webui): Add format config.
Add prettier config to confirm all developer have same code format style.
* Feat(WebUI): 深色模式行为优化
新增:
1、添加了「自动跟随系统主题」模式,并设置为默认行为,该模式将实时监控系统/浏览器主题,并同步变化;
2、添加了自动切换模式按钮。
修改:
1、修改按钮深色模式、浅色模式显示为对应当前模式;
2、修改默认主题为自动跟随系统。
* Update prettier config.
限定解析器的部分格式,避免影响其他语言的格式化功能。
* Disassemble some components.
1.move theme hooks to /lib folder;
2.disassemble ThemeButton component;
3.refactor ThemeButton related code.
2024-12-09 02:34:38 +00:00
ForgQi
cd353f609d
update README.md
2024-06-02 21:33:07 +08:00
ForgQi
2ae7a2d39e
Bumping version
2024-05-28 01:48:50 +08:00
ForgQi
55ebbfee61
Bumping version
2024-05-13 23:29:32 +08:00
ForgQi
a9de10e338
Bumping version
2024-05-01 01:18:44 +08:00
ForgQi
8db64e2d8b
add manually operated
2024-04-25 01:47:44 +08:00
ForgQi
d4d238e6a7
add login by qrcode
2024-04-24 01:10:33 +08:00
ForgQi
4940fceec6
Bumping version
2024-04-17 01:06:07 +08:00
ForgQi
e51e357665
Bumping version
2024-04-10 23:44:35 +08:00
ForgQi
9344ab750a
Bumping version
2024-03-18 02:08:06 +08:00
ForgQi
6d1695347c
webui初版
2023-12-08 23:37:40 +08:00
luckycat0426
f260a50582
Fix ui bug,add login page for biliup ( #155 )
...
* Add Douyin Mainpage Url Support For Douyin Recorder
* Fix issues 119
* Delete Tid Table
2022-06-14 14:09:31 +08:00
ForgQi
ad5c715381
use ncp for copy dist
2022-04-27 22:34:11 +08:00
ForgQi
0b9fc87ca9
mv public
2022-04-27 09:05:31 +08:00
luckycat
e39b4b5383
Add User Interface For Biliup
2022-04-04 01:40:03 +08:00