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 ConcurrentDictionary<ulong, DiscordAudioClient> _voiceClients;
private ConcurrentDictionary<User, bool> _talkingUsers;
private int _nextClientId;
//private int _nextClientId;

internal DiscordClient Client => _client;
private DiscordClient _client;
@@ -143,13 +143,14 @@ namespace Discord.Audio
_defaultClient.SetServerId(server.Id);
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);
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.VoiceSocket.OnPacket += (s, e) =>
@@ -165,7 +166,7 @@ namespace Discord.Audio
return voiceClient;
});
//await client.Connect(gatewaySocket.Host, _client.Token).ConfigureAwait(false);
return Task.FromResult(client);
return Task.FromResult(client);*/
}

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.Net.WebSockets;
using Newtonsoft.Json;
using System;
using System.Threading;
using System.Threading.Tasks;

namespace Discord.Audio
{
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; }
public int Id { get; }
public GatewaySocket GatewaySocket { get; }
@@ -26,6 +28,9 @@ namespace Discord.Audio
Service = service;
Id = id;
Logger = logger;
GatewaySocket = gatewaySocket;

_connectionLock = new Semaphore(1, 1);

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

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

/*_voiceSocket.Connected += (s, e) => RaiseVoiceConnected();
@@ -72,33 +76,7 @@ namespace Discord.Audio
_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;
}
public async Task Join(Channel channel)
public Task Join(Channel channel)
{
if (channel == null) throw new ArgumentNullException(nameof(channel));
ulong? serverId = channel.Server?.Id;
if (serverId != ServerId)
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>
/// <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);
}
public async Task Connect()
{
await BeginConnect().ConfigureAwait(false);
}
public Task Connect()
=> BeginConnect();
public async Task Reconnect()
{
try
@@ -473,21 +471,6 @@ namespace Discord.Net.WebSockets
{
_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()
=> 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)]
public sealed class UpdateVoiceCommand : IWebSocketMessage
{
int IWebSocketMessage.OpCode => (int)OpCodes.StatusUpdate;
int IWebSocketMessage.OpCode => (int)OpCodes.VoiceStateUpdate;
object IWebSocketMessage.Payload => this;
bool IWebSocketMessage.IsPrivate => false;



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

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

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

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

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

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

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

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

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

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

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

#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 System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace Discord.Net.WebSockets
@@ -176,5 +177,9 @@ namespace Discord.Net.WebSockets
=> QueueMessage(new UpdateVoiceCommand { GuildId = serverId, ChannelId = channelId, IsSelfMuted = isSelfMuted, IsSelfDeafened = isSelfDeafened });
public void SendRequestMembers(ulong serverId, string query, int 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 void WaitForConnection(CancellationToken cancelToken)
public virtual void WaitForConnection(CancellationToken cancelToken)
{
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))
throw new TimeoutException();
}


Loading…
Cancel
Save