初步完成 LiveClient 用于获取直播间实时弹幕

This commit is contained in:
czp 2017-11-14 12:19:16 +08:00
parent 189301a4a9
commit e6037e6c02
28 changed files with 2216 additions and 0 deletions

View File

@ -0,0 +1,43 @@
{
"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
],
"cmd": "DANMU_MSG"
}

View File

@ -0,0 +1,68 @@
{
"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
],
"cmd": "DANMU_MSG"
}

View File

@ -0,0 +1,4 @@
{
"cmd": "LIVE",
"roomid": 1110317
}

View File

@ -0,0 +1,4 @@
{
"cmd": "PREPARING",
"roomid": 1110317
}

View File

@ -0,0 +1,80 @@
{
"cmd": "SEND_GIFT",
"data": {
"giftName": "\u4ebf\u5706",
"num": 1,
"uname": "\u30c5\u4ee3\u6211\u56de\u5bb6",
"rcost": 106855699,
"uid": 14146398,
"top_list": [
{
"uid": 10952886,
"uname": "\u5b89\u4e36\u664b",
"coin": 498900,
"face": "http://static.hdslb.com/images/member/noface.gif",
"guard_level": 0,
"rank": 1,
"score": 498900
},
{
"uid": 13174983,
"uname": "-\u56db\u5b63\u8c46-",
"coin": 384300,
"face": "http://i0.hdslb.com/bfs/face/23f9f57a69378736f68b50fc4cac4f6b01683e97.jpg",
"guard_level": "3",
"rank": 2,
"score": 384300
},
{
"uid": 87444977,
"uname": "\u964c\u964c\u821e\u98ce",
"coin": 377700,
"face": "http://i2.hdslb.com/bfs/face/c06c835bf8ed6401535847bf21e78d4d3b89d402.jpg",
"guard_level": 0,
"rank": 3,
"score": 377700
}
],
"timestamp": 1510565032,
"giftId": 6,
"giftType": 0,
"action": "\u8d60\u9001",
"super": 0,
"price": 1000,
"rnd": "541907145",
"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": {
"normal": {
"coin": 6,
"change": 0,
"progress": {
"now": 4800,
"max": 10000
}
},
"colorful": {
"coin": 0,
"change": 0,
"progress": {
"now": 0,
"max": 5000
}
}
},
"addFollow": 0
}
}

View File

@ -0,0 +1,11 @@
{
"cmd": "SYS_GIFT",
"msg": "sakamakiryoryo\u5728\u76f4\u64ad\u95f471084\u5f00\u542f\u4e86\u4e30\u6536\u796d\u5178\uff0c\u4e00\u8d77\u6765\u5206\u4eab\u6536\u83b7\u7684\u798f\u5229\u5427\uff01",
"msg_text": "sakamakiryoryo\u5728\u76f4\u64ad\u95f471084\u5f00\u542f\u4e86\u4e30\u6536\u796d\u5178\uff0c\u4e00\u8d77\u6765\u5206\u4eab\u6536\u83b7\u7684\u798f\u5229\u5427\uff01",
"tips": "sakamakiryoryo\u5728\u76f4\u64ad\u95f471084\u5f00\u542f\u4e86\u4e30\u6536\u796d\u5178\uff0c\u4e00\u8d77\u6765\u5206\u4eab\u6536\u83b7\u7684\u798f\u5229\u5427\uff01",
"url": "http:\/\/live.bilibili.com\/71084",
"roomid": 71084,
"real_roomid": 71084,
"giftId": 102,
"msgTips": 0
}

View File

@ -0,0 +1,12 @@
{
"cmd": "SYS_MSG",
"msg": "\u3010\u5e7d\u5c0f\u591c\u5929\u5c0f\u52ab\u3011:?\u5728\u76f4\u64ad\u95f4:?\u3010392\u3011:?\u8d60\u9001 \u5c0f\u7535\u89c6\u4e00\u4e2a\uff0c\u8bf7\u524d\u5f80\u62bd\u5956",
"msg_text": "\u3010\u5e7d\u5c0f\u591c\u5929\u5c0f\u52ab\u3011:?\u5728\u76f4\u64ad\u95f4:?\u3010392\u3011:?\u8d60\u9001 \u5c0f\u7535\u89c6\u4e00\u4e2a\uff0c\u8bf7\u524d\u5f80\u62bd\u5956",
"rep": 1,
"styleType": 2,
"url": "http:\/\/live.bilibili.com\/392",
"roomid": 392,
"real_roomid": 71084,
"rnd": 44332151,
"tv_id": "29349"
}

View File

@ -0,0 +1,10 @@
{
"cmd": "WELCOME",
"data": {
"uid": 18625858,
"uname": "\u662f\u767d\u8272\u70e4\u6f06",
"isadmin": 0,
"svip": 1
},
"roomid": 39189
}

View File

@ -0,0 +1,10 @@
{
"cmd": "WELCOME_GUARD",
"data": {
"uid": 23598108,
"username": "lovevael",
"guard_level": 3,
"water_god": 0
},
"roomid": 43001
}

View File

