mirror of
https://gh-proxy.org/https://github.com/tiajinsha/JKVideo
synced 2026-07-08 15:33:51 +08:00
1
This commit is contained in:
218
docs/bilibili-live-danmaku-research.md
Normal file
218
docs/bilibili-live-danmaku-research.md
Normal file
@@ -0,0 +1,218 @@
|
||||
# B站直播弹幕连接 — 学习笔记
|
||||
|
||||
> 参考项目:https://github.com/Minteea/bilibili-live-danmaku (v0.7.15)
|
||||
|
||||
---
|
||||
|
||||
## 一、完整连接流程
|
||||
|
||||
```
|
||||
1. 初始化 Cookie
|
||||
GET https://www.bilibili.com/ → 种植初始 cookie
|
||||
POST .../bilibili.api.ticket.v1.Ticket/GenWebTicket → 获取 bili_ticket + WBI 签名密钥
|
||||
GET .../x/frontend/finger/spi → 获取 buvid4
|
||||
POST .../x/internal/gaia-gateway/ExClimbWuzhi → 激活 buvid(模拟指纹)
|
||||
|
||||
2. 获取弹幕服务器信息(需要 WBI 签名)
|
||||
GET https://api.live.bilibili.com/xlive/web-room/v1/index/getDanmuInfo?id={roomId}
|
||||
响应:{ token, host_list: [{ host, wss_port, ws_port }] }
|
||||
|
||||
3. 建立 WebSocket 连接
|
||||
wss://{host_list[0].host}/sub
|
||||
fallback: wss://broadcastlv.chat.bilibili.com/sub
|
||||
|
||||
4. onopen → 发送 AUTH 包(op=7)
|
||||
5. 收到 op=8 (CONNECT_SUCCESS) → 立即发送一次心跳(op=2)
|
||||
6. 每次收到 op=3 (HEARTBEAT_REPLY) → 30秒后发下一次心跳
|
||||
7. 收到 op=5 (MESSAGE) → 解析弹幕数据
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 二、二进制包格式(16字节包头)
|
||||
|
||||
```
|
||||
┌──────────┬──────────┬──────────┬──────────┬──────────┐
|
||||
│ 0–3 │ 4–5 │ 6–7 │ 8–11 │ 12–15 │
|
||||
│ 包总长度 │ 头部长度 │ 协议版本 │ 操作码 │ 序列号 │
|
||||
│ uint32 │ uint16 │ uint16 │ uint32 │ uint32 │
|
||||
│ (BE) │ 固定=16 │ ver │ op │ 固定=1 │
|
||||
└──────────┴──────────┴──────────┴──────────┴──────────┘
|
||||
│ 16+ bytes: body (JSON 字符串 或 压缩数据) │
|
||||
└────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 操作码 (op)
|
||||
| op | 名称 | 方向 | 说明 |
|
||||
|----|------|------|------|
|
||||
| 2 | HEARTBEAT | 客→服 | 心跳包,body 为空 |
|
||||
| 3 | HEARTBEAT_REPLY | 服→客 | 心跳回应,body 是 uint32 在线人数 |
|
||||
| 5 | MESSAGE | 服→客 | 弹幕/通知消息 |
|
||||
| 7 | USER_AUTHENTICATION | 客→服 | 进房认证包 |
|
||||
| 8 | CONNECT_SUCCESS | 服→客 | 认证成功 |
|
||||
|
||||
### 协议版本 (ver)
|
||||
| ver | 说明 | 解压方式 |
|
||||
|-----|------|---------|
|
||||
| 0 | 原始 JSON | 直接 `JSON.parse` |
|
||||
| 1 | uint32 | `DataView.getUint32(0)` |
|
||||
| 2 | zlib 压缩包束 | `pako.inflate()` → 递归解包 |
|
||||
| 3 | brotli 压缩包束 | `BrotliDecode()` → 递归解包 |
|
||||
|
||||
> ver=2/3 解压后包含**多个子包**拼接,需按每个包头的 totalLen 逐个切割。
|
||||
|
||||
---
|
||||
|
||||
## 三、认证包 (op=7) 的 body 字段
|
||||
|
||||
```json
|
||||
{
|
||||
"uid": 0,
|
||||
"roomid": 12345678,
|
||||
"protover": 3,
|
||||
"platform": "web",
|
||||
"type": 2,
|
||||
"key": "<token from getDanmuInfo>",
|
||||
"buvid": "<buvid3 cookie>"
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 必填 | 说明 |
|
||||
|------|------|------|
|
||||
| uid | 是 | 未登录填 0 |
|
||||
| roomid | 是 | 真实房间号(非短号) |
|
||||
| protover | 是 | 推荐 3(brotli),或 2(zlib) |
|
||||
| platform | 是 | 固定 `"web"` |
|
||||
| type | 是 | 固定 `2` |
|
||||
| key | 是 | getDanmuInfo 返回的 token |
|
||||
| buvid | 推荐 | buvid3 cookie 值,不填可能被限流 |
|
||||
|
||||
---
|
||||
|
||||
## 四、心跳逻辑
|
||||
|
||||
```
|
||||
onopen
|
||||
└─ 发送 AUTH (op=7)
|
||||
|
||||
收到 op=8 (CONNECT_SUCCESS)
|
||||
└─ 立即发送第一次心跳 (op=2, body='')
|
||||
|
||||
收到 op=3 (HEARTBEAT_REPLY)
|
||||
├─ 清除上次计时器
|
||||
└─ 30秒后发送下次心跳 (op=2)
|
||||
```
|
||||
|
||||
**重点**:心跳是在收到 op=3 后才重排计时,而不是固定 setInterval。
|
||||
这样即使因网络延迟导致心跳回复慢,也不会提前发第二次心跳。
|
||||
|
||||
---
|
||||
|
||||
## 五、getDanmuInfo API
|
||||
|
||||
```
|
||||
GET https://api.live.bilibili.com/xlive/web-room/v1/index/getDanmuInfo
|
||||
?id={roomId}
|
||||
&w_rid=xxx ← WBI 签名参数
|
||||
&wts=xxx
|
||||
```
|
||||
|
||||
响应:
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": {
|
||||
"token": "L_DSj3cX...",
|
||||
"host_list": [
|
||||
{ "host": "tx-gz-live-comet.chat.bilibili.com", "wss_port": 443, "ws_port": 2244 },
|
||||
{ "host": "broadcastlv.chat.bilibili.com", "wss_port": 443, "ws_port": 2244 }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
也可使用旧版 `getConf`(无需 WBI 签名,但已被标记为 deprecated):
|
||||
```
|
||||
GET https://api.live.bilibili.com/room/v1/Danmu/getConf?room_id={roomId}&platform=h5
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 六、与我们现有实现的差异
|
||||
|
||||
| 对比项 | 我们现在 | 参考项目 | 建议 |
|
||||
|--------|---------|---------|------|
|
||||
| API | `getConf`(老接口) | `getDanmuInfo`(新接口) | 可选,两个都能用 |
|
||||
| WBI 签名 | 有(之前因 -352 移除) | 有 | 如果遇到 -352 可用 getConf |
|
||||
| `protover` | 2(zlib) | 3(brotli)或 2 | 先用 2,brotli 需 polyfill |
|
||||
| auth 字段 | 缺少 `platform`、`buvid` | 有 `platform: "web"`、`buvid` | **需补充** |
|
||||
| 心跳方式 | `setInterval` 固定 30s | 收到 op=3 后计时 30s | 建议改为 op=3 驱动 |
|
||||
| buvid3 | 未传入 auth | 作为 `buvid` 字段传入 auth | **需补充** |
|
||||
|
||||
---
|
||||
|
||||
## 七、我们代码需要的改进
|
||||
|
||||
### 1. 补充 auth 字段
|
||||
```ts
|
||||
const authBody = JSON.stringify({
|
||||
uid: 0,
|
||||
roomid: roomId,
|
||||
protover: 2,
|
||||
platform: 'web', // ← 缺少
|
||||
type: 2,
|
||||
key: token,
|
||||
buvid: buvid3, // ← 缺少(从 AsyncStorage 读取)
|
||||
});
|
||||
```
|
||||
|
||||
### 2. 心跳改为 op=3 驱动
|
||||
```ts
|
||||
// 收到 op=8 → 立即发第一次心跳
|
||||
if (pkt.op === 8) {
|
||||
sendHeartbeat();
|
||||
}
|
||||
// 收到 op=3 → 30秒后再发
|
||||
if (pkt.op === 3) {
|
||||
clearTimeout(heartbeatTimer);
|
||||
heartbeatTimer = setTimeout(sendHeartbeat, 30000);
|
||||
}
|
||||
```
|
||||
|
||||
### 3. ver=3 (brotli) 解压支持
|
||||
如果 protover 设为 3,需要引入 brotli 解码库(React Native 需要 polyfill)。
|
||||
建议暂时保持 protover=2(zlib),pako 已支持。
|
||||
|
||||
---
|
||||
|
||||
## 八、弹幕消息 cmd 类型
|
||||
|
||||
op=5 的 body 是 JSON,`cmd` 字段标识消息类型:
|
||||
|
||||
| cmd | 说明 |
|
||||
|-----|------|
|
||||
| `DANMU_MSG` | 弹幕消息,`info[1]` 是文字 |
|
||||
| `SEND_GIFT` | 送礼物 |
|
||||
| `GUARD_BUY` | 上舰 |
|
||||
| `SUPER_CHAT_MESSAGE` | SC 醒目留言 |
|
||||
| `INTERACT_WORD` | 进入直播间/关注 |
|
||||
| `WATCHED_CHANGE` | 观看人数变化 |
|
||||
| `ONLINE_RANK_COUNT` | 在线排名人数 |
|
||||
| `ROOM_CHANGE` | 直播间信息变更 |
|
||||
| `LIVE` | 开播 |
|
||||
| `PREPARING` | 下播 |
|
||||
|
||||
---
|
||||
|
||||
## 九、DANMU_MSG info 数组结构
|
||||
|
||||
```
|
||||
info[0][2] 弹幕颜色(十进制 RGB)
|
||||
info[1] 弹幕文字内容
|
||||
info[2][0] 用户 uid
|
||||
info[2][1] 用户名
|
||||
info[2][2] 是否房管(1=是)
|
||||
info[3][0] 粉丝勋章等级
|
||||
info[3][1] 粉丝勋章名
|
||||
info[7] 舰长等级(0无,1总督,2提督,3舰长)
|
||||
```
|
||||
Reference in New Issue
Block a user