Skip to content

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

BiliLoader

一个针对性缓解海外观看 B 站卡顿的 Chrome 扩展。

它不是加速器、不改路由、不碰代理。它只做两件事,都直接对应下面实测出来的根因。

非官方项目,与上海宽娱数码科技有限公司 / bilibili 无任何关联,未获其授权或认可。 「bilibili」「哔哩哔哩」为其各自所有者的商标,此处仅用于说明本扩展的适用范围。

⚠️ 当前为 v0.1.0,尚未经过长期实际使用验证。 详见「已知限制」。


根因(2026-08-03 于美国网络实测)

不是网络问题

  • 采样 20 个视频的 playurl,20/20 都被分配到海外 CDN 节点 (upos-sz-mirrorcosov.bilivideo.com ICMP 60ms、upos-hz-mirrorakam.akamaized.net 6ms)。 没有被丢到国内 PCDN。作为对照,通用的 upos-sz-mirrorcos(无 ov)解析出的全是国内 IP,RTT 215–256ms。
  • 链路容量充足:16MB 连续下载 31 Mbps,命中缓存的分片可达 150–180 Mbps。
  • 稳态播放实测 52 秒:缓冲从 32s 涨到 71s,零 stall,掉帧 18/7351。

真正的问题:边缘缓存未命中,回源到中国

同一个 URL、同一个 host、同样 512KB:

请求 TTFB 总耗时 吞吐
已取过的区间 20 ms 59 ms 70.85 Mbps
换一个没碰过的 offset 9476 ms 15667 ms 0.27 Mbps
同一 offset 立刻再取 6 ms 6 ms 710.9 Mbps

到 Akamai 的网络 RTT 是 6ms。那 9.5 秒不是传输时间,是边缘节点回中国源站取数据的时间。

这解释了两个现象:有的视频流畅有的卡到没法看;以及一拖进度条就卡(跳到未缓存的字节区间 = 必然 miss)。

放大机制:被污染的带宽估计器

播放器把带宽估计持久化在 localStorage["bilibili_dash_throughput_lru_v1"], key 只按 host|网络类型|流类型 区分。实测抓到的中毒状态:

"XHR|upos-sz-mirrorcosov.bilivideo.com|4g|video": {
  "samples": [2727, 2379, 3078, 2853, 2225, 3695, 30000, 2993],
  "p25Kbps": 2379, "p50Kbps": 2853, "latencyMs": 4406
}

真实链路是 31 Mbps。ABR 选流用的是 p25/p50 这类保守分位数,一次 miss 注入的 0.3 Mbps 样本就能把它拉下来, 而且这个状态跨视频、跨页面刷新保留 —— 视频 A 的一次 miss 会压着视频 B 的清晰度。

叠加:缓冲目标 nc_sdk_policy.dash_buffer_time 只有 30 秒,扛不住一次 15 秒的回源填充。


扩展做什么

1. 边缘预热预取

既然「同一段字节第二次取只要 6ms」,就抢在播放器前面把它取一遍。

  • 被动观测播放器的 XHR/fetch,记录每个媒体文件已请求到的最大字节位置(anchor)。 只观测,不拦截、不改写、不代答 —— 正常播放路径完全不受影响。
  • 在 anchor 前方拉一个滑动窗口(默认 48MB),按块预取,把边缘节点喂热。
  • 数据读完即丢弃 —— 要的不是数据本身,是 CDN 被喂热这个副作用。

按 pathname 归并 track,所以 playurl 重新签名后仍然是同一条轨道。

2. 带宽估计器护栏

  • 启动时(document_start,早于播放器读取)清洗一次已有的脏值。
  • 之后 patch Storage.prototype.setItem,每次写入都就地清洗,保证它不会再变脏。
  • 低于下限的样本被丢弃,p25/p50/ewma 重算,latencyMs 压到上限。
  • 健康数据原样返回,不做无谓改写。

3. 浪费防护

预热的本质是强制触发本来可能不会发生的跨境回源 —— 而跨境传输恰恰是 B 站 CDN 成本最贵的部分。 所以有两道闸门:

  • 起播观察(默认 20s):页面上累计播放满 20 秒才开始预取。 打开扫两眼就关掉的视频完全不产生任何预取。
  • 单文件上限(默认 200MB):封顶单个视频的预取总量,防止在一个长视频上无限拉取。

再加上默认的 auto 模式(只对已经出现慢请求的视频预取)、标签页隐藏时暂停、 播放器缓冲告急时让出带宽 —— 平时的额外开销应当接近于零。

4. 可观测

popup 显示实时的媒体请求数、慢请求数、已预热 MB、冷文件数、各 CDN 的 TTFB p50/p95, 以及估计器当前的 p25/p50 —— 卡的时候能直接看出是哪一类问题。


预取参数是实测定出来的

冷区间预取的实测(693MB 文件,upos-sz-mirrorcosov):

