mirror of
https://github.com/czp3009/bilibili-api.git
synced 2025-02-19 20:50:28 +08:00
实现视频弹幕的获取和解析
This commit is contained in:
parent
174fd92be8
commit
7c6297f6d2
50
README.md
50
README.md
@ -138,7 +138,7 @@ val view = bilibiliClient.appAPI.view(aid = 41517911).await()
|
||||
|
||||
该接口返回对一个视频页面的描述信息(甚至包含广告和推荐), 客户端根据这些信息生成视频页面.
|
||||
|
||||
其中 `data.cid` 为默认 `p` 的 `cid`. `data.pages[n].cid` 为每个 `p` 的 `cid`. 如果没有分 `p` 则使用外层那个 `cid` 来请求视频地址.
|
||||
其中 `data.cid` 为默认 `p` 的 `cid`. `data.pages[n].cid` 为每个 `p` 的 `cid`. 如果只有一个 `p` 那么说明视频没有分 `p`.
|
||||
|
||||
请求视频地址将访问如下结构的内容
|
||||
|
||||
@ -254,6 +254,10 @@ val reply = bilibiliClient.mainAPI.reply(oid = 44154463).await()
|
||||
|
||||
评论是不分 `p` 的, 所有评论都是在一起的.
|
||||
|
||||
可以额外使用一个 `next` 参数来指定返回的起始楼层(即翻页).
|
||||
|
||||
楼层是越翻越小的, 所以 `next` 也要越来越小.
|
||||
|
||||
看到了傻吊网友们的评论是不够的, 我们还想看到杠精与其隔着屏幕对喷的场景, 因此我们要获取评论的子评论, 即评论的评论
|
||||
|
||||
```kotlin
|
||||
@ -266,12 +270,52 @@ val childReply = bilibiliClient.mainAPI.childReply(oid = 16622855, root = 140560
|
||||
|
||||
(子评论原理上可以再有子评论, 但是 B站 在逻辑上只有一层子评论)
|
||||
|
||||
番剧下面的评论用一样的方式获取, 下同.
|
||||
用额外的 `minId` 参数来指定返回的起始子楼层.
|
||||
|
||||
注意, 子楼层是越翻越大的.
|
||||
|
||||
番剧下面的评论用一样的方式获取.
|
||||
|
||||
## 获得一个视频的弹幕
|
||||
看评论自然不够刺激, 我们想看到弹幕!
|
||||
|
||||
//TODO
|
||||
获取弹幕非常简单
|
||||
|
||||
```kotlin
|
||||
val danmakuFile = bilibiliClient.danmakuAPI.list(aid = 810872, oid = 1176840).await()
|
||||
```
|
||||
|
||||
弹幕是一个文件, 可能非常大, 里面是二进制内容.
|
||||
|
||||
为了解析弹幕, 我们要用到另一个类
|
||||
|
||||
```kotlin
|
||||
val (flagMap, danmakuList) = DanmakuParser.parser(danmakuFile.byteStream())
|
||||
```
|
||||
|
||||
`flagMap` 类型为 `Map<Long, Int>` 键和值分别表示 弹幕ID 与 弹幕等级.
|
||||
|
||||
弹幕等级在区间 \[1, 10\] 内, 低于客户端设置的 "弹幕云屏蔽等级" 的弹幕将不会显示出来.
|
||||
|
||||
`danmakuList` 类型为 `List<Danmaku>`, 内含所有解析得到的弹幕.
|
||||
|
||||
使用以下代码来输出全部弹幕的内容
|
||||
|
||||
```kotlin
|
||||
danmakuList.forEach {
|
||||
println(it.content)
|
||||
}
|
||||
```
|
||||
|
||||
客户端的弹幕屏蔽设置是对弹幕中的 `user` 属性做的. 而实际上 `danmaku.user` 是一个字符串.
|
||||
|
||||
这个字符串是 用户ID 的 `CRC32` 的校验和.
|
||||
|
||||
众所周知, 一切 hash 算法都有冲突的问题. 这也就意味着, 屏蔽一个用户的同时可能屏蔽掉了多个与该用户 hash 值相同的用户.
|
||||
|
||||
在另一方面, 通过这个 `CRC32` 校验和进行用户 ID 反查, 将查询到多个可能的用户, 因此无法确定一条弹幕到底是哪个用户发送的.
|
||||
|
||||
番剧的弹幕同理.
|
||||
|
||||
# License
|
||||
GPL V3
|
||||
|
@ -1,7 +1,7 @@
|
||||
buildscript {
|
||||
ext {
|
||||
kotlin_version = '1.3.21'
|
||||
kotlin_coroutines_version= '1.1.1'
|
||||
kotlin_coroutines_version = '1.1.1'
|
||||
jvm_target = JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
@ -65,6 +65,12 @@ dependencies {
|
||||
compile group: 'com.squareup.okhttp3', name: 'logging-interceptor', version: '3.13.1'
|
||||
}
|
||||
|
||||
//utils
|
||||
dependencies {
|
||||
// https://mvnrepository.com/artifact/commons-io/commons-io
|
||||
compile group: 'commons-io', name: 'commons-io', version: '2.6'
|
||||
}
|
||||
|
||||
//unit test
|
||||
dependencies {
|
||||
// https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter
|
||||
|
@ -1,9 +0,0 @@
|
||||
{
|
||||
"cmd": "ACTIVITY_EVENT",
|
||||
"data": {
|
||||
"keyword": "newspring_2018",
|
||||
"type": "cracker",
|
||||
"limit": 300000,
|
||||
"progress": 158912
|
||||
}
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
{
|
||||
"cmd": "CHANGE_ROOM_INFO",
|
||||
"background": "http://static.hdslb.com/live-static/images/bg/4.jpg"
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
{
|
||||
"cmd": "COMBO_END",
|
||||
"data": {
|
||||
"uname": "打死安迷修-雷狮",
|
||||
"r_uname": "Jinko_神子",
|
||||
"combo_num": 1,
|
||||
"price": 200,
|
||||
"gift_name": "flag",
|
||||
"gift_id": 20002,
|
||||
"start_time": 1527929335,
|
||||
"end_time": 1527929335
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
{
|
||||
"cmd": "COMBO_SEND",
|
||||
"data": {
|
||||
"uid": 33012231,
|
||||
"uname": "我就是讨厌你这样",
|
||||
"combo_num": 3,
|
||||
"gift_name": "凉了",
|
||||
"gift_id": 20010,
|
||||
"action": "赠送"
|
||||
}
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
{
|
||||
"cmd": "CUT_OFF",
|
||||
"msg": "禁播游戏",
|
||||
"roomid": 8446134
|
||||
}
|
@ -1,72 +0,0 @@
|
||||
{
|
||||
"info": [
|
||||
//弹幕基本属性
|
||||
[
|
||||
0,
|
||||
//pool
|
||||
1,
|
||||
//mode
|
||||
25,
|
||||
//fontSize
|
||||
16777215,
|
||||
//color
|
||||
1510498713,
|
||||
//弹幕发送时间
|
||||
"1510498712",
|
||||
//用户进房时间(Android 客户端发送的弹幕, 这个值会是随机数)
|
||||
0,
|
||||
"8a0f75dc",
|
||||
0
|
||||
],
|
||||
"网易云音乐库在当前直播间已停留0天0时39分41秒",
|
||||
//发送者有关信息
|
||||
[
|
||||
39042255,
|
||||
//发送者 ID
|
||||
"夏沫丶琉璃浅梦",
|
||||
//发送者用户名
|
||||
0,
|
||||
//是否是管理员
|
||||
1,
|
||||
//是否是 VIP
|
||||
0,
|
||||
//是否是 svip
|
||||
10000,
|
||||
1,
|
||||
""
|
||||
],
|
||||
//发送者的粉丝勋章有关信息(没有粉丝勋章的发送者, 这个 JsonArray 将是空的)
|
||||
[
|
||||
13,
|
||||
//勋章等级
|
||||
"夏沫",
|
||||
//勋章名称
|
||||
"乄夏沫丶",
|
||||
//勋章对应的主播的名字
|
||||
"1547306",
|
||||
//勋章对应的主播的直播间
|
||||
16746162,
|
||||
""
|
||||
],
|
||||
//用户经验有关信息
|
||||
[
|
||||
41,
|
||||
//发送者的观众等级
|
||||
0,
|
||||
16746162,
|
||||
6603
|
||||
//排名
|
||||
],
|
||||
//用户头衔有关信息(里面有两个元素, 但是总是一样的, 不知道为什么)
|
||||
[
|
||||
"title-131-1",
|
||||
"title-131-1"
|
||||
],
|
||||
0,
|
||||
0,
|
||||
{
|
||||
"uname_color": ""
|
||||
}
|
||||
],
|
||||
"cmd": "DANMU_MSG"
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
{
|
||||
"info": [
|
||||
[
|
||||
0,
|
||||
1,
|
||||
25,
|
||||
16777215,
|
||||
1510498713,
|
||||
"1510498712",
|
||||
0,
|
||||
"8a0f75dc",
|
||||
0
|
||||
],
|
||||
"网易云音乐库在当前直播间已停留0天0时39分41秒",
|
||||
[
|
||||
39042255,
|
||||
"夏沫丶琉璃浅梦",
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
10000,
|
||||
1,
|
||||
""
|
||||
],
|
||||
[
|
||||
13,
|
||||
"夏沫",
|
||||
"乄夏沫丶",
|
||||
"1547306",
|
||||
16746162,
|
||||
""
|
||||
],
|
||||
[
|
||||
41,
|
||||
0,
|
||||
16746162,
|
||||
6603
|
||||
],
|
||||
[],
|
||||
0,
|
||||
0,
|
||||
{
|
||||
"uname_color": ""
|
||||
}
|
||||
],
|
||||
"cmd": "DANMU_MSG"
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
{
|
||||
"cmd": "ENTRY_EFFECT",
|
||||
"data": {
|
||||
"id": 3,
|
||||
"uid": 9359447,
|
||||
"target_id": 275592903,
|
||||
"show_avatar": 1,
|
||||
"copy_writing": "欢迎 \u003c%藏拙当成玉%\u003e 进入房间",
|
||||
"highlight_color": "#FFF100",
|
||||
"basemap_url": "http://i0.hdslb.com/bfs/live/d208b9654b93a70b4177e1aa7e2f0343f8a5ff1a.png",
|
||||
"effective_time": 1,
|
||||
"priority": 50,
|
||||
"privilege_type": 0,
|
||||
"face": "http://i1.hdslb.com/bfs/face/12cb1ea6eea79667e3fb722bbd8995bb96f4cd6f.jpg"
|
||||
}
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
{
|
||||
"roomid": 234024,
|
||||
"cmd": "EVENT_CMD",
|
||||
"data": {
|
||||
"event_type": "flower_rain-16915",
|
||||
"event_img": "http://s1.hdslb.com/bfs/static/blive/live-assets/mobile/activity/lover_2018/raffle.png"
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
{
|
||||
"cmd": "GUARD_BUY",
|
||||
"data": {
|
||||
"uid": 4561799,
|
||||
"username": "微笑The迪妮莎",
|
||||
"guard_level": 1,
|
||||
"num": 1
|
||||
},
|
||||
"roomid": "5279"
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
{
|
||||
"cmd": "GUARD_LOTTERY_START",
|
||||
"data": {
|
||||
"id": 396410,
|
||||
"roomid": 56998,
|
||||
"message": "ちゆき蝙蝠公主 在【56998】购买了舰长,请前往抽奖",
|
||||
"type": "guard",
|
||||
"privilege_type": 3,
|
||||
"link": "https://live.bilibili.com/56998",
|
||||
"lottery": {
|
||||
"id": 396410,
|
||||
"sender": {
|
||||
"uid": 11206312,
|
||||
"uname": "ちゆき蝙蝠公主",
|
||||
"face": "http://i0.hdslb.com/bfs/face/06d0d58131100acf13d75d3c092b1a58d41b0129.jpg"
|
||||
},
|
||||
"keyword": "guard",
|
||||
"time": 1200,
|
||||
"status": 1,
|
||||
"mobile_display_mode": 2,
|
||||
"mobile_static_asset": "",
|
||||
"mobile_animation_asset": ""
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
{
|
||||
"cmd": "GUARD_MSG",
|
||||
"msg": "乘客 :?想不想joice:? 成功购买1313366房间总督船票1张,欢迎登船!"
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
{
|
||||
"cmd": "LIVE",
|
||||
"roomid": "1110317"
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
{
|
||||
"cmd": "NOTICE_MSG",
|
||||
"full": {
|
||||
"head_icon": "",
|
||||
"is_anim": 1,
|
||||
"tail_icon": "",
|
||||
"background": "#33ffffff",
|
||||
"color": "#33ffffff",
|
||||
"highlight": "#33ffffff",
|
||||
"border": "#33ffffff",
|
||||
"time": 10
|
||||
},
|
||||
"half": {
|
||||
"head_icon": "",
|
||||
"is_anim": 0,
|
||||
"tail_icon": "",
|
||||
"background": "#33ffffff",
|
||||
"color": "#33ffffff",
|
||||
"highlight": "#33ffffff",
|
||||
"border": "#33ffffff",
|
||||
"time": 8
|
||||
},
|
||||
"roomid": "11415406",
|
||||
"real_roomid": "0",
|
||||
"msg_common": "恭喜\u003c%汤圆老师%\u003e获得大奖\u003c%23333x银瓜子%\u003e, 感谢\u003c%林发发爱林小兔%\u003e的赠送",
|
||||
"msg_self": "恭喜\u003c%汤圆老师%\u003e获得大奖\u003c%23333x银瓜子%\u003e, 感谢\u003c%林发发爱林小兔%\u003e的赠送",
|
||||
"link_url": "http://live.bilibili.com/0",
|
||||
"msg_type": 4
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
{
|
||||
"cmd": "PK_AGAIN",
|
||||
"pk_id": 60672,
|
||||
"pk_status": 400,
|
||||
"data": {
|
||||
"new_pk_id": 60678,
|
||||
"init_id": 10817769,
|
||||
"match_id": 1489926,
|
||||
"escape_all_time": 10,
|
||||
"escape_time": 10,
|
||||
"is_portrait": false,
|
||||
"uname": "穆阿是给你的mua",
|
||||
"face": "http://i0.hdslb.com/bfs/face/07fa1057b60afe74cdd477f123c6ccf460ee8f2c.jpg",
|
||||
"uid": 38105366
|
||||
},
|
||||
"roomid": 1489926
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
{
|
||||
"pk_status": 400,
|
||||
"pk_id": 60672,
|
||||
"cmd": "PK_CLICK_AGAIN",
|
||||
"roomid": 1489926
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
{
|
||||
"cmd": "PK_END",
|
||||
"pk_id": 8797,
|
||||
"pk_status": 400,
|
||||
"data": {
|
||||
"init_id": 8049573,
|
||||
"match_id": 1409458,
|
||||
"punish_topic": "惩罚:模仿面筋哥"
|
||||
}
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
{
|
||||
"cmd": "PK_INVITE_FAIL",
|
||||
"pk_invite_status": 1100
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
{
|
||||
"cmd": "PK_INVITE_INIT",
|
||||
"pk_invite_status": 200,
|
||||
"invite_id": 408,
|
||||
"face": "http://i0.hdslb.com/bfs/face/e1ad4df39e95180e990fdd565b216662bdb2503c.jpg",
|
||||
"uname": "Tocci椭奇",
|
||||
"area_name": "视频聊天",
|
||||
"user_level": 24,
|
||||
"master_level": 31,
|
||||
"roomid": 883802
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
{
|
||||
"cmd": "PK_INVITE_SWITCH_CLOSE",
|
||||
"roomid": 1938890
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
{
|
||||
"cmd": "PK_INVITE_SWITCH_OPEN",
|
||||
"roomid": 9615419
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
{
|
||||
"cmd": "PK_MATCH",
|
||||
"pk_status": 100,
|
||||
"pk_id": 3596,
|
||||
"data": {
|
||||
"init_id": 9615419,
|
||||
"match_id": 10185039,
|
||||
"escape_time": 5,
|
||||
"is_portrait": false,
|
||||
"uname": "茉莉艿",
|
||||
"face": "http://i2.hdslb.com/bfs/face/3f2833a3ac598d9757ba33b79ec219cf941bdda8.jpg",
|
||||
"uid": 18963076
|
||||
},
|
||||
"roomid": 9615419
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
{
|
||||
"cmd": "PK_MIC_END",
|
||||
"pk_id": 3596,
|
||||
"pk_status": 1300,
|
||||
"data": {
|
||||
"type": 0
|
||||
}
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
{
|
||||
"cmd": "PK_PRE",
|
||||
"pk_id": 3597,
|
||||
"pk_status": 200,
|
||||
"data": {
|
||||
"init_id": 9615419,
|
||||
"match_id": 10185039,
|
||||
"count_down": 5,
|
||||
"pk_topic": "模仿游戏角色让对方猜",
|
||||
"pk_pre_time": 1529476609,
|
||||
"pk_start_time": 1529476614,
|
||||
"pk_end_time": 1529476914,
|
||||
"end_time": 1529477034
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
{
|
||||
"cmd": "PK_PROCESS",
|
||||
"pk_id": 8798,
|
||||
"pk_status": 300,
|
||||
"data": {
|
||||
"uid": 0,
|
||||
"init_votes": 30,
|
||||
"match_votes": 20,
|
||||
"user_votes": 0
|
||||
},
|
||||
"roomid": 346075
|
||||
}
|
@ -1,69 +0,0 @@
|
||||
{
|
||||
"cmd": "PK_SETTLE",
|
||||
"pk_id": 8806,
|
||||
"pk_status": 400,
|
||||
"data": {
|
||||
"pk_id": 8806,
|
||||
"init_info": {
|
||||
"uid": 7799328,
|
||||
"init_id": 10979759,
|
||||
"uname": "筱宇淅淅",
|
||||
"face": "http://i0.hdslb.com/bfs/face/e16515ac39329aa125bb8de5bb1fa9455f06337c.jpg",
|
||||
"votes": 0,
|
||||
"is_winner": false
|
||||
},
|
||||
"match_info": {
|
||||
"uid": 18654316,
|
||||
"match_id": 430063,
|
||||
"uname": "卖丸子尕害羞",
|
||||
"face": "http://i1.hdslb.com/bfs/face/1c579a244ec0c66bbb6e2ad6c770a2a498268735.jpg",
|
||||
"votes": 129,
|
||||
"is_winner": true,
|
||||
"vip_type": 0,
|
||||
"exp": {
|
||||
"color": 5805790,
|
||||
"user_level": 31,
|
||||
"master_level": {
|
||||
"level": 26,
|
||||
"color": 10512625
|
||||
}
|
||||
},
|
||||
"vip": {
|
||||
"vip": 0,
|
||||
"svip": 0
|
||||
},
|
||||
"face_frame": "",
|
||||
"badge": {
|
||||
"url": "http://i0.hdslb.com/bfs/live/74b2f9a48ce14d752dd27559c4a0df297243a3fd.png",
|
||||
"desc": "bilibili直播签约主播\r\n",
|
||||
"position": 3
|
||||
}
|
||||
},
|
||||
"best_user": {
|
||||
"uid": 31459309,
|
||||
"uname": "七友球球",
|
||||
"face": "http://i1.hdslb.com/bfs/face/09406a4fe632dda9d523da14f3e3735ee02efbab.jpg",
|
||||
"vip_type": 0,
|
||||
"exp": {
|
||||
"color": 6406234,
|
||||
"user_level": 19,
|
||||
"master_level": {
|
||||
"level": 1,
|
||||
"color": 6406234
|
||||
}
|
||||
},
|
||||
"vip": {
|
||||
"vip": 0,
|
||||
"svip": 0
|
||||
},
|
||||
"privilege_type": 0,
|
||||
"face_frame": "",
|
||||
"badge": {
|
||||
"url": "",
|
||||
"desc": "",
|
||||
"position": 0
|
||||
}
|
||||
},
|
||||
"punish_topic": "惩罚:模仿一款表情包"
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
{
|
||||
"cmd": "PK_START",
|
||||
"pk_id": 3597,
|
||||
"pk_status": 300,
|
||||
"data": {
|
||||
"init_id": 9615419,
|
||||
"match_id": 10185039,
|
||||
"pk_topic": "模仿游戏角色让对方猜"
|
||||
}
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
{
|
||||
"cmd": "PREPARING",
|
||||
"roomid": "1110317"
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
{
|
||||
"cmd": "RAFFLE_END",
|
||||
"roomid": 521429,
|
||||
"data": {
|
||||
"raffleId": 16897,
|
||||
"type": "flower_rain",
|
||||
"from": "鷺沢怜人",
|
||||
"fromFace": "http://i1.hdslb.com/bfs/face/09eafe44f913012512014e91f25001edf6e072d0.jpg",
|
||||
"win": {
|
||||
"uname": "nbqgd",
|
||||
"face": "http://i1.hdslb.com/bfs/face/09eafe44f913012512014e91f25001edf6e072d0.jpg",
|
||||
"giftId": 115,
|
||||
"giftName": "桃花",
|
||||
"giftNum": 66
|
||||
}
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
{
|
||||
"cmd": "RAFFLE_START",
|
||||
"roomid": 234024,
|
||||
"data": {
|
||||
"raffleId": 16915,
|
||||
"type": "flower_rain",
|
||||
"from": "爱吃喵姐的鱼",
|
||||
"time": 60
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
{
|
||||
"cmd": "ROOM_ADMINS",
|
||||
"uids": [
|
||||
4561799,
|
||||
432672,
|
||||
2179804,
|
||||
7928207,
|
||||
94380,
|
||||
1626161,
|
||||
3168349,
|
||||
13182672
|
||||
],
|
||||
"roomid": 5279
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
{
|
||||
"cmd": "ROOM_BLOCK_MSG",
|
||||
"uid": "60244207",
|
||||
"uname": "承包rose",
|
||||
"roomid": 5279
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
{
|
||||
"cmd": "ROOM_LOCK",
|
||||
"expire": "2018-03-15 10:24:18",
|
||||
"roomid": 6477301
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
{
|
||||
"cmd": "ROOM_RANK",
|
||||
"data": {
|
||||
"roomid": 1241012,
|
||||
"rank_desc": "小时榜 182",
|
||||
"color": "#FB7299",
|
||||
"h5_url": "https://live.bilibili.com/p/eden/rank-h5-current?anchor_uid\u003d35577726",
|
||||
"timestamp": 1527148082
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
{
|
||||
"cmd": "ROOM_SHIELD",
|
||||
"type": 1,
|
||||
"user": "",
|
||||
"keyword": "",
|
||||
"roomid": 234024
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
{
|
||||
"cmd": "ROOM_SHIELD",
|
||||
"type": 1,
|
||||
"user": [],
|
||||
"keyword": [
|
||||
"暗号",
|
||||
"摄像头",
|
||||
"色相头"
|
||||
],
|
||||
"roomid": 505447
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
{
|
||||
"cmd": "ROOM_SILENT_OFF",
|
||||
"data": [],
|
||||
"roomid": "29434"
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
{
|
||||
"cmd": "ROOM_SILENT_ON",
|
||||
"data": {
|
||||
"type": "level",
|
||||
"level": 1,
|
||||
"second": 1520424615
|
||||
},
|
||||
"roomid": 5279
|
||||
}
|
@ -1,97 +0,0 @@
|
||||
{
|
||||
"cmd": "SEND_GIFT",
|
||||
"data": {
|
||||
"giftName": "节奏风暴",
|
||||
"num": 1,
|
||||
"uname": "爱上熹",
|
||||
"rcost": 569788,
|
||||
"uid": 230845505,
|
||||
"top_list": [
|
||||
{
|
||||
"uid": 288348879,
|
||||
"uname": "我爱我家一生",
|
||||
"face": "http://i1.hdslb.com/bfs/face/dd52e4f2dfe881751816e45522f504f10458b514.jpg",
|
||||
"rank": 1,
|
||||
"score": 1852300,
|
||||
"guard_level": 0,
|
||||
"isSelf": 0
|
||||
},
|
||||
{
|
||||
"uid": 287551243,
|
||||
"uname": "熹上城的专属天使菲",
|
||||
"face": "http://i1.hdslb.com/bfs/face/c3ef04ba6c267c41067cd7708b7abd60c0c5c49f.jpg",
|
||||
"rank": 2,
|
||||
"score": 1245200,
|
||||
"guard_level": 3,
|
||||
"isSelf": 0
|
||||
},
|
||||
{
|
||||
"uid": 32416351,
|
||||
"uname": "镜子。。",
|
||||
"face": "http://i1.hdslb.com/bfs/face/08c54c2c97434811a99e9d070d621ccbb5d3f2c4.jpg",
|
||||
"rank": 3,
|
||||
"score": 332862,
|
||||
"guard_level": 3,
|
||||
"isSelf": 0
|
||||
}
|
||||
],
|
||||
"timestamp": 1520992553,
|
||||
"giftId": 39,
|
||||
"giftType": 0,
|
||||
"action": "赠送",
|
||||
"super": 1,
|
||||
"super_gift_num": 1,
|
||||
"price": 100000,
|
||||
"rnd": "1980508331",
|
||||
"newMedal": 0,
|
||||
"newTitle": 0,
|
||||
"medal": {
|
||||
"medalId": "95723",
|
||||
"medalName": "布丁诶",
|
||||
"level": 1
|
||||
},
|
||||
"title": "",
|
||||
"beatId": "4",
|
||||
"biz_source": "live",
|
||||
"metadata": "",
|
||||
"remain": 0,
|
||||
"gold": 88570,
|
||||
"silver": 127492,
|
||||
"eventScore": 0,
|
||||
"eventNum": 0,
|
||||
"smalltv_msg": [],
|
||||
"specialGift": {
|
||||
"id": "316221038798",
|
||||
"time": 90,
|
||||
"hadJoin": 0,
|
||||
"num": 1,
|
||||
"content": "你们城里人真会玩",
|
||||
"action": "start",
|
||||
"storm_gif": "http://static.hdslb.com/live-static/live-room/images/gift-section/mobilegift/2/jiezou.gif?2017011901"
|
||||
},
|
||||
"notice_msg": [],
|
||||
"capsule": {
|
||||
"normal": {
|
||||
"coin": 166,
|
||||
"change": 10,
|
||||
"progress": {
|
||||
"now": 3630,
|
||||
"max": 10000
|
||||
}
|
||||
},
|
||||
"colorful": {
|
||||
"coin": 2,
|
||||
"change": 0,
|
||||
"progress": {
|
||||
"now": 0,
|
||||
"max": 5000
|
||||
}
|
||||
},
|
||||
"move": 1
|
||||
},
|
||||
"addFollow": 0,
|
||||
"effect_block": 0,
|
||||
"coin_type": "gold",
|
||||
"total_coin": 100000
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
{
|
||||
"cmd": "SPECIAL_GIFT",
|
||||
"data": {
|
||||
"39": {
|
||||
"id": 214692,
|
||||
"time": 90,
|
||||
"hadJoin": 0,
|
||||
"num": 1,
|
||||
"content": "前方高能预警,注意这不是演习",
|
||||
"action": "start",
|
||||
"storm_gif": "http://static.hdslb.com/live-static/live-room/images/gift-section/mobilegift/2/jiezou.gif?2017011901"
|
||||
}
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
{
|
||||
"cmd": "SPECIAL_GIFT",
|
||||
"data": {
|
||||
"39": {
|
||||
"id": 214692,
|
||||
"time": 0,
|
||||
"hadJoin": 0,
|
||||
"num": 0,
|
||||
"action": "end"
|
||||
}
|
||||
}
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
{
|
||||
"cmd": "SYS_GIFT",
|
||||
"msg": "jjhghhfgh:? 在蜜桃姐姐w的:?直播间7813816:?内赠送:?6:?共450个",
|
||||
"msg_text": "jjhghhfgh在蜜桃姐姐w的直播间7813816内赠送亿圆共450个",
|
||||
"roomid": 0,
|
||||
"real_roomid": 0,
|
||||
"giftId": 0,
|
||||
"msgTips": 0
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
{
|
||||
"cmd": "SYS_GIFT",
|
||||
"msg": "【情怀家的尹蓝ovo】在直播间【147191】洒下漫天花雨,快来拾撷桃花,邂逅你的缘分!",
|
||||
"msg_text": "【情怀家的尹蓝ovo】在直播间【147191】洒下漫天花雨,快来拾撷桃花,邂逅你的缘分!",
|
||||
"tips": "【情怀家的尹蓝ovo】在直播间【147191】洒下漫天花雨,快来拾撷桃花,邂逅你的缘分!",
|
||||
"url": "http://live.bilibili.com/147191",
|
||||
"roomid": 147191,
|
||||
"real_roomid": 147191,
|
||||
"giftId": 116,
|
||||
"msgTips": 0
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
{
|
||||
"cmd": "SYS_GIFT",
|
||||
"msg": "十四不落:? 在直播间 :?590:? 使用了 20 倍节奏风暴,大家快去跟风领取奖励吧!",
|
||||
"tips": "【十四不落】在直播间【590】使用了 20 倍节奏风暴,大家快去跟风领取奖励吧!",
|
||||
"url": "http://live.bilibili.com/590",
|
||||
"roomid": 847617,
|
||||
"real_roomid": 0,
|
||||
"giftId": 39,
|
||||
"msgTips": 1
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
{
|
||||
"cmd": "SYS_MSG",
|
||||
"msg": "【天南地狗-】:?在直播间:?【531】:?赠送 小电视一个,请前往抽奖",
|
||||
"msg_text": "【天南地狗-】:?在直播间:?【531】:?赠送 小电视一个,请前往抽奖",
|
||||
"rep": 1,
|
||||
"styleType": 2,
|
||||
"url": "http://live.bilibili.com/531",
|
||||
"roomid": 531,
|
||||
"real_roomid": 22237,
|
||||
"rnd": 1520992662,
|
||||
"tv_id": "40478"
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
{
|
||||
"cmd": "TV_END",
|
||||
"data": {
|
||||
"id": "39077",
|
||||
"uname": "かこゆきこvew",
|
||||
"sname": "是你的苏苏吖",
|
||||
"giftName": "10W银瓜子",
|
||||
"mobileTips": "恭喜 かこゆきこvew 获得10W银瓜子",
|
||||
"raffleId": "39077",
|
||||
"type": "small_tv",
|
||||
"from": "是你的苏苏吖",
|
||||
"fromFace": "http://i0.hdslb.com/bfs/face/147f137d24138d1cfec5443d98ac8b03c4332398.jpg",
|
||||
"win": {
|
||||
"uname": "かこゆきこvew",
|
||||
"face": "http://i0.hdslb.com/bfs/face/4d63bd62322e7f3ef38723a91440bc6930626d9f.jpg",
|
||||
"giftName": "银瓜子",
|
||||
"giftId": "silver",
|
||||
"giftNum": 100000
|
||||
}
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
{
|
||||
"cmd": "TV_START",
|
||||
"data": {
|
||||
"id": "40072",
|
||||
"dtime": 180,
|
||||
"msg": {
|
||||
"cmd": "SYS_MSG",
|
||||
"msg": "【杰宝Yvan生命倒计时】:?在直播间:?【102】:?赠送 小电视一个,请前往抽奖",
|
||||
"msg_text": "【杰宝Yvan生命倒计时】:?在直播间:?【102】:?赠送 小电视一个,请前往抽奖",
|
||||
"rep": 1,
|
||||
"styleType": 2,
|
||||
"url": "http://live.bilibili.com/102",
|
||||
"roomid": 102,
|
||||
"real_roomid": 5279,
|
||||
"rnd": 12987955,
|
||||
"tv_id": "40072"
|
||||
},
|
||||
"raffleId": 40072,
|
||||
"type": "small_tv",
|
||||
"from": "杰宝Yvan生命倒计时",
|
||||
"time": 180
|
||||
}
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
{
|
||||
"cmd": "WARNING",
|
||||
"msg": "违反直播分区规范,请立即更换至游戏区",
|
||||
"roomid": 1365604
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
{
|
||||
"cmd": "WELCOME",
|
||||
"data": {
|
||||
"uid": 18625858,
|
||||
"uname": "\u662f\u767d\u8272\u70e4\u6f06",
|
||||
"isadmin": 0,
|
||||
"svip": 1
|
||||
},
|
||||
"roomid": 39189
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
{
|
||||
"cmd": "WELCOME_ACTIVITY",
|
||||
"data": {
|
||||
"uid": 49427998,
|
||||
"uname": "起个名真tm费事",
|
||||
"type": "forever_love",
|
||||
"display_mode": 1
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
{
|
||||
"cmd": "WELCOME_GUARD",
|
||||
"data": {
|
||||
"uid": 23598108,
|
||||
"username": "lovevael",
|
||||
"guard_level": 3,
|
||||
"water_god": 0
|
||||
},
|
||||
"roomid": 43001
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
{
|
||||
"cmd": "WISH_BOTTLE",
|
||||
"data": {
|
||||
"action": "update",
|
||||
"id": 1832,
|
||||
"wish": {
|
||||
"id": 1832,
|
||||
"uid": 110631,
|
||||
"type": 1,
|
||||
"type_id": 7,
|
||||
"wish_limit": 99999,
|
||||
"wish_progress": 14381,
|
||||
"status": 1,
|
||||
"content": "女装直播",
|
||||
"ctime": "2018-01-12 17:25:58",
|
||||
"count_map": [
|
||||
1,
|
||||
3,
|
||||
5
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
132
record/视频弹幕/danmaku.xml
Normal file
132
record/视频弹幕/danmaku.xml
Normal file
@ -0,0 +1,132 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<i>
|
||||
<!--aid 44514794-->
|
||||
<oid>77932184</oid>
|
||||
<ps>0</ps>
|
||||
<pe>196000</pe>
|
||||
<pc>1</pc>
|
||||
<pn>1</pn>
|
||||
<state>0</state>
|
||||
<real_name>0</real_name>
|
||||
<!--https://github.com/bilibili/DanmakuFlameMaster/blob/master/Sample/src/main/java/com/sample/BiliDanmukuParser.java 与现在版本不一致-->
|
||||
<!--0: 弹幕 id-->
|
||||
<!--1: 不明确(可能是弹幕池 id)-->
|
||||
<!--2: 弹幕出现时间(从视频开始播放开始计算)(ms)(不一定与弹幕发送者的播放器时间一致)-->
|
||||
<!--3: 类型(1从右至左滚动弹幕|6从左至右滚动弹幕|5顶端固定弹幕|4底端固定弹幕|7高级弹幕|8脚本弹幕)-->
|
||||
<!--4: 字号-->
|
||||
<!--5: 颜色-->
|
||||
<!--6: 时间戳(弹幕的发送时间)-->
|
||||
<!--7: 不明确-->
|
||||
<!--8: 用户 id 的 hash(ITU I.363.5, 即 CRC32)-->
|
||||
<d p="12509048833835076,0,117373,5,25,16777215,1551001292,0,d2c5fc5">硬核劈柴</d>
|
||||
<d p="12509130082746372,0,10149,1,25,16777215,1551001447,0,36762f3f">2222</d>
|
||||
<d p="12509469902635008,0,167322,1,25,16777215,1551002095,0,a54ebcbd">神奇的三哥,没有什么是他们顶不了的</d>
|
||||
<d p="12509518345273348,0,99416,1,25,16777215,1551002187,0,fdfc821d">这水是甜的吧</d>
|
||||
<d p="12509689741312004,0,143359,1,25,16777215,1551002514,0,d9141364">真 逃生</d>
|
||||
<d p="12510325574729732,0,31553,1,25,16777215,1551003727,0,9be0083c">非常优秀</d>
|
||||
<d p="12510425270190082,0,112103,1,25,16777215,1551003917,0,6b7e70e1">中国,赞</d>
|
||||
<d p="12511511077453828,0,145335,1,25,16777215,1551005988,0,4640575b">逃离生活的窗</d>
|
||||
<d p="12511622457196548,0,35862,1,25,16777215,1551006200,0,32249eeb">嘴冲?</d>
|
||||
<d p="12511627372396548,0,38222,1,25,16777215,1551006210,0,32249eeb">口冲</d>
|
||||
<d p="12515287960125440,0,33997,1,25,16777215,1551013192,0,31f305c2">自取其乳</d>
|
||||
<d p="12515547915223040,0,168259,1,25,16777215,1551013688,0,5a09b397">666</d>
|
||||
<d p="12516693915467780,0,177747,1,25,16777215,1551015874,0,eee14fa1">这脖子</d>
|
||||
<d p="12516704495075332,0,177747,1,25,16777215,1551015894,0,eee14fa1">这脖子是铁打的吧</d>
|
||||
<d p="12517097740435458,0,14248,1,25,16777215,1551016644,0,4acca5b2">333</d>
|
||||
<d p="12517272835326020,0,54028,1,25,16777215,1551016978,0,d087ac22">666啊</d>
|
||||
<d p="12517313871872002,0,84671,1,25,41194,1551017056,0,c27bae2f">四倍体草莓</d>
|
||||
<d p="12521502397169668,0,152101,1,25,16777215,1551025045,0,d425ebe6">高层建筑通云梯的窗台</d>
|
||||
<d p="12521739791630340,0,13170,1,25,16777215,1551025498,0,ae910367">3338</d>
|
||||
<d p="12526503713046532,0,51669,1,25,16777215,1551034584,0,782f992b">哈哈</d>
|
||||
<d p="12528652852396036,0,151912,1,25,16777215,1551038683,0,24780dca">你告诉我哪里有这么高的云梯</d>
|
||||
<d p="12531482545356802,0,151162,4,25,15138834,1551044081,0,7ba95a84">逃离生活窗</d>
|
||||
<d p="12534806507159556,0,174937,1,25,16777215,1551050421,0,23e291df">自重最少三百公斤的玩意顶在头上还能单手爬楼梯,这是人能做到的吗</d>
|
||||
<d p="12535841481555968,0,28234,1,25,16777215,1551052395,0,463ae566">前功尽弃系列</d>
|
||||
<d p="12536174091436036,0,187603,1,25,16777215,1551053029,0,2f9d3c2b">深圳会展中心?</d>
|
||||
<d p="12536447369216000,0,25105,1,25,16777215,1551053550,0,c2dd98f1">求这女孩的的体重 马上</d>
|
||||
<d p="12536668506030082,0,45319,1,25,16777215,1551053972,0,7095f6c8">蘸糖墩儿</d>
|
||||
<d p="12536737092337668,0,97592,1,25,16777215,1551054103,0,24fc5da4">我们家的</d>
|
||||
<d p="12537151663636480,0,52026,1,25,16777215,1551054894,0,85067ef">牛逼</d>
|
||||
<d p="12537154059632644,0,56703,1,25,16777215,1551054898,0,85067ef">卧槽</d>
|
||||
<d p="12537358213709826,0,147955,1,25,16777215,1551055288,0,c0f9fca3">生活重来窗</d>
|
||||
<d p="12538283349770244,0,98274,1,25,16777215,1551057052,0,9089266e">糖墩儿那个,我们是老乡</d>
|
||||
<d p="12540637177970692,0,53562,1,25,16777215,1551061542,0,630553e8">卧槽</d>
|
||||
<d p="12541039516057604,0,172384,1,25,16777215,1551062309,0,18afe75f">上化佛他们能顶么?</d>
|
||||
<d p="12541168076718084,0,87307,1,25,16777215,1551062554,0,87411221">九星虹梅</d>
|
||||
<d p="12541180367601668,0,62312,1,25,16777215,1551062578,0,84bbd366">6666</d>
|
||||
<d p="12541421086572548,0,124646,1,25,16777215,1551063037,0,89e08e5b">快看 是岳云鹏</d>
|
||||
<d p="12541523945586692,0,58595,1,25,16777215,1551063233,0,89853200">军人nb</d>
|
||||
<d p="12541681330552836,0,55322,1,25,16777215,1551063533,0,7fc58f5">这是练什么,你们成天笑印度人,敢不敢把这个给印度人看</d>
|
||||
<d p="12541710838005764,0,69226,1,25,16777215,1551063589,0,919f1906">一辈子单身</d>
|
||||
<d p="12541713491951620,0,85083,1,25,16777215,1551063595,0,7fc58f5">这tm成了灵芝了</d>
|
||||
<d p="12541729957216256,0,81506,1,25,16777215,1551063626,0,c99a3c2d">要坚强</d>
|
||||
<d p="12541757011525636,0,83999,1,25,16777215,1551063678,0,c574fa94">草莓:我控制不住我的生长</d>
|
||||
<d p="12541766596034560,0,175567,1,25,16777215,1551063696,0,919f1906">铁头功</d>
|
||||
<d p="12541807514615812,0,58938,1,25,16777215,1551063774,0,795d594d">站军姿,身体要前倾</d>
|
||||
<d p="12541814231793668,0,6716,1,25,16777215,1551063787,0,159c9870">牛逼</d>
|
||||
<d p="12542220192186372,0,52988,1,25,16777215,1551064561,0,18ccd294">这个上初中时被班主任罚站,就是在台阶上用脚尖站</d>
|
||||
<d p="12542324742553604,0,57888,1,25,16777215,1551064760,0,aba65a57">小腿肚子疼</d>
|
||||
<d p="12542490122911748,0,52502,1,25,16777215,1551065076,0,dc79c826">江科炸出来</d>
|
||||
<d p="12542873415188484,0,4141,4,25,15138834,1551065807,0,4fca51a">一句卧槽行天下</d>
|
||||
<d p="12542894059028484,0,36129,1,25,16777215,1551065846,0,e86f7dbe">禁止自娱自乐</d>
|
||||
<d p="12542915894575108,0,59905,4,25,15138834,1551065888,0,4fca51a">一句卧槽行走天下</d>
|
||||
<d p="12543067077738500,0,55463,1,25,16777215,1551066176,0,47c66d00">这个是真的难受!!!</d>
|
||||
<d p="12543107687514116,0,18613,1,25,16777215,1551066254,0,cb4a9077">还有卧槽</d>
|
||||
<d p="12543134014636032,0,148627,1,25,16777215,1551066304,0,c830b87b">逃出升天</d>
|
||||
<d p="12543278968209412,0,51770,1,25,16777215,1551066580,0,6090150d">我也这么站过</d>
|
||||
<d p="12543327467995140,0,76223,1,25,16777215,1551066673,0,fbb62223">除了牛逼还可以说盖帽</d>
|
||||
<d p="12543367314931716,0,135928,5,25,16777215,1551066749,0,88f119f1">半挂</d>
|
||||
<d p="12543439578595332,0,59536,1,25,16777215,1551066887,0,8665586e">一个下去一排倒</d>
|
||||
<d p="12543474239799300,0,47028,1,25,16711680,1551066953,0,3b59d8ed">强迫症不能忍</d>
|
||||
<d p="12543478613409796,0,120357,1,25,16777215,1551066961,0,8665586e">硬核劈材</d>
|
||||
<d p="12543729190043652,0,58468,1,25,16777215,1551067439,0,2158075b">我们也这么站过</d>
|
||||
<d p="12543770022641666,0,12159,1,25,16777215,1551067517,0,1b84016c">卧槽</d>
|
||||
<d p="12543874514812932,0,54399,1,25,16777215,1551067716,0,b8e8864e">我大江科</d>
|
||||
<d p="12544083492339716,0,27741,1,25,16777215,1551068115,0,42bd1786">功亏一篑</d>
|
||||
<d p="12544113036492804,0,54983,1,25,16777215,1551068171,0,42bd1786">我只会说:卧槽</d>
|
||||
<d p="12544135804223492,0,68683,1,25,16777215,1551068215,0,42bd1786">亲媳妇</d>
|
||||
<d p="12544157188358148,0,86981,1,25,16777215,1551068256,0,42bd1786">手炉?</d>
|
||||
<d p="12544171444797444,0,101377,1,25,16777215,1551068283,0,42bd1786">甜辣口的</d>
|
||||
<d p="12544201995059204,0,121207,1,25,16777215,1551068341,0,42bd1786">这个是高手</d>
|
||||
<d p="12544234053173252,0,151787,1,25,16777215,1551068402,0,42bd1786">逃离生命</d>
|
||||
<d p="12544262875381764,0,167925,1,25,16777215,1551068457,0,42bd1786">摩托精?</d>
|
||||
<d p="12544293120507908,0,35820,1,25,16777215,1551068515,0,54681ea3">公的</d>
|
||||
<d p="12544313787940868,0,190580,1,25,16777215,1551068554,0,9a0c194e">想看三哥顶汽车</d>
|
||||
<d p="12544316839821316,0,54579,1,25,16777215,1551068560,0,54681ea3">好了,站5个小时</d>
|
||||
<d p="12544374657777668,0,151059,1,25,16777215,1551068670,0,54681ea3">逃离生活的窗户</d>
|
||||
<d p="12544396354387970,0,145747,5,25,16707842,1551068712,0,1c2904f3">众所周知,逃生=逃出生天=逃出,生天</d>
|
||||
<d p="12544659980025860,0,163561,1,25,16777215,1551069215,0,dac77b12">金字塔是不是他们顶上去的</d>
|
||||
<d p="12545464756862980,0,174150,1,25,16777215,1551070749,0,7927ad01">腰间盘突出了解一下</d>
|
||||
<d p="12546357659172868,0,65518,1,25,16777215,1551072453,0,d1b711e0">俺也一样</d>
|
||||
<d p="12546551898963972,0,11939,1,25,16777215,1551072823,0,37017ae0">可以说 卧槽</d>
|
||||
<d p="12546668542033924,0,31087,1,25,16777215,1551073046,0,a81360fc">我了个大草</d>
|
||||
<d p="12546701081444354,0,82303,1,25,16777215,1551073108,0,a81360fc">兄弟帽子不错</d>
|
||||
<d p="12546720176013316,0,175796,1,25,16777215,1551073144,0,46c90882">梯子牛逼</d>
|
||||
<d p="12547003792228356,0,66404,1,25,16777215,1551073685,0,359613b4">老子最讨厌女人了!滚!</d>
|
||||
<d p="12547078239027200,0,117822,1,25,16777215,1551073827,0,d586fc8f">还有这种操作!?</d>
|
||||
<d p="12547123676971012,0,159473,1,25,16777215,1551073914,0,e08d6d5e">被谁淹没不知所措</d>
|
||||
<d p="12547149768163328,0,18573,1,25,16777215,1551073963,0,af799cfa">奈何本人无文化,一句卧槽走天下</d>
|
||||
<d p="12547183239757828,0,138761,1,25,16777215,1551074027,0,8ab298ba">好心酸。</d>
|
||||
<d p="12547367960051714,0,174706,1,25,16777215,1551074380,0,d66698a9">我顶不住</d>
|
||||
<d p="12547463784693762,0,98486,1,25,16777215,1551074562,0,315e19b2">她有一个大胆的想法</d>
|
||||
<d p="12547504946544642,0,105119,1,25,16777215,1551074641,0,2bb0a0d5">那个不是西葫芦么。。西葫芦甜的???</d>
|
||||
<d p="12547560521072644,0,162119,1,25,16777215,1551074747,0,590724e4">舒服</d>
|
||||
<d p="12547667420250116,0,11704,1,25,16777215,1551074951,0,2bf4452b">牛了个逼</d>
|
||||
<d p="12547706826260482,0,9199,1,25,16777215,1551075026,0,ac40f732">还可以说卧槽</d>
|
||||
<d p="12547714786525184,0,4020,1,25,16777215,1551075041,0,ac40f732">还可以说卧槽</d>
|
||||
<d p="12547717997789188,0,48483,1,25,16777215,1551075047,0,3c0c3293">他说弯腰下去继续蘸酱么?</d>
|
||||
<d p="12547798832513028,0,49654,1,25,16777215,1551075201,0,6655f47c">好</d>
|
||||
<d p="12547845801377794,0,168066,1,25,16777215,1551075291,0,51068b32">牛逼</d>
|
||||
<d p="12548000418627588,0,114144,1,25,16777215,1551075586,0,dff175b5">水瓜</d>
|
||||
<d p="12548114018205700,0,56942,1,25,16777215,1551075803,0,10f78b15">模型出了点问题</d>
|
||||
<d p="12548138132832260,0,152683,1,25,16777215,1551075849,0,a914360c">逃出生天</d>
|
||||
<d p="12548268299911172,0,188366,1,25,16777215,1551076097,0,50865f2c">阿三是真牛逼…</d>
|
||||
<d p="12548273663377412,0,31422,1,25,16777215,1551076107,0,33e5f873">回首掏 惨不忍睹</d>
|
||||
<d p="12548290776137728,0,58698,1,25,16777215,1551076140,0,124b4e53">帅啊</d>
|
||||
<d p="12548323870769154,0,186805,1,25,16777215,1551076203,0,dbc23261">这孩子的弹跳力惊人</d>
|
||||
<d p="12548337070768128,0,170612,1,25,16777215,1551076228,0,552b1633">这是高手</d>
|
||||
<d p="12548341505720322,0,170612,1,25,16777215,1551076237,0,552b1633">这是高手</d>
|
||||
<d p="12548375489544196,0,191208,1,25,16777215,1551076301,0,124b4e53">吉尼斯记录三哥就顶的车</d>
|
||||
<d p="12548479186370564,0,170612,1,25,16777215,1551076499,0,552b1633">这是高手</d>
|
||||
<d p="12548593167630340,0,87039,1,25,16777215,1551076717,0,b3367171">八倍体草莓</d>
|
||||
</i>
|
BIN
record/视频弹幕/list.so
Normal file
BIN
record/视频弹幕/list.so
Normal file
Binary file not shown.
@ -1,6 +1,7 @@
|
||||
package com.hiczp.bilibili.api
|
||||
|
||||
import com.hiczp.bilibili.api.app.AppAPI
|
||||
import com.hiczp.bilibili.api.danmaku.DanmakuAPI
|
||||
import com.hiczp.bilibili.api.main.MainAPI
|
||||
import com.hiczp.bilibili.api.member.MemberAPI
|
||||
import com.hiczp.bilibili.api.message.MessageAPI
|
||||
@ -8,6 +9,7 @@ import com.hiczp.bilibili.api.passport.PassportAPI
|
||||
import com.hiczp.bilibili.api.passport.model.LoginResponse
|
||||
import com.hiczp.bilibili.api.player.PlayerAPI
|
||||
import com.hiczp.bilibili.api.player.PlayerInterceptor
|
||||
import com.hiczp.bilibili.api.retrofit.Header
|
||||
import com.hiczp.bilibili.api.retrofit.Param
|
||||
import com.hiczp.bilibili.api.retrofit.ParamType
|
||||
import com.hiczp.bilibili.api.retrofit.exception.BilibiliApiException
|
||||
@ -72,21 +74,21 @@ class BilibiliClient(
|
||||
|
||||
@Suppress("SpellCheckingInspection")
|
||||
private val defaultCommonHeaderInterceptor = CommonHeaderInterceptor(
|
||||
"Display-ID" to { "${billingClientProperties.buildVersionId}-$initTime" },
|
||||
"Buvid" to { billingClientProperties.buildVersionId },
|
||||
"User-Agent" to { "Mozilla/5.0 BiliDroid/5.37.0 (bbcallen@gmail.com)" },
|
||||
"Device-ID" to { billingClientProperties.hardwareId }
|
||||
Header.DISPLAY_ID to { "${billingClientProperties.buildVersionId}-$initTime" },
|
||||
Header.BUILD_VERSION_ID to { billingClientProperties.buildVersionId },
|
||||
Header.USER_AGENT to { billingClientProperties.defaultUserAgent },
|
||||
Header.DEVICE_ID to { billingClientProperties.hardwareId }
|
||||
)
|
||||
|
||||
@Suppress("SpellCheckingInspection")
|
||||
private val defaultCommonParamArray = arrayOf(
|
||||
Param.ACCESS_KEY to { token },
|
||||
Param.APP_KEY to { billingClientProperties.appKey },
|
||||
"build" to { billingClientProperties.build },
|
||||
"channel" to { billingClientProperties.channel },
|
||||
"mobi_app" to { billingClientProperties.platform },
|
||||
"platform" to { billingClientProperties.platform },
|
||||
"ts" to { Instant.now().epochSecond.toString() }
|
||||
Param.BUILD to { billingClientProperties.build },
|
||||
Param.CHANNEL to { billingClientProperties.channel },
|
||||
Param.MOBILE_APP to { billingClientProperties.platform },
|
||||
Param.PLATFORM to { billingClientProperties.platform },
|
||||
Param.TIMESTAMP to { Instant.now().epochSecond.toString() }
|
||||
)
|
||||
|
||||
private val defaultCommonQueryParamInterceptor = CommonParamInterceptor(ParamType.QUERY, *defaultCommonParamArray)
|
||||
@ -100,11 +102,11 @@ class BilibiliClient(
|
||||
defaultCommonHeaderInterceptor,
|
||||
CommonParamInterceptor(ParamType.FORM_URL_ENCODED,
|
||||
Param.APP_KEY to { billingClientProperties.appKey },
|
||||
"build" to { billingClientProperties.build },
|
||||
"channel" to { billingClientProperties.channel },
|
||||
"mobi_app" to { billingClientProperties.platform },
|
||||
"platform" to { billingClientProperties.platform },
|
||||
"ts" to { Instant.now().epochSecond.toString() }
|
||||
Param.BUILD to { billingClientProperties.build },
|
||||
Param.CHANNEL to { billingClientProperties.channel },
|
||||
Param.MOBILE_APP to { billingClientProperties.platform },
|
||||
Param.PLATFORM to { billingClientProperties.platform },
|
||||
Param.TIMESTAMP to { Instant.now().epochSecond.toString() }
|
||||
)
|
||||
)
|
||||
}
|
||||
@ -117,7 +119,7 @@ class BilibiliClient(
|
||||
createAPI<MessageAPI>(BaseUrl.message,
|
||||
defaultCommonHeaderInterceptor,
|
||||
CommonParamInterceptor(ParamType.QUERY, *defaultCommonParamArray,
|
||||
"actionKey" to { "appkey" },
|
||||
Param.ACTION_KEY to { Param.APP_KEY },
|
||||
"has_up" to { "1" }
|
||||
)
|
||||
)
|
||||
@ -142,10 +144,10 @@ class BilibiliClient(
|
||||
createAPI<MainAPI>(BaseUrl.main,
|
||||
CommonHeaderInterceptor(
|
||||
//如果未登陆则没有 Display-ID
|
||||
"Display-ID" to { userId?.let { "$it-$initTime" } },
|
||||
"Buvid" to { billingClientProperties.buildVersionId },
|
||||
"User-Agent" to { "Mozilla/5.0 BiliDroid/5.37.0 (bbcallen@gmail.com)" },
|
||||
"Device-ID" to { billingClientProperties.hardwareId }
|
||||
Header.DISPLAY_ID to { userId?.let { "$it-$initTime" } },
|
||||
Header.BUILD_VERSION_ID to { billingClientProperties.buildVersionId },
|
||||
Header.USER_AGENT to { billingClientProperties.defaultUserAgent },
|
||||
Header.DEVICE_ID to { billingClientProperties.hardwareId }
|
||||
),
|
||||
defaultCommonQueryParamInterceptor
|
||||
)
|
||||
@ -159,12 +161,12 @@ class BilibiliClient(
|
||||
createAPI<VcAPI>(BaseUrl.vc,
|
||||
defaultCommonHeaderInterceptor,
|
||||
CommonParamInterceptor(ParamType.QUERY, *defaultCommonParamArray,
|
||||
"_device" to { billingClientProperties.platform },
|
||||
"_hwid" to { billingClientProperties.hardwareId },
|
||||
"src" to { billingClientProperties.channel },
|
||||
"trace_id" to { generateTraceId() },
|
||||
"uid" to { userId?.toString() },
|
||||
"version" to { billingClientProperties.version }
|
||||
Param._DEVICE to { billingClientProperties.platform },
|
||||
Param._HARDWARE_ID to { billingClientProperties.hardwareId },
|
||||
Param.SOURCE to { billingClientProperties.channel },
|
||||
Param.TRACE_ID to { generateTraceId() },
|
||||
Param.USER_ID to { userId?.toString() },
|
||||
Param.VERSION to { billingClientProperties.version }
|
||||
)
|
||||
)
|
||||
}
|
||||
@ -190,15 +192,33 @@ class BilibiliClient(
|
||||
.client(OkHttpClient.Builder().apply {
|
||||
addInterceptor(PlayerInterceptor(billingClientProperties) { loginResponse })
|
||||
addInterceptor(FailureResponseInterceptor)
|
||||
//log
|
||||
if (logLevel != HttpLoggingInterceptor.Level.NONE) {
|
||||
addNetworkInterceptor(HttpLoggingInterceptor().setLevel(logLevel))
|
||||
}
|
||||
addNetworkInterceptor(httpLoggingInterceptor)
|
||||
}.build())
|
||||
.build()
|
||||
.create(PlayerAPI::class.java)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取弹幕所用的 API
|
||||
*/
|
||||
val danmakuAPI: DanmakuAPI by lazy {
|
||||
Retrofit.Builder()
|
||||
.baseUrl(BaseUrl.main)
|
||||
.addCallAdapterFactory(coroutineCallAdapterFactory)
|
||||
.client(OkHttpClient.Builder().apply {
|
||||
addInterceptor(CommonHeaderInterceptor(
|
||||
Header.ACCEPT to { "application/xhtml+xml,application/xml" },
|
||||
Header.ACCEPT_ENCODING to { "gzip, deflate" },
|
||||
Header.USER_AGENT to { billingClientProperties.defaultUserAgent }
|
||||
))
|
||||
addInterceptor(defaultCommonQueryParamInterceptor)
|
||||
addInterceptor(sortAndSignInterceptor)
|
||||
addNetworkInterceptor(httpLoggingInterceptor)
|
||||
}.build())
|
||||
.build()
|
||||
.create(DanmakuAPI::class.java)
|
||||
}
|
||||
|
||||
/**
|
||||
* 登陆
|
||||
* v3 登陆接口会同时返回 cookies 和 token
|
||||
@ -269,6 +289,7 @@ class BilibiliClient(
|
||||
fun logoutFuture() = GlobalScope.future { logout() }
|
||||
|
||||
private val sortAndSignInterceptor = SortAndSignInterceptor(billingClientProperties.appSecret)
|
||||
private val httpLoggingInterceptor = HttpLoggingInterceptor().setLevel(logLevel)
|
||||
private inline fun <reified T : Any> createAPI(
|
||||
baseUrl: String,
|
||||
vararg interceptors: Interceptor
|
||||
@ -282,10 +303,7 @@ class BilibiliClient(
|
||||
}
|
||||
addInterceptor(sortAndSignInterceptor)
|
||||
addInterceptor(FailureResponseInterceptor)
|
||||
//log
|
||||
if (logLevel != HttpLoggingInterceptor.Level.NONE) {
|
||||
addNetworkInterceptor(HttpLoggingInterceptor().setLevel(logLevel))
|
||||
}
|
||||
addNetworkInterceptor(httpLoggingInterceptor)
|
||||
}.build())
|
||||
.build()
|
||||
.create(T::class.java)
|
||||
|
@ -5,6 +5,12 @@ package com.hiczp.bilibili.api
|
||||
* 默认值对应 5.37.0(release-b220051) 版本.
|
||||
*/
|
||||
class BilibiliClientProperties {
|
||||
/**
|
||||
* 默认 UA, 用于大多数访问
|
||||
*/
|
||||
@Suppress("SpellCheckingInspection")
|
||||
var defaultUserAgent = "Mozilla/5.0 BiliDroid/5.37.0 (bbcallen@gmail.com)"
|
||||
|
||||
/**
|
||||
* Android 平台的 appKey(该默认值为普通版客户端, 非概念版)
|
||||
*/
|
||||
|
@ -5,7 +5,7 @@ import java.security.MessageDigest
|
||||
//MD5
|
||||
private val md5Instance = MessageDigest.getInstance("MD5")
|
||||
|
||||
internal fun String.md5() =
|
||||
fun String.md5() =
|
||||
StringBuilder(32).apply {
|
||||
//优化过的 md5 字符串生成算法
|
||||
md5Instance.digest(toByteArray()).forEach {
|
||||
|
30
src/main/kotlin/com/hiczp/bilibili/api/StreamExtension.kt
Normal file
30
src/main/kotlin/com/hiczp/bilibili/api/StreamExtension.kt
Normal file
@ -0,0 +1,30 @@
|
||||
package com.hiczp.bilibili.api
|
||||
|
||||
import org.apache.commons.io.input.BoundedInputStream
|
||||
import org.apache.commons.io.input.BoundedReader
|
||||
import java.io.InputStream
|
||||
import java.nio.charset.Charset
|
||||
|
||||
/**
|
||||
* 以大端模式从流中读取一个 int
|
||||
*/
|
||||
fun InputStream.readInt(): Int {
|
||||
val byteArray = ByteArray(4).apply {
|
||||
read(this)
|
||||
}
|
||||
return (byteArray[0].toInt() shl 24) or
|
||||
(byteArray[1].toInt() shl 16) or
|
||||
(byteArray[2].toInt() shl 8) or
|
||||
(byteArray[3].toInt())
|
||||
}
|
||||
|
||||
@Suppress("EXPERIMENTAL_API_USAGE")
|
||||
fun InputStream.readUInt() = readInt().toUInt()
|
||||
|
||||
fun InputStream.boundedReader(maxCharsFromTargetReader: Int, charset: Charset = Charsets.UTF_8) =
|
||||
BoundedReader(reader(charset), maxCharsFromTargetReader)
|
||||
|
||||
fun InputStream.bounded(size: Long) = BoundedInputStream(this, size)
|
||||
|
||||
@Suppress("EXPERIMENTAL_API_USAGE")
|
||||
fun InputStream.bounded(size: UInt) = bounded(size.toLong())
|
@ -82,6 +82,7 @@ interface AppAPI {
|
||||
* 视频页面(普通视频, 非番剧)
|
||||
* 包含视频基本信息, 推荐和广告
|
||||
* 从这个接口得到视频的 cid
|
||||
* 如果返回内容里的 pages 有多个表明有多个 p, 每个 p 有自己的 cid(外层的 cid 为默认的那个 p 的 cid)
|
||||
*
|
||||
* @param aid 视频的唯一标识
|
||||
*/
|
||||
|
54
src/main/kotlin/com/hiczp/bilibili/api/danmaku/Danmaku.kt
Normal file
54
src/main/kotlin/com/hiczp/bilibili/api/danmaku/Danmaku.kt
Normal file
@ -0,0 +1,54 @@
|
||||
package com.hiczp.bilibili.api.danmaku
|
||||
|
||||
data class Danmaku(
|
||||
/**
|
||||
* 弹幕 id
|
||||
*/
|
||||
val id: Long,
|
||||
|
||||
/**
|
||||
* 下标 1, 不明属性
|
||||
*/
|
||||
val unknownAttribute1: String,
|
||||
|
||||
/**
|
||||
* 弹幕出现时间(播放器时间)(ms)
|
||||
*/
|
||||
val time: Long,
|
||||
|
||||
/**
|
||||
* 弹幕类型
|
||||
* (1从右至左滚动弹幕|6从左至右滚动弹幕|5顶端固定弹幕|4底端固定弹幕|7高级弹幕|8脚本弹幕)
|
||||
*/
|
||||
val type: Int,
|
||||
|
||||
/**
|
||||
* 字号
|
||||
*/
|
||||
val fontSize: Int,
|
||||
|
||||
/**
|
||||
* 颜色
|
||||
*/
|
||||
val color: Int,
|
||||
|
||||
/**
|
||||
* 弹幕的发送时间(时间戳)(s)
|
||||
*/
|
||||
val timestamp: Long,
|
||||
|
||||
/**
|
||||
* 下标 7, 不明属性
|
||||
*/
|
||||
val unknownAttribute7: String,
|
||||
|
||||
/**
|
||||
* 弹幕发送者的 hash(用户 id 的 CRC32 校验和)
|
||||
*/
|
||||
val user: String,
|
||||
|
||||
/**
|
||||
* 弹幕的内容
|
||||
*/
|
||||
val content: String
|
||||
)
|
26
src/main/kotlin/com/hiczp/bilibili/api/danmaku/DanmakuAPI.kt
Normal file
26
src/main/kotlin/com/hiczp/bilibili/api/danmaku/DanmakuAPI.kt
Normal file
@ -0,0 +1,26 @@
|
||||
package com.hiczp.bilibili.api.danmaku
|
||||
|
||||
import kotlinx.coroutines.Deferred
|
||||
import okhttp3.ResponseBody
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Query
|
||||
|
||||
@Suppress("DeferredIsResult")
|
||||
interface DanmakuAPI {
|
||||
/**
|
||||
* 获取弹幕(视频或者番剧)
|
||||
*
|
||||
* @param aid 视频的唯一标识
|
||||
* @param oid 注意, 此处的 oid 是 cid
|
||||
*
|
||||
* @return 返回的内容是二进制数据, 由于数据量可能很大, 此处不做解析
|
||||
*/
|
||||
@GET("/x/v2/dm/list.so")
|
||||
fun list(
|
||||
@Query("aid") aid: Long,
|
||||
@Query("oid") oid: Long,
|
||||
@Query("plat") plat: Int? = 2,
|
||||
@Query("ps") pageSize: Int = 0,
|
||||
@Query("type") type: Int = 1
|
||||
): Deferred<ResponseBody>
|
||||
}
|
126
src/main/kotlin/com/hiczp/bilibili/api/danmaku/DanmakuParser.kt
Normal file
126
src/main/kotlin/com/hiczp/bilibili/api/danmaku/DanmakuParser.kt
Normal file
@ -0,0 +1,126 @@
|
||||
package com.hiczp.bilibili.api.danmaku
|
||||
|
||||
import com.google.gson.stream.JsonReader
|
||||
import com.hiczp.bilibili.api.bounded
|
||||
import com.hiczp.bilibili.api.readUInt
|
||||
import java.io.InputStream
|
||||
import java.util.*
|
||||
import java.util.zip.GZIPInputStream
|
||||
import javax.xml.namespace.QName
|
||||
import javax.xml.stream.XMLInputFactory
|
||||
import javax.xml.stream.XMLStreamConstants
|
||||
|
||||
/**
|
||||
* 弹幕文件解析器.
|
||||
* 弹幕文件(list.so)有三个部分
|
||||
* 第一个部分为一个 Int, 表示第二部分的长度
|
||||
* 第二部分为一个 Json, 标识各个弹幕的等级(用于屏蔽设置)
|
||||
* 第三部分为一个 gzip 压缩过的 xml
|
||||
*
|
||||
* Web 端的弹幕是一个明文 xml, 与 APP 的接口是不一样的.
|
||||
*
|
||||
* json 部分形如 {"dmflags":[{"dmid":12551893546958848,"flag":10}],"rec_flag":1,"rec_text":"开启后,全站视频将按等级等优化弹幕","rec_switch":1}
|
||||
* xml 部分形如 <d p="12509048833835076,0,117373,5,25,16777215,1551001292,0,d2c5fc5">硬核劈柴</d>
|
||||
*
|
||||
* @see com.hiczp.bilibili.api.danmaku.DanmakuAPI.list
|
||||
*/
|
||||
@Suppress("SpellCheckingInspection")
|
||||
class DanmakuParser {
|
||||
companion object {
|
||||
/**
|
||||
* 解析弹幕文件
|
||||
*
|
||||
* @param inputStream 输入流, 可以指向任何位置
|
||||
* @param autoClose 是否在解析后自动关闭流
|
||||
*
|
||||
* @return 返回 flags map 与 弹幕列表. 注意, 原始的弹幕顺序是按发送时间来排的, 而非播放器时间.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun parser(inputStream: InputStream, autoClose: Boolean = true): Pair<Map<Long, Int>, List<Danmaku>> {
|
||||
//Json 的长度
|
||||
val jsonLength = inputStream.readUInt()
|
||||
|
||||
//弹幕ID-Flag
|
||||
val danmakuFlags = HashMap<Long, Int>()
|
||||
//gson 会从 reader 中自行缓冲 1024 个字符, 这会导致额外的字符被消费. 因此要限制其读取数量
|
||||
//流式解析 Json
|
||||
with(JsonReader(inputStream.bounded(jsonLength).reader())) {
|
||||
beginObject()
|
||||
while (hasNext()) {
|
||||
when (nextName()) {
|
||||
"dmflags" -> {
|
||||
beginArray()
|
||||
while (hasNext()) {
|
||||
var danmakuId = 0L
|
||||
var flag = 0
|
||||
beginObject()
|
||||
while (hasNext()) {
|
||||
when (nextName()) {
|
||||
"dmid" -> danmakuId = nextLong()
|
||||
"flag" -> flag = nextInt()
|
||||
else -> skipValue()
|
||||
}
|
||||
}
|
||||
endObject()
|
||||
danmakuFlags[danmakuId] = flag
|
||||
}
|
||||
endArray()
|
||||
}
|
||||
else -> skipValue()
|
||||
}
|
||||
}
|
||||
endObject()
|
||||
}
|
||||
|
||||
//json 解析完毕后, 剩下的内容是一个 gzip 压缩过的 xml
|
||||
val reader = GZIPInputStream(inputStream).reader()
|
||||
val danmakus = LinkedList<Danmaku>()
|
||||
//流式解析 xml
|
||||
val xmlEventReader = XMLInputFactory.newInstance().createXMLEventReader(reader)
|
||||
var startD = false //之前解析到的 element 是否是 d
|
||||
var p: String? = null //之前解析到的 p 的值
|
||||
while (xmlEventReader.hasNext()) {
|
||||
val event = xmlEventReader.nextEvent()
|
||||
when (event.eventType) {
|
||||
XMLStreamConstants.START_ELEMENT -> {
|
||||
with(event.asStartElement()) {
|
||||
startD = name.localPart == "d"
|
||||
if (startD) {
|
||||
p = getAttributeByName(P).value
|
||||
}
|
||||
}
|
||||
}
|
||||
XMLStreamConstants.CHARACTERS -> {
|
||||
//如果前一个解析到的是 d 标签, 那么此处得到的一定是 d 标签的 body
|
||||
if (startD) {
|
||||
val danmaku = with(StringTokenizer(p, ",")) {
|
||||
Danmaku(
|
||||
nextToken().toLong(),
|
||||
nextToken(),
|
||||
nextToken().toLong(),
|
||||
nextToken().toInt(),
|
||||
nextToken().toInt(),
|
||||
nextToken().toInt(),
|
||||
nextToken().toLong(),
|
||||
nextToken(),
|
||||
nextToken(),
|
||||
event.asCharacters().data
|
||||
)
|
||||
}
|
||||
danmakus.add(danmaku)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//自动关闭流
|
||||
if (autoClose) inputStream.close()
|
||||
|
||||
return danmakuFlags to danmakus
|
||||
}
|
||||
|
||||
//常量, 用于加快速度
|
||||
@JvmStatic
|
||||
private val P = QName("p")
|
||||
}
|
||||
}
|
@ -10,6 +10,11 @@ import java.lang.management.ManagementFactory
|
||||
|
||||
/**
|
||||
* 这里是播放器会访问的 API
|
||||
* 返回内容中会有多个视频下载地址, 他们代表不同的视频质量(音频同理)
|
||||
* 音频和视频是分开的
|
||||
* 下载视频得到的是一个 m4s 文件, 但是实际上是完整的视频(例如整个番剧而非片段)
|
||||
* 下载音频得到的也是一个 m4s 文件, 也是完整的
|
||||
* 将视频和音频合在一起, 就可以播放了
|
||||
*/
|
||||
@Suppress("DeferredIsResult", "SpellCheckingInspection")
|
||||
interface PlayerAPI {
|
||||
@ -38,7 +43,7 @@ interface PlayerAPI {
|
||||
*
|
||||
* @param aid 番剧的唯一标识
|
||||
* @param cid 在番剧详情页的返回值里
|
||||
* @param seasonType 分级类型, 不明确, 似乎总为 1
|
||||
* @param seasonType 分季类型, 不明确, 似乎总为 1
|
||||
* @param session 其值为 系统已运行时间(ms)的MD5值, 此处的默认值为 JVM 已启动时间, 在 Android 上请使用 SystemClock
|
||||
* @param trackPath 不明确
|
||||
*
|
||||
|
@ -26,9 +26,9 @@ class PlayerInterceptor(
|
||||
|
||||
//添加 header
|
||||
val header = request.headers().newBuilder().apply {
|
||||
add("Accept", "*/*")
|
||||
add("User-Agent", "Bilibili Freedoooooom/MarkII")
|
||||
add("Accept-Language", Header.ZH_CN)
|
||||
add(Header.ACCEPT, "*/*")
|
||||
add(Header.USER_AGENT, "Bilibili Freedoooooom/MarkII")
|
||||
add(Header.ACCEPT_LANGUAGE, "zh-CN,zh;q=0.8")
|
||||
}.build()
|
||||
|
||||
//添加 Query Params
|
||||
@ -50,13 +50,13 @@ class PlayerInterceptor(
|
||||
addParamEncode(Param.MID, "0")
|
||||
}
|
||||
//公共参数
|
||||
addParamEncode("device", bilibiliClientProperties.platform)
|
||||
addParamEncode("mobi_app", bilibiliClientProperties.platform)
|
||||
addParamEncode("platform", bilibiliClientProperties.platform)
|
||||
addParamEncode(Param.DEVICE, bilibiliClientProperties.platform)
|
||||
addParamEncode(Param.MOBILE_APP, bilibiliClientProperties.platform)
|
||||
addParamEncode(Param.PLATFORM, bilibiliClientProperties.platform)
|
||||
addParamEncode("otype", "json")
|
||||
addParamEncode("ts", Instant.now().epochSecond.toString())
|
||||
addParamEncode("build", bilibiliClientProperties.build)
|
||||
addParamEncode("buvid", bilibiliClientProperties.buildVersionId)
|
||||
addParamEncode(Param.TIMESTAMP, Instant.now().epochSecond.toString())
|
||||
addParamEncode(Param.BUILD, bilibiliClientProperties.build)
|
||||
addParamEncode(Param.BUILD_VERSION_ID, bilibiliClientProperties.buildVersionId)
|
||||
}.toString().let {
|
||||
//排序
|
||||
val sortedEncodedQuery = it.split('&').sorted().joinToString(separator = "&")
|
||||
|
@ -12,15 +12,38 @@ object Method {
|
||||
}
|
||||
|
||||
object Header {
|
||||
const val JSON = "application/json"
|
||||
const val FORM_URLENCODED = "application/x-www-form-urlencoded; charset=utf-8"
|
||||
const val ZH_CN = "zh-CN,zh;q=0.8"
|
||||
const val DISPLAY_ID = "Display-ID"
|
||||
@Suppress("SpellCheckingInspection")
|
||||
const val BUILD_VERSION_ID = "Buvid"
|
||||
const val DEVICE_ID = "Device-ID"
|
||||
const val USER_AGENT = "User-Agent"
|
||||
const val ACCEPT = "Accept"
|
||||
const val ACCEPT_LANGUAGE = "Accept-Language"
|
||||
const val ACCEPT_ENCODING = "Accept-Encoding"
|
||||
}
|
||||
|
||||
object Param {
|
||||
const val ACCESS_KEY = "access_key"
|
||||
@Suppress("SpellCheckingInspection")
|
||||
const val APP_KEY = "appkey"
|
||||
const val ACTION_KEY = "actionKey"
|
||||
const val BUILD = "build"
|
||||
@Suppress("SpellCheckingInspection")
|
||||
const val BUILD_VERSION_ID = "buvid"
|
||||
const val CHANNEL = "channel"
|
||||
@Suppress("ObjectPropertyName")
|
||||
const val _DEVICE = "_device"
|
||||
const val DEVICE = "device"
|
||||
@Suppress("ObjectPropertyName", "SpellCheckingInspection")
|
||||
const val _HARDWARE_ID = "_hwid"
|
||||
const val SOURCE = "src"
|
||||
const val TRACE_ID = "trace_id"
|
||||
const val USER_ID = "uid"
|
||||
const val VERSION = "version"
|
||||
@Suppress("SpellCheckingInspection")
|
||||
const val MOBILE_APP = "mobi_app"
|
||||
const val PLATFORM = "platform"
|
||||
const val TIMESTAMP = "ts"
|
||||
const val EXPIRE = "expire"
|
||||
const val MID = "mid"
|
||||
const val SIGN = "sign"
|
||||
|
@ -30,11 +30,20 @@ object FailureResponseInterceptor : Interceptor {
|
||||
contentType.charset(Charsets.UTF_8)!!
|
||||
}
|
||||
|
||||
//拷贝流并读取其内容
|
||||
val jsonObject = body.source().also {
|
||||
//拷贝流
|
||||
val inputStreamReader = body.source().also {
|
||||
it.request(Long.MAX_VALUE)
|
||||
}.buffer.clone().inputStream().reader(charset).let {
|
||||
jsonParser.parse(it).obj
|
||||
}.buffer.clone().inputStream().reader(charset)
|
||||
|
||||
//读取其内容
|
||||
val jsonObject = try {
|
||||
jsonParser.parse(inputStreamReader).obj
|
||||
} catch (exception: Exception) {
|
||||
//如果返回内容解析失败, 说明它不是一个合法的 json
|
||||
//如果在拦截器抛出 MalformedJsonException 会导致 Retrofit 的异步请求一直卡着直到超时
|
||||
return response
|
||||
} finally {
|
||||
inputStreamReader.close()
|
||||
}
|
||||
|
||||
//判断 code 是否为 0
|
||||
|
21
src/test/kotlin/com/hiczp/bilibili/api/test/DanmakuTest.kt
Normal file
21
src/test/kotlin/com/hiczp/bilibili/api/test/DanmakuTest.kt
Normal file
@ -0,0 +1,21 @@
|
||||
package com.hiczp.bilibili.api.test
|
||||
|
||||
import com.hiczp.bilibili.api.danmaku.DanmakuParser
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class DanmakuTest {
|
||||
//339.6kib 的弹幕文件在 0.5s 内解析完毕, 通常视频的弹幕不会超过这个容量
|
||||
@Test
|
||||
fun fetchAndParseDanmaku() {
|
||||
runBlocking {
|
||||
//著名的炮姐视频 你指尖跃动的闪光是我此生不变的信仰
|
||||
bilibiliClient.danmakuAPI.list(aid = 810872, oid = 1176840).await().let {
|
||||
DanmakuParser.parser(it.byteStream())
|
||||
}.let { (map, list) ->
|
||||
println(map)
|
||||
println(list)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
package com.hiczp.bilibili.api.test
|
||||
|
||||
import com.google.gson.GsonBuilder
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
@ -17,7 +18,9 @@ class PlayUrlTest {
|
||||
fun bangumiPlayUrl() {
|
||||
runBlocking {
|
||||
bilibiliClient.playerAPI.run {
|
||||
bangumiPlayUrl(aid = 42714241, cid = 74921228).await()
|
||||
bangumiPlayUrl(aid = 42714241, cid = 74921228).await().let {
|
||||
GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create().toJson(it)
|
||||
}.let(::println)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user