Browse Source

Readded basic voice support

tags/docs-0.9
RogueException 9 years ago
parent
commit
23639b227a
7 changed files with 95 additions and 86 deletions
  1. +6
    -5
      src/Discord.Net.Audio/AudioService.cs
  2. +69
    -42
      src/Discord.Net.Audio/DiscordAudioClient.cs
  3. +2
    -19
      src/Discord.Net.Audio/Net/WebSockets/VoiceWebSocket.cs
  4. +1
    -1
      src/Discord.Net/API/Client/GatewaySocket/Commands/UpdateVoice.cs
  5. +10
    -15
      src/Discord.Net/DiscordClient.cs
  6. +6
    -1
      src/Discord.Net/Net/WebSockets/GatewaySocket.cs
  7. +1
    -3
      src/Discord.Net/Net/WebSockets/WebSocket.cs

+ 6
- 5
src/Discord.Net.Audio/AudioService.cs View File

@@ -49,7 +49,7 @@ namespace Discord.Audio
private DiscordAudioClient _defaultClient; private DiscordAudioClient _defaultClient;
private ConcurrentDictionary<ulong, DiscordAudioClient> _voiceClients; private ConcurrentDictionary<ulong, DiscordAudioClient> _voiceClients;
private ConcurrentDictionary<User, bool> _talkingUsers; private ConcurrentDictionary<User, bool> _talkingUsers;
private int _nextClientId;
//private int _nextClientId;


internal DiscordClient Client => _client; internal DiscordClient Client => _client;
private DiscordClient _client; private DiscordClient _client;
@@ -143,13 +143,14 @@ namespace Discord.Audio
_defaultClient.SetServerId(server.Id); _defaultClient.SetServerId(server.Id);
return Task.FromResult(_defaultClient); return Task.FromResult(_defaultClient);
} }
else
throw new InvalidOperationException("Multiserver voice is not currently supported");


var client = _voiceClients.GetOrAdd(server.Id, _ =>
/*var client = _voiceClients.GetOrAdd(server.Id, _ =>
{ {
int id = unchecked(++_nextClientId); int id = unchecked(++_nextClientId);
var logger = Client.Log.CreateLogger($"Voice #{id}"); var logger = Client.Log.CreateLogger($"Voice #{id}");
GatewaySocket gatewaySocket = null;
var voiceClient = new DiscordAudioClient(this, id, logger, gatewaySocket);
var voiceClient = new DiscordAudioClient(this, id, logger, Client.GatewaySocket);
voiceClient.SetServerId(server.Id); voiceClient.SetServerId(server.Id);


voiceClient.VoiceSocket.OnPacket += (s, e) => voiceClient.VoiceSocket.OnPacket += (s, e) =>
@@ -165,7 +166,7 @@ namespace Discord.Audio
return voiceClient; return voiceClient;
}); });
//await client.Connect(gatewaySocket.Host, _client.Token).ConfigureAwait(false); //await client.Connect(gatewaySocket.Host, _client.Token).ConfigureAwait(false);
return Task.FromResult(client);
return Task.FromResult(client);*/
} }


public async Task<DiscordAudioClient> Join(Channel channel) public async Task<DiscordAudioClient> Join(Channel channel)


+ 69
- 42
src/Discord.Net.Audio/DiscordAudioClient.cs View File

@@ -1,18 +1,20 @@
using Discord.API;
using Discord.API.Client.GatewaySocket;
using Discord.API.Client.GatewaySocket;
using Discord.Logging; using Discord.Logging;
using Discord.Net.WebSockets; using Discord.Net.WebSockets;
using Newtonsoft.Json; using Newtonsoft.Json;
using System; using System;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;


