mirror of
https://github.com/czp3009/bilibili-api.git
synced 2025-02-19 20:50:28 +08:00
使用 Netty 重新实现 LiveClient, 使用 guava EventBus 来作为事件机制实现
This commit is contained in:
parent
7b85c8a956
commit
a8e3aa15b8
@ -24,7 +24,7 @@ dependencies {
|
||||
// https://mvnrepository.com/artifact/org.slf4j/slf4j-api
|
||||
compile group: 'org.slf4j', name: 'slf4j-api', version: '1.7.25'
|
||||
// https://mvnrepository.com/artifact/io.netty/netty-all
|
||||
compile group: 'io.netty', name: 'netty-all', version: '4.1.19.Final'
|
||||
compile group: 'io.netty', name: 'netty-all', version: '4.1.20.Final'
|
||||
// https://mvnrepository.com/artifact/com.google.guava/guava
|
||||
compile group: 'com.google.guava', name: 'guava', version: '23.6-jre'
|
||||
}
|
||||
|
9
record/bullet_screen_stream_json/ACITIVITY_EVENT.json
Normal file
9
record/bullet_screen_stream_json/ACITIVITY_EVENT.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"cmd": "ACTIVITY_EVENT",
|
||||
"data": {
|
||||
"keyword": "newspring_2018",
|
||||
"type": "cracker",
|
||||
"limit": 300000,
|
||||
"progress": 158912
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
{
|
||||
"cmd": "LIVE",
|
||||
"roomid": 1110317
|
||||
"roomid": "1110317"
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
{
|
||||
"cmd": "PREPARING",
|
||||
"roomid": 1110317
|
||||
"roomid": "1110317"
|
||||
}
|
||||
|
@ -55,7 +55,6 @@ public class BilibiliAPI implements BilibiliServiceProvider, LiveClientProvider
|
||||
@Override
|
||||
public PassportService getPassportService() {
|
||||
if (passportService == null) {
|
||||
LOGGER.debug("Init PassportService in BilibiliAPI instance {}", this.hashCode());
|
||||
OkHttpClient okHttpClient = new OkHttpClient().newBuilder()
|
||||
.addInterceptor(new AddFixedParamsInterceptor(
|
||||
"build", bilibiliClientProperties.getBuild(),
|
||||
@ -84,7 +83,6 @@ public class BilibiliAPI implements BilibiliServiceProvider, LiveClientProvider
|
||||
@Override
|
||||
public LiveService getLiveService() {
|
||||
if (liveService == null) {
|
||||
LOGGER.debug("Init LiveService in BilibiliAPI instance {}", this.hashCode());
|
||||
OkHttpClient okHttpClient = new OkHttpClient.Builder()
|
||||
.addInterceptor(new AddFixedHeadersInterceptor(
|
||||
"Buvid", bilibiliClientProperties.getBuvId(),
|
||||
|
@ -1,15 +1,32 @@
|
||||
package com.hiczp.bilibili.api.live.socket;
|
||||
|
||||
import com.google.common.eventbus.EventBus;
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
import com.hiczp.bilibili.api.BilibiliServiceProvider;
|
||||
import com.hiczp.bilibili.api.live.entity.LiveRoomInfoEntity;
|
||||
import com.hiczp.bilibili.api.live.socket.codec.PackageDecoder;
|
||||
import com.hiczp.bilibili.api.live.socket.codec.PackageEncoder;
|
||||
import com.hiczp.bilibili.api.live.socket.event.ConnectSucceedEvent;
|
||||
import com.hiczp.bilibili.api.live.socket.event.ConnectionCloseEvent;
|
||||
import com.hiczp.bilibili.api.live.socket.handler.LiveClientHandler;
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
|
||||
import io.netty.handler.timeout.IdleStateHandler;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class LiveClient {
|
||||
public class LiveClient implements Closeable {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(LiveClient.class);
|
||||
|
||||
private final EventBus eventBus = new EventBus("BilibiliLiveClient");
|
||||
@ -17,42 +34,158 @@ public class LiveClient {
|
||||
private final long showRoomId;
|
||||
private final long userId;
|
||||
|
||||
private LiveRoomInfoEntity.LiveRoomEntity liveRoomEntity;
|
||||
private long reconnectLimit = 0;
|
||||
private long reconnectAttempt = 0;
|
||||
private long reconnectDelay = 5L;
|
||||
private boolean userWantClose = false;
|
||||
|
||||
private Long roomId;
|
||||
private EventLoopGroup eventLoopGroup;
|
||||
private Channel channel;
|
||||
|
||||
public LiveClient(BilibiliServiceProvider bilibiliServiceProvider, long showRoomId) {
|
||||
this.bilibiliServiceProvider = bilibiliServiceProvider;
|
||||
this.showRoomId = showRoomId;
|
||||
this.userId = 0;
|
||||
initEventBus();
|
||||
}
|
||||
|
||||
public LiveClient(BilibiliServiceProvider bilibiliServiceProvider, long showRoomId, long userId) {
|
||||
this.bilibiliServiceProvider = bilibiliServiceProvider;
|
||||
this.showRoomId = showRoomId;
|
||||
this.userId = userId;
|
||||
initEventBus();
|
||||
}
|
||||
|
||||
private void fetchRoomInfo() throws IOException {
|
||||
liveRoomEntity = bilibiliServiceProvider.getLiveService()
|
||||
private void initEventBus() {
|
||||
eventBus.register(this);
|
||||
}
|
||||
|
||||
public synchronized LiveClient connect() throws IOException {
|
||||
if (channel != null && channel.isActive()) {
|
||||
LOGGER.warn("Already connected to server, connect method can not be invoked twice");
|
||||
return this;
|
||||
}
|
||||
|
||||
reconnectAttempt++;
|
||||
|
||||
LOGGER.info("Fetching info of live room {}", showRoomId);
|
||||
LiveRoomInfoEntity.LiveRoomEntity liveRoomEntity = bilibiliServiceProvider.getLiveService()
|
||||
.getRoomInfo(showRoomId)
|
||||
.execute()
|
||||
.body()
|
||||
.getData();
|
||||
roomId = liveRoomEntity.getRoomId();
|
||||
LOGGER.info("Get actual room id {}", roomId);
|
||||
|
||||
if (eventLoopGroup != null) {
|
||||
eventLoopGroup.shutdownGracefully();
|
||||
}
|
||||
eventLoopGroup = new NioEventLoopGroup(1);
|
||||
LOGGER.debug("Init SocketChannel Bootstrap");
|
||||
Bootstrap bootstrap = new Bootstrap()
|
||||
.group(eventLoopGroup)
|
||||
.channel(NioSocketChannel.class)
|
||||
.handler(new ChannelInitializer<SocketChannel>() {
|
||||
@Override
|
||||
protected void initChannel(SocketChannel socketChannel) throws Exception {
|
||||
socketChannel.pipeline()
|
||||
.addLast(new LengthFieldBasedFrameDecoder(
|
||||
Integer.MAX_VALUE,
|
||||
0,
|
||||
Package.LENGTH_FIELD_LENGTH,
|
||||
-Package.LENGTH_FIELD_LENGTH,
|
||||
0
|
||||
))
|
||||
.addLast(new IdleStateHandler(40, 0, 0))
|
||||
//.addLast(new LoggingHandler(LogLevel.DEBUG))
|
||||
.addLast(new PackageEncoder())
|
||||
.addLast(new PackageDecoder())
|
||||
.addLast(new LiveClientHandler(roomId, userId, eventBus));
|
||||
}
|
||||
});
|
||||
|
||||
String address = liveRoomEntity.getCmt();
|
||||
int port = liveRoomEntity.getCmtPortGoim();
|
||||
LOGGER.info("Connecting to Bullet Screen server {}:{}", address, port);
|
||||
try {
|
||||
channel = bootstrap.connect(address, port)
|
||||
.sync()
|
||||
.channel();
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
close();
|
||||
} catch (Exception e) { //有可能在此时出现网络错误
|
||||
close();
|
||||
throw new IOException(e);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public LiveClient connect() throws IOException {
|
||||
LOGGER.info("Try to fetch info of live room {}", showRoomId);
|
||||
fetchRoomInfo();
|
||||
LOGGER.info("Get actual room id {}", liveRoomEntity.getRoomId());
|
||||
@Override
|
||||
public synchronized void close() {
|
||||
userWantClose = true;
|
||||
if (eventLoopGroup != null) {
|
||||
LOGGER.info("Closing connection");
|
||||
try {
|
||||
eventLoopGroup.shutdownGracefully().sync();
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
eventLoopGroup = null;
|
||||
}
|
||||
}
|
||||
|
||||
LOGGER.debug("Init NioEventLoop");
|
||||
@Subscribe
|
||||
public void onConnectSucceed(ConnectSucceedEvent connectSucceedEvent) {
|
||||
LOGGER.info("Connect succeed");
|
||||
if (userWantClose) {
|
||||
reconnectAttempt = 0;
|
||||
}
|
||||
}
|
||||
|
||||
throw new UnsupportedOperationException();
|
||||
@Subscribe
|
||||
public void onConnectionClose(ConnectionCloseEvent connectionCloseEvent) {
|
||||
LOGGER.info("Connection closed");
|
||||
if (!userWantClose && reconnectAttempt <= reconnectLimit) {
|
||||
LOGGER.info("Reconnect attempt {}, limit {}", reconnectAttempt, reconnectLimit);
|
||||
LOGGER.info("Auto reconnect after {} second", reconnectDelay);
|
||||
eventLoopGroup.schedule(
|
||||
() -> {
|
||||
try {
|
||||
connect();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
},
|
||||
reconnectDelay,
|
||||
TimeUnit.SECONDS
|
||||
);
|
||||
} else {
|
||||
eventLoopGroup.shutdownGracefully();
|
||||
}
|
||||
|
||||
if (userWantClose) {
|
||||
userWantClose = false;
|
||||
reconnectAttempt = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public EventBus getEventBus() {
|
||||
return eventBus;
|
||||
}
|
||||
|
||||
public LiveClient registerListener(Object object) {
|
||||
eventBus.register(object);
|
||||
return this;
|
||||
}
|
||||
|
||||
public LiveClient unregisterListeners(Object object) {
|
||||
eventBus.unregister(object);
|
||||
return this;
|
||||
}
|
||||
|
||||
public long getShowRoomId() {
|
||||
return showRoomId;
|
||||
}
|
||||
@ -61,7 +194,17 @@ public class LiveClient {
|
||||
return userId;
|
||||
}
|
||||
|
||||
public Optional<LiveRoomInfoEntity.LiveRoomEntity> getLiveRoomEntity() {
|
||||
return Optional.of(liveRoomEntity);
|
||||
public Optional<Long> getRoomId() {
|
||||
return Optional.of(roomId);
|
||||
}
|
||||
|
||||
public LiveClient setReconnectLimit(long reconnectLimit) {
|
||||
this.reconnectLimit = reconnectLimit;
|
||||
return this;
|
||||
}
|
||||
|
||||
public LiveClient setReconnectDelay(long reconnectDelay) {
|
||||
this.reconnectDelay = reconnectDelay;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
115
src/main/java/com/hiczp/bilibili/api/live/socket/Package.java
Normal file
115
src/main/java/com/hiczp/bilibili/api/live/socket/Package.java
Normal file
@ -0,0 +1,115 @@
|
||||
package com.hiczp.bilibili.api.live.socket;
|
||||
|
||||
//数据包结构说明
|
||||
//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 Package {
|
||||
public static final short LENGTH_FIELD_LENGTH = 4;
|
||||
|
||||
private static final short PROTOCOL_HEAD_LENGTH = 16;
|
||||
|
||||
private final int packageLength;
|
||||
private final short protocolHeadLength;
|
||||
private final DeviceType shortDeviceType;
|
||||
private final PackageType packageType;
|
||||
private final DeviceType longDeviceType;
|
||||
private final byte[] content;
|
||||
|
||||
public Package(int packageLength, short protocolHeadLength, DeviceType shortDeviceType, PackageType packageType, DeviceType longDeviceType, byte[] content) {
|
||||
this.packageLength = packageLength;
|
||||
this.protocolHeadLength = protocolHeadLength;
|
||||
this.shortDeviceType = shortDeviceType;
|
||||
this.packageType = packageType;
|
||||
this.longDeviceType = longDeviceType;
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
public Package(PackageType packageType, byte[] content) {
|
||||
this(PROTOCOL_HEAD_LENGTH + content.length,
|
||||
PROTOCOL_HEAD_LENGTH,
|
||||
DeviceType.ANDROID,
|
||||
packageType,
|
||||
DeviceType.ANDROID,
|
||||
content
|
||||
);
|
||||
}
|
||||
|
||||
public int getPackageLength() {
|
||||
return packageLength;
|
||||
}
|
||||
|
||||
public short getProtocolHeadLength() {
|
||||
return protocolHeadLength;
|
||||
}
|
||||
|
||||
public DeviceType getShortDeviceType() {
|
||||
return shortDeviceType;
|
||||
}
|
||||
|
||||
public PackageType getPackageType() {
|
||||
return packageType;
|
||||
}
|
||||
|
||||
public DeviceType getLongDeviceType() {
|
||||
return longDeviceType;
|
||||
}
|
||||
|
||||
public byte[] getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public enum DeviceType {
|
||||
ANDROID(0x00);
|
||||
|
||||
private final int value;
|
||||
|
||||
DeviceType(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public static DeviceType valueOf(int value) {
|
||||
for (DeviceType deviceType : DeviceType.values()) {
|
||||
if (deviceType.value == value) {
|
||||
return deviceType;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("No matching constant for [" + value + "]");
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
public enum PackageType {
|
||||
HEART_BEAT(0x02),
|
||||
VIEWER_COUNT(0x03),
|
||||
DATA(0x05),
|
||||
ENTER_ROOM(0x07),
|
||||
ENTER_ROOM_SUCCESS(0x08);
|
||||
|
||||
private final int value;
|
||||
|
||||
PackageType(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public static PackageType valueOf(int value) {
|
||||
for (PackageType packageType : PackageType.values()) {
|
||||
if (packageType.value == value) {
|
||||
return packageType;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("No matching constant for [" + value + "]");
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package com.hiczp.bilibili.api.live.socket;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.hiczp.bilibili.api.live.socket.entity.EnterRoomEntity;
|
||||
|
||||
public class PackageHelper {
|
||||
private static final Gson GSON = new Gson();
|
||||
|
||||
public static Package createEnterRoomPackage(long roomId, long userId) {
|
||||
return new Package(
|
||||
Package.PackageType.ENTER_ROOM,
|
||||
GSON.toJson(new EnterRoomEntity(roomId, userId)).getBytes()
|
||||
);
|
||||
}
|
||||
|
||||
public static Package createHeatBeatPackage() {
|
||||
return new Package(
|
||||
Package.PackageType.HEART_BEAT,
|
||||
new byte[0]
|
||||
);
|
||||
}
|
||||
}
|
@ -1,143 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
@ -1,54 +0,0 @@
|
||||
package com.hiczp.bilibili.api.live.socket;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package com.hiczp.bilibili.api.live.socket.codec;
|
||||
|
||||
import com.hiczp.bilibili.api.live.socket.Package;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class PackageDecoder extends ByteToMessageDecoder {
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
|
||||
int packageLength = in.readInt();
|
||||
short protocolHeadLength = in.readShort();
|
||||
Package.DeviceType shortDeviceType = Package.DeviceType.valueOf(in.readShort());
|
||||
Package.PackageType packageType = Package.PackageType.valueOf(in.readInt());
|
||||
Package.DeviceType longDeviceType = Package.DeviceType.valueOf(in.readInt());
|
||||
byte[] content = new byte[packageLength - protocolHeadLength];
|
||||
in.readBytes(content);
|
||||
|
||||
out.add(new Package(
|
||||
packageLength,
|
||||
protocolHeadLength,
|
||||
shortDeviceType,
|
||||
packageType,
|
||||
longDeviceType,
|
||||
content
|
||||
));
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package com.hiczp.bilibili.api.live.socket.codec;
|
||||
|
||||
import com.hiczp.bilibili.api.live.socket.Package;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.MessageToByteEncoder;
|
||||
|
||||
public class PackageEncoder extends MessageToByteEncoder<Package> {
|
||||
@Override
|
||||
protected void encode(ChannelHandlerContext ctx, Package msg, ByteBuf out) throws Exception {
|
||||
out.writeInt(msg.getPackageLength())
|
||||
.writeShort(msg.getProtocolHeadLength())
|
||||
.writeShort(msg.getShortDeviceType().getValue())
|
||||
.writeInt(msg.getPackageType().getValue())
|
||||
.writeInt(msg.getLongDeviceType().getValue())
|
||||
.writeBytes(msg.getContent());
|
||||
}
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
package com.hiczp.bilibili.api.live.socket.entity;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
public class ActivityEventEntity {
|
||||
/**
|
||||
* cmd : ACTIVITY_EVENT
|
||||
* data : {"keyword":"newspring_2018","type":"cracker","limit":300000,"progress":158912}
|
||||
*/
|
||||
|
||||
@SerializedName("cmd")
|
||||
private String cmd;
|
||||
@SerializedName("data")
|
||||
private Data data;
|
||||
|
||||
public String getCmd() {
|
||||
return cmd;
|
||||
}
|
||||
|
||||
public void setCmd(String cmd) {
|
||||
this.cmd = cmd;
|
||||
}
|
||||
|
||||
public Data getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public void setData(Data data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public static class Data {
|
||||
/**
|
||||
* keyword : newspring_2018
|
||||
* type : cracker
|
||||
* limit : 300000
|
||||
* progress : 158912
|
||||
*/
|
||||
|
||||
@SerializedName("keyword")
|
||||
private String keyword;
|
||||
@SerializedName("type")
|
||||
private String type;
|
||||
@SerializedName("limit")
|
||||
private int limit;
|
||||
@SerializedName("progress")
|
||||
private int progress;
|
||||
|
||||
public String getKeyword() {
|
||||
return keyword;
|
||||
}
|
||||
|
||||
public void setKeyword(String keyword) {
|
||||
this.keyword = keyword;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public int getLimit() {
|
||||
return limit;
|
||||
}
|
||||
|
||||
public void setLimit(int limit) {
|
||||
this.limit = limit;
|
||||
}
|
||||
|
||||
public int getProgress() {
|
||||
return progress;
|
||||
}
|
||||
|
||||
public void setProgress(int progress) {
|
||||
this.progress = progress;
|
||||
}
|
||||
}
|
||||
}
|
@ -9,7 +9,7 @@ import java.lang.reflect.Type;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public class DanMuMSGEntity {
|
||||
public class DanMuMsgEntity {
|
||||
private static final Gson GSON = new Gson();
|
||||
private static final Type STRING_LIST_TYPE = new TypeToken<List<String>>() {
|
||||
}.getType();
|
||||
@ -76,8 +76,8 @@ public class DanMuMSGEntity {
|
||||
}
|
||||
|
||||
//得到发送者的用户 ID
|
||||
public int getUserId() {
|
||||
return info.get(2).getAsJsonArray().get(0).getAsInt();
|
||||
public long getUserId() {
|
||||
return info.get(2).getAsJsonArray().get(0).getAsLong();
|
||||
}
|
||||
|
||||
//得到发送者的用户名
|
@ -4,28 +4,28 @@ import com.google.gson.annotations.SerializedName;
|
||||
|
||||
public class EnterRoomEntity {
|
||||
@SerializedName("roomid")
|
||||
private int roomId;
|
||||
private long roomId;
|
||||
@SerializedName("uid")
|
||||
private int userId;
|
||||
private long userId;
|
||||
|
||||
public EnterRoomEntity(int roomId, int userId) {
|
||||
public EnterRoomEntity(long roomId, long userId) {
|
||||
this.roomId = roomId;
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
public int getRoomId() {
|
||||
public long getRoomId() {
|
||||
return roomId;
|
||||
}
|
||||
|
||||
public void setRoomId(int roomId) {
|
||||
public void setRoomId(long roomId) {
|
||||
this.roomId = roomId;
|
||||
}
|
||||
|
||||
public int getUserId() {
|
||||
public long getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
public void setUserId(int userId) {
|
||||
public void setUserId(long userId) {
|
||||
this.userId = userId;
|
||||
}
|
||||
}
|
||||
|
@ -75,9 +75,9 @@ public class SendGiftEntity {
|
||||
@SerializedName("rcost")
|
||||
private int rCost;
|
||||
@SerializedName("uid")
|
||||
private int uid;
|
||||
private long uid;
|
||||
@SerializedName("timestamp")
|
||||
private int timestamp;
|
||||
private long timestamp;
|
||||
@SerializedName("giftId")
|
||||
private int giftId;
|
||||
@SerializedName("giftType")
|
||||
@ -157,19 +157,19 @@ public class SendGiftEntity {
|
||||
this.rCost = rCost;
|
||||
}
|
||||
|
||||
public int getUid() {
|
||||
public long getUid() {
|
||||
return uid;
|
||||
}
|
||||
|
||||
public void setUid(int uid) {
|
||||
public void setUid(long uid) {
|
||||
this.uid = uid;
|
||||
}
|
||||
|
||||
public int getTimestamp() {
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public void setTimestamp(int timestamp) {
|
||||
public void setTimestamp(long timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
|
@ -26,9 +26,9 @@ public class SysGiftEntity {
|
||||
@SerializedName("url")
|
||||
private String url;
|
||||
@SerializedName("roomid")
|
||||
private int roomId;
|
||||
private long roomId;
|
||||
@SerializedName("real_roomid")
|
||||
private int realRoomId;
|
||||
private long realRoomId;
|
||||
@SerializedName("giftId")
|
||||
private int giftId;
|
||||
@SerializedName("msgTips")
|
||||
@ -74,19 +74,19 @@ public class SysGiftEntity {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public int getRoomId() {
|
||||
public long getRoomId() {
|
||||
return roomId;
|
||||
}
|
||||
|
||||
public void setRoomId(int roomId) {
|
||||
public void setRoomId(long roomId) {
|
||||
this.roomId = roomId;
|
||||
}
|
||||
|
||||
public int getRealRoomId() {
|
||||
public long getRealRoomId() {
|
||||
return realRoomId;
|
||||
}
|
||||
|
||||
public void setRealRoomId(int realRoomId) {
|
||||
public void setRealRoomId(long realRoomId) {
|
||||
this.realRoomId = realRoomId;
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,7 @@ package com.hiczp.bilibili.api.live.socket.entity;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
public class SysMSGEntity {
|
||||
public class SysMsgEntity {
|
||||
/**
|
||||
* cmd : SYS_MSG
|
||||
* msg : 【瑾然-】:?在直播间:?【3939852】:?赠送 小电视一个,请前往抽奖
|
||||
@ -30,11 +30,11 @@ public class SysMSGEntity {
|
||||
@SerializedName("url")
|
||||
private String url;
|
||||
@SerializedName("roomid")
|
||||
private int roomId;
|
||||
private long roomId;
|
||||
@SerializedName("real_roomid")
|
||||
private int realRoomId;
|
||||
private long realRoomId;
|
||||
@SerializedName("rnd")
|
||||
private int rnd;
|
||||
private long rnd;
|
||||
@SerializedName("tv_id")
|
||||
private String tvId;
|
||||
|
||||
@ -86,27 +86,27 @@ public class SysMSGEntity {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public int getRoomId() {
|
||||
public long getRoomId() {
|
||||
return roomId;
|
||||
}
|
||||
|
||||
public void setRoomId(int roomId) {
|
||||
public void setRoomId(long roomId) {
|
||||
this.roomId = roomId;
|
||||
}
|
||||
|
||||
public int getRealRoomId() {
|
||||
public long getRealRoomId() {
|
||||
return realRoomId;
|
||||
}
|
||||
|
||||
public void setRealRoomId(int realRoomId) {
|
||||
public void setRealRoomId(long realRoomId) {
|
||||
this.realRoomId = realRoomId;
|
||||
}
|
||||
|
||||
public int getRnd() {
|
||||
public long getRnd() {
|
||||
return rnd;
|
||||
}
|
||||
|
||||
public void setRnd(int rnd) {
|
||||
public void setRnd(long rnd) {
|
||||
this.rnd = rnd;
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ public class WelcomeEntity {
|
||||
*/
|
||||
|
||||
@SerializedName("uid")
|
||||
private int uid;
|
||||
private long uid;
|
||||
@SerializedName("uname")
|
||||
private String userName;
|
||||
@SerializedName("is_admin")
|
||||
@ -46,11 +46,11 @@ public class WelcomeEntity {
|
||||
@SerializedName("vip")
|
||||
private int vip;
|
||||
|
||||
public int getUid() {
|
||||
public long getUid() {
|
||||
return uid;
|
||||
}
|
||||
|
||||
public void setUid(int uid) {
|
||||
public void setUid(long uid) {
|
||||
this.uid = uid;
|
||||
}
|
||||
|
||||
@ -62,11 +62,11 @@ public class WelcomeEntity {
|
||||
this.userName = userName;
|
||||
}
|
||||
|
||||
public boolean isIsAdmin() {
|
||||
public boolean isAdmin() {
|
||||
return isAdmin;
|
||||
}
|
||||
|
||||
public void setIsAdmin(boolean isAdmin) {
|
||||
public void setAdmin(boolean isAdmin) {
|
||||
this.isAdmin = isAdmin;
|
||||
}
|
||||
|
||||
|
@ -14,7 +14,7 @@ public class WelcomeGuardEntity {
|
||||
@SerializedName("data")
|
||||
private DataEntity data;
|
||||
@SerializedName("roomid")
|
||||
private int roomId;
|
||||
private long roomId;
|
||||
|
||||
public String getCmd() {
|
||||
return cmd;
|
||||
@ -32,11 +32,11 @@ public class WelcomeGuardEntity {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public int getRoomId() {
|
||||
public long getRoomId() {
|
||||
return roomId;
|
||||
}
|
||||
|
||||
public void setRoomId(int roomId) {
|
||||
public void setRoomId(long roomId) {
|
||||
this.roomId = roomId;
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,18 @@
|
||||
package com.hiczp.bilibili.api.live.socket.event;
|
||||
|
||||
import com.hiczp.bilibili.api.live.socket.entity.ActivityEventEntity;
|
||||
|
||||
import java.util.EventObject;
|
||||
|
||||
public class ActivityEventPackageEvent extends EventObject {
|
||||
private ActivityEventEntity activityEventEntity;
|
||||
|
||||
public ActivityEventPackageEvent(Object source, ActivityEventEntity activityEventEntity) {
|
||||
super(source);
|
||||
this.activityEventEntity = activityEventEntity;
|
||||
}
|
||||
|
||||
public ActivityEventEntity getActivityEventEntity() {
|
||||
return activityEventEntity;
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package com.hiczp.bilibili.api.live.socket.event;
|
||||
|
||||
import java.util.EventObject;
|
||||
|
||||
public class ConnectSucceedEvent extends EventObject {
|
||||
public ConnectSucceedEvent(Object source) {
|
||||
super(source);
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package com.hiczp.bilibili.api.live.socket.event;
|
||||
|
||||
import java.util.EventObject;
|
||||
|
||||
public class ConnectionCloseEvent extends EventObject {
|
||||
public ConnectionCloseEvent(Object source) {
|
||||
super(source);
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package com.hiczp.bilibili.api.live.socket.event;
|
||||
|
||||
import com.hiczp.bilibili.api.live.socket.entity.DanMuMsgEntity;
|
||||
|
||||
import java.util.EventObject;
|
||||
|
||||
public class DanMuMsgPackageEvent extends EventObject {
|
||||
private DanMuMsgEntity danMuMsgEntity;
|
||||
|
||||
public DanMuMsgPackageEvent(Object source, DanMuMsgEntity danMuMsgEntity) {
|
||||
super(source);
|
||||
this.danMuMsgEntity = danMuMsgEntity;
|
||||
}
|
||||
|
||||
public DanMuMsgEntity getDanMuMsgEntity() {
|
||||
return danMuMsgEntity;
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package com.hiczp.bilibili.api.live.socket.event;
|
||||
|
||||
import com.hiczp.bilibili.api.live.socket.entity.LiveEntity;
|
||||
|
||||
import java.util.EventObject;
|
||||
|
||||
public class LivePackageEvent extends EventObject {
|
||||
private LiveEntity liveEntity;
|
||||
|
||||
public LivePackageEvent(Object source, LiveEntity liveEntity) {
|
||||
super(source);
|
||||
this.liveEntity = liveEntity;
|
||||
}
|
||||
|
||||
public LiveEntity getLiveEntity() {
|
||||
return liveEntity;
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package com.hiczp.bilibili.api.live.socket.event;
|
||||
|
||||
import com.hiczp.bilibili.api.live.socket.entity.PreparingEntity;
|
||||
|
||||
import java.util.EventObject;
|
||||
|
||||
public class PreparingPackageEvent extends EventObject {
|
||||
private PreparingEntity preparingEntity;
|
||||
|
||||
public PreparingPackageEvent(Object source, PreparingEntity preparingEntity) {
|
||||
super(source);
|
||||
this.preparingEntity = preparingEntity;
|
||||
}
|
||||
|
||||
public PreparingEntity getPreparingEntity() {
|
||||
return preparingEntity;
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package com.hiczp.bilibili.api.live.socket.event;
|
||||
|
||||
import com.hiczp.bilibili.api.live.socket.entity.SendGiftEntity;
|
||||
|
||||
import java.util.EventObject;
|
||||
|
||||
public class SendGiftPackageEvent extends EventObject {
|
||||
private SendGiftEntity sendGiftEntity;
|
||||
|
||||
public SendGiftPackageEvent(Object source, SendGiftEntity sendGiftEntity) {
|
||||
super(source);
|
||||
this.sendGiftEntity = sendGiftEntity;
|
||||
}
|
||||
|
||||
public SendGiftEntity getSendGiftEntity() {
|
||||
return sendGiftEntity;
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package com.hiczp.bilibili.api.live.socket.event;
|
||||
|
||||
import com.hiczp.bilibili.api.live.socket.entity.SysGiftEntity;
|
||||
|
||||
import java.util.EventObject;
|
||||
|
||||
public class SysGiftPackageEvent extends EventObject {
|
||||
private SysGiftEntity sysGiftEntity;
|
||||
|
||||
public SysGiftPackageEvent(Object source, SysGiftEntity sysGiftEntity) {
|
||||
super(source);
|
||||
this.sysGiftEntity = sysGiftEntity;
|
||||
}
|
||||
|
||||
public SysGiftEntity getSysGiftEntity() {
|
||||
return sysGiftEntity;
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package com.hiczp.bilibili.api.live.socket.event;
|
||||
|
||||
import com.hiczp.bilibili.api.live.socket.entity.SysMsgEntity;
|
||||
|
||||
import java.util.EventObject;
|
||||
|
||||
public class SysMsgPackageEvent extends EventObject {
|
||||
private SysMsgEntity sysMsgEntity;
|
||||
|
||||
public SysMsgPackageEvent(Object source, SysMsgEntity sysMsgEntity) {
|
||||
super(source);
|
||||
this.sysMsgEntity = sysMsgEntity;
|
||||
}
|
||||
|
||||
public SysMsgEntity getSysMsgEntity() {
|
||||
return sysMsgEntity;
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package com.hiczp.bilibili.api.live.socket.event;
|
||||
|
||||
import java.util.EventObject;
|
||||
|
||||
public class UnknownPackageEvent extends EventObject {
|
||||
private String json;
|
||||
|
||||
public UnknownPackageEvent(Object source, String json) {
|
||||
super(source);
|
||||
this.json = json;
|
||||
}
|
||||
|
||||
public String getJson() {
|
||||
return json;
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package com.hiczp.bilibili.api.live.socket.event;
|
||||
|
||||
import java.util.EventObject;
|
||||
|
||||
public class ViewerCountPackageEvent extends EventObject {
|
||||
private int viewerCount;
|
||||
|
||||
public ViewerCountPackageEvent(Object source, int viewerCount) {
|
||||
super(source);
|
||||
this.viewerCount = viewerCount;
|
||||
}
|
||||
|
||||
public int getViewerCount() {
|
||||
return viewerCount;
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package com.hiczp.bilibili.api.live.socket.event;
|
||||
|
||||
import com.hiczp.bilibili.api.live.socket.entity.WelcomeGuardEntity;
|
||||
|
||||
import java.util.EventObject;
|
||||
|
||||
public class WelcomeGuardPackageEvent extends EventObject {
|
||||
private WelcomeGuardEntity welcomeGuardEntity;
|
||||
|
||||
public WelcomeGuardPackageEvent(Object source, WelcomeGuardEntity welcomeGuardEntity) {
|
||||
super(source);
|
||||
this.welcomeGuardEntity = welcomeGuardEntity;
|
||||
}
|
||||
|
||||
public WelcomeGuardEntity getWelcomeGuardEntity() {
|
||||
return welcomeGuardEntity;
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package com.hiczp.bilibili.api.live.socket.event;
|
||||
|
||||
import com.hiczp.bilibili.api.live.socket.entity.WelcomeEntity;
|
||||
|
||||
import java.util.EventObject;
|
||||
|
||||
public class WelcomePackageEvent extends EventObject {
|
||||
private WelcomeEntity welcomeEntity;
|
||||
|
||||
public WelcomePackageEvent(Object source, WelcomeEntity welcomeEntity) {
|
||||
super(source);
|
||||
this.welcomeEntity = welcomeEntity;
|
||||
}
|
||||
|
||||
public WelcomeEntity getWelcomeEntity() {
|
||||
return welcomeEntity;
|
||||
}
|
||||
}
|
@ -0,0 +1,146 @@
|
||||
package com.hiczp.bilibili.api.live.socket.handler;
|
||||
|
||||
import com.google.common.eventbus.EventBus;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.hiczp.bilibili.api.live.socket.Package;
|
||||
import com.hiczp.bilibili.api.live.socket.PackageHelper;
|
||||
import com.hiczp.bilibili.api.live.socket.entity.*;
|
||||
import com.hiczp.bilibili.api.live.socket.event.*;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import io.netty.handler.timeout.IdleState;
|
||||
import io.netty.handler.timeout.IdleStateEvent;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class LiveClientHandler extends SimpleChannelInboundHandler<Package> {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(LiveClientHandler.class);
|
||||
private static final Gson GSON = new Gson();
|
||||
private static final JsonParser JSON_PARSER = new JsonParser();
|
||||
|
||||
private long roomId;
|
||||
private long userId;
|
||||
private EventBus eventBus;
|
||||
|
||||
public LiveClientHandler(long roomId, long userId, EventBus eventBus) {
|
||||
this.roomId = roomId;
|
||||
this.userId = userId;
|
||||
this.eventBus = eventBus;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||
super.channelActive(ctx);
|
||||
LOGGER.debug("Sending Enter Room package");
|
||||
ctx.writeAndFlush(PackageHelper.createEnterRoomPackage(roomId, userId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||
super.channelInactive(ctx);
|
||||
eventBus.post(new ConnectionCloseEvent(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
|
||||
super.userEventTriggered(ctx, evt);
|
||||
if (evt instanceof IdleStateEvent) {
|
||||
IdleStateEvent idleStateEvent = (IdleStateEvent) evt;
|
||||
if (idleStateEvent.state() == IdleState.READER_IDLE) {
|
||||
ctx.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, Package msg) throws Exception {
|
||||
switch (msg.getPackageType()) {
|
||||
case VIEWER_COUNT: {
|
||||
eventBus.post(new ViewerCountPackageEvent(this, ByteBuffer.wrap(msg.getContent()).getInt()));
|
||||
}
|
||||
break;
|
||||
case ENTER_ROOM_SUCCESS: {
|
||||
eventBus.post(new ConnectSucceedEvent(this));
|
||||
ctx.executor().scheduleAtFixedRate(
|
||||
() -> ctx.writeAndFlush(PackageHelper.createHeatBeatPackage()),
|
||||
0L,
|
||||
30L,
|
||||
TimeUnit.SECONDS
|
||||
);
|
||||
}
|
||||
break;
|
||||
case DATA: {
|
||||
String content = new String(msg.getContent());
|
||||
String cmd = JSON_PARSER.parse(content)
|
||||
.getAsJsonObject()
|
||||
.get("cmd")
|
||||
.getAsString();
|
||||
Supplier<Object> eventCreationExpression; //try 不能写在 switch 外面, 用 lambda 来延迟执行
|
||||
switch (cmd) {
|
||||
case "DANMU_MSG": {
|
||||
eventCreationExpression = () -> new DanMuMsgPackageEvent(this, GSON.fromJson(content, DanMuMsgEntity.class));
|
||||
}
|
||||
break;
|
||||
case "SEND_GIFT": {
|
||||
eventCreationExpression = () -> new SendGiftPackageEvent(this, GSON.fromJson(content, SendGiftEntity.class));
|
||||
}
|
||||
break;
|
||||
case "SYS_GIFT": {
|
||||
eventCreationExpression = () -> new SysGiftPackageEvent(this, GSON.fromJson(content, SysGiftEntity.class));
|
||||
}
|
||||
break;
|
||||
case "SYS_MSG": {
|
||||
eventCreationExpression = () -> new SysMsgPackageEvent(this, GSON.fromJson(content, SysMsgEntity.class));
|
||||
}
|
||||
break;
|
||||
case "WELCOME": {
|
||||
eventCreationExpression = () -> new WelcomePackageEvent(this, GSON.fromJson(content, WelcomeEntity.class));
|
||||
}
|
||||
break;
|
||||
case "WELCOME_GUARD": {
|
||||
eventCreationExpression = () -> new WelcomeGuardPackageEvent(this, GSON.fromJson(content, WelcomeGuardEntity.class));
|
||||
}
|
||||
break;
|
||||
case "LIVE": {
|
||||
eventCreationExpression = () -> new LivePackageEvent(this, GSON.fromJson(content, LiveEntity.class));
|
||||
}
|
||||
break;
|
||||
case "PREPARING": {
|
||||
eventCreationExpression = () -> new PreparingPackageEvent(this, GSON.fromJson(content, PreparingEntity.class));
|
||||
}
|
||||
break;
|
||||
case "ACTIVITY_EVENT": {
|
||||
eventCreationExpression = () -> new ActivityEventPackageEvent(this, GSON.fromJson(content, ActivityEventEntity.class));
|
||||
}
|
||||
break;
|
||||
default: {
|
||||
LOGGER.error("Received unknown json below: \n{}", formatJson(content));
|
||||
eventCreationExpression = () -> new UnknownPackageEvent(this, content);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
eventBus.post(eventCreationExpression.get());
|
||||
} catch (JsonParseException e) {
|
||||
LOGGER.error("Json parse error: {}, json below: \n{}", e.getMessage(), formatJson(content));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private String formatJson(String json) {
|
||||
return new GsonBuilder()
|
||||
.setPrettyPrinting()
|
||||
.create()
|
||||
.toJson(JSON_PARSER.parse(json));
|
||||
}
|
||||
}
|
178
src/test/java/com/hiczp/bilibili/api/test/LiveClientTest.java
Normal file
178
src/test/java/com/hiczp/bilibili/api/test/LiveClientTest.java
Normal file
@ -0,0 +1,178 @@
|
||||
package com.hiczp.bilibili.api.test;
|
||||
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
import com.hiczp.bilibili.api.BilibiliAPI;
|
||||
import com.hiczp.bilibili.api.live.socket.LiveClient;
|
||||
import com.hiczp.bilibili.api.live.socket.entity.*;
|
||||
import com.hiczp.bilibili.api.live.socket.event.*;
|
||||
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 java.util.List;
|
||||
|
||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||
public class LiveClientTest {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(LiveClientTest.class);
|
||||
private static final BilibiliAPI BILIBILI_API = Config.getBilibiliAPI();
|
||||
private static final int ROOM_ID = 3;
|
||||
private static final long TEST_TIME = 70 * 1000;
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
public void _0_duplicateConnectAndCloseTest() throws Exception {
|
||||
LiveClient liveClient = BILIBILI_API
|
||||
.getLiveClient(ROOM_ID)
|
||||
.setReconnectLimit(5)
|
||||
.setReconnectDelay(1);
|
||||
LOGGER.debug("Connecting!");
|
||||
liveClient.connect();
|
||||
Thread.sleep(5000);
|
||||
LOGGER.debug("Connecting!");
|
||||
liveClient.connect();
|
||||
Thread.sleep(5000);
|
||||
LOGGER.debug("Disconnecting!");
|
||||
liveClient.close();
|
||||
Thread.sleep(5000);
|
||||
LOGGER.debug("Disconnecting!");
|
||||
liveClient.close();
|
||||
Thread.sleep(5000);
|
||||
LOGGER.debug("Connecting!");
|
||||
liveClient.connect();
|
||||
Thread.sleep(5000);
|
||||
LOGGER.debug("Disconnecting!");
|
||||
liveClient.close();
|
||||
Thread.sleep(5000);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void _1_longTimeTest() throws Exception {
|
||||
LiveClient liveClient = BILIBILI_API
|
||||
.getLiveClient(ROOM_ID)
|
||||
.setReconnectLimit(5)
|
||||
.setReconnectDelay(1)
|
||||
.registerListener(new Listener());
|
||||
LOGGER.debug("Start long-time test");
|
||||
LOGGER.debug("Connecting!");
|
||||
liveClient.connect();
|
||||
Thread.sleep(TEST_TIME);
|
||||
LOGGER.debug("Disconnecting!");
|
||||
liveClient.close();
|
||||
Thread.sleep(5000);
|
||||
}
|
||||
|
||||
private class Listener {
|
||||
private final Logger LOGGER = LoggerFactory.getLogger(Listener.class);
|
||||
|
||||
@Subscribe
|
||||
public void activityEvent(ActivityEventPackageEvent activityEventPackageEvent) {
|
||||
ActivityEventEntity.Data data = activityEventPackageEvent.getActivityEventEntity().getData();
|
||||
LOGGER.info("[ActivityEvent] keyword: {}, type: {}, progress: {}%",
|
||||
data.getKeyword(),
|
||||
data.getType(),
|
||||
((float) data.getProgress() / data.getLimit()) * 100
|
||||
);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void connectionClose(ConnectionCloseEvent connectionCloseEvent) {
|
||||
LOGGER.info("[ConnectionClose] Connection closed");
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void connectSucceed(ConnectSucceedEvent connectSucceedEvent) {
|
||||
LOGGER.info("[ConnectSucceed] Connect succeed");
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void danMuMsg(DanMuMsgPackageEvent danMuMsgPackageEvent) {
|
||||
DanMuMsgEntity danMuMsgEntity = danMuMsgPackageEvent.getDanMuMsgEntity();
|
||||
StringBuilder stringBuilder = new StringBuilder("[DanMuMsg] ");
|
||||
|
||||
danMuMsgEntity.getFansMedalName().ifPresent(fansMedalName ->
|
||||
stringBuilder.append(String.format("[%s %d] ", fansMedalName, danMuMsgEntity.getFansMedalLevel().get()))
|
||||
);
|
||||
|
||||
List<String> userTitles = danMuMsgEntity.getUserTitles();
|
||||
if (!userTitles.isEmpty()) {
|
||||
stringBuilder.append(userTitles.get(0))
|
||||
.append(" ");
|
||||
}
|
||||
|
||||
stringBuilder.append(String.format("[UL %d] ", danMuMsgEntity.getUserLevel()));
|
||||
|
||||
stringBuilder.append(String.format("%s: ", danMuMsgEntity.getUsername()));
|
||||
|
||||
stringBuilder.append(danMuMsgEntity.getMessage());
|
||||
|
||||
LOGGER.info(stringBuilder.toString());
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void live(LivePackageEvent livePackageEvent) {
|
||||
LOGGER.info("[Live] Room {} start live", livePackageEvent.getLiveEntity().getRoomId());
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void preparing(PreparingPackageEvent preparingPackageEvent) {
|
||||
LOGGER.info("[Preparing] Room {} stop live", preparingPackageEvent.getPreparingEntity().getRoomId());
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void sendGift(SendGiftPackageEvent sendGiftPackageEvent) {
|
||||
SendGiftEntity.DataEntity dataEntity = sendGiftPackageEvent.getSendGiftEntity().getData();
|
||||
LOGGER.info("[SendGift] {} give {}*{}",
|
||||
dataEntity.getUserName(),
|
||||
dataEntity.getGiftName(),
|
||||
dataEntity.getNum()
|
||||
);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void SysGift(SysGiftPackageEvent sysGiftPackageEvent) {
|
||||
SysGiftEntity sysGiftEntity = sysGiftPackageEvent.getSysGiftEntity();
|
||||
LOGGER.info("[SysGift] {}: {}",
|
||||
sysGiftEntity.getMsg(),
|
||||
sysGiftEntity.getUrl()
|
||||
);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void SysMsg(SysMsgPackageEvent sysMsgPackageEvent) {
|
||||
SysMsgEntity sysMsgEntity = sysMsgPackageEvent.getSysMsgEntity();
|
||||
LOGGER.info("[SysMsg] {}: {}",
|
||||
sysMsgEntity.getMsg(),
|
||||
sysMsgEntity.getUrl()
|
||||
);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void ViewerCount(ViewerCountPackageEvent viewerCountPackageEvent) {
|
||||
LOGGER.info("[ViewerCount] {}", viewerCountPackageEvent.getViewerCount());
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void WelcomeGuard(WelcomeGuardPackageEvent welcomeGuardPackageEvent) {
|
||||
WelcomeGuardEntity.DataEntity dataEntity = welcomeGuardPackageEvent.getWelcomeGuardEntity().getData();
|
||||
LOGGER.info("[WelcomeGuard] [GL {}] {}",
|
||||
dataEntity.getGuardLevel(),
|
||||
dataEntity.getUsername()
|
||||
);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void Welcome(WelcomePackageEvent welcomePackageEvent) {
|
||||
WelcomeEntity.DataEntity dataEntity = welcomePackageEvent.getWelcomeEntity().getData();
|
||||
StringBuilder stringBuilder = new StringBuilder("[Welcome] ");
|
||||
if (dataEntity.isAdmin()) {
|
||||
stringBuilder.append("[ADMIN] ");
|
||||
}
|
||||
stringBuilder.append(String.format("[VIP %d] ", dataEntity.getVip()))
|
||||
.append(dataEntity.getUserName());
|
||||
LOGGER.info(stringBuilder.toString());
|
||||
}
|
||||
}
|
||||
}
|
@ -14,7 +14,8 @@ import java.io.InputStreamReader;
|
||||
|
||||
@RunWith(Suite.class)
|
||||
@Suite.SuiteClasses({
|
||||
UserInfoTest.class
|
||||
UserInfoTest.class,
|
||||
LiveClientTest.class
|
||||
})
|
||||
public class RuleSuite {
|
||||
@ClassRule
|
||||
|
@ -11,13 +11,12 @@ import org.slf4j.LoggerFactory;
|
||||
public class UserInfoTest {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(UserInfoTest.class);
|
||||
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
|
||||
|
||||
private final BilibiliAPI bilibiliAPI = Config.getBilibiliAPI();
|
||||
private static final BilibiliAPI BILIBILI_API = Config.getBilibiliAPI();
|
||||
|
||||
@Test
|
||||
public void getUserInfo() throws Exception {
|
||||
InfoEntity infoEntity = bilibiliAPI.getPassportService()
|
||||
.getInfo(bilibiliAPI.getBilibiliAccount().getAccessToken())
|
||||
InfoEntity infoEntity = BILIBILI_API.getPassportService()
|
||||
.getInfo(BILIBILI_API.getBilibiliAccount().getAccessToken())
|
||||
.execute()
|
||||
.body();
|
||||
LOGGER.debug("UserInfo below: \n{}", GSON.toJson(infoEntity));
|
||||
|
Loading…
Reference in New Issue
Block a user