Repository navigation
Releases
v0.3.0 — seek bar works + bilingual docs
The seek bar now works. Media is served over real http:// instead of the
dsh-app:// scheme, which Chromium's media stack refuses to seek.
dsh plugin --profile web add dsh-bili-wallpaperOn the desktop build, replace --profile web with --profile desktop.
English
Fixes "dragging the seek bar does nothing — the video never jumps".
Fixed
- Media played through a custom scheme, so the browser refused to seek.
The desktop build serves the page fromdsh-app://app, which turned the relative
media path/api/bili-wallpaper/proxy…intodsh-app://app/api/…. Chromium's media
stack does not do random access over non-http(s) schemes: the video would play and
buffer (readyState = 4) butseekablestayed[0, 0], and assigningcurrentTime
was silently ignored.
Media URLs are now absolutehttp://127.0.0.1:<port>/…built from
webServer.getOrigin(), so the<video>element goes through plain HTTP.
Measured before/after:seekable[0, 0]→[0, 3604.23],currentTime = 600
now reads back600.24. /proxyrejected non-same-origin requests. A page served fromdsh-app://app
requestinghttp://127.0.0.1:<port>never matches the same-origin rule, and a
<video>element cannot send the auth cookie either. The media endpoint now only
requires a loopbackHost; its real boundary stays the HMAC signature plus the
Bilibili CDN host allowlist (both still enforced and tested).- Missing CORS headers on the media endpoint. Added
Access-Control-Allow-Origin,
Allow-Headers: range,if-rangeandExpose-Headers: content-range,content-length,accept-ranges,
plus anOPTIONSpreflight reply.
Added
HEADsupport on/proxy. Browsers often probe a media resource withHEAD
before playing. OnlyGETwas routed, soHEADfell through to404and the
browser concluded the resource was not randomly accessible.HEADnow returns the
full headers (Content-Length,Accept-Ranges,Content-Type) with no body.Content-Lengthbackfill. When the CDN answers a range request with chunked
transfer (noContent-Length), the proxy now derives the length from
Content-Range(bytes a-b/total→b-a+1). Large stream recordings hit this path.
Changed
- Range-related upstream statuses (
401,403,404,405,416,429) are now
passed through unchanged instead of being collapsed into502. In particular416
is a normal answer to an out-of-range probe, and wrapping it in502made Chrome
treat the whole media as unseekable.
Tests
test/test-absolute-media-url.mjs: media URL shape, cross-origin range access,
HEAD, and that a tampered signature / non-allowlisted host is still rejected.test/test-head-request.mjs,test/test-416-passthrough.mjs,
test/test-content-length.mjs: the three transport fixes above.
简体中文
修掉「拖进度条完全没反应,视频不跳」。
修复
- 媒体走了自定义协议,浏览器因此拒绝随机访问。 桌面版页面源是
dsh-app://app,相对路径/api/bili-wallpaper/proxy…被解析成
dsh-app://app/api/…。Chromium 的媒体栈对非 http(s) scheme 不做随机访问:
视频能播、能缓冲(readyState = 4),但seekable恒为[0, 0],
给currentTime赋值被静默忽略。
现在媒体地址改为用webServer.getOrigin()拼出的绝对http://127.0.0.1:<port>/…,
<video>走标准 HTTP。实测前后对比:seekable[0, 0]→[0, 3604.23],
currentTime = 600能读回600.24。 /proxy拒绝非同源请求。 源为dsh-app://app的页面请求
http://127.0.0.1:<port>永远不满足同源规则,而<video>又无法携带认证
cookie。媒体端点现在只要求Host是回环地址;真正的边界仍是 HMAC 签名 +
B 站 CDN 域名白名单(两者都保留且仍有测试覆盖)。- 媒体端点缺 CORS 头。 补上
Access-Control-Allow-Origin、
Allow-Headers: range,if-range、Expose-Headers: content-range,content-length,accept-ranges,
以及OPTIONS预检应答。
新增
/proxy支持HEAD。 浏览器播放前常先用HEAD探测媒体资源。之前只路由
GET,HEAD落到404,浏览器据此判定资源不可随机访问。现在HEAD
返回完整头(Content-Length、Accept-Ranges、Content-Type)且不带 body。Content-Length回填。 当 CDN 用分块传输(无Content-Length)应答
Range 请求时,代理从Content-Range(bytes a-b/total→b-a+1)推算出长度。
大体积录播会走到这条路径。
变更
- Range 相关的上游状态码(
401、403、404、405、416、429)改为原样透传,
不再一律折叠成502。尤其是416——它是越界探测的正常答复,裹成502
会让 Chrome 认为整个媒体不可 seek。
测试
test/test-absolute-media-url.mjs:媒体地址形态、跨源 Range 访问、HEAD,
以及篡改签名 / 非白名单域名仍被拒绝。test/test-head-request.mjs、test/test-416-passthrough.mjs、
test/test-content-length.mjs:上面三项传输层修复。
Docs are now bilingual: README.en.md ·
CHANGELOG.en.md
(releases from here on include both languages).
v0.2.2 — 进度条不再弹回
修掉「拖动进度条松手后弹回开场」。
dsh plugin --profile web add dsh-bili-wallpaper桌面版把 --profile web 换成 --profile desktop。
修掉「拖动进度条松手后弹回开场」。
修复
- 进度条松手即弹回:松手时立刻删掉本地拖动值
__seek,滑块value回落到
snap.position.at。但seek是异步的(currentTime→seeking→seeked→
timeupdate),那一刻at仍是旧值,于是滑块弹回原位置。
现在松手后保留__seek,等seeked事件回报真实位置再清理;
另有 1.5s 兜底,防止seeked不触发(目标位置与当前相同时会这样)导致滑块卡住。 - 切P 后滑块卡在旧位置:换源时本地拖动态没有重置,新片段的滑块会停在上一P 拖到的位置。
现在refresh检测到 media 变化就通知 UI 清空。
测试
test/test-seek-ui.mjs:进度条状态机测试,含旧实现的缺陷对照,以及兜底与切P 重置场景。test/test-seek-e2e.mjs:真 Host + 真 client,验证 seek 后位置正确、不重建 video、仍在播放。
完整变更记录见 CHANGELOG.md。
v0.2.1 — 播完正确跳到下一P
修掉「选了播下一P,实际却在重播当前P」。
dsh plugin --profile web add dsh-bili-wallpaper桌面版把 --profile web 换成 --profile desktop。
修掉「选了播下一P,实际却在重播当前P」。
修复
next分支重复 refresh:patch({page})内部已经await refresh(true),
之后又await refresh(true)了一次,导致连续两次拆掉并重建<video>,
刚启动的新P 播放被打断,观感上就是「回到当前P 开头」。现在只换源一次。- 切P 时进度串台:切P 后旧
<video>仍会零星触发pause/timeupdate,
而flushProgress读的是「当前 media」,于是旧P 的秒数被写进了新P 的进度记录。
现在进度上报会带上自己所属的media.url/page/bvid,身份不一致直接丢弃。
改进
- 媒体卡片显式显示
P2/11 · 分P标题。多P 录播相邻两P 的画面往往几乎一样,
以前没有任何地方能确认「到底切没切」,现在一眼可见。 - 播完切P 的提示改为
播完 P1 → 接着播 P2 · 标题,写明从哪一P 到哪一P。
测试
test/test-next-part.mjs:端到端断言「触发一次ended只新建 1 个<video>」,
这正是原缺陷的直接探针;同时检查新源已播放、后端 page 已切、播完的P 进度被清除。
完整变更记录见 CHANGELOG.md。
v0.2.0 — 播完动作、分P 切换、进度条与续播
视频不再只会「无限循环第一P」——现在可以播下一P、拖进度、接着上次看。
dsh plugin --profile web add dsh-bili-wallpaper桌面版把 --profile web 换成 --profile desktop。
视频不再只会「无限循环第一P」——现在可以播下一P、拖进度、接着上次看。
新增
- 播完之后的行为(设置 → B站背景 → 播放 → 播完之后),四选一:
播下一P,最后一P停住(默认)重头循环(同一P)停在最后一帧关闭背景
- 分P 切换:多P 视频有下拉选择框,外加「上一P / 下一P」按钮。
- 进度条:视频可拖动跳到任意时间点,实时显示
当前 / 总时长。 - 记住进度:再打开同一个视频(同一P)从上次位置继续。
- 按「视频 + 分P」分别保存,单P 与 P2 互不覆盖。
- 某一P播完会自动清掉该P的进度,否则下次打开会「续播」到片尾。
- 剩不到 15 秒(或 2%)时不续播,避免一打开就结束。
- Host 新增
GET/POST /api/bili-wallpaper/progress路由,进度存在
$DSH_HOME/bili-wallpaper/progress.json。 resolve返回值新增parts[](分P 列表)与totalDuration。
变更
video.loop不再无条件为true,改由endAction决定。- 配置新增
page/endAction/resume,configVersion升至 3。 - 单P 视频的
duration现在取该P时长,另给totalDuration表示全视频总时长。
修复
- 修正「分多段的视频只循环第一段」的措辞——实际情况是只播放第一段,不是循环。
测试
test/test-playback.mjs:打真实接口验证 parts 列表(11 个分P)、切P 后 cid 变化、
进度读写与清除、单P/P2 键隔离。test/test-end-decision.mjs:四种播完动作的纯函数测试,并断言源码实现与测试同构,
防止改了实现却忘了改测试。- 移除两个基于假 DOM 的端到端测试:定时器模拟导致 store 无限递归,测不出真问题。
完整变更记录见 CHANGELOG.md。
v0.1.3 — 修掉 -351 的真正根因
-351 受到神秘力量干扰 的真正原因找到了:登录态里不该带 buvid3。
dsh plugin --profile web add dsh-bili-wallpaper桌面版把 --profile web 换成 --profile desktop。
修掉 -351 风控的真正根因。
修复
- 登录态不再发送
buvid3。实测:SESSDATA + buvid3同时出现会被 B 站判为
「同一账号 + 陌生设备指纹」,playurl直接返回-351;同一次请求去掉buvid3
立刻code=0。SESSDATA单独、SESSDATA+bili_jct、SESSDATA+DedeUserID都正常。 - 匿名请求仍然带
buvid3(在那里它是加分项)。
文档
- README 的
-351排查顺序改为:先退出登录试——账号被风控时匿名反而能通。
测试
test/test-cookie-composition.mjs:一旦有人在登录请求里加回buvid3,测试立刻失败。
完整变更记录见 CHANGELOG.md。
v0.1.2 — 风控退避重试与可读提示
开始处理 -351:自动重试 + 说人话的错误提示。(根因在 0.1.3 才找到)
dsh plugin --profile web add dsh-bili-wallpaper桌面版把 --profile web 换成 --profile desktop。
开始着手处理 -351(此时判断还不完整,0.1.3 才找到根因)。
新增
- 风控码自动退避重试:
-351/-352/-412/-509按 0.6s → 1.6s → 3.2s
重试 3 次;网络抖动也会重试。 - 可读的错误提示:风控失败不再原样抛出「B站接口 -351:…」,而是说明这是风控、
以及可以怎么做。/resolve响应新增code/risk字段。 - 匿名请求带
buvid3设备指纹(来自finger/spi,进程内缓存)。
修复
- 未登录时裸奔发请求的问题(当时认为这是
-351的主因)。
测试
test/test-risk-351.mjs:用模拟响应强制触发-351,验证重试次数、退避等待、
risk标记与提示文案,以及「两次风控后第三次成功自动恢复」。test/test-anon-buvid3.mjs:验证匿名请求确实带上buvid3、且不误带SESSDATA。
完整变更记录见 CHANGELOG.md。
v0.1.1 — 首个公开版本
把 B 站直播间或视频铺到 DeepSeek Harness 聊天窗口背后。
dsh plugin --profile web add dsh-bili-wallpaper桌面版把 --profile web 换成 --profile desktop。
首次发布到 npm 与 GitHub。
新增
- 直播背景:直播间链接或房号,实时画面铺在聊天背后。
- 视频背景:视频链接、
BV号、av号、b23.tv短链。 - 扫码登录:二维码直接画在设置页,登录后直播可选「原画」。
- 清晰度选择:直播取
g_qn_desc,视频取accept_quality。 - 画面调节:铺满/完整、背景模糊、压暗遮罩、面板不透明度、静音/音量/播放速度。
- 切到后台自动暂停。
- 半透明 UI:面板、侧栏、气泡变成能透出画面的玻璃面。
- 开播自愈:直播地址过期或主播下播后重开,会自动重新解析。
- Host 端媒体代理:带
Referer绕过防盗链、改写 HLS 清单、HMAC-SHA256签名、
B 站 CDN 域名白名单。
修复(相对 0.1.0 未发布版本)
- 出厂静音导致直播没声音:0.1.0 默认
muted: true且静音时把音量滑条整个隐藏,
用户既听不到声音也找不到开关。改为默认出声 + 音量滑条常驻,并按configVersion
把旧配置里那条「不是用户选的静音」一次性翻过来。 - 直播优先选 ts 而不是 fmp4:AAC 音轨直接混在 ts 分片里,音画天然同步
(实测Audio: aac (LC), 48000 Hz, stereo)。 - 浏览器拦截自动播放时:先静音放起来并在页面提示「打开声音」,点一下即恢复,
不再悄悄静音。
完整变更记录见 CHANGELOG.md。