Chrome DevTools can now directly view binary data packets!
Connection#
Through Chrome DevTools, it can be seen that the barrage of the web version of Bilibili's live broadcast is transmitted through wss://tx-sh3-live-comet-04.chat.bilibili.com/sub
, and this address is different each time. The wss address needs to be obtained through https://api.live.bilibili.com/room/v1/Danmu/getConf?room_id=room_number&platform=pc&player=web
to get the wss address (probably due to load balancing, any wss address can be used to obtain the barrage normally during actual testing).
The first 16 bytes of the packet header are used to identify the length and type of the data packet. The format of the data packet is detailed in the table below. The byte order is big-endian. Reference: https://blog.csdn.net/xfgryujk/article/details/80306776
Offset | Length | Type | Meaning |
---|---|---|---|
0 | 4 | int | Data packet length |
4 | 2 | int | Data packet header length, fixed at 16 |
6 | 2 | int | Data packet protocol version (see below) |
8 | 4 | int | Data packet type (see below) |
12 | 4 | int | Fixed at 1 |
16 | - | byte[] | Data body |
Data packet protocol version | Meaning |
---|---|
0 | Data packet payload is uncompressed JSON format data |
1 | Client heartbeat packet, or server heartbeat response (with popularity value) |
2 | Data packet payload is JSON format data compressed with zlib |
After the client establishes a connection, it needs to send a data packet to join the room (authentication) within 5 seconds, otherwise the server will forcibly disconnect the connection. The content of the key field in the payload can be obtained from the previous https://api.live.bilibili.com/room/v1/Danmu/getConf?room_id=room_number&platform=pc&player=web
. If the format of the authentication packet sent is incorrect, the server will immediately force the disconnection. The detailed explanation of the JSON fields is shown in the table below.
Field | Type | Required | Meaning |
---|---|---|---|
uid | number | × | User UID |
roomid | number | √ | Room number |
protover | number | × | Protocol version, currently 2 |
platform | string | × | Platform, can be web |
clientver | string | × | Client version, can be "1.8.5" |
type | number | × | Unknown, can be 2 |
key | string | × | User identifier, obtained through the mentioned interface |
Heartbeat#
It is recommended to send a heartbeat packet every 30 seconds. The first 16 bytes of the header follow the rules mentioned above, and the payload content can be arbitrary. (Bilibili generates a heartbeat packet by passing an empty object, which explains the content of the heartbeat packet that I couldn't understand before)
Notification (Barrage, Announcement, Gift, etc.)#
When there are new barrages, gifts, or other announcements, the server will send data packets similar to the one shown below. First, you need to use zlib.inflate
to decompress the data body part (excluding the first 16 bytes of the header). The decompressed data has the same first 16 bytes of the header, and the JSON format data is obtained by removing the header.
Here we take a barrage data packet as an example.
It can be seen that the decompressed data still contains the header (16 bytes), and the JSON format data is obtained by removing the header. The cmd field in the data describes the type of the data packet in more detail. One obvious example is that ['info'][1]
represents the barrage content, ['info'][2][1]
represents the sender, and ['info'][9]['ts']
represents the sending timestamp. The known formats of cmd are listed in the table below.
cmd field | Meaning |
---|---|
DANMU_MSG | Received barrage |
SEND_GIFT | Someone sent a gift |
WELCOME | Welcome to the room |
WELCOME_GUARD | Welcome the room guard |
SYS_MSG | System message |
PREPARING | Host is preparing |
LIVE | Live broadcast starts |
Below, I show the JSON format of several common cmd data packets. There are many other cmd types for activity notifications, which you can capture by yourself (for example, ACTIVITY_BANNER_UPDATE_V2
below is the activity notification cmd type that appeared on the day I wrote this article).
{
"cmd": "SEND_GIFT",
"data": {
"giftName": "Spicy Strips",
"num": 5,
"uname": "Didomaso",
"face": "http://i2.hdslb.com/bfs/face/1a3b795aafc5887f3f33909c7e66876d23911979.jpg",
"guard_level": 0,
"rcost": 42593386,
"uid": 415822879,
"top_list": [],
"timestamp": 1570368091,
"giftId": 1,
"giftType": 0,
"action": "Feed",
"super": 0,
"super_gift_num": 0,
"price": 100,
"rnd": "EF27025C-4C20-440F-B36F-64CCFABBF68E",
"newMedal": 0,
"newTitle": 0,
"medal": [],
"title": "",
"beatId": "",
"biz_source": "live",
"metadata": "",
"remain": 0,
"gold": 0,
"silver": 0,
"eventScore": 0,
"eventNum": 0,
"smalltv_msg": [],
"specialGift": null,
"notice_msg": [],
"capsule": null,
"addFollow": 0,
"effect_block": 1,
"coin_type": "silver",
"total_coin": 500,
"effect": 0,
"broadcast_id": 0,
"draw": 0,
"crit_prob": 0,
"tag_image": "",
"user_count": 0,
"send_master": null
}
}
{
"cmd": "ACTIVITY_BANNER_UPDATE_V2",
"data": {
"id": 378,
"title": "6th Place",
"cover": "",
"background": "https://i0.hdslb.com/bfs/activity-plat/static/20190904/b5e210ef68e55c042f407870de28894b/14vZu7h9pK.png",
"jump_url": "https://live.bilibili.com/p/html/live-app-rankcurrent/index.html?is_live_half_webview=1&hybrid_half_ui=1,5,85p,70p,FFE293,0,30,100,10;2,2,320,100p,FFE293,0,30,100,0;4,2,320,100p,FFE293,0,30,100,0;6,5,65p,60p,FFE293,0,30,100,10;5,5,55p,60p,FFE293,0,30,100,10;3,5,85p,70p,FFE293,0,30,100,10;7,5,65p,60p,FFE293,0,30,100,10;&anchor_uid=22550271&is_new_rank_container=1&area_v2_id=163&area_v2_parent_id=3&rank_type=master_realtime_area_hour&area_hour=1",
"title_color": "#8B5817",
"closeable": 1,
"banner_type": 4,
"weight": 18,
"add_banner": 0
}
}
{
"cmd": "ROOM_REAL_TIME_MESSAGE_UPDATE",
"data": {
"roomid": 101526,
"fans": 294665,
"red_notice": -1
}
}
Afterword: Chrome's ability to directly display WebSocket binary packets really saves a lot of time. Chrome is awesome!