方案 耗时 有效吞吐 成功率
单发 4MB 60s 后 network error – 0/1
单发 1MB 13.0s 0.64 Mbps 1/1
8×512KB 并发 24.2s 1.39 Mbps 8/8
4×1MB 并发 12.2s 2.76 Mbps 4/4
8×1MB 并发 34.5s 1.70 Mbps 7/8
12×1MB 并发 50.5s 1.49 Mbps 9/12
复读已预热区间 47ms 89.81 Mbps 1/1

结论:回源本身是瓶颈,块太大会超时,并发太高会互相挤且失败率上升。 默认取 chunkMB=1, concurrency=4,并对失败重试 2 次。

最后一行是这个扩展成立的依据:预热后同区间快了 140 倍。


已知限制(重要)

冷填充的聚合速率只有约 2.8 Mbps。

这意味着对一个完全冷的高码率视频,预取追不上实时播放:

流 码率 预取能否追上
1080P60 AVC 9876 kbps ❌ 远追不上
1080P AVC 4461 kbps ❌ 追不上
1080P60 HEVC 3723 kbps ⚠️ 勉强
1080P HEVC 1920 kbps ✅
720P AVC 1889 kbps ✅

所以这个扩展在下列场景收益最大:拖动进度条后的局部冷区间、暂停时提前铺路、中等码率的流。 对于全程冷的 1080P60 AVC,它能减少停顿次数但无法消除。

配套建议:在播放器设置里优先使用 HEVC / AV1。 同画质下 1080P60 的 AVC 是 9876 kbps,HEVC 3723、AV1 3493 —— 字节数直接降 2.6 倍, 既少踩 miss,也让预取真的追得上。服务端 dash_config.hevc_enable 和 av1_enable 都是开的。

其他限制:

  • world: "MAIN" 的内容脚本需要 Chrome 111+。
  • 预取的流量是额外的(预取一遍 + 播放器再取一遍)。单个视频最多 200MB(可调), 且需先累计播放 20 秒才启动。默认的 auto 模式只对已探测到慢请求的视频预取, 平时零额外流量;always 模式则对每个视频都预热。
  • 这些额外请求会落在 B 站的 CDN 与跨境回源上。个人使用无所谓, 但这是公开分发前应当知情的一点。
  • 只处理网页版播放器。

安装

  1. 打开 chrome://extensions/
  2. 右上角开启「开发者模式」
  3. 点「加载已解压的扩展程序」,选择本目录
node test/inject.test.js

23 项单元测试,其中估计器清洗部分直接用实测抓到的中毒快照做输入。


配置项

项 默认 说明
预取模式 auto auto 只在探测到慢请求后预取;always 总是预取;off 只统计
前方预热 48 MB anchor 之后预热多少
分块大小 1 MB 实测最优,别调大
并发 4 实测最优,调高反而更慢且失败率上升
慢请求阈值 800 ms TTFB 超过此值判定该文件为冷
起播观察 20 s 累计播放满此时长才开始预取
单文件上限 200 MB 单个媒体文件的预取总量封顶
写入时清洗 开 估计器护栏
下限 8000 kbps 估计器的 p25/p50/ewma 下限

popup 里还有「立即清空估计器并刷新页面」—— 卡住之后一直卡时按这个。

目录

manifest.json
icons/              16/32/48/128 图标(128 亦用作商店图标)
src/inject.js       MAIN world:观测、预取、估计器护栏
src/bridge.js       ISOLATED world:chrome.storage 与页面之间转发
src/popup.html      popup UI
src/popup.js
test/inject.test.js 单元测试(在 vm 里加载真实的 inject.js)
scripts/build.mjs   打包成商店可上传的 zip
PRIVACY.md          隐私政策

构建

npm test
npm run build

npm run build 产出 dist/bililoader-<version>.zip,并校验三件事:

  • manifest.json 位于 zip 根目录
  • 条目名使用正斜杠(Windows PowerShell 5.1 的 Compress-Archive 会生成反斜杠, 违反 ZIP 规范,所以脚本优先使用 pwsh)
  • manifest 里引用的每个文件(图标、popup、content scripts)都确实被打进了包

验证情况

已验证:

  • 26 项单元测试,在 vm 里加载真实的 inject.js 源码执行; 估计器清洗部分直接用实测抓到的中毒快照做输入
  • 估计器护栏在真实 B 站页面上验证:普通 localStorage key 与 sessionStorage 不受影响、 中毒值写入即被清洗、非法 JSON 不抛异常
  • 预热有效性与全部预取参数均来自实测(见上文两张表)

尚未验证: 扩展作为完整扩展在真实 Chrome 中加载运行的端到端链路 (popup、chrome.storage 桥接)。装上后打开 popup 看「媒体请求」计数是否在涨即可确认注入成功。

许可

MIT

About

缓解海外观看 B 站卡顿的 Chrome 扩展:预热 CDN 边缘缓存 + 修正被污染的播放器带宽估计器。根因与参数均由实测确定。

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages