Files
JKVideo/docs/bilibili-live-danmaku-research.md

219 lines
6.5 KiB
Markdown
Raw Normal View History

2026-03-17 19:52:26 +08:00
# 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字节包头
```
┌──────────┬──────────┬──────────┬──────────┬──────────┐
│ 03 │ 45 │ 67 │ 811 │ 1215 │
│ 包总长度 │ 头部长度 │ 协议版本 │ 操作码 │ 序列号 │
│ 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 | 是 | 推荐 3brotli或 2zlib |
| 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` | 2zlib | 3brotli或 2 | 先用 2brotli 需 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=2zlibpako 已支持。
---
## 八、弹幕消息 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舰长
```