@ -12,6 +12,7 @@ public class BulletScreenEntity {
@SerializedName("msg")
private String message;
// web 端发送弹幕, 该字段是固定的, 为用户进入直播页面的时间的时间戳. 但是在 Android , 这是一个随机数
//该随机数不包括符号位有 9
@SerializedName("rnd")
private long random = (long) (Math.random() * (999999999 - (-999999999)) + (-999999999));

View File

@ -0,0 +1,116 @@
package com.hiczp.bilibili.api.live.socket;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.hiczp.bilibili.api.live.socket.entity.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Arrays;
import java.util.function.Consumer;
public class BulletScreenDispatcherRunnable implements Runnable {
private static final Logger LOGGER = LoggerFactory.getLogger(BulletScreenDispatcherRunnable.class);
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
private static final JsonParser JSON_PARSER = new JsonParser();
private SocketChannel socketChannel;
private LiveClient liveClient;
public BulletScreenDispatcherRunnable(SocketChannel socketChannel, LiveClient liveClient) {
this.socketChannel = socketChannel;
this.liveClient = liveClient;
}
private void invokeCallback(Consumer<BulletScreenListener> consumer) {
Utils.invokeCallback(liveClient.getBulletScreenListeners(), consumer);
}
@Override
public void run() {
while (true) {
try {
ByteBuffer[] byteBuffers = PackageRepository.readNextPackageSplit(socketChannel);
//如果没有回调则不解析数据包, 直接开始接收下一个数据包
if (liveClient.getBulletScreenListeners().size() == 0) {
continue;
}
//判断数据包类型
byte[] packageTypeBytes = byteBuffers[3].array();
Consumer<BulletScreenListener> consumer = null;
if (Arrays.equals(packageTypeBytes, PackageRepository.DATA_PACKAGE_TYPE_BYTES)) { //弹幕
String json = new String(byteBuffers[5].array());
JsonObject jsonObject = JSON_PARSER.parse(json).getAsJsonObject();
String cmd = jsonObject.get("cmd").getAsString();
//判断 cmd
switch (cmd) {
case "DANMU_MSG": {
consumer = bulletScreenListener -> bulletScreenListener.onDanMuMSGPackage(GSON.fromJson(json, DanMuMSGEntity.class));
}
break;
case "SEND_GIFT": {
consumer = bulletScreenListener -> bulletScreenListener.onSendGiftPackage(GSON.fromJson(json, SendGiftEntity.class));
}
break;
case "SYS_MSG": {
consumer = bulletScreenListener -> bulletScreenListener.onSysMSGPackage(GSON.fromJson(json, SysMSGEntity.class));
}
break;
case "SYS_GIFT": {
consumer = bulletScreenListener -> bulletScreenListener.onSysGiftPackage(GSON.fromJson(json, SysGiftEntity.class));
}
break;
case "WELCOME": {
consumer = bulletScreenListener -> bulletScreenListener.onWelcomePackage(GSON.fromJson(json, WelcomeEntity.class));
}
break;
case "WELCOME_GUARD": {
consumer = bulletScreenListener -> bulletScreenListener.onWelcomeGuardPackage(GSON.fromJson(json, WelcomeGuardEntity.class));
}
break;
case "LIVE": {
consumer = bulletScreenListener -> bulletScreenListener.onLivePackage(GSON.fromJson(json, LiveEntity.class));
}
break;
case "PREPARING": {
consumer = bulletScreenListener -> bulletScreenListener.onPreparingPackage(GSON.fromJson(json, PreparingEntity.class));
}
break;
default: { //未知的 cmd
LOGGER.error("Unknown json below: ");
GSON.toJson(jsonObject, System.out);
System.out.println();
}
}
} else if (Arrays.equals(packageTypeBytes, PackageRepository.VIEWER_COUNT_PACKAGE_TYPE_BYTES)) { //在线人数
consumer = bulletScreenListener -> bulletScreenListener.onViewerCountPackage(byteBuffers[5].getInt());
} else { //未知类型
ByteBuffer byteBuffer = ByteBuffer.allocate(Arrays.stream(byteBuffers).mapToInt(ByteBuffer::limit).sum());
Arrays.stream(byteBuffers).forEach(byteBuffer::put);
byte[] bytes = byteBuffer.array();
LOGGER.error("Unknown package below: ");
Utils.printBytes(bytes);
consumer = bulletScreenListener -> bulletScreenListener.onUnknownPackage(bytes);
}
//执行回调
invokeCallback(consumer);
} catch (IOException e) {
LOGGER.debug("Connection closed, BulletScreenDispatcherThread prepare to exit");
try {
liveClient.close();
} catch (IOException e1) {
e1.printStackTrace();
}
//调用 onDisconnect 回调
invokeCallback(BulletScreenListener::onDisconnect);
break;
}
}
}
}

View File

@ -0,0 +1,29 @@
package com.hiczp.bilibili.api.live.socket;
import com.hiczp.bilibili.api.live.socket.entity.*;
public interface BulletScreenListener {
void onConnect();
void onDisconnect();
void onViewerCountPackage(int viewerCount);
void onDanMuMSGPackage(DanMuMSGEntity danMuMSGEntity);
void onSendGiftPackage(SendGiftEntity sendGiftEntity);
void onSysMSGPackage(SysMSGEntity sysMSGEntity);
void onSysGiftPackage(SysGiftEntity sysGiftEntity);
void onWelcomePackage(WelcomeEntity welcomeEntity);
void onWelcomeGuardPackage(WelcomeGuardEntity welcomeGuardEntity);
void onLivePackage(LiveEntity liveEntity);
void onPreparingPackage(PreparingEntity preparingEntity);
void onUnknownPackage(byte[] raw);
}

View File

@ -0,0 +1,65 @@
package com.hiczp.bilibili.api.live.socket;
import com.hiczp.bilibili.api.live.socket.entity.*;
public class BulletScreenListenerAdaptor implements BulletScreenListener {
@Override
public void onConnect() {
}
@Override
public void onDisconnect() {
}
@Override
public void onViewerCountPackage(int viewerCount) {
}
@Override
public void onDanMuMSGPackage(DanMuMSGEntity danMuMSGEntity) {
}
@Override
public void onSendGiftPackage(SendGiftEntity sendGiftEntity) {
}
@Override
public void onSysMSGPackage(SysMSGEntity sysMSGEntity) {
}
@Override
public void onSysGiftPackage(SysGiftEntity sysGiftEntity) {
}
@Override
public void onWelcomePackage(WelcomeEntity welcomeEntity) {
}
@Override
public void onWelcomeGuardPackage(WelcomeGuardEntity welcomeGuardEntity) {
}
@Override
public void onLivePackage(LiveEntity liveEntity) {
}
@Override
public void onPreparingPackage(PreparingEntity preparingEntity) {
}
@Override
public void onUnknownPackage(byte[] raw) {
}
}

View File

@ -0,0 +1,138 @@
package com.hiczp.bilibili.api.live.socket;
import com.hiczp.bilibili.api.BilibiliRESTAPI;
import com.hiczp.bilibili.api.live.LiveService;
import com.hiczp.bilibili.api.live.entity.LiveRoomInfoEntity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Closeable;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.nio.channels.SocketChannel;
import java.util.Timer;
import java.util.TimerTask;
import java.util.Vector;
public class LiveClient implements Closeable {
private static final Logger LOGGER = LoggerFactory.getLogger(LiveClient.class);
private int showRoomId;
private int roomId;
private int userId = BilibiliRESTAPI.getMid();
private LiveService liveService = BilibiliRESTAPI.getLiveService();
private Vector<BulletScreenListener> bulletScreenListeners = new Vector<>();
private LiveRoomInfoEntity.LiveRoomEntity liveRoomEntity;
private SocketChannel socketChannel;
private Thread bulletScreenDispatcherThread;
private Timer heartBeatTimer;
public LiveClient(int showRoomId) {
this.showRoomId = showRoomId;
}
//如果不传入 userId, 将使用默认值 0
public LiveClient(int showRoomId, int userId) {
this.showRoomId = showRoomId;
this.userId = userId;
}
public LiveClient addListener(BulletScreenListener bulletScreenListener) {
bulletScreenListeners.add(bulletScreenListener);
return this;
}
public LiveClient removeListener(BulletScreenListener bulletScreenListener) {
bulletScreenListeners.remove(bulletScreenListener);
return this;
}
public LiveClient connect() throws IOException {
LOGGER.info("Entering live room {} with uid {}", showRoomId, userId);
//获取直播间信息
LiveRoomInfoEntity liveRoomInfoEntity = liveService.getRoomInfo(showRoomId).execute().body();
if (liveRoomInfoEntity.getCode() != 0) {
LOGGER.error("Can't get live room info");
throw new IOException(liveRoomInfoEntity.getMessage());
}
liveRoomEntity = liveRoomInfoEntity.getData();
roomId = liveRoomEntity.getRoomId();
LOGGER.debug("Real room id: {}", roomId);
//socket 连接
String address = liveRoomEntity.getCmt();
int port = liveRoomEntity.getCmtPortGoim();
LOGGER.info("Connect to {}:{}", address, port);
socketChannel = SocketChannel.open(new InetSocketAddress(address, port));
//发送进房包
socketChannel.write(PackageRepository.createEnterRoomPackage(roomId, userId));
//验证下一个数据包是否是进房成功数据包
if (PackageRepository.isNextPackageIsEnterRoomSuccessPackage(socketChannel)) {
LOGGER.info("Socket connection success");
//调用 onConnect 回调
Utils.invokeCallback(bulletScreenListeners, BulletScreenListener::onConnect);
} else {
LOGGER.error("Socket connection failed");
socketChannel.close();
throw new SocketException("Can't connection to Bullet Screen server");
}
//启动回调分发线程
bulletScreenDispatcherThread = new Thread(new BulletScreenDispatcherRunnable(socketChannel, this));
bulletScreenDispatcherThread.setName("BulletScreenDispatcherThread");
bulletScreenDispatcherThread.start();
LOGGER.debug("BulletScreenDispatcherThread started");
//启动心跳包线程
heartBeatTimer = new Timer("LiveHeartBeatThread");
heartBeatTimer.schedule(new TimerTask() {
private final Logger logger = LoggerFactory.getLogger(TimerTask.class);
@Override
public void run() {
try {
socketChannel.socket().getOutputStream().write(PackageRepository.createHeartBeatPackage(roomId, userId).array());
logger.debug("Send heart beat package success");
} catch (IOException e) {
logger.debug("Connection closed, cancel HeartBeatTimerTask");
cancel();
try {
close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
}, 0, 30 * 1000);
LOGGER.debug("HeatBeatTimer started");
return this;
}
@Override
public synchronized void close() throws IOException {
if (heartBeatTimer != null) {
heartBeatTimer.cancel();
LOGGER.debug("HeartBeatTimer canceled");
heartBeatTimer = null;
}
if (socketChannel != null && socketChannel.isConnected()) {
socketChannel.close();
LOGGER.debug("Socket closed");
socketChannel = null;
}
}
public Vector<BulletScreenListener> getBulletScreenListeners() {
return bulletScreenListeners;
}
public void setBulletScreenListeners(Vector<BulletScreenListener> bulletScreenListeners) {
this.bulletScreenListeners = bulletScreenListeners;
}
public LiveRoomInfoEntity.LiveRoomEntity getLiveRoomEntity() {
return liveRoomEntity;
}
}

View File

@ -0,0 +1,143 @@
package com.hiczp.bilibili.api.live.socket;
import com.google.gson.Gson;
import com.hiczp.bilibili.api.live.socket.entity.EnterRoomEntity;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Arrays;
//数据包结构说明
//00 00 00 28 00 10 00 00 00 00 00 07 00 00 00 00
//00 00 00 28/00 10/00 00 00 00 00 07/00 00 00 00
//1-4 字节: 数据包长度
//5-6 字节: 协议头长度, 固定值 0x10
//7-8 字节: 设备类型, Android 固定为 0
//9-12 字节: 数据包类型
//13-16 字节: 设备类型, 7-8 字节
public class PackageRepository {
//数据包总长标识占用的 bytes 长度
static final short PACKAGE_LENGTH_BYTES_LENGTH = 4;
//协议头长度标识占用的 bytes 长度
static final short PROTOCOL_HEAD_LENGTH_BYTES_LENGTH = 2;
//设备类型短标识 bytes
static final byte[] SHORT_DEVICE_TYPE_BYTES = {0x00, 0x00};
//数据包类型标识 bytes
static final short PACKAGE_TYPE_BYTES_LENGTH = 4;
static final byte[] HEART_BEAT_PACKAGE_TYPE_BYTES = {0x00, 0x00, 0x00, 0x02}; //心跳包
static final byte[] VIEWER_COUNT_PACKAGE_TYPE_BYTES = {0x00, 0x00, 0x00, 0x03}; //观众人数
static final byte[] DATA_PACKAGE_TYPE_BYTES = {0x00, 0x00, 0x00, 0x05}; //弹幕, 礼物, 系统消息 etc
static final byte[] ENTER_ROOM_PACKAGE_TYPE_BYTES = {0x00, 0x00, 0x00, 0x07}; //进入房间
static final byte[] ENTER_ROOM_SUCCESS_PACKAGE_TYPE_BYTES = {0x00, 0x00, 0x00, 0x08}; //进入房间的响应包
//设备类型长标识 bytes
static final byte[] LONG_DEVICE_TYPE_BYTES = {0x00, 0x00, 0x00, 0x00};
//协议头总长度
static final short PROTOCOL_HEAD_LENGTH = (short) (PACKAGE_LENGTH_BYTES_LENGTH + PROTOCOL_HEAD_LENGTH_BYTES_LENGTH + SHORT_DEVICE_TYPE_BYTES.length + PACKAGE_TYPE_BYTES_LENGTH + LONG_DEVICE_TYPE_BYTES.length);
//协议头长度标识 bytes
static final byte[] PROTOCOL_HEAD_LENGTH_BYTES = ByteBuffer.allocate(PROTOCOL_HEAD_LENGTH_BYTES_LENGTH).putShort(PROTOCOL_HEAD_LENGTH).array();
private static final Gson GSON = new Gson();
private static ByteBuffer createPackage(byte[] packageTypeBytes, String content) {
int totalLength = PROTOCOL_HEAD_LENGTH + content.length();
ByteBuffer byteBuffer = ByteBuffer.allocate(totalLength)
.putInt(totalLength)
.put(PROTOCOL_HEAD_LENGTH_BYTES)
.put(SHORT_DEVICE_TYPE_BYTES)
.put(packageTypeBytes)
.put(LONG_DEVICE_TYPE_BYTES)
.put(content.getBytes());
byteBuffer.flip();
return byteBuffer;
}
private static void readDataFromSocketChannel(SocketChannel socketChannel, ByteBuffer byteBuffer) throws IOException {
while (byteBuffer.hasRemaining()) {
socketChannel.read(byteBuffer);
}
}
//userId 0 表示用户未登录, 并不影响弹幕推送, 但是可能和用户统计有关
public static ByteBuffer createEnterRoomPackage(int roomId, int userId) {
return createPackage(ENTER_ROOM_PACKAGE_TYPE_BYTES, GSON.toJson(new EnterRoomEntity(roomId, userId)));
}
public static ByteBuffer createHeartBeatPackage(int roomId, int userId) {
return createPackage(HEART_BEAT_PACKAGE_TYPE_BYTES, GSON.toJson(new EnterRoomEntity(roomId, userId)));
}
public static ByteBuffer readNextPackage(SocketChannel socketChannel) throws IOException {
//获取数据包总长度
ByteBuffer packageLengthByteBuffer = ByteBuffer.allocate(PACKAGE_LENGTH_BYTES_LENGTH);
readDataFromSocketChannel(socketChannel, packageLengthByteBuffer);
packageLengthByteBuffer.flip();
int packageLength = packageLengthByteBuffer.getInt();
packageLengthByteBuffer.rewind();
//获取数据包剩下的部分
ByteBuffer restPackageByteBuffer = ByteBuffer.allocate(packageLength - PACKAGE_LENGTH_BYTES_LENGTH);
readDataFromSocketChannel(socketChannel, restPackageByteBuffer);
restPackageByteBuffer.flip();
//合并 ByteBuffer
ByteBuffer byteBuffer = ByteBuffer.allocate(packageLengthByteBuffer.limit() + restPackageByteBuffer.limit());
byteBuffer.put(packageLengthByteBuffer).put(restPackageByteBuffer);
byteBuffer.flip();
return byteBuffer;
}
public static ByteBuffer[] readNextPackageSplit(SocketChannel socketChannel) throws IOException {
//获取数据包总长度
ByteBuffer packageLengthByteBuffer = ByteBuffer.allocate(PACKAGE_LENGTH_BYTES_LENGTH);
readDataFromSocketChannel(socketChannel, packageLengthByteBuffer);
packageLengthByteBuffer.flip();
int packageLength = packageLengthByteBuffer.getInt();
packageLengthByteBuffer.rewind();
//获取协议头长度
ByteBuffer protocolHeadLengthByteBuffer = ByteBuffer.allocate(PROTOCOL_HEAD_LENGTH_BYTES_LENGTH);
readDataFromSocketChannel(socketChannel, protocolHeadLengthByteBuffer);
protocolHeadLengthByteBuffer.flip();
int protocolHeadLength = protocolHeadLengthByteBuffer.getShort();
protocolHeadLengthByteBuffer.rewind();
//获取剩余的协议头
ByteBuffer restProtocolHeadByteBuffer = ByteBuffer.allocate(protocolHeadLength - PACKAGE_LENGTH_BYTES_LENGTH - PROTOCOL_HEAD_LENGTH_BYTES_LENGTH);
readDataFromSocketChannel(socketChannel, restProtocolHeadByteBuffer);
restProtocolHeadByteBuffer.flip();
//得到设备类型短标识
ByteBuffer shortDeviceTypeByteBuffer = ByteBuffer.allocate(SHORT_DEVICE_TYPE_BYTES.length);
shortDeviceTypeByteBuffer.putShort(restProtocolHeadByteBuffer.getShort());
shortDeviceTypeByteBuffer.flip();
//得到数据包类型
ByteBuffer packageTypeByteBuffer = ByteBuffer.allocate(PACKAGE_TYPE_BYTES_LENGTH);
packageTypeByteBuffer.putInt(restProtocolHeadByteBuffer.getInt());
packageTypeByteBuffer.flip();
//得到设备类型长标识
ByteBuffer longDeviceTypeByteBuffer = ByteBuffer.allocate(LONG_DEVICE_TYPE_BYTES.length);
longDeviceTypeByteBuffer.putInt(restProtocolHeadByteBuffer.getInt());
longDeviceTypeByteBuffer.flip();
//获取正文
ByteBuffer contentByteBuffer = ByteBuffer.allocate(packageLength - protocolHeadLength);
readDataFromSocketChannel(socketChannel, contentByteBuffer);
contentByteBuffer.flip();
//组成数组
return new ByteBuffer[]{
packageLengthByteBuffer, //0
protocolHeadLengthByteBuffer, //1
shortDeviceTypeByteBuffer, //2
packageTypeByteBuffer, //3
longDeviceTypeByteBuffer, //4
contentByteBuffer //5
};
}
public static boolean isNextPackageIsEnterRoomSuccessPackage(SocketChannel socketChannel) throws IOException {
return Arrays.equals(readNextPackageSplit(socketChannel)[3].array(), ENTER_ROOM_SUCCESS_PACKAGE_TYPE_BYTES);
}
}

View File

@ -0,0 +1,70 @@
package com.hiczp.bilibili.api.live.socket;
import com.sun.istack.internal.Nullable;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
public class Utils {
private static byte[][] splitBytes(byte[] bytes, int n) {
int lineCount = bytes.length % n == 0 ? bytes.length / n : bytes.length / n + 1;
byte[][] result = new byte[lineCount][];
int to;
for (int line = 1; line <= lineCount; line++) {
if (line != lineCount) {
to = line * n;
} else {
to = bytes.length;
}
result[line - 1] = Arrays.copyOfRange(bytes, (line - 1) * n, to);
}
return result;
}
static void invokeCallback(List<BulletScreenListener> bulletScreenListeners, @Nullable Consumer<BulletScreenListener> consumer) {
if (consumer != null) {
for (int i = bulletScreenListeners.size() - 1; i >= 0; i--) {
try {
consumer.accept(bulletScreenListeners.get(i));
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
public static void printBytes(byte[] bytes) {
byte[][] data = splitBytes(bytes, 16);
byte[] currentRow;
char c;
for (int i = 0; i < data.length; i++) {
System.out.printf("%08x ", i * 16);
currentRow = data[i];
for (int j = 0; j < currentRow.length; j++) {
System.out.printf("%02x ", currentRow[j]);
if (j == 7) {
System.out.print(" ");
}
}
if (currentRow.length < 16) {
for (int k = 0; k < (48 - currentRow.length * 2 - (currentRow.length - 1)); k++) {
System.out.print(" ");
}
}
System.out.print(" ");
for (int j = 0; j < currentRow.length; j++) {
if (currentRow[j] < ' ') {
c = '.';
} else {
c = (char) currentRow[j];
}
System.out.print(c);
if (j == 7) {
System.out.print(" ");
}
}
System.out.println();
}
}
}

View File

@ -0,0 +1,154 @@
package com.hiczp.bilibili.api.live.socket.entity;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.annotations.SerializedName;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Optional;
public class DanMuMSGEntity {
private static final Gson GSON = new Gson();
private static final Type LIST_STRING_TYPE = new TypeToken<List<String>>() {
}.getType();
/**
* 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]
* cmd : DANMU_MSG
*/
@SerializedName("cmd")
private String cmd;
@SerializedName("info")
private JsonArray info;
public String getCmd() {
return cmd;
}
public void setCmd(String cmd) {
this.cmd = cmd;
}
public JsonArray getInfo() {
return info;
}
public void setInfo(JsonArray info) {
this.info = info;
}
//pool 发布的弹幕池 (0 普通 1 字幕 2 特殊)
public int getPool() {
return info.get(0).getAsJsonArray().get(0).getAsInt();
}
//mode 弹幕的模式 (1 普通 4 底端 5 顶端 6 逆向 7 特殊 9 高级)
public int getMode() {
return info.get(0).getAsJsonArray().get(1).getAsInt();
}
//fontSize 字体大小
public int getFontSize() {
return info.get(0).getAsJsonArray().get(2).getAsInt();
}
//color 字体颜色
public int getColor() {
return info.get(0).getAsJsonArray().get(3).getAsInt();
}
//弹幕发送时间(Unix 时间戳)(其实是服务器接收到弹幕的时间)
public long getSendTime() {
return info.get(0).getAsJsonArray().get(4).getAsInt();
}
//用户进入房间的时间(Unix 时间戳)(但是 Android 发送的弹幕, 这个值会是随机数)
public String getUserEnterTime() {
return info.get(0).getAsJsonArray().get(5).getAsString();
}
//得到弹幕内容
public String getMessage() {
return info.get(1).getAsString();
}
//得到发送者的用户 ID
public int getUserId() {
return info.get(2).getAsJsonArray().get(0).getAsInt();
}
//得到发送者的用户名
public String getUsername() {
return info.get(2).getAsJsonArray().get(1).getAsString();
}
//发送者是否是管理员
public boolean isAdmin() {
return info.get(2).getAsJsonArray().get(2).getAsBoolean();
}
//发送者是否是 VIP
public boolean isVip() {
return info.get(2).getAsJsonArray().get(3).getAsBoolean();
}
//发送者是否是 SVip
public boolean isSVip() {
return info.get(2).getAsJsonArray().get(4).getAsBoolean();
}
//表示粉丝勋章有关信息的 JsonArray 可能是空的
//获取粉丝勋章等级
public Optional<Integer> getFansMedalLevel() {
if (info.get(3).getAsJsonArray().size() > 0) {
return Optional.of(info.get(3).getAsJsonArray().get(0).getAsInt());
} else {
return Optional.empty();
}
}
//获取粉丝勋章名称
public Optional<String> getFansMedalName() {
if (info.get(3).getAsJsonArray().size() > 0) {
return Optional.of(info.get(3).getAsJsonArray().get(1).getAsString());
} else {
return Optional.empty();
}
}
//获取粉丝勋章对应的主播的名字
public Optional<String> getFansMedalOwnerName() {
if (info.get(3).getAsJsonArray().size() > 0) {
return Optional.of(info.get(3).getAsJsonArray().get(2).getAsString());
} else {
return Optional.empty();
}
}
//获取粉丝勋章对应的主播的直播间 ID
public Optional<String> getFansMedalOwnerRoomId() {
if (info.get(3).getAsJsonArray().size() > 0) {
return Optional.of(info.get(3).getAsJsonArray().get(3).getAsString());
} else {
return Optional.empty();
}
}
//获得用户的观众等级
public int getUserLevel() {
return info.get(4).getAsJsonArray().get(0).getAsInt();
}
//获得用户的观众等级排名
public String getUserRank() {
return info.get(4).getAsJsonArray().get(3).getAsString();
}
//获得用户头衔
public List<String> getUserTitles() {
return GSON.fromJson(info.get(5), LIST_STRING_TYPE);
}
}

View File

@ -0,0 +1,31 @@
package com.hiczp.bilibili.api.live.socket.entity;
import com.google.gson.annotations.SerializedName;
public class EnterRoomEntity {
@SerializedName("roomid")
private int roomId;
@SerializedName("uid")
private int userId;
public EnterRoomEntity(int roomId, int userId) {
this.roomId = roomId;
this.userId = userId;
}
public int getRoomId() {
return roomId;
}
public void setRoomId(int roomId) {
this.roomId = roomId;
}
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
}

View File

@ -0,0 +1,31 @@
package com.hiczp.bilibili.api.live.socket.entity;
import com.google.gson.annotations.SerializedName;
public class LiveEntity {
/**
* cmd : LIVE
* roomid : 1110317
*/
@SerializedName("cmd")
private String cmd;
@SerializedName("roomid")
private String roomId;
public String getCmd() {
return cmd;
}
public void setCmd(String cmd) {
this.cmd = cmd;
}
public String getRoomId() {
return roomId;
}
public void setRoomId(String roomId) {
this.roomId = roomId;
}
}

View File

@ -0,0 +1,31 @@
package com.hiczp.bilibili.api.live.socket.entity;
import com.google.gson.annotations.SerializedName;
public class PreparingEntity {
/**
* cmd : PREPARING
* roomid : 1110317
*/
@SerializedName("cmd")
private String cmd;
@SerializedName("roomid")
private String roomId;
public String getCmd() {
return cmd;
}
public void setCmd(String cmd) {
this.cmd = cmd;
}
public String getRoomId() {
return roomId;
}
public void setRoomId(String roomId) {
this.roomId = roomId;
}
}

View File

@ -0,0 +1,605 @@
package com.hiczp.bilibili.api.live.socket.entity;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.annotations.SerializedName;
import java.util.List;
public class SendGiftEntity {
/**
* cmd : SEND_GIFT
* data : {"giftName":"辣条","num":64,"uname":"明暗纠结星","rcost":66347035,"uid":12768615,"top_list":[{"uid":9256,"userName":"SSR","coin":2905900,"face":"http://i0.hdslb.com/bfs/face/eba52abb1daaf3aecd7b986b9731451872d66613.jpg","guard_level":"3","rank":1,"score":2905900},{"uid":4986301,"userName":"乄夏沫丶","coin":1386000,"face":"http://i2.hdslb.com/bfs/face/b3969027a263d2610711317addf437fe59a9b97e.jpg","guard_level":0,"rank":2,"score":1386000},{"uid":5211302,"userName":"朝雾怜","coin":805700,"face":"http://i1.hdslb.com/bfs/face/d366be69d716469514d355642aa324ceba3fa122.jpg","guard_level":0,"rank":3,"score":805700}],"timestamp":1510498496,"giftId":1,"giftType":0,"action":"喂食","super":0,"price":100,"rnd":"1510498460","newMedal":0,"newTitle":0,"medal":[],"title":"","beatId":"0","biz_source":"live","metadata":"","remain":0,"gold":0,"silver":0,"eventScore":0,"eventNum":0,"smalltv_msg":[],"notice_msg":[],"capsule":{"normal":{"coin":13,"change":1,"progress":{"now":4000,"max":10000}},"colorful":{"coin":0,"change":0,"progress":{"now":0,"max":5000}}},"addFollow":0}
*/
@SerializedName("cmd")
private String cmd;
@SerializedName("data")
private DataEntity data;
public String getCmd() {
return cmd;
}
public void setCmd(String cmd) {
this.cmd = cmd;
}
public DataEntity getData() {
return data;
}
public void setData(DataEntity data) {
this.data = data;
}
public static class DataEntity {
/**
* giftName : 辣条
* num : 64
* uname : 明暗纠结星
* rcost : 66347035
* uid : 12768615
* top_list : [{"uid":9256,"uname":"SSR","coin":2905900,"face":"http://i0.hdslb.com/bfs/face/eba52abb1daaf3aecd7b986b9731451872d66613.jpg","guard_level":"3","rank":1,"score":2905900},{"uid":4986301,"userName":"乄夏沫丶","coin":1386000,"face":"http://i2.hdslb.com/bfs/face/b3969027a263d2610711317addf437fe59a9b97e.jpg","guard_level":0,"rank":2,"score":1386000},{"uid":5211302,"userName":"朝雾怜","coin":805700,"face":"http://i1.hdslb.com/bfs/face/d366be69d716469514d355642aa324ceba3fa122.jpg","guard_level":0,"rank":3,"score":805700}]
* timestamp : 1510498496
* giftId : 1
* giftType : 0
* action : 喂食
* super : 0
* price : 100
* rnd : 1510498460
* newMedal : 0
* newTitle : 0
* medal : []
* title :
* beatId : 0
* biz_source : live
* metadata :
* remain : 0
* gold : 0
* silver : 0
* eventScore : 0
* eventNum : 0
* smalltv_msg : []
* notice_msg : []
* capsule : {"normal":{"coin":13,"change":1,"progress":{"now":4000,"max":10000}},"colorful":{"coin":0,"change":0,"progress":{"now":0,"max":5000}}}
* addFollow : 0
*/
@SerializedName("giftName")
private String giftName;
@SerializedName("num")
private int num;
@SerializedName("uname")
private String userName;
@SerializedName("rcost")
private int rCost;
@SerializedName("uid")
private int uid;
@SerializedName("timestamp")
private int timestamp;
@SerializedName("giftId")
private int giftId;
@SerializedName("giftType")
private int giftType;
@SerializedName("action")
private String action;
@SerializedName("super")
private int superX;
@SerializedName("price")
private int price;
@SerializedName("rnd")
private String rnd;
@SerializedName("newMedal")
private int newMedal;
@SerializedName("newTitle")
private int newTitle;
@SerializedName("title")
private String title;
@SerializedName("beatId")
private String beatId;
@SerializedName("biz_source")
private String bizSource;
@SerializedName("metadata")
private String metadata;
@SerializedName("remain")
private int remain;
@SerializedName("gold")
private int gold;
@SerializedName("silver")
private int silver;
@SerializedName("eventScore")
private int eventScore;
@SerializedName("eventNum")
private int eventNum;
@SerializedName("capsule")
private CapsuleEntity capsule;
@SerializedName("addFollow")
private int addFollow;
@SerializedName("top_list")
private List<TopListEntity> topList;
@SerializedName("medal")
private JsonElement medal;
@SerializedName("smalltv_msg")
private JsonElement smallTVMsg;
@SerializedName("notice_msg")
private List<?> noticeMsg;
public String getGiftName() {
return giftName;
}
public void setGiftName(String giftName) {
this.giftName = giftName;
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public int getrCost() {
return rCost;
}
public void setrCost(int rCost) {
this.rCost = rCost;
}
public int getUid() {
return uid;
}
public void setUid(int uid) {
this.uid = uid;
}
public int getTimestamp() {
return timestamp;
}
public void setTimestamp(int timestamp) {
this.timestamp = timestamp;
}
public int getGiftId() {
return giftId;
}
public void setGiftId(int giftId) {
this.giftId = giftId;
}
public int getGiftType() {
return giftType;
}
public void setGiftType(int giftType) {
this.giftType = giftType;
}
public String getAction() {
return action;
}
public void setAction(String action) {
this.action = action;
}
public int getSuperX() {
return superX;
}
public void setSuperX(int superX) {
this.superX = superX;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public String getRnd() {
return rnd;
}
public void setRnd(String rnd) {
this.rnd = rnd;
}
public int getNewMedal() {
return newMedal;
}
public void setNewMedal(int newMedal) {
this.newMedal = newMedal;
}
public int getNewTitle() {
return newTitle;
}
public void setNewTitle(int newTitle) {
this.newTitle = newTitle;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getBeatId() {
return beatId;
}
public void setBeatId(String beatId) {
this.beatId = beatId;
}
public String getBizSource() {
return bizSource;
}
public void setBizSource(String bizSource) {
this.bizSource = bizSource;
}
public String getMetadata() {
return metadata;
}
public void setMetadata(String metadata) {
this.metadata = metadata;
}
public int getRemain() {
return remain;
}
public void setRemain(int remain) {
this.remain = remain;
}
public int getGold() {
return gold;
}
public void setGold(int gold) {
this.gold = gold;
}
public int getSilver() {
return silver;
}
public void setSilver(int silver) {
this.silver = silver;
}
public int getEventScore() {
return eventScore;
}
public void setEventScore(int eventScore) {
this.eventScore = eventScore;
}
public int getEventNum() {
return eventNum;
}
public void setEventNum(int eventNum) {
this.eventNum = eventNum;
}
public CapsuleEntity getCapsule() {
return capsule;
}
public void setCapsule(CapsuleEntity capsule) {
this.capsule = capsule;
}
public int getAddFollow() {
return addFollow;
}
public void setAddFollow(int addFollow) {
this.addFollow = addFollow;
}
public List<TopListEntity> getTopList() {
return topList;
}
public void setTopList(List<TopListEntity> topList) {
this.topList = topList;
}
public JsonElement getMedal() {
return medal;
}
public void setMedal(JsonElement medal) {
this.medal = medal;
}
public JsonElement getSmallTVMsg() {
return smallTVMsg;
}
public void setSmallTVMsg(JsonObject smallTVMsg) {
this.smallTVMsg = smallTVMsg;
}
public List<?> getNoticeMsg() {
return noticeMsg;
}
public void setNoticeMsg(List<?> noticeMsg) {
this.noticeMsg = noticeMsg;
}
public static class CapsuleEntity {
/**
* normal : {"coin":13,"change":1,"progress":{"now":4000,"max":10000}}
* colorful : {"coin":0,"change":0,"progress":{"now":0,"max":5000}}
*/
@SerializedName("normal")
private NormalEntity normal;
@SerializedName("colorful")
private ColorfulEntity colorful;
public NormalEntity getNormal() {
return normal;
}
public void setNormal(NormalEntity normal) {
this.normal = normal;
}
public ColorfulEntity getColorful() {
return colorful;
}
public void setColorful(ColorfulEntity colorful) {
this.colorful = colorful;
}
public static class NormalEntity {
/**
* coin : 13
* change : 1
* progress : {"now":4000,"max":10000}
*/
@SerializedName("coin")
private int coin;
@SerializedName("change")
private int change;
@SerializedName("progress")
private ProgressEntity progress;
public int getCoin() {
return coin;
}
public void setCoin(int coin) {
this.coin = coin;
}
public int getChange() {
return change;
}
public void setChange(int change) {
this.change = change;
}
public ProgressEntity getProgress() {
return progress;
}
public void setProgress(ProgressEntity progress) {
this.progress = progress;
}
public static class ProgressEntity {
/**
* now : 4000
* max : 10000
*/
@SerializedName("now")
private int now;
@SerializedName("max")
private int max;
public int getNow() {
return now;
}
public void setNow(int now) {
this.now = now;
}
public int getMax() {
return max;
}
public void setMax(int max) {
this.max = max;
}
}
}
public static class ColorfulEntity {
/**
* coin : 0
* change : 0
* progress : {"now":0,"max":5000}
*/
@SerializedName("coin")
private int coin;
@SerializedName("change")
private int change;
@SerializedName("progress")
private ProgressEntityX progress;
public int getCoin() {
return coin;
}
public void setCoin(int coin) {
this.coin = coin;
}
public int getChange() {
return change;
}
public void setChange(int change) {
this.change = change;
}
public ProgressEntityX getProgress() {
return progress;
}
public void setProgress(ProgressEntityX progress) {
this.progress = progress;
}
public static class ProgressEntityX {
/**
* now : 0
* max : 5000
*/
@SerializedName("now")
private int now;
@SerializedName("max")
private int max;
public int getNow() {
return now;
}
public void setNow(int now) {
this.now = now;
}
public int getMax() {
return max;
}
public void setMax(int max) {
this.max = max;
}
}
}
}
public static class TopListEntity {
/**
* uid : 9256
* uname : SSR
* coin : 2905900
* face : http://i0.hdslb.com/bfs/face/eba52abb1daaf3aecd7b986b9731451872d66613.jpg
* guard_level : 3
* rank : 1
* score : 2905900
*/
@SerializedName("uid")
private int uid;
@SerializedName("uname")
private String userName;
@SerializedName("coin")
private int coin;
@SerializedName("face")
private String face;
@SerializedName("guard_level")
private String guardLevel;
@SerializedName("rank")
private int rank;
@SerializedName("score")
private int score;
public int getUid() {
return uid;
}
public void setUid(int uid) {
this.uid = uid;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public int getCoin() {
return coin;
}
public void setCoin(int coin) {
this.coin = coin;
}
public String getFace() {
return face;
}
public void setFace(String face) {
this.face = face;
}
public String getGuardLevel() {
return guardLevel;
}
public void setGuardLevel(String guardLevel) {
this.guardLevel = guardLevel;
}
public int getRank() {
return rank;
}
public void setRank(int rank) {
this.rank = rank;
}
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
}
}
}

View File

@ -0,0 +1,108 @@
package com.hiczp.bilibili.api.live.socket.entity;
import com.google.gson.annotations.SerializedName;
public class SysGiftEntity {
/**
* cmd : SYS_GIFT
* msg : あさひなみよう在直播间5135178开启了丰收祭典一起来分享收获的福利吧
* msg_text : あさひなみよう在直播间5135178开启了丰收祭典一起来分享收获的福利吧
* tips : あさひなみよう在直播间5135178开启了丰收祭典一起来分享收获的福利吧
* url : http://live.bilibili.com/5135178
* roomid : 5135178
* real_roomid : 5135178
* giftId : 103
* msgTips : 0
*/
@SerializedName("cmd")
private String cmd;
@SerializedName("msg")
private String msg;
@SerializedName("msg_text")
private String msgText;
@SerializedName("tips")
private String tips;
@SerializedName("url")
private String url;
@SerializedName("roomid")
private int roomId;
@SerializedName("real_roomid")
private int realRoomId;
@SerializedName("giftId")
private int giftId;
@SerializedName("msgTips")
private int msgTips;
public String getCmd() {
return cmd;
}
public void setCmd(String cmd) {
this.cmd = cmd;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public String getMsgText() {
return msgText;
}
public void setMsgText(String msgText) {
this.msgText = msgText;
}
public String getTips() {
return tips;
}
public void setTips(String tips) {
this.tips = tips;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public int getRoomId() {
return roomId;
}
public void setRoomId(int roomId) {
this.roomId = roomId;
}
public int getRealRoomId() {
return realRoomId;
}
public void setRealRoomId(int realRoomId) {
this.realRoomId = realRoomId;
}
public int getGiftId() {
return giftId;
}
public void setGiftId(int giftId) {
this.giftId = giftId;
}
public int getMsgTips() {
return msgTips;
}
public void setMsgTips(int msgTips) {
this.msgTips = msgTips;
}
}

View File

@ -0,0 +1,120 @@
package com.hiczp.bilibili.api.live.socket.entity;
import com.google.gson.annotations.SerializedName;
public class SysMSGEntity {
/**
* cmd : SYS_MSG
* msg : 瑾然-:?在直播间:?3939852:?赠送 小电视一个请前往抽奖
* msg_text : 瑾然-:?在直播间:?3939852:?赠送 小电视一个请前往抽奖
* rep : 1
* styleType : 2
* url : http://live.bilibili.com/3939852
* roomid : 3939852
* real_roomid : 3939852
* rnd : 1510499432
* tv_id : 29318
*/
@SerializedName("cmd")
private String cmd;
@SerializedName("msg")
private String msg;
//B站自己的广告, msgText 可能是空的
@SerializedName("msg_text")
private String msgText;
@SerializedName("rep")
private int rep;
@SerializedName("styleType")
private int styleType;
@SerializedName("url")
private String url;
@SerializedName("roomid")
private int roomId;
@SerializedName("real_roomid")
private int realRoomId;
@SerializedName("rnd")
private int rnd;
@SerializedName("tv_id")
private String tvId;
public String getCmd() {
return cmd;
}
public void setCmd(String cmd) {
this.cmd = cmd;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public String getMsgText() {
return msgText;
}
public void setMsgText(String msgText) {
this.msgText = msgText;
}
public int getRep() {
return rep;
}
public void setRep(int rep) {
this.rep = rep;
}
public int getStyleType() {
return styleType;
}
public void setStyleType(int styleType) {
this.styleType = styleType;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public int getRoomId() {
return roomId;
}
public void setRoomId(int roomId) {
this.roomId = roomId;
}
public int getRealRoomId() {
return realRoomId;
}
public void setRealRoomId(int realRoomId) {
this.realRoomId = realRoomId;
}
public int getRnd() {
return rnd;
}
public void setRnd(int rnd) {
this.rnd = rnd;
}
public String getTvId() {
return tvId;
}
public void setTvId(String tvId) {
this.tvId = tvId;
}
}

View File

@ -0,0 +1,81 @@
package com.hiczp.bilibili.api.live.socket.entity;
import com.google.gson.annotations.SerializedName;
public class WelcomeEntity {
/**
* cmd : WELCOME
* data : {"uid":516505,"uname":"圣蝎","is_admin":false,"vip":1}
*/
@SerializedName("cmd")
private String cmd;
@SerializedName("data")
private DataEntity data;
public String getCmd() {
return cmd;
}
public void setCmd(String cmd) {
this.cmd = cmd;
}
public DataEntity getData() {
return data;
}
public void setData(DataEntity data) {
this.data = data;
}
public static class DataEntity {
/**
* uid : 516505
* uname : 圣蝎
* is_admin : false
* vip : 1
*/
@SerializedName("uid")
private int uid;
@SerializedName("uname")
private String userName;
@SerializedName("is_admin")
private boolean isAdmin;
@SerializedName("vip")
private int vip;
public int getUid() {
return uid;
}
public void setUid(int uid) {
this.uid = uid;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public boolean isIsAdmin() {
return isAdmin;
}
public void setIsAdmin(boolean isAdmin) {
this.isAdmin = isAdmin;
}
public int getVip() {
return vip;
}
public void setVip(int vip) {
this.vip = vip;
}
}
}

View File

@ -0,0 +1,92 @@
package com.hiczp.bilibili.api.live.socket.entity;
import com.google.gson.annotations.SerializedName;
public class WelcomeGuardEntity {
/**
* cmd : WELCOME_GUARD
* data : {"uid":23598108,"username":"lovevael","guard_level":3,"water_god":0}
* roomid : 43001
*/
@SerializedName("cmd")
private String cmd;
@SerializedName("data")
private DataEntity data;
@SerializedName("roomid")
private int roomId;
public String getCmd() {
return cmd;
}
public void setCmd(String cmd) {
this.cmd = cmd;
}
public DataEntity getData() {
return data;
}
public void setData(DataEntity data) {
this.data = data;
}
public int getRoomId() {
return roomId;
}
public void setRoomId(int roomId) {
this.roomId = roomId;
}
public static class DataEntity {
/**
* uid : 23598108
* username : lovevael
* guard_level : 3
* water_god : 0
*/
@SerializedName("uid")
private int uid;
@SerializedName("username")
private String username;
@SerializedName("guard_level")
private int guardLevel;
@SerializedName("water_god")
private int waterGod;
public int getUid() {
return uid;
}
public void setUid(int uid) {
this.uid = uid;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public int getGuardLevel() {
return guardLevel;
}
public void setGuardLevel(int guardLevel) {
this.guardLevel = guardLevel;
}
public int getWaterGod() {
return waterGod;
}
public void setWaterGod(int waterGod) {
this.waterGod = waterGod;
}
}
}

View File

@ -0,0 +1,156 @@
package com.hiczp.bilibili.api.test;
import com.google.gson.Gson;
import com.hiczp.bilibili.api.BilibiliRESTAPI;
import com.hiczp.bilibili.api.live.entity.BulletScreenEntity;
import com.hiczp.bilibili.api.live.entity.LiveRoomInfoEntity;
import com.hiczp.bilibili.api.live.entity.SendBulletScreenResponseEntity;
import com.hiczp.bilibili.api.live.socket.BulletScreenListenerAdaptor;
import com.hiczp.bilibili.api.live.socket.LiveClient;
import com.hiczp.bilibili.api.live.socket.PackageRepository;
import com.hiczp.bilibili.api.live.socket.Utils;
import com.hiczp.bilibili.api.live.socket.entity.*;
import org.junit.FixMethodOrder;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runners.MethodSorters;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SocketChannel;
@Ignore
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class LiveRoomTest {
private static final Logger LOGGER = LoggerFactory.getLogger(LiveRoomTest.class);
private static final Config CONFIG = Config.getInstance();
private static final int USER_ID = BilibiliRESTAPI.getMid();
private static final int STOP_AFTER_N_HEART_BEATS = 3;
private static final int STOP_AFTER_SECOND = 90;
@Ignore
@Test
public void _0socketTest() throws IOException {
LOGGER.info("Start socket connection to live Bullet Screen stream server, test continue for {} heart beat", STOP_AFTER_N_HEART_BEATS);
LiveRoomInfoEntity.LiveRoomEntity liveRoomEntity = BilibiliRESTAPI.getLiveService().getRoomInfo(CONFIG.getRoomId()).execute().body().getData();
int roomId = liveRoomEntity.getRoomId();
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress(liveRoomEntity.getCmt(), liveRoomEntity.getCmtPortGoim()));
socketChannel.write(PackageRepository.createEnterRoomPackage(roomId, USER_ID));
Thread thread = new Thread(() -> {
while (true) {
try {
Utils.printBytes(PackageRepository.readNextPackage(socketChannel).array());
System.out.println();
} catch (IOException e) {
break;
}
}
});
thread.start();
for (int i = STOP_AFTER_N_HEART_BEATS; i > 0; i--) {
socketChannel.write(PackageRepository.createHeartBeatPackage(roomId, USER_ID));
LOGGER.debug("Send heart beat package");
BilibiliRESTAPI.getLiveService().sendBulletScreen(new BulletScreenEntity(roomId, "send heart beat")).enqueue(new Callback<SendBulletScreenResponseEntity>() {
private Gson gson = new Gson();
@Override
public void onResponse(Call<SendBulletScreenResponseEntity> call, Response<SendBulletScreenResponseEntity> response) {
gson.toJson(response.body(), System.out);
System.out.println();
}
@Override
public void onFailure(Call<SendBulletScreenResponseEntity> call, Throwable throwable) {
throwable.printStackTrace();
}
});
try {
Thread.sleep(30 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
}
socketChannel.close();
}
@Test
public void _1liveClientTest() throws IOException {
int roomId = CONFIG.getRoomId();
LOGGER.info("Start LiveClientTest for room {}", roomId);
LiveClient liveClient = new LiveClient(roomId, USER_ID)
.addListener(new BulletScreenListenerAdaptor() {
@Override
public void onConnect() {
LOGGER.info("Connected");
}
@Override
public void onDisconnect() {
LOGGER.info("Disconnected");
}
@Override
public void onViewerCountPackage(int viewerCount) {
LOGGER.info("Current viewers: {}", viewerCount);
}
@Override
public void onDanMuMSGPackage(DanMuMSGEntity danMuMSGEntity) {
LOGGER.info("[{}]{}", danMuMSGEntity.getUsername(), danMuMSGEntity.getMessage());
}
@Override
public void onSendGiftPackage(SendGiftEntity sendGiftEntity) {
SendGiftEntity.DataEntity dataEntity = sendGiftEntity.getData();
LOGGER.info("{} send {} * {}", dataEntity.getUserName(), dataEntity.getGiftName(), dataEntity.getNum());
}
@Override
public void onSysMSGPackage(SysMSGEntity sysMSGEntity) {
LOGGER.info("System message: {} {}", sysMSGEntity.getMsg(), sysMSGEntity.getUrl());
}
@Override
public void onSysGiftPackage(SysGiftEntity sysGiftEntity) {
LOGGER.info("System gift: {} {}", sysGiftEntity.getMsg(), sysGiftEntity.getUrl());
}
@Override
public void onWelcomePackage(WelcomeEntity welcomeEntity) {
LOGGER.info("Welcome {}", welcomeEntity.getData().getUserName());
}
@Override
public void onWelcomeGuardPackage(WelcomeGuardEntity welcomeGuardEntity) {
WelcomeGuardEntity.DataEntity dataEntity = welcomeGuardEntity.getData();
LOGGER.info("Welcome guard [Lv{}]{}", dataEntity.getGuardLevel(), dataEntity.getUsername());
}
@Override
public void onLivePackage(LiveEntity liveEntity) {
LOGGER.info("Room {} start live", liveEntity.getRoomId());
}
@Override
public void onPreparingPackage(PreparingEntity preparingEntity) {
LOGGER.info("Room {} stop live", preparingEntity.getRoomId());
}
})
.connect();
try {
Thread.sleep(STOP_AFTER_SECOND * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
liveClient.close();
}
}

View File

@ -5,6 +5,7 @@ import com.google.gson.GsonBuilder;
import com.hiczp.bilibili.api.BilibiliRESTAPI;
import org.junit.After;
import org.junit.FixMethodOrder;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runners.MethodSorters;
import org.slf4j.Logger;
@ -34,6 +35,7 @@ public class LoginTest {
);
}
@Ignore
@Test
public void _2refreshToken() throws Exception {
LOGGER.info("Refreshing token");

View File

@ -14,6 +14,7 @@ import java.io.InputStreamReader;
@Suite.SuiteClasses({
LoginTest.class,
LiveServiceTest.class,
LiveRoomTest.class,
LogoutTest.class
})
public class RuleSuite {