namespace Discord.Audio namespace Discord.Audio
{ {
public partial class DiscordAudioClient public partial class DiscordAudioClient
{
private JsonSerializer _serializer;
{
private readonly Semaphore _connectionLock;
private readonly JsonSerializer _serializer;
private CancellationTokenSource _cancelTokenSource;


internal AudioService Service { get; }
internal AudioService Service { get; }
internal Logger Logger { get; } internal Logger Logger { get; }
public int Id { get; } public int Id { get; }
public GatewaySocket GatewaySocket { get; } public GatewaySocket GatewaySocket { get; }
@@ -26,6 +28,9 @@ namespace Discord.Audio
Service = service; Service = service;
Id = id; Id = id;
Logger = logger; Logger = logger;
GatewaySocket = gatewaySocket;

_connectionLock = new Semaphore(1, 1);


_serializer = new JsonSerializer(); _serializer = new JsonSerializer();
_serializer.DateTimeZoneHandling = DateTimeZoneHandling.Utc; _serializer.DateTimeZoneHandling = DateTimeZoneHandling.Utc;
@@ -35,7 +40,6 @@ namespace Discord.Audio
Logger.Error("Serialization Failed", e.ErrorContext.Error); Logger.Error("Serialization Failed", e.ErrorContext.Error);
}; };


GatewaySocket = gatewaySocket;
VoiceSocket = new VoiceWebSocket(service.Client, this, _serializer, logger); VoiceSocket = new VoiceWebSocket(service.Client, this, _serializer, logger);


/*_voiceSocket.Connected += (s, e) => RaiseVoiceConnected(); /*_voiceSocket.Connected += (s, e) => RaiseVoiceConnected();
@@ -72,33 +76,7 @@ namespace Discord.Audio
_voiceSocket.ParentCancelToken = _cancelToken; _voiceSocket.ParentCancelToken = _cancelToken;
};*/ };*/


GatewaySocket.ReceivedDispatch += async (s, e) =>
{
try
{
switch (e.Type)
{
case "VOICE_SERVER_UPDATE":
{
var data = e.Payload.ToObject<VoiceServerUpdateEvent>(_serializer);
var serverId = data.GuildId;

if (serverId == ServerId)
{
var client = Service.Client;
VoiceSocket.Token = data.Token;
VoiceSocket.Host = "wss://" + e.Payload.Value<string>("endpoint").Split(':')[0];
await VoiceSocket.Connect().ConfigureAwait(false);
}
}
break;
}
}
catch (Exception ex)
{
Logger.Error($"Error handling {e.Type} event", ex);
}
};
GatewaySocket.ReceivedDispatch += OnReceivedDispatch;
} }


@@ -106,22 +84,71 @@ namespace Discord.Audio
{ {
VoiceSocket.ServerId = serverId; VoiceSocket.ServerId = serverId;
} }
public async Task Join(Channel channel)
public Task Join(Channel channel)
{ {
if (channel == null) throw new ArgumentNullException(nameof(channel)); if (channel == null) throw new ArgumentNullException(nameof(channel));
ulong? serverId = channel.Server?.Id; ulong? serverId = channel.Server?.Id;
if (serverId != ServerId) if (serverId != ServerId)
throw new InvalidOperationException("Cannot join a channel on a different server than this voice client."); throw new InvalidOperationException("Cannot join a channel on a different server than this voice client.");
//CheckReady(checkVoice: true);
//CheckReady(checkVoice: true);


await VoiceSocket.Disconnect().ConfigureAwait(false);
VoiceSocket.ChannelId = channel.Id;
GatewaySocket.SendUpdateVoice(channel.Server.Id, channel.Id,
(Service.Config.Mode | AudioMode.Outgoing) == 0,
(Service.Config.Mode | AudioMode.Incoming) == 0);
await VoiceSocket.WaitForConnection(Service.Config.ConnectionTimeout).ConfigureAwait(false);
return Task.Run(async () =>
{
_connectionLock.WaitOne();
try
{
await VoiceSocket.Disconnect().ConfigureAwait(false);

_cancelTokenSource = new CancellationTokenSource();
var cancelToken = _cancelTokenSource.Token;
VoiceSocket.ParentCancelToken = cancelToken;

VoiceSocket.ChannelId = channel.Id;
GatewaySocket.SendUpdateVoice(channel.Server.Id, channel.Id,
(Service.Config.Mode | AudioMode.Outgoing) == 0,
(Service.Config.Mode | AudioMode.Incoming) == 0);

VoiceSocket.WaitForConnection(cancelToken);
}
finally
{
_connectionLock.Release();
}
});
}
public Task Disconnect()
{
GatewaySocket.ReceivedDispatch -= OnReceivedDispatch;
return VoiceSocket.Disconnect();
}

private async void OnReceivedDispatch(object sender, WebSocketEventEventArgs e)
{
try
{
switch (e.Type)
{
case "VOICE_SERVER_UPDATE":
{
var data = e.Payload.ToObject<VoiceServerUpdateEvent>(_serializer);
var serverId = data.GuildId;

if (serverId == ServerId)
{
var client = Service.Client;
VoiceSocket.Token = data.Token;
VoiceSocket.Host = "wss://" + e.Payload.Value<string>("endpoint").Split(':')[0];
await VoiceSocket.Connect().ConfigureAwait(false);
}
}
break;
}
}
catch (Exception ex)
{
Logger.Error($"Error handling {e.Type} event", ex);
}
} }
public Task Disconnect() => VoiceSocket.Disconnect();


