Added multi-server voice support
This commit is contained in:
@@ -136,15 +136,6 @@
|
|||||||
<Compile Include="..\Discord.Net\DiscordAPIClient.cs">
|
<Compile Include="..\Discord.Net\DiscordAPIClient.cs">
|
||||||
<Link>DiscordAPIClient.cs</Link>
|
<Link>DiscordAPIClient.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="..\Discord.Net\DiscordBaseClient.cs">
|
|
||||||
<Link>DiscordBaseClient.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Discord.Net\DiscordBaseClient.Events.cs">
|
|
||||||
<Link>DiscordBaseClient.Events.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Discord.Net\DiscordBaseClient.Voice.cs">
|
|
||||||
<Link>DiscordBaseClient.Voice.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Discord.Net\DiscordClient.API.cs">
|
<Compile Include="..\Discord.Net\DiscordClient.API.cs">
|
||||||
<Link>DiscordClient.API.cs</Link>
|
<Link>DiscordClient.API.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
@@ -160,6 +151,15 @@
|
|||||||
<Compile Include="..\Discord.Net\DiscordClientConfig.cs">
|
<Compile Include="..\Discord.Net\DiscordClientConfig.cs">
|
||||||
<Link>DiscordClientConfig.cs</Link>
|
<Link>DiscordClientConfig.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="..\Discord.Net\DiscordSimpleClient.cs">
|
||||||
|
<Link>DiscordSimpleClient.cs</Link>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="..\Discord.Net\DiscordSimpleClient.Events.cs">
|
||||||
|
<Link>DiscordSimpleClient.Events.cs</Link>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="..\Discord.Net\DiscordSimpleClient.Voice.cs">
|
||||||
|
<Link>DiscordSimpleClient.Voice.cs</Link>
|
||||||
|
</Compile>
|
||||||
<Compile Include="..\Discord.Net\Enums\ChannelTypes.cs">
|
<Compile Include="..\Discord.Net\Enums\ChannelTypes.cs">
|
||||||
<Link>Enums\ChannelTypes.cs</Link>
|
<Link>Enums\ChannelTypes.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
|||||||
@@ -6,19 +6,22 @@ using Discord.WebSockets.Data;
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using VoiceWebSocket = Discord.WebSockets.Voice.VoiceWebSocket;
|
||||||
|
|
||||||
namespace Discord
|
namespace Discord
|
||||||
{
|
{
|
||||||
/// <summary> Provides a connection to the DiscordApp service. </summary>
|
/// <summary> Provides a connection to the DiscordApp service. </summary>
|
||||||
public partial class DiscordClient : DiscordBaseClient
|
public partial class DiscordClient : DiscordSimpleClient
|
||||||
{
|
{
|
||||||
protected readonly DiscordAPIClient _api;
|
protected readonly DiscordAPIClient _api;
|
||||||
private readonly Random _rand;
|
private readonly Random _rand;
|
||||||
private readonly JsonSerializer _serializer;
|
private readonly JsonSerializer _serializer;
|
||||||
private readonly ConcurrentQueue<Message> _pendingMessages;
|
private readonly ConcurrentQueue<Message> _pendingMessages;
|
||||||
|
private readonly ConcurrentDictionary<string, DiscordSimpleClient> _voiceClients;
|
||||||
|
|
||||||
/// <summary> Returns the current logged-in user. </summary>
|
/// <summary> Returns the current logged-in user. </summary>
|
||||||
public User CurrentUser => _currentUser;
|
public User CurrentUser => _currentUser;
|
||||||
@@ -52,6 +55,8 @@ namespace Discord
|
|||||||
_api = new DiscordAPIClient(_config.LogLevel, _config.APITimeout);
|
_api = new DiscordAPIClient(_config.LogLevel, _config.APITimeout);
|
||||||
if (_config.UseMessageQueue)
|
if (_config.UseMessageQueue)
|
||||||
_pendingMessages = new ConcurrentQueue<Message>();
|
_pendingMessages = new ConcurrentQueue<Message>();
|
||||||
|
if (_config.EnableVoiceMultiserver)
|
||||||
|
_voiceClients = new ConcurrentDictionary<string, DiscordSimpleClient>();
|
||||||
|
|
||||||
object cacheLock = new object();
|
object cacheLock = new object();
|
||||||
_channels = new Channels(this, cacheLock);
|
_channels = new Channels(this, cacheLock);
|
||||||
@@ -61,38 +66,20 @@ namespace Discord
|
|||||||
_servers = new Servers(this, cacheLock);
|
_servers = new Servers(this, cacheLock);
|
||||||
_users = new Users(this, cacheLock);
|
_users = new Users(this, cacheLock);
|
||||||
|
|
||||||
if (Config.VoiceMode != DiscordVoiceMode.Disabled)
|
|
||||||
{
|
|
||||||
this.VoiceDisconnected += (s, e) =>
|
|
||||||
{
|
|
||||||
foreach (var member in _members)
|
|
||||||
{
|
|
||||||
if (member.IsSpeaking)
|
|
||||||
{
|
|
||||||
member.IsSpeaking = false;
|
|
||||||
RaiseUserIsSpeaking(member, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
_voiceSocket.IsSpeaking += (s, e) =>
|
|
||||||
{
|
|
||||||
if (_voiceSocket.State == WebSocketState.Connected)
|
|
||||||
{
|
|
||||||
var member = _members[e.UserId, _voiceSocket.CurrentServerId];
|
|
||||||
bool value = e.IsSpeaking;
|
|
||||||
if (member.IsSpeaking != value)
|
|
||||||
{
|
|
||||||
member.IsSpeaking = value;
|
|
||||||
RaiseUserIsSpeaking(member, value);
|
|
||||||
if (Config.TrackActivity)
|
|
||||||
member.UpdateActivity();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
this.Connected += (s,e) => _api.CancelToken = CancelToken;
|
this.Connected += (s,e) => _api.CancelToken = CancelToken;
|
||||||
|
|
||||||
|
VoiceDisconnected += (s, e) =>
|
||||||
|
{
|
||||||
|
foreach (var member in _members)
|
||||||
|
{
|
||||||
|
if (member.ServerId == e.ServerId && member.IsSpeaking)
|
||||||
|
{
|
||||||
|
member.IsSpeaking = false;
|
||||||
|
RaiseUserIsSpeaking(member, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if (_config.LogLevel >= LogMessageSeverity.Verbose)
|
if (_config.LogLevel >= LogMessageSeverity.Verbose)
|
||||||
{
|
{
|
||||||
bool isDebug = _config.LogLevel >= LogMessageSeverity.Debug;
|
bool isDebug = _config.LogLevel >= LogMessageSeverity.Debug;
|
||||||
@@ -207,6 +194,27 @@ namespace Discord
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal override VoiceWebSocket CreateVoiceSocket()
|
||||||
|
{
|
||||||
|
var socket = base.CreateVoiceSocket();
|
||||||
|
socket.IsSpeaking += (s, e) =>
|
||||||
|
{
|
||||||
|
if (_voiceSocket.State == WebSocketState.Connected)
|
||||||
|
{
|
||||||
|
var member = _members[e.UserId, socket.CurrentServerId];
|
||||||
|
bool value = e.IsSpeaking;
|
||||||
|
if (member.IsSpeaking != value)
|
||||||
|
{
|
||||||
|
member.IsSpeaking = value;
|
||||||
|
RaiseUserIsSpeaking(member, value);
|
||||||
|
if (_config.TrackActivity)
|
||||||
|
member.UpdateActivity();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return socket;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary> Connects to the Discord server with the provided email and password. </summary>
|
/// <summary> Connects to the Discord server with the provided email and password. </summary>
|
||||||
/// <returns> Returns a token for future connections. </returns>
|
/// <returns> Returns a token for future connections. </returns>
|
||||||
public new async Task<string> Connect(string email, string password)
|
public new async Task<string> Connect(string email, string password)
|
||||||
@@ -249,6 +257,18 @@ namespace Discord
|
|||||||
{
|
{
|
||||||
await base.Cleanup().ConfigureAwait(false);
|
await base.Cleanup().ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (_config.VoiceMode != DiscordVoiceMode.Disabled)
|
||||||
|
{
|
||||||
|
if (_config.EnableVoiceMultiserver)
|
||||||
|
{
|
||||||
|
var tasks = _voiceClients
|
||||||
|
.Select(x => x.Value.Disconnect())
|
||||||
|
.ToArray();
|
||||||
|
_voiceClients.Clear();
|
||||||
|
await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (_config.UseMessageQueue)
|
if (_config.UseMessageQueue)
|
||||||
{
|
{
|
||||||
Message ignored;
|
Message ignored;
|
||||||
@@ -624,20 +644,6 @@ namespace Discord
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "VOICE_SERVER_UPDATE":
|
|
||||||
{
|
|
||||||
var data = e.Payload.ToObject<VoiceServerUpdateEvent>(_serializer);
|
|
||||||
if (data.GuildId == _voiceSocket.CurrentServerId)
|
|
||||||
{
|
|
||||||
var server = _servers[data.GuildId];
|
|
||||||
if (_config.VoiceMode != DiscordVoiceMode.Disabled)
|
|
||||||
{
|
|
||||||
_voiceSocket.Host = "wss://" + data.Endpoint.Split(':')[0];
|
|
||||||
await _voiceSocket.Login(CurrentUserId, _dataSocket.SessionId, data.Token, CancelToken).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
//Settings
|
//Settings
|
||||||
case "USER_UPDATE":
|
case "USER_UPDATE":
|
||||||
@@ -657,11 +663,76 @@ namespace Discord
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
//Internal (handled in DiscordSimpleClient)
|
||||||
|
case "VOICE_SERVER_UPDATE":
|
||||||
|
break;
|
||||||
|
|
||||||
//Others
|
//Others
|
||||||
default:
|
default:
|
||||||
RaiseOnLog(LogMessageSeverity.Warning, LogMessageSource.DataWebSocket, $"Unknown message type: {e.Type}");
|
RaiseOnLog(LogMessageSeverity.Warning, LogMessageSource.DataWebSocket, $"Unknown message type: {e.Type}");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IDiscordVoiceClient GetVoiceClient(string serverId)
|
||||||
|
{
|
||||||
|
if (serverId == null) throw new ArgumentNullException(nameof(serverId));
|
||||||
|
|
||||||
|
if (!_config.EnableVoiceMultiserver)
|
||||||
|
{
|
||||||
|
if (serverId == _voiceServerId)
|
||||||
|
return this;
|
||||||
|
else
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
DiscordSimpleClient client;
|
||||||
|
if (_voiceClients.TryGetValue(serverId, out client))
|
||||||
|
return client;
|
||||||
|
else
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
private async Task<IDiscordVoiceClient> CreateVoiceClient(string serverId)
|
||||||
|
{
|
||||||
|
if (!_config.EnableVoiceMultiserver)
|
||||||
|
{
|
||||||
|
_voiceServerId = serverId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
var client = _voiceClients.GetOrAdd(serverId, _ =>
|
||||||
|
{
|
||||||
|
var config = _config.Clone();
|
||||||
|
config.LogLevel = (LogMessageSeverity)Math.Min((int)_config.LogLevel, (int)LogMessageSeverity.Warning);
|
||||||
|
config.EnableVoiceMultiserver = false;
|
||||||
|
return new DiscordSimpleClient(config, serverId);
|
||||||
|
});
|
||||||
|
await client.Connect(_gateway, _token).ConfigureAwait(false);
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task JoinVoiceServer(Channel channel)
|
||||||
|
=> JoinVoiceServer(channel?.ServerId, channel?.Id);
|
||||||
|
public Task JoinVoiceServer(Server server, string channelId)
|
||||||
|
=> JoinVoiceServer(server?.Id, channelId);
|
||||||
|
public async Task JoinVoiceServer(string serverId, string channelId)
|
||||||
|
{
|
||||||
|
CheckReady(); //checkVoice is done inside the voice client
|
||||||
|
if (serverId == null) throw new ArgumentNullException(nameof(serverId));
|
||||||
|
if (channelId == null) throw new ArgumentNullException(nameof(channelId));
|
||||||
|
|
||||||
|
var client = await CreateVoiceClient(serverId).ConfigureAwait(false);
|
||||||
|
await client.JoinChannel(channelId).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task LeaveVoiceServer(string serverId)
|
||||||
|
{
|
||||||
|
CheckReady(); //checkVoice is done inside the voice client
|
||||||
|
if (serverId == null) throw new ArgumentNullException(nameof(serverId));
|
||||||
|
|
||||||
|
DiscordSimpleClient client;
|
||||||
|
if (_voiceClients.TryRemove(serverId, out client))
|
||||||
|
await client.Disconnect();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ namespace Discord
|
|||||||
Both = Outgoing | Incoming
|
Both = Outgoing | Incoming
|
||||||
}
|
}
|
||||||
|
|
||||||
public class DiscordClientConfig
|
public sealed class DiscordClientConfig
|
||||||
{
|
{
|
||||||
/// <summary> Specifies the minimum log level severity that will be sent to the LogMessage event. Warning: setting this to debug will really hurt performance but should help investigate any internal issues. </summary>
|
/// <summary> Specifies the minimum log level severity that will be sent to the LogMessage event. Warning: setting this to debug will really hurt performance but should help investigate any internal issues. </summary>
|
||||||
public LogMessageSeverity LogLevel { get { return _logLevel; } set { SetValue(ref _logLevel, value); } }
|
public LogMessageSeverity LogLevel { get { return _logLevel; } set { SetValue(ref _logLevel, value); } }
|
||||||
@@ -72,5 +72,12 @@ namespace Discord
|
|||||||
throw new InvalidOperationException("Unable to modify a discord client's configuration after it has been created.");
|
throw new InvalidOperationException("Unable to modify a discord client's configuration after it has been created.");
|
||||||
storage = value;
|
storage = value;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public DiscordClientConfig Clone()
|
||||||
|
{
|
||||||
|
var config = this.MemberwiseClone() as DiscordClientConfig;
|
||||||
|
config._isLocked = false;
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,16 @@ namespace Discord
|
|||||||
Error = error;
|
Error = error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public class VoiceDisconnectedEventArgs : DisconnectedEventArgs
|
||||||
|
{
|
||||||
|
public readonly string ServerId;
|
||||||
|
|
||||||
|
internal VoiceDisconnectedEventArgs(string serverId, DisconnectedEventArgs e)
|
||||||
|
: base(e.WasUnexpected, e.Error)
|
||||||
|
{
|
||||||
|
ServerId = serverId;
|
||||||
|
}
|
||||||
|
}
|
||||||
public sealed class LogMessageEventArgs : EventArgs
|
public sealed class LogMessageEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
public LogMessageSeverity Severity { get; }
|
public LogMessageSeverity Severity { get; }
|
||||||
@@ -63,7 +73,7 @@ namespace Discord
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract partial class DiscordBaseClient
|
public partial class DiscordSimpleClient
|
||||||
{
|
{
|
||||||
public event EventHandler Connected;
|
public event EventHandler Connected;
|
||||||
private void RaiseConnected()
|
private void RaiseConnected()
|
||||||
@@ -90,11 +100,11 @@ namespace Discord
|
|||||||
if (VoiceConnected != null)
|
if (VoiceConnected != null)
|
||||||
RaiseEvent(nameof(VoiceConnected), () => VoiceConnected(this, EventArgs.Empty));
|
RaiseEvent(nameof(VoiceConnected), () => VoiceConnected(this, EventArgs.Empty));
|
||||||
}
|
}
|
||||||
public event EventHandler<DisconnectedEventArgs> VoiceDisconnected;
|
public event EventHandler<VoiceDisconnectedEventArgs> VoiceDisconnected;
|
||||||
private void RaiseVoiceDisconnected(DisconnectedEventArgs e)
|
private void RaiseVoiceDisconnected(string serverId, DisconnectedEventArgs e)
|
||||||
{
|
{
|
||||||
if (VoiceDisconnected != null)
|
if (VoiceDisconnected != null)
|
||||||
RaiseEvent(nameof(VoiceDisconnected), () => VoiceDisconnected(this, e));
|
RaiseEvent(nameof(VoiceDisconnected), () => VoiceDisconnected(this, new VoiceDisconnectedEventArgs(serverId, e)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public event EventHandler<VoicePacketEventArgs> OnVoicePacket;
|
public event EventHandler<VoicePacketEventArgs> OnVoicePacket;
|
||||||
@@ -6,47 +6,53 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace Discord
|
namespace Discord
|
||||||
{
|
{
|
||||||
public partial class DiscordBaseClient
|
public interface IDiscordVoiceClient
|
||||||
{
|
{
|
||||||
public Task JoinVoiceServer(Channel channel)
|
Task JoinChannel(string channelId);
|
||||||
=> JoinVoiceServer(channel?.ServerId, channel?.Id);
|
Task Disconnect();
|
||||||
public Task JoinVoiceServer(Server server, string channelId)
|
|
||||||
=> JoinVoiceServer(server?.Id, channelId);
|
void SendVoicePCM(byte[] data, int count);
|
||||||
public async Task JoinVoiceServer(string serverId, string channelId)
|
void ClearVoicePCM();
|
||||||
|
|
||||||
|
Task WaitVoice();
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class DiscordSimpleClient : IDiscordVoiceClient
|
||||||
|
{
|
||||||
|
async Task IDiscordVoiceClient.JoinChannel(string channelId)
|
||||||
{
|
{
|
||||||
CheckReady(checkVoice: true);
|
CheckReady(checkVoice: true);
|
||||||
if (serverId == null) throw new ArgumentNullException(nameof(serverId));
|
|
||||||
if (channelId == null) throw new ArgumentNullException(nameof(channelId));
|
if (channelId == null) throw new ArgumentNullException(nameof(channelId));
|
||||||
|
|
||||||
await LeaveVoiceServer().ConfigureAwait(false);
|
await ((IDiscordVoiceClient)this).Disconnect().ConfigureAwait(false);
|
||||||
_voiceSocket.SetChannel(serverId, channelId);
|
_voiceSocket.SetChannel(_voiceServerId, channelId);
|
||||||
_dataSocket.SendJoinVoice(serverId, channelId);
|
_dataSocket.SendJoinVoice(_voiceServerId, channelId);
|
||||||
|
|
||||||
CancellationTokenSource tokenSource = new CancellationTokenSource();
|
CancellationTokenSource tokenSource = new CancellationTokenSource();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await Task.Run(() => _voiceSocket.WaitForConnection(tokenSource.Token))
|
await Task.Run(() => _voiceSocket.WaitForConnection(tokenSource.Token))
|
||||||
.Timeout(_config.ConnectionTimeout, tokenSource)
|
.Timeout(_config.ConnectionTimeout, tokenSource)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (TimeoutException)
|
catch (TimeoutException)
|
||||||
{
|
{
|
||||||
tokenSource.Cancel();
|
tokenSource.Cancel();
|
||||||
await LeaveVoiceServer().ConfigureAwait(false);
|
await ((IDiscordVoiceClient)this).Disconnect().ConfigureAwait(false);
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public async Task LeaveVoiceServer()
|
|
||||||
|
async Task IDiscordVoiceClient.Disconnect()
|
||||||
{
|
{
|
||||||
CheckReady(checkVoice: true);
|
CheckReady(checkVoice: true);
|
||||||
|
|
||||||
if (_voiceSocket.State != WebSocketState.Disconnected)
|
if (_voiceSocket.State != WebSocketState.Disconnected)
|
||||||
{
|
{
|
||||||
var serverId = _voiceSocket.CurrentServerId;
|
if (_voiceSocket.CurrentServerId != null)
|
||||||
if (serverId != null)
|
|
||||||
{
|
{
|
||||||
await _voiceSocket.Disconnect().ConfigureAwait(false);
|
await _voiceSocket.Disconnect().ConfigureAwait(false);
|
||||||
_dataSocket.SendLeaveVoice(serverId);
|
_dataSocket.SendLeaveVoice(_voiceSocket.CurrentServerId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -54,7 +60,7 @@ namespace Discord
|
|||||||
/// <summary> Sends a PCM frame to the voice server. Will block until space frees up in the outgoing buffer. </summary>
|
/// <summary> Sends a PCM frame to the voice server. Will block until space frees up in the outgoing buffer. </summary>
|
||||||
/// <param name="data">PCM frame to send. This must be a single or collection of uncompressed 48Kz monochannel 20ms PCM frames. </param>
|
/// <param name="data">PCM frame to send. This must be a single or collection of uncompressed 48Kz monochannel 20ms PCM frames. </param>
|
||||||
/// <param name="count">Number of bytes in this frame. </param>
|
/// <param name="count">Number of bytes in this frame. </param>
|
||||||
public void SendVoicePCM(byte[] data, int count)
|
void IDiscordVoiceClient.SendVoicePCM(byte[] data, int count)
|
||||||
{
|
{
|
||||||
CheckReady(checkVoice: true);
|
CheckReady(checkVoice: true);
|
||||||
if (data == null) throw new ArgumentException(nameof(data));
|
if (data == null) throw new ArgumentException(nameof(data));
|
||||||
@@ -64,7 +70,7 @@ namespace Discord
|
|||||||
_voiceSocket.SendPCMFrames(data, count);
|
_voiceSocket.SendPCMFrames(data, count);
|
||||||
}
|
}
|
||||||
/// <summary> Clears the PCM buffer. </summary>
|
/// <summary> Clears the PCM buffer. </summary>
|
||||||
public void ClearVoicePCM()
|
void IDiscordVoiceClient.ClearVoicePCM()
|
||||||
{
|
{
|
||||||
CheckReady(checkVoice: true);
|
CheckReady(checkVoice: true);
|
||||||
|
|
||||||
@@ -72,7 +78,7 @@ namespace Discord
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Returns a task that completes once the voice output buffer is empty. </summary>
|
/// <summary> Returns a task that completes once the voice output buffer is empty. </summary>
|
||||||
public async Task WaitVoice()
|
async Task IDiscordVoiceClient.WaitVoice()
|
||||||
{
|
{
|
||||||
CheckReady(checkVoice: true);
|
CheckReady(checkVoice: true);
|
||||||
|
|
||||||
@@ -1,10 +1,6 @@
|
|||||||
using Discord.API;
|
using Discord.Helpers;
|
||||||
using Discord.Collections;
|
|
||||||
using Discord.Helpers;
|
|
||||||
using Discord.WebSockets.Data;
|
using Discord.WebSockets.Data;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Net;
|
|
||||||
using System.Runtime.ExceptionServices;
|
using System.Runtime.ExceptionServices;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -21,18 +17,24 @@ namespace Discord
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Provides a barebones connection to the Discord service </summary>
|
/// <summary> Provides a barebones connection to the Discord service </summary>
|
||||||
public partial class DiscordBaseClient
|
public partial class DiscordSimpleClient
|
||||||
{
|
{
|
||||||
internal readonly DataWebSocket _dataSocket;
|
internal readonly DataWebSocket _dataSocket;
|
||||||
internal readonly VoiceWebSocket _voiceSocket;
|
internal readonly VoiceWebSocket _voiceSocket;
|
||||||
protected readonly ManualResetEvent _disconnectedEvent;
|
protected readonly ManualResetEvent _disconnectedEvent;
|
||||||
protected readonly ManualResetEventSlim _connectedEvent;
|
protected readonly ManualResetEventSlim _connectedEvent;
|
||||||
|
protected readonly bool _enableVoice;
|
||||||
|
protected string _gateway, _token;
|
||||||
|
protected string _voiceServerId;
|
||||||
private Task _runTask;
|
private Task _runTask;
|
||||||
private string _gateway, _token;
|
|
||||||
|
|
||||||
protected ExceptionDispatchInfo _disconnectReason;
|
protected ExceptionDispatchInfo _disconnectReason;
|
||||||
private bool _wasDisconnectUnexpected;
|
private bool _wasDisconnectUnexpected;
|
||||||
|
|
||||||
|
/// <summary> Returns the configuration object used to make this client. Note that this object cannot be edited directly - to change the configuration of this client, use the DiscordClient(DiscordClientConfig config) constructor. </summary>
|
||||||
|
public DiscordClientConfig Config => _config;
|
||||||
|
protected readonly DiscordClientConfig _config;
|
||||||
|
|
||||||
/// <summary> Returns the id of the current logged-in user. </summary>
|
/// <summary> Returns the id of the current logged-in user. </summary>
|
||||||
public string CurrentUserId => _currentUserId;
|
public string CurrentUserId => _currentUserId;
|
||||||
private string _currentUserId;
|
private string _currentUserId;
|
||||||
@@ -43,64 +45,78 @@ namespace Discord
|
|||||||
public DiscordClientState State => (DiscordClientState)_state;
|
public DiscordClientState State => (DiscordClientState)_state;
|
||||||
private int _state;
|
private int _state;
|
||||||
|
|
||||||
/// <summary> Returns the configuration object used to make this client. Note that this object cannot be edited directly - to change the configuration of this client, use the DiscordClient(DiscordClientConfig config) constructor. </summary>
|
|
||||||
public DiscordClientConfig Config => _config;
|
|
||||||
protected readonly DiscordClientConfig _config;
|
|
||||||
|
|
||||||
public CancellationToken CancelToken => _cancelToken;
|
public CancellationToken CancelToken => _cancelToken;
|
||||||
private CancellationTokenSource _cancelTokenSource;
|
private CancellationTokenSource _cancelTokenSource;
|
||||||
private CancellationToken _cancelToken;
|
private CancellationToken _cancelToken;
|
||||||
|
|
||||||
/// <summary> Initializes a new instance of the DiscordClient class. </summary>
|
/// <summary> Initializes a new instance of the DiscordClient class. </summary>
|
||||||
public DiscordBaseClient(DiscordClientConfig config = null)
|
public DiscordSimpleClient(DiscordClientConfig config = null)
|
||||||
{
|
{
|
||||||
_config = config ?? new DiscordClientConfig();
|
_config = config ?? new DiscordClientConfig();
|
||||||
_config.Lock();
|
_config.Lock();
|
||||||
|
|
||||||
|
_enableVoice = config.VoiceMode != DiscordVoiceMode.Disabled && !config.EnableVoiceMultiserver;
|
||||||
|
|
||||||
_state = (int)DiscordClientState.Disconnected;
|
_state = (int)DiscordClientState.Disconnected;
|
||||||
_cancelToken = new CancellationToken(true);
|
_cancelToken = new CancellationToken(true);
|
||||||
_disconnectedEvent = new ManualResetEvent(true);
|
_disconnectedEvent = new ManualResetEvent(true);
|
||||||
_connectedEvent = new ManualResetEventSlim(false);
|
_connectedEvent = new ManualResetEventSlim(false);
|
||||||
|
|
||||||
_dataSocket = new DataWebSocket(this);
|
_dataSocket = CreateDataSocket();
|
||||||
_dataSocket.Connected += (s, e) => { if (_state == (int)DiscordClientState.Connecting) CompleteConnect(); };
|
if (_enableVoice)
|
||||||
_dataSocket.Disconnected += async (s, e) =>
|
_voiceSocket = CreateVoiceSocket();
|
||||||
|
}
|
||||||
|
internal DiscordSimpleClient(DiscordClientConfig config = null, string serverId = null)
|
||||||
|
: this(config)
|
||||||
|
{
|
||||||
|
_voiceServerId = serverId;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal virtual DataWebSocket CreateDataSocket()
|
||||||
|
{
|
||||||
|
var socket = new DataWebSocket(this);
|
||||||
|
socket.Connected += (s, e) =>
|
||||||
|
{
|
||||||
|
if (_state == (int)DiscordClientState.Connecting)
|
||||||
|
CompleteConnect(); }
|
||||||
|
;
|
||||||
|
socket.Disconnected += async (s, e) =>
|
||||||
{
|
{
|
||||||
RaiseDisconnected(e);
|
RaiseDisconnected(e);
|
||||||
if (e.WasUnexpected)
|
if (e.WasUnexpected)
|
||||||
await _dataSocket.Reconnect(_token);
|
await socket.Reconnect(_token);
|
||||||
};
|
};
|
||||||
if (Config.VoiceMode != DiscordVoiceMode.Disabled)
|
socket.LogMessage += (s, e) => RaiseOnLog(e.Severity, LogMessageSource.DataWebSocket, e.Message);
|
||||||
{
|
|
||||||
_voiceSocket = new VoiceWebSocket(this);
|
|
||||||
_voiceSocket.Connected += (s, e) => RaiseVoiceConnected();
|
|
||||||
_voiceSocket.Disconnected += async (s, e) =>
|
|
||||||
{
|
|
||||||
RaiseVoiceDisconnected(e);
|
|
||||||
if (e.WasUnexpected)
|
|
||||||
await _voiceSocket.Reconnect();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
_dataSocket.LogMessage += (s, e) => RaiseOnLog(e.Severity, LogMessageSource.DataWebSocket, e.Message);
|
|
||||||
if (_config.VoiceMode != DiscordVoiceMode.Disabled)
|
|
||||||
_voiceSocket.LogMessage += (s, e) => RaiseOnLog(e.Severity, LogMessageSource.VoiceWebSocket, e.Message);
|
|
||||||
if (_config.LogLevel >= LogMessageSeverity.Info)
|
if (_config.LogLevel >= LogMessageSeverity.Info)
|
||||||
{
|
{
|
||||||
_dataSocket.Connected += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.DataWebSocket, "Connected");
|
socket.Connected += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.DataWebSocket, "Connected");
|
||||||
_dataSocket.Disconnected += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.DataWebSocket, "Disconnected");
|
socket.Disconnected += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.DataWebSocket, "Disconnected");
|
||||||
if (_config.VoiceMode != DiscordVoiceMode.Disabled)
|
|
||||||
{
|
|
||||||
_voiceSocket.Connected += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.VoiceWebSocket, "Connected");
|
|
||||||
_voiceSocket.Disconnected += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.VoiceWebSocket, "Disconnected");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_dataSocket.ReceivedEvent += (s, e) => OnReceivedEvent(e);
|
socket.ReceivedEvent += (s, e) => OnReceivedEvent(e);
|
||||||
|
return socket;
|
||||||
|
}
|
||||||
|
internal virtual VoiceWebSocket CreateVoiceSocket()
|
||||||
|
{
|
||||||
|
var socket = new VoiceWebSocket(this);
|
||||||
|
socket.LogMessage += (s, e) => RaiseOnLog(e.Severity, LogMessageSource.VoiceWebSocket, e.Message);
|
||||||
|
socket.Connected += (s, e) => RaiseVoiceConnected();
|
||||||
|
socket.Disconnected += async (s, e) =>
|
||||||
|
{
|
||||||
|
RaiseVoiceDisconnected(socket.CurrentServerId, e);
|
||||||
|
if (e.WasUnexpected)
|
||||||
|
await socket.Reconnect();
|
||||||
|
};
|
||||||
|
if (_config.LogLevel >= LogMessageSeverity.Info)
|
||||||
|
{
|
||||||
|
socket.Connected += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.VoiceWebSocket, "Connected");
|
||||||
|
socket.Disconnected += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.VoiceWebSocket, "Disconnected");
|
||||||
|
}
|
||||||
|
return socket;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Connection
|
//Connection
|
||||||
protected async Task<string> Connect(string gateway, string token)
|
public async Task<string> Connect(string gateway, string token)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -132,7 +148,6 @@ namespace Discord
|
|||||||
}
|
}
|
||||||
|
|
||||||
//_state = (int)DiscordClientState.Connected;
|
//_state = (int)DiscordClientState.Connected;
|
||||||
_token = token;
|
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
@@ -222,7 +237,7 @@ namespace Discord
|
|||||||
protected virtual async Task Cleanup()
|
protected virtual async Task Cleanup()
|
||||||
{
|
{
|
||||||
await _dataSocket.Disconnect().ConfigureAwait(false);
|
await _dataSocket.Disconnect().ConfigureAwait(false);
|
||||||
if (_config.VoiceMode != DiscordVoiceMode.Disabled)
|
if (_enableVoice)
|
||||||
await _voiceSocket.Disconnect().ConfigureAwait(false);
|
await _voiceSocket.Disconnect().ConfigureAwait(false);
|
||||||
|
|
||||||
_currentUserId = null;
|
_currentUserId = null;
|
||||||
@@ -249,7 +264,7 @@ namespace Discord
|
|||||||
throw new InvalidOperationException("The client is connecting.");
|
throw new InvalidOperationException("The client is connecting.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (checkVoice && _config.VoiceMode == DiscordVoiceMode.Disabled)
|
if (checkVoice && !_enableVoice)
|
||||||
throw new InvalidOperationException("Voice is not enabled for this client.");
|
throw new InvalidOperationException("Voice is not enabled for this client.");
|
||||||
}
|
}
|
||||||
protected void RaiseEvent(string name, Action action)
|
protected void RaiseEvent(string name, Action action)
|
||||||
@@ -264,8 +279,24 @@ namespace Discord
|
|||||||
|
|
||||||
internal virtual Task OnReceivedEvent(WebSocketEventEventArgs e)
|
internal virtual Task OnReceivedEvent(WebSocketEventEventArgs e)
|
||||||
{
|
{
|
||||||
if (e.Type == "READY")
|
switch (e.Type)
|
||||||
_currentUserId = e.Payload["user"].Value<string>("id");
|
{
|
||||||
|
case "READY":
|
||||||
|
_currentUserId = e.Payload["user"].Value<string>("id");
|
||||||
|
break;
|
||||||
|
case "VOICE_SERVER_UPDATE":
|
||||||
|
{
|
||||||
|
string guildId = e.Payload.Value<string>("guild_id");
|
||||||
|
|
||||||
|
if (_enableVoice && guildId == _voiceSocket.CurrentServerId)
|
||||||
|
{
|
||||||
|
string token = e.Payload.Value<string>("token");
|
||||||
|
_voiceSocket.Host = "wss://" + e.Payload.Value<string>("endpoint").Split(':')[0];
|
||||||
|
return _voiceSocket.Login(_currentUserId, _dataSocket.SessionId, token, CancelToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
return TaskHelper.CompletedTask;
|
return TaskHelper.CompletedTask;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -12,7 +12,7 @@ namespace Discord.WebSockets.Data
|
|||||||
public string SessionId => _sessionId;
|
public string SessionId => _sessionId;
|
||||||
private string _sessionId;
|
private string _sessionId;
|
||||||
|
|
||||||
public DataWebSocket(DiscordBaseClient client)
|
public DataWebSocket(DiscordSimpleClient client)
|
||||||
: base(client)
|
: base(client)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using Newtonsoft.Json;
|
|||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
@@ -45,7 +46,7 @@ namespace Discord.WebSockets.Voice
|
|||||||
public string CurrentServerId => _serverId;
|
public string CurrentServerId => _serverId;
|
||||||
public string CurrentChannelId => _channelId;
|
public string CurrentChannelId => _channelId;
|
||||||
|
|
||||||
public VoiceWebSocket(DiscordBaseClient client)
|
public VoiceWebSocket(DiscordSimpleClient client)
|
||||||
: base(client)
|
: base(client)
|
||||||
{
|
{
|
||||||
_rand = new Random();
|
_rand = new Random();
|
||||||
@@ -121,25 +122,32 @@ namespace Discord.WebSockets.Voice
|
|||||||
msg.Payload.UserId = _userId;
|
msg.Payload.UserId = _userId;
|
||||||
QueueMessage(msg);
|
QueueMessage(msg);
|
||||||
|
|
||||||
|
List<Task> tasks = new List<Task>();
|
||||||
|
if ((_client.Config.VoiceMode & DiscordVoiceMode.Outgoing) != 0)
|
||||||
|
{
|
||||||
#if USE_THREAD
|
#if USE_THREAD
|
||||||
_sendThread = new Thread(new ThreadStart(() => SendVoiceAsync(_cancelToken)));
|
_sendThread = new Thread(new ThreadStart(() => SendVoiceAsync(_cancelToken)));
|
||||||
_sendThread.Start();
|
_sendThread.Start();
|
||||||
_receiveThread = new Thread(new ThreadStart(() => ReceiveVoiceAsync(_cancelToken)));
|
|
||||||
_receiveThread.Start();
|
|
||||||
#if !DNXCORE50
|
|
||||||
return new Task[] { WatcherAsync() }.Concat(base.Run()).ToArray();
|
|
||||||
#else
|
#else
|
||||||
return base.Run();
|
tasks.Add(SendVoiceAsync());
|
||||||
#endif
|
#endif
|
||||||
#else //!USE_THREAD
|
}
|
||||||
return new Task[] { Task.WhenAll(
|
if ((_client.Config.VoiceMode & DiscordVoiceMode.Incoming) != 0)
|
||||||
ReceiveVoiceAsync(),
|
{
|
||||||
SendVoiceAsync(),
|
#if USE_THREAD
|
||||||
|
_receiveThread = new Thread(new ThreadStart(() => ReceiveVoiceAsync(_cancelToken)));
|
||||||
|
_receiveThread.Start();
|
||||||
|
#else
|
||||||
|
tasks.Add(ReceiveVoiceAsync());
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
#if !DNXCORE50
|
#if !DNXCORE50
|
||||||
WatcherAsync()
|
tasks.Add(WatcherAsync());
|
||||||
#endif
|
|
||||||
)}.Concat(base.Run()).ToArray();
|
|
||||||
#endif
|
#endif
|
||||||
|
tasks.AddRange(base.Run());
|
||||||
|
|
||||||
|
return tasks.ToArray();
|
||||||
}
|
}
|
||||||
protected override Task Cleanup()
|
protected override Task Cleanup()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ namespace Discord.WebSockets
|
|||||||
internal abstract partial class WebSocket
|
internal abstract partial class WebSocket
|
||||||
{
|
{
|
||||||
protected readonly IWebSocketEngine _engine;
|
protected readonly IWebSocketEngine _engine;
|
||||||
protected readonly DiscordBaseClient _client;
|
protected readonly DiscordSimpleClient _client;
|
||||||
protected readonly LogMessageSeverity _logLevel;
|
protected readonly LogMessageSeverity _logLevel;
|
||||||
protected readonly ManualResetEventSlim _connectedEvent;
|
protected readonly ManualResetEventSlim _connectedEvent;
|
||||||
|
|
||||||
@@ -57,7 +57,7 @@ namespace Discord.WebSockets
|
|||||||
public WebSocketState State => (WebSocketState)_state;
|
public WebSocketState State => (WebSocketState)_state;
|
||||||
protected int _state;
|
protected int _state;
|
||||||
|
|
||||||
public WebSocket(DiscordBaseClient client)
|
public WebSocket(DiscordSimpleClient client)
|
||||||
{
|
{
|
||||||
_client = client;
|
_client = client;
|
||||||
_logLevel = client.Config.LogLevel;
|
_logLevel = client.Config.LogLevel;
|
||||||
|
|||||||
Reference in New Issue
Block a user