Skip to content

Releases

v0.3.0 — seek bar works + bilingual docs

Choose a tag to compare

@st21a1 st21a1 released this 02 Nov 12:27

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-wallpaper

On 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 from dsh-app://app, which turned the relative
    media path /api/bili-wallpaper/proxy… into dsh-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) but seekable stayed [0, 0], and assigning currentTime
    was silently ignored.
    Media URLs are now absolute http://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 back 600.24.
  • /proxy rejected non-same-origin requests. A page served from dsh-app://app
    requesting http://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 loopback Host; 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-range and Expose-Headers: content-range,content-length,accept-ranges,
    plus an OPTIONS preflight reply.

Added

  • HEAD support on /proxy. Browsers often probe a media resource with HEAD
    before playing. Only GET was routed, so HEAD fell through to 404 and the
    browser concluded the resource was not randomly accessible. HEAD now returns the
    full headers (Content-Length, Accept-Ranges, Content-Type) with no body.
  • Content-Length backfill. When the CDN answers a range request with chunked
    transfer (no Content-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 into 502. In particular 416
    is a normal answer to an out-of-range probe, and wrapping it in 502 made 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 — 进度条不再弹回

Choose a tag to compare

@st21a1 st21a1 released this 02 Nov 11:18

修掉「拖动进度条松手后弹回开场」。

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

Choose a tag to compare

@st21a1 st21a1 released this 02 Nov 10:41

修掉「选了播下一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 切换、进度条与续播

Choose a tag to compare

@st21a1 st21a1 released this 02 Nov 08:07

视频不再只会「无限循环第一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 的真正根因

Choose a tag to compare

@st21a1 st21a1 released this 02 Nov 08:07

-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 — 风控退避重试与可读提示

Choose a tag to compare

@st21a1 st21a1 released this 02 Nov 08:07

开始处理 -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 — 首个公开版本

Choose a tag to compare

@st21a1 st21a1 released this 02 Nov 08:07

把 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。