/// <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>


+ 2
- 19
src/Discord.Net.Audio/Net/WebSockets/VoiceWebSocket.cs View File

@@ -63,10 +63,8 @@ namespace Discord.Net.WebSockets
_sendBuffer = new VoiceBuffer((int)Math.Ceiling(_config.BufferLength / (double)_encoder.FrameLength), _encoder.FrameSize); _sendBuffer = new VoiceBuffer((int)Math.Ceiling(_config.BufferLength / (double)_encoder.FrameLength), _encoder.FrameSize);
} }
public async Task Connect()
{
await BeginConnect().ConfigureAwait(false);
}
public Task Connect()
=> BeginConnect();
public async Task Reconnect() public async Task Reconnect()
{ {
try try
@@ -473,21 +471,6 @@ namespace Discord.Net.WebSockets
{ {
_sendBuffer.Wait(CancelToken); _sendBuffer.Wait(CancelToken);
} }
public Task WaitForConnection(int timeout)
{
return Task.Run(() =>
{
try
{
if (!_connectedEvent.Wait(timeout, CancelToken))
throw new TimeoutException();
}
catch (OperationCanceledException)
{
_taskManager.ThrowException();
}
});
}


public override void SendHeartbeat() public override void SendHeartbeat()
=> QueueMessage(new HeartbeatCommand()); => QueueMessage(new HeartbeatCommand());


+ 1
- 1
src/Discord.Net/API/Client/GatewaySocket/Commands/UpdateVoice.cs View File

@@ -6,7 +6,7 @@ namespace Discord.API.Client.GatewaySocket
[JsonObject(MemberSerialization.OptIn)] [JsonObject(MemberSerialization.OptIn)]
public sealed class UpdateVoiceCommand : IWebSocketMessage public sealed class UpdateVoiceCommand : IWebSocketMessage
{ {
int IWebSocketMessage.OpCode => (int)OpCodes.StatusUpdate;
int IWebSocketMessage.OpCode => (int)OpCodes.VoiceStateUpdate;
object IWebSocketMessage.Payload => this; object IWebSocketMessage.Payload => this;
bool IWebSocketMessage.IsPrivate => false; bool IWebSocketMessage.IsPrivate => false;




+ 10
- 15
src/Discord.Net/DiscordClient.cs View File

@@ -126,11 +126,6 @@ namespace Discord


if (Config.UseMessageQueue) if (Config.UseMessageQueue)
MessageQueue = new MessageQueue(this, Log.CreateLogger("MessageQueue")); MessageQueue = new MessageQueue(this, Log.CreateLogger("MessageQueue"));
Connected += async (s, e) =>
{
ClientAPI.CancelToken = CancelToken;
await SendStatus().ConfigureAwait(false);
};


//Extensibility //Extensibility
Services = new ServiceManager(this); Services = new ServiceManager(this);
@@ -172,6 +167,10 @@ namespace Discord
State = ConnectionState.Connecting; State = ConnectionState.Connecting;
_disconnectedEvent.Reset(); _disconnectedEvent.Reset();


_cancelTokenSource = new CancellationTokenSource();
CancelToken = _cancelTokenSource.Token;
GatewaySocket.ParentCancelToken = CancelToken;

await Login(email, password, token).ConfigureAwait(false); await Login(email, password, token).ConfigureAwait(false);
await GatewaySocket.Connect().ConfigureAwait(false); await GatewaySocket.Connect().ConfigureAwait(false);


@@ -196,10 +195,6 @@ namespace Discord
} }
private async Task Login(string email, string password, string token) private async Task Login(string email, string password, string token)
{ {
_cancelTokenSource = new CancellationTokenSource();
CancelToken = _cancelTokenSource.Token;
GatewaySocket.ParentCancelToken = CancelToken;

bool useCache = Config.CacheToken; bool useCache = Config.CacheToken;
while (true) while (true)
{ {
@@ -261,6 +256,9 @@ namespace Discord
{ {
State = ConnectionState.Connected; State = ConnectionState.Connected;
_connectedEvent.Set(); _connectedEvent.Set();

ClientAPI.CancelToken = CancelToken;
SendStatus();
OnConnected(); OnConnected();
} }


@@ -293,21 +291,19 @@ namespace Discord
_disconnectedEvent.Set(); _disconnectedEvent.Set();
} }
public Task SetStatus(UserStatus status)
public void SetStatus(UserStatus status)
{ {
if (status == null) throw new ArgumentNullException(nameof(status)); if (status == null) throw new ArgumentNullException(nameof(status));
if (status != UserStatus.Online && status != UserStatus.Idle) if (status != UserStatus.Online && status != UserStatus.Idle)
throw new ArgumentException($"Invalid status, must be {UserStatus.Online} or {UserStatus.Idle}", nameof(status)); throw new ArgumentException($"Invalid status, must be {UserStatus.Online} or {UserStatus.Idle}", nameof(status));


Status = status; Status = status;
return SendStatus();
} }
public Task SetGame(string game)
public void SetGame(string game)
{ {
CurrentGame = game; CurrentGame = game;
return SendStatus();
} }
private Task SendStatus()
private void SendStatus()
{ {
PrivateUser.Status = Status; PrivateUser.Status = Status;
PrivateUser.CurrentGame = CurrentGame; PrivateUser.CurrentGame = CurrentGame;
@@ -321,7 +317,6 @@ namespace Discord
} }
} }
GatewaySocket.SendUpdateStatus(Status == UserStatus.Idle ? EpochTime.GetMilliseconds() - (10 * 60 * 1000) : (long?)null, CurrentGame); GatewaySocket.SendUpdateStatus(Status == UserStatus.Idle ? EpochTime.GetMilliseconds() - (10 * 60 * 1000) : (long?)null, CurrentGame);
return TaskHelper.CompletedTask;
} }


