mirror of
https://github.com/BililiveRecorder/BililiveRecorder.git
synced 2024-11-16 03:32:20 +08:00
Add DanmakuReceiver
This commit is contained in:
parent
3b33e9d65f
commit
98c4fc8da5
34
BililiveRecorder.Core/DanmakuEvents.cs
Normal file
34
BililiveRecorder.Core/DanmakuEvents.cs
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace BililiveRecorder.Core
|
||||||
|
{
|
||||||
|
public delegate void ConnectedEvt(object sender, ConnectedEvtArgs e);
|
||||||
|
public class ConnectedEvtArgs
|
||||||
|
{
|
||||||
|
public int roomid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public delegate void DisconnectEvt(object sender, DisconnectEvtArgs e);
|
||||||
|
public class DisconnectEvtArgs
|
||||||
|
{
|
||||||
|
public Exception Error;
|
||||||
|
}
|
||||||
|
|
||||||
|
public delegate void ReceivedRoomCountEvt(object sender, ReceivedRoomCountArgs e);
|
||||||
|
public class ReceivedRoomCountArgs
|
||||||
|
{
|
||||||
|
public uint UserCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public delegate void ReceivedDanmakuEvt(object sender, ReceivedDanmakuArgs e);
|
||||||
|
public class ReceivedDanmakuArgs
|
||||||
|
{
|
||||||
|
public DanmakuModel Danmaku;
|
||||||
|
}
|
||||||
|
|
||||||
|
public delegate void LogMessageEvt(object sender, LogMessageArgs e);
|
||||||
|
public class LogMessageArgs
|
||||||
|
{
|
||||||
|
public string message = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
250
BililiveRecorder.Core/DanmakuModel.cs
Normal file
250
BililiveRecorder.Core/DanmakuModel.cs
Normal file
|
@ -0,0 +1,250 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace BililiveRecorder.Core
|
||||||
|
{
|
||||||
|
public enum MsgTypeEnum
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 彈幕
|
||||||
|
/// </summary>
|
||||||
|
Comment,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 禮物
|
||||||
|
/// </summary>
|
||||||
|
GiftSend,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 歡迎老爷
|
||||||
|
/// </summary>
|
||||||
|
Welcome,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 直播開始
|
||||||
|
/// </summary>
|
||||||
|
LiveStart,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 直播結束
|
||||||
|
/// </summary>
|
||||||
|
LiveEnd,
|
||||||
|
/// <summary>
|
||||||
|
/// 其他
|
||||||
|
/// </summary>
|
||||||
|
Unknown,
|
||||||
|
/// <summary>
|
||||||
|
/// 欢迎船员
|
||||||
|
/// </summary>
|
||||||
|
WelcomeGuard,
|
||||||
|
/// <summary>
|
||||||
|
/// 购买船票(上船)
|
||||||
|
/// </summary>
|
||||||
|
GuardBuy
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DanmakuModel
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 消息類型
|
||||||
|
/// </summary>
|
||||||
|
public MsgTypeEnum MsgType { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 彈幕內容
|
||||||
|
/// <para>此项有值的消息类型:<list type="bullet">
|
||||||
|
/// <item><see cref="MsgTypeEnum.Comment"/></item>
|
||||||
|
/// </list></para>
|
||||||
|
/// </summary>
|
||||||
|
public string CommentText { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 彈幕用戶
|
||||||
|
/// </summary>
|
||||||
|
[Obsolete("请使用 UserName")]
|
||||||
|
public string CommentUser
|
||||||
|
{
|
||||||
|
get { return UserName; }
|
||||||
|
set { UserName = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 消息触发者用户名
|
||||||
|
/// <para>此项有值的消息类型:<list type="bullet">
|
||||||
|
/// <item><see cref="MsgTypeEnum.Comment"/></item>
|
||||||
|
/// <item><see cref="MsgTypeEnum.GiftSend"/></item>
|
||||||
|
/// <item><see cref="MsgTypeEnum.Welcome"/></item>
|
||||||
|
/// <item><see cref="MsgTypeEnum.WelcomeGuard"/></item>
|
||||||
|
/// <item><see cref="MsgTypeEnum.GuardBuy"/></item>
|
||||||
|
/// </list></para>
|
||||||
|
/// </summary>
|
||||||
|
public string UserName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 消息触发者用户ID
|
||||||
|
/// <para>此项有值的消息类型:<list type="bullet">
|
||||||
|
/// <item><see cref="MsgTypeEnum.Comment"/></item>
|
||||||
|
/// <item><see cref="MsgTypeEnum.GiftSend"/></item>
|
||||||
|
/// <item><see cref="MsgTypeEnum.Welcome"/></item>
|
||||||
|
/// <item><see cref="MsgTypeEnum.WelcomeGuard"/></item>
|
||||||
|
/// <item><see cref="MsgTypeEnum.GuardBuy"/></item>
|
||||||
|
/// </list></para>
|
||||||
|
/// </summary>
|
||||||
|
public int UserID { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 用户舰队等级
|
||||||
|
/// <para>0 为非船员 1 为总督 2 为提督 3 为舰长</para>
|
||||||
|
/// <para>此项有值的消息类型:<list type="bullet">
|
||||||
|
/// <item><see cref="MsgTypeEnum.Comment"/></item>
|
||||||
|
/// <item><see cref="MsgTypeEnum.WelcomeGuard"/></item>
|
||||||
|
/// <item><see cref="MsgTypeEnum.GuardBuy"/></item>
|
||||||
|
/// </list></para>
|
||||||
|
/// </summary>
|
||||||
|
public int UserGuardLevel { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 禮物用戶
|
||||||
|
/// </summary>
|
||||||
|
[Obsolete("请使用 UserName")]
|
||||||
|
public string GiftUser
|
||||||
|
{
|
||||||
|
get { return UserName; }
|
||||||
|
set { UserName = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 禮物名稱
|
||||||
|
/// </summary>
|
||||||
|
public string GiftName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 禮物數量
|
||||||
|
/// </summary>
|
||||||
|
[Obsolete("请使用 GiftCount")]
|
||||||
|
public string GiftNum { get { return GiftCount.ToString(); } }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 礼物数量
|
||||||
|
/// <para>此项有值的消息类型:<list type="bullet">
|
||||||
|
/// <item><see cref="MsgTypeEnum.GiftSend"/></item>
|
||||||
|
/// <item><see cref="MsgTypeEnum.GuardBuy"/></item>
|
||||||
|
/// </list></para>
|
||||||
|
/// <para>此字段也用于标识上船 <see cref="MsgTypeEnum.GuardBuy"/> 的数量(月数)</para>
|
||||||
|
/// </summary>
|
||||||
|
public int GiftCount { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前房间的礼物积分(Room Cost)
|
||||||
|
/// 因以前出现过不传递rcost的礼物,并且用处不大,所以弃用
|
||||||
|
/// </summary>
|
||||||
|
[Obsolete("如有需要请自行解析RawData", true)]
|
||||||
|
public string Giftrcost { get { return "0"; } set { } }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 该用户是否为房管(包括主播)
|
||||||
|
/// <para>此项有值的消息类型:<list type="bullet">
|
||||||
|
/// <item><see cref="MsgTypeEnum.Comment"/></item>
|
||||||
|
/// <item><see cref="MsgTypeEnum.GiftSend"/></item>
|
||||||
|
/// </list></para>
|
||||||
|
/// </summary>
|
||||||
|
public bool isAdmin { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否VIP用戶(老爺)
|
||||||
|
/// <para>此项有值的消息类型:<list type="bullet">
|
||||||
|
/// <item><see cref="MsgTypeEnum.Comment"/></item>
|
||||||
|
/// <item><see cref="MsgTypeEnum.Welcome"/></item>
|
||||||
|
/// </list></para>
|
||||||
|
/// </summary>
|
||||||
|
public bool isVIP { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="MsgTypeEnum.LiveStart"/>,<see cref="MsgTypeEnum.LiveEnd"/> 事件对应的房间号
|
||||||
|
/// </summary>
|
||||||
|
public string roomID { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 原始数据, 高级开发用
|
||||||
|
/// </summary>
|
||||||
|
public string RawData { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 内部用, JSON数据版本号 通常应该是2
|
||||||
|
/// </summary>
|
||||||
|
public int JSON_Version { get; set; }
|
||||||
|
|
||||||
|
public DanmakuModel()
|
||||||
|
{ }
|
||||||
|
|
||||||
|
public DanmakuModel(string JSON)
|
||||||
|
{
|
||||||
|
RawData = JSON;
|
||||||
|
JSON_Version = 2;
|
||||||
|
var obj = new JSONObject(JSON);
|
||||||
|
string cmd = obj["cmd"].str;
|
||||||
|
switch (cmd)
|
||||||
|
{
|
||||||
|
case "LIVE":
|
||||||
|
MsgType = MsgTypeEnum.LiveStart;
|
||||||
|
roomID = obj["roomid"].str;
|
||||||
|
break;
|
||||||
|
case "PREPARING":
|
||||||
|
MsgType = MsgTypeEnum.LiveEnd;
|
||||||
|
roomID = obj["roomid"].str;
|
||||||
|
break;
|
||||||
|
case "DANMU_MSG":
|
||||||
|
MsgType = MsgTypeEnum.Comment;
|
||||||
|
CommentText = obj["info"][1].str;
|
||||||
|
UserID = (int)obj["info"][2][0].i;
|
||||||
|
UserName = obj["info"][2][1].str;
|
||||||
|
isAdmin = obj["info"][2][2].str == "1";
|
||||||
|
isVIP = obj["info"][2][3].str == "1";
|
||||||
|
UserGuardLevel = (int)obj["info"][7].i;
|
||||||
|
break;
|
||||||
|
case "SEND_GIFT":
|
||||||
|
MsgType = MsgTypeEnum.GiftSend;
|
||||||
|
GiftName = obj["data"]["giftName"].str;
|
||||||
|
UserName = obj["data"]["uname"].str;
|
||||||
|
UserID = (int)obj["data"]["uid"].i;
|
||||||
|
// Giftrcost = obj["data"]["rcost"].ToString();
|
||||||
|
GiftCount = (int)obj["data"]["num"].i;
|
||||||
|
break;
|
||||||
|
case "WELCOME":
|
||||||
|
{
|
||||||
|
MsgType = MsgTypeEnum.Welcome;
|
||||||
|
UserName = obj["data"]["uname"].str;
|
||||||
|
UserID = (int)obj["data"]["uid"].i;
|
||||||
|
isVIP = true;
|
||||||
|
isAdmin = obj["data"]["isadmin"].str == "1";
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
case "WELCOME_GUARD":
|
||||||
|
{
|
||||||
|
MsgType = MsgTypeEnum.WelcomeGuard;
|
||||||
|
UserName = obj["data"]["username"].str;
|
||||||
|
UserID = (int)obj["data"]["uid"].i;
|
||||||
|
UserGuardLevel = (int)obj["data"]["guard_level"].i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "GUARD_BUY":
|
||||||
|
{
|
||||||
|
MsgType = MsgTypeEnum.GuardBuy;
|
||||||
|
UserID = (int)obj["data"]["uid"].i;
|
||||||
|
UserName = obj["data"]["username"].str;
|
||||||
|
UserGuardLevel = (int)obj["data"]["guard_level"].i;
|
||||||
|
GiftName = UserGuardLevel == 3 ? "舰长" : UserGuardLevel == 2 ? "提督" : UserGuardLevel == 1 ? "总督" : "";
|
||||||
|
GiftCount = (int)obj["data"]["num"].i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
MsgType = MsgTypeEnum.Unknown;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
285
BililiveRecorder.Core/DanmakuReceiver.cs
Normal file
285
BililiveRecorder.Core/DanmakuReceiver.cs
Normal file
|
@ -0,0 +1,285 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Xml;
|
||||||
|
|
||||||
|
namespace BililiveRecorder.Core
|
||||||
|
{
|
||||||
|
public class DanmakuReceiver
|
||||||
|
{
|
||||||
|
private const string defaulthosts = "broadcastlv.chat.bilibili.com";
|
||||||
|
private string CIDInfoUrl = "http://live.bilibili.com/api/player?id=cid:";
|
||||||
|
private string ChatHost = defaulthosts;
|
||||||
|
private int ChatPort = 2243;
|
||||||
|
|
||||||
|
private TcpClient Client;
|
||||||
|
private NetworkStream NetStream;
|
||||||
|
|
||||||
|
private Thread ReceiveMessageLoopThread;
|
||||||
|
private Thread HeartbeatLoopThread;
|
||||||
|
private CancellationTokenSource HeartbeatLoopSource;
|
||||||
|
|
||||||
|
public bool isConnected
|
||||||
|
{
|
||||||
|
get => _isConnected;
|
||||||
|
private set
|
||||||
|
{
|
||||||
|
_isConnected = value; if (!value) HeartbeatLoopSource.Cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private bool _isConnected = false;
|
||||||
|
public Exception Error { get; private set; }
|
||||||
|
public uint ViewerCount { get; private set; }
|
||||||
|
|
||||||
|
public event ConnectedEvt Connected;
|
||||||
|
public event DisconnectEvt Disconnected;
|
||||||
|
public event ReceivedRoomCountEvt ReceivedRoomCount;
|
||||||
|
public event ReceivedDanmakuEvt ReceivedDanmaku;
|
||||||
|
public event LogMessageEvt LogMessage;
|
||||||
|
|
||||||
|
public bool Connect(int roomId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (this.isConnected) throw new InvalidOperationException();
|
||||||
|
int channelId = roomId;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var request2 = WebRequest.Create(CIDInfoUrl + channelId);
|
||||||
|
request2.Timeout = 2000;
|
||||||
|
var response2 = request2.GetResponse();
|
||||||
|
using (var stream = response2.GetResponseStream())
|
||||||
|
{
|
||||||
|
using (var sr = new StreamReader(stream))
|
||||||
|
{
|
||||||
|
var text = sr.ReadToEnd();
|
||||||
|
var xml = "<root>" + text + "</root>";
|
||||||
|
XmlDocument doc = new XmlDocument();
|
||||||
|
doc.LoadXml(xml);
|
||||||
|
ChatHost = doc["root"]["dm_server"].InnerText;
|
||||||
|
ChatPort = int.Parse(doc["root"]["dm_port"].InnerText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (WebException ex)
|
||||||
|
{
|
||||||
|
HttpWebResponse errorResponse = ex.Response as HttpWebResponse;
|
||||||
|
if (errorResponse.StatusCode == HttpStatusCode.NotFound)
|
||||||
|
{ // 直播间不存在(HTTP 404)
|
||||||
|
LogMessage?.Invoke(this, new LogMessageArgs() { message = "该直播间疑似不存在,弹幕姬只支持使用原房间号连接" });
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{ // B站服务器响应错误
|
||||||
|
LogMessage?.Invoke(this, new LogMessageArgs() { message = "B站服务器响应弹幕服务器地址出错,尝试使用常见地址连接" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{ // 其他错误(XML解析错误?)
|
||||||
|
LogMessage?.Invoke(this, new LogMessageArgs() { message = "获取弹幕服务器地址时出现未知错误,尝试使用常见地址连接" });
|
||||||
|
}
|
||||||
|
|
||||||
|
Client = new TcpClient();
|
||||||
|
Client.Connect(ChatHost, ChatPort);
|
||||||
|
if (!Client.Connected)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
NetStream = Client.GetStream();
|
||||||
|
SendSocketData(7, "{\"roomid\":" + channelId + ",\"uid\":0}");
|
||||||
|
isConnected = true;
|
||||||
|
|
||||||
|
ReceiveMessageLoopThread = new Thread(this.ReceiveMessageLoop);
|
||||||
|
ReceiveMessageLoopThread.IsBackground = true;
|
||||||
|
ReceiveMessageLoopThread.Start();
|
||||||
|
|
||||||
|
HeartbeatLoopSource = new CancellationTokenSource();
|
||||||
|
Repeat.Interval(TimeSpan.FromSeconds(30), this.SendHeartbeat, HeartbeatLoopSource.Token);
|
||||||
|
|
||||||
|
// HeartbeatLoopThread = new Thread(this.HeartbeatLoop);
|
||||||
|
// HeartbeatLoopThread.IsBackground = true;
|
||||||
|
// HeartbeatLoopThread.Start();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
this.Error = ex;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Disconnect()
|
||||||
|
{
|
||||||
|
isConnected = false;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Client.Close();
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
NetStream = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ReceiveMessageLoop()
|
||||||
|
{
|
||||||
|
Debug.WriteLine("ReceiveMessageLoop Started!");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var stableBuffer = new byte[Client.ReceiveBufferSize];
|
||||||
|
while (this.isConnected)
|
||||||
|
{
|
||||||
|
|
||||||
|
NetStream.ReadB(stableBuffer, 0, 4);
|
||||||
|
var packetlength = BitConverter.ToInt32(stableBuffer, 0);
|
||||||
|
packetlength = IPAddress.NetworkToHostOrder(packetlength);
|
||||||
|
|
||||||
|
if (packetlength < 16)
|
||||||
|
throw new NotSupportedException("协议失败: (L:" + packetlength + ")");
|
||||||
|
|
||||||
|
NetStream.ReadB(stableBuffer, 0, 2);//magic
|
||||||
|
NetStream.ReadB(stableBuffer, 0, 2);//protocol_version
|
||||||
|
NetStream.ReadB(stableBuffer, 0, 4);
|
||||||
|
var typeId = BitConverter.ToInt32(stableBuffer, 0);
|
||||||
|
typeId = IPAddress.NetworkToHostOrder(typeId);
|
||||||
|
|
||||||
|
Console.WriteLine(typeId);
|
||||||
|
NetStream.ReadB(stableBuffer, 0, 4);//magic, params?
|
||||||
|
var playloadlength = packetlength - 16;
|
||||||
|
if (playloadlength == 0)
|
||||||
|
continue;//没有内容了
|
||||||
|
typeId = typeId - 1;//和反编译的代码对应
|
||||||
|
var buffer = new byte[playloadlength];
|
||||||
|
NetStream.ReadB(buffer, 0, playloadlength);
|
||||||
|
switch (typeId)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
case 1:
|
||||||
|
case 2:
|
||||||
|
{
|
||||||
|
var viewer = BitConverter.ToUInt32(buffer.Take(4).Reverse().ToArray(), 0); //观众人数
|
||||||
|
ViewerCount = viewer;
|
||||||
|
ReceivedRoomCount?.Invoke(this, new ReceivedRoomCountArgs() { UserCount = viewer });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 3:
|
||||||
|
case 4://playerCommand
|
||||||
|
{
|
||||||
|
var json = Encoding.UTF8.GetString(buffer, 0, playloadlength);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ReceivedDanmaku?.Invoke(this, new ReceivedDanmakuArgs() { Danmaku = new DanmakuModel(json) });
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{ } // ignored
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 5://newScrollMessage
|
||||||
|
case 7:
|
||||||
|
case 16:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
this.Error = ex;
|
||||||
|
_disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HeartbeatLoop()
|
||||||
|
{
|
||||||
|
Debug.WriteLine("HeartbeatLoop Started!");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
while (this.isConnected)
|
||||||
|
{
|
||||||
|
this.SendHeartbeat();
|
||||||
|
for (int i = 0; i < 30; i++)
|
||||||
|
{
|
||||||
|
Thread.Sleep(1000);//1s
|
||||||
|
if (!isConnected)
|
||||||
|
{
|
||||||
|
Debug.WriteLine("HeartbeatLoop Break");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
this.Error = ex;
|
||||||
|
_disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void _disconnect()
|
||||||
|
{
|
||||||
|
if (isConnected)
|
||||||
|
{
|
||||||
|
Debug.WriteLine("Disconnected");
|
||||||
|
isConnected = false;
|
||||||
|
Client.Close();
|
||||||
|
NetStream = null;
|
||||||
|
Disconnected?.Invoke(this, new DisconnectEvtArgs() { Error = Error });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SendHeartbeat()
|
||||||
|
{
|
||||||
|
SendSocketData(2);
|
||||||
|
Debug.WriteLine("Message Sent: Heartbeat");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SendSocketData(int action, string body = "")
|
||||||
|
{
|
||||||
|
SendSocketData(0, 16, /*protocolversion*/1, action, 1, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SendSocketData(int packetlength, short magic, short ver, int action, int param = 1, string body = "")
|
||||||
|
{
|
||||||
|
var playload = Encoding.UTF8.GetBytes(body);
|
||||||
|
if (packetlength == 0)
|
||||||
|
{
|
||||||
|
packetlength = playload.Length + 16;
|
||||||
|
}
|
||||||
|
var buffer = new byte[packetlength];
|
||||||
|
using (var ms = new MemoryStream(buffer))
|
||||||
|
{
|
||||||
|
var b = BitConverter.GetBytes(buffer.Length).ToBE();
|
||||||
|
ms.Write(b, 0, 4);
|
||||||
|
b = BitConverter.GetBytes(magic).ToBE();
|
||||||
|
ms.Write(b, 0, 2);
|
||||||
|
b = BitConverter.GetBytes(ver).ToBE();
|
||||||
|
ms.Write(b, 0, 2);
|
||||||
|
b = BitConverter.GetBytes(action).ToBE();
|
||||||
|
ms.Write(b, 0, 4);
|
||||||
|
b = BitConverter.GetBytes(param).ToBE();
|
||||||
|
ms.Write(b, 0, 4);
|
||||||
|
if (playload.Length > 0)
|
||||||
|
{
|
||||||
|
ms.Write(playload, 0, playload.Length);
|
||||||
|
}
|
||||||
|
NetStream.Write(buffer, 0, buffer.Length);
|
||||||
|
NetStream.Flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Use this for initialization
|
||||||
|
void Awake()
|
||||||
|
{
|
||||||
|
isConnected = false;
|
||||||
|
ViewerCount = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
31
BililiveRecorder.Core/DanmakuUtils.cs
Normal file
31
BililiveRecorder.Core/DanmakuUtils.cs
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
|
||||||
|
namespace BililiveRecorder.Core
|
||||||
|
{
|
||||||
|
internal static class DanmakuUtils
|
||||||
|
{
|
||||||
|
public static byte[] ToBE(this byte[] b)
|
||||||
|
{
|
||||||
|
if (BitConverter.IsLittleEndian) return b.Reverse().ToArray(); else return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ReadB(this NetworkStream stream, byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
if (offset + count > buffer.Length)
|
||||||
|
throw new ArgumentException();
|
||||||
|
int read = 0;
|
||||||
|
while (read < count)
|
||||||
|
{
|
||||||
|
var available = stream.Read(buffer, offset, count - read);
|
||||||
|
if (available == 0)
|
||||||
|
{
|
||||||
|
throw new ObjectDisposedException(null);
|
||||||
|
}
|
||||||
|
read += available;
|
||||||
|
offset += available;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1375
BililiveRecorder.Core/JSONObject.cs
Normal file
1375
BililiveRecorder.Core/JSONObject.cs
Normal file
File diff suppressed because it is too large
Load Diff
43
BililiveRecorder.Core/Repeat.cs
Normal file
43
BililiveRecorder.Core/Repeat.cs
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
/**
|
||||||
|
* Author: Roger Lipscombe
|
||||||
|
* Source: https://stackoverflow.com/a/7472334
|
||||||
|
* */
|
||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace BililiveRecorder.Core
|
||||||
|
{
|
||||||
|
internal static class Repeat
|
||||||
|
{
|
||||||
|
public static Task Interval(
|
||||||
|
TimeSpan pollInterval,
|
||||||
|
Action action,
|
||||||
|
CancellationToken token)
|
||||||
|
{
|
||||||
|
// We don't use Observable.Interval:
|
||||||
|
// If we block, the values start bunching up behind each other.
|
||||||
|
return Task.Factory.StartNew(
|
||||||
|
() =>
|
||||||
|
{
|
||||||
|
for (; ; )
|
||||||
|
{
|
||||||
|
if (token.WaitCancellationRequested(pollInterval))
|
||||||
|
break;
|
||||||
|
|
||||||
|
action();
|
||||||
|
}
|
||||||
|
}, token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class CancellationTokenExtensions
|
||||||
|
{
|
||||||
|
public static bool WaitCancellationRequested(
|
||||||
|
this CancellationToken token,
|
||||||
|
TimeSpan timeout)
|
||||||
|
{
|
||||||
|
return token.WaitHandle.WaitOne(timeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user