#region Channels #region Channels


+ 6
- 1
src/Discord.Net/Net/WebSockets/GatewaySocket.cs View File

@@ -5,6 +5,7 @@ using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;


namespace Discord.Net.WebSockets namespace Discord.Net.WebSockets
@@ -176,5 +177,9 @@ namespace Discord.Net.WebSockets
=> QueueMessage(new UpdateVoiceCommand { GuildId = serverId, ChannelId = channelId, IsSelfMuted = isSelfMuted, IsSelfDeafened = isSelfDeafened }); => QueueMessage(new UpdateVoiceCommand { GuildId = serverId, ChannelId = channelId, IsSelfMuted = isSelfMuted, IsSelfDeafened = isSelfDeafened });
public void SendRequestMembers(ulong serverId, string query, int limit) public void SendRequestMembers(ulong serverId, string query, int limit)
=> QueueMessage(new RequestMembersCommand { GuildId = serverId, Query = query, Limit = limit }); => QueueMessage(new RequestMembersCommand { GuildId = serverId, Query = query, Limit = limit });
}

//Cancel if either DiscordClient.Disconnect is called, data socket errors or timeout is reached
public override void WaitForConnection(CancellationToken cancelToken)
=> base.WaitForConnection(CancellationTokenSource.CreateLinkedTokenSource(cancelToken, CancelToken).Token);
}
} }

+ 1
- 3
src/Discord.Net/Net/WebSockets/WebSocket.cs View File

@@ -173,12 +173,10 @@ namespace Discord.Net.WebSockets
} }
public abstract void SendHeartbeat(); public abstract void SendHeartbeat();


public void WaitForConnection(CancellationToken cancelToken)
public virtual void WaitForConnection(CancellationToken cancelToken)
{ {
try try
{ {
//Cancel if either DiscordClient.Disconnect is called, data socket errors or timeout is reached
cancelToken = CancellationTokenSource.CreateLinkedTokenSource(cancelToken, CancelToken).Token;
if (!_connectedEvent.Wait(_client.Config.ConnectionTimeout, cancelToken)) if (!_connectedEvent.Wait(_client.Config.ConnectionTimeout, cancelToken))
throw new TimeoutException(); throw new TimeoutException();
} }


Loading…
Cancel
Save