|
@@ -1,242 +0,0 @@ |
|
|
using Discord.API.Client.GatewaySocket; |
|
|
|
|
|
using Discord.Logging; |
|
|
|
|
|
using Discord.Net.Rest; |
|
|
|
|
|
using Discord.Net.WebSockets; |
|
|
|
|
|
using Newtonsoft.Json; |
|
|
|
|
|
using Nito.AsyncEx; |
|
|
|
|
|
using System; |
|
|
|
|
|
using System.IO; |
|
|
|
|
|
using System.Threading; |
|
|
|
|
|
using System.Threading.Tasks; |
|
|
|
|
|
|
|
|
|
|
|
namespace Discord.Audio |
|
|
|
|
|
{ |
|
|
|
|
|
internal class AudioClient : IAudioClient |
|
|
|
|
|
{ |
|
|
|
|
|
private class OutStream : Stream |
|
|
|
|
|
{ |
|
|
|
|
|
public override bool CanRead => false; |
|
|
|
|
|
public override bool CanSeek => false; |
|
|
|
|
|
public override bool CanWrite => true; |
|
|
|
|
|
|
|
|
|
|
|
private readonly AudioClient _client; |
|
|
|
|
|
|
|
|
|
|
|
internal OutStream(AudioClient client) |
|
|
|
|
|
{ |
|
|
|
|
|
_client = client; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
public override long Length { get { throw new InvalidOperationException(); } } |
|
|
|
|
|
public override long Position |
|
|
|
|
|
{ |
|
|
|
|
|
get { throw new InvalidOperationException(); } |
|
|
|
|
|
set { throw new InvalidOperationException(); } |
|
|
|
|
|
} |
|
|
|
|
|
public override void Flush() { throw new InvalidOperationException(); } |
|
|
|
|
|
public override long Seek(long offset, SeekOrigin origin) { throw new InvalidOperationException(); } |
|
|
|
|
|
public override void SetLength(long value) { throw new InvalidOperationException(); } |
|
|
|
|
|
public override int Read(byte[] buffer, int offset, int count) { throw new InvalidOperationException(); } |
|
|
|
|
|
public override void Write(byte[] buffer, int offset, int count) |
|
|
|
|
|
{ |
|
|
|
|
|
_client.Send(buffer, offset, count); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private readonly JsonSerializer _serializer; |
|
|
|
|
|
private readonly bool _ownsGateway; |
|
|
|
|
|
private TaskManager _taskManager; |
|
|
|
|
|
private CancellationToken _cancelToken; |
|
|
|
|
|
|
|
|
|
|
|
internal AudioService Service { get; } |
|
|
|
|
|
internal Logger Logger { get; } |
|
|
|
|
|
public int Id { get; } |
|
|
|
|
|
public GatewaySocket GatewaySocket { get; } |
|
|
|
|
|
public VoiceSocket VoiceSocket { get; } |
|
|
|
|
|
public Stream OutputStream { get; } |
|
|
|
|
|
|
|
|
|
|
|
public ConnectionState State => VoiceSocket.State; |
|
|
|
|
|
public Server Server => VoiceSocket.Server; |
|
|
|
|
|
public Channel Channel => VoiceSocket.Channel; |
|
|
|
|
|
|
|
|
|
|
|
public AudioClient(AudioService service, int clientId, Server server, GatewaySocket gatewaySocket, bool ownsGateway, Logger logger) |
|
|
|
|
|
{ |
|
|
|
|
|
Service = service; |
|
|
|
|
|
_serializer = service.Client.Serializer; |
|
|
|
|
|
Id = clientId; |
|
|
|
|
|
GatewaySocket = gatewaySocket; |
|
|
|
|
|
_ownsGateway = ownsGateway; |
|
|
|
|
|
Logger = logger; |
|
|
|
|
|
OutputStream = new OutStream(this); |
|
|
|
|
|
_taskManager = new TaskManager(Cleanup, true); |
|
|
|
|
|
|
|
|
|
|
|
GatewaySocket.ReceivedDispatch += OnReceivedDispatch; |
|
|
|
|
|
|
|
|
|
|
|
VoiceSocket = new VoiceSocket(service.Client.Config, service.Config, service.Client.Serializer, logger); |
|
|
|
|
|
VoiceSocket.Server = server; |
|
|
|
|
|
|
|
|
|
|
|
/*_voiceSocket.Connected += (s, e) => RaiseVoiceConnected(); |
|
|
|
|
|
_voiceSocket.Disconnected += async (s, e) => |
|
|
|
|
|
{ |
|
|
|
|
|
_voiceSocket.CurrentServerId; |
|
|
|
|
|
if (voiceServerId != null) |
|
|
|
|
|
_gatewaySocket.SendLeaveVoice(voiceServerId.Value); |
|
|
|
|
|
await _voiceSocket.Disconnect().ConfigureAwait(false); |
|
|
|
|
|
RaiseVoiceDisconnected(socket.CurrentServerId.Value, e); |
|
|
|
|
|
if (e.WasUnexpected) |
|
|
|
|
|
await socket.Reconnect().ConfigureAwait(false); |
|
|
|
|
|
};*/ |
|
|
|
|
|
|
|
|
|
|
|
/*_voiceSocket.IsSpeaking += (s, e) => |
|
|
|
|
|
{ |
|
|
|
|
|
if (_voiceSocket.State == WebSocketState.Connected) |
|
|
|
|
|
{ |
|
|
|
|
|
var user = _users[e.UserId, socket.CurrentServerId]; |
|
|
|
|
|
bool value = e.IsSpeaking; |
|
|
|
|
|
if (user.IsSpeaking != value) |
|
|
|
|
|
{ |
|
|
|
|
|
user.IsSpeaking = value; |
|
|
|
|
|
var channel = _channels[_voiceSocket.CurrentChannelId]; |
|
|
|
|
|
RaiseUserIsSpeaking(user, channel, value); |
|
|
|
|
|
if (Config.TrackActivity) |
|
|
|
|
|
user.UpdateActivity(); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
};*/ |
|
|
|
|
|
|
|
|
|
|
|
/*this.Connected += (s, e) => |
|
|
|
|
|
{ |
|
|
|
|
|
_voiceSocket.ParentCancelToken = _cancelToken; |
|
|
|
|
|
};*/ |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
public async Task Join(Channel channel) |
|
|
|
|
|
{ |
|
|
|
|
|
if (channel == null) throw new ArgumentNullException(nameof(channel)); |
|
|
|
|
|
if (channel.Type != ChannelType.Voice) |
|
|
|
|
|
throw new ArgumentException("Channel must be a voice channel.", nameof(channel)); |
|
|
|
|
|
if (channel.Server != VoiceSocket.Server) |
|
|
|
|
|
throw new ArgumentException("This is channel is not part of the current server.", nameof(channel)); |
|
|
|
|
|
if (channel == VoiceSocket.Channel) return; |
|
|
|
|
|
if (VoiceSocket.Server == null) |
|
|
|
|
|
throw new InvalidOperationException("This client has been closed."); |
|
|
|
|
|
|
|
|
|
|
|
SendVoiceUpdate(channel.Server.Id, channel.Id); |
|
|
|
|
|
await Task.Run(() => VoiceSocket.WaitForConnection(_cancelToken)); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
public async Task Connect(RestClient rest = null) |
|
|
|
|
|
{ |
|
|
|
|
|
var cancelSource = new CancellationTokenSource(); |
|
|
|
|
|
_cancelToken = cancelSource.Token; |
|
|
|
|
|
|
|
|
|
|
|
Task[] tasks; |
|
|
|
|
|
if (rest != null) |
|
|
|
|
|
tasks = new Task[] { GatewaySocket.Connect(rest, _cancelToken) }; |
|
|
|
|
|
else |
|
|
|
|
|
tasks = new Task[0]; |
|
|
|
|
|
|
|
|
|
|
|
await _taskManager.Start(tasks, cancelSource); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
public Task Disconnect() => _taskManager.Stop(true); |
|
|
|
|
|
|
|
|
|
|
|
private async Task Cleanup() |
|
|
|
|
|
{ |
|
|
|
|
|
var server = VoiceSocket.Server; |
|
|
|
|
|
VoiceSocket.Server = null; |
|
|
|
|
|
VoiceSocket.Channel = null; |
|
|
|
|
|
|
|
|
|
|
|
await Service.RemoveClient(server, this).ConfigureAwait(false); |
|
|
|
|
|
SendVoiceUpdate(server.Id, null); |
|
|
|
|
|
|
|
|
|
|
|
await VoiceSocket.Disconnect().ConfigureAwait(false); |
|
|
|
|
|
if (_ownsGateway) |
|
|
|
|
|
await GatewaySocket.Disconnect().ConfigureAwait(false); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private async void OnReceivedDispatch(object sender, WebSocketEventEventArgs e) |
|
|
|
|
|
{ |
|
|
|
|
|
try |
|
|
|
|
|
{ |
|
|
|
|
|
switch (e.Type) |
|
|
|
|
|
{ |
|
|
|
|
|
case "VOICE_STATE_UPDATE": |
|
|
|
|
|
{ |
|
|
|
|
|
var data = e.Payload.ToObject<VoiceStateUpdateEvent>(_serializer); |
|
|
|
|
|
if (data.GuildId == VoiceSocket.Server?.Id && data.UserId == Service.Client.CurrentUser?.Id) |
|
|
|
|
|
{ |
|
|
|
|
|
if (data.ChannelId == null) |
|
|
|
|
|
await Disconnect().ConfigureAwait(false); |
|
|
|
|
|
else |
|
|
|
|
|
{ |
|
|
|
|
|
var channel = Service.Client.GetChannel(data.ChannelId.Value); |
|
|
|
|
|
if (channel != null) |
|
|
|
|
|
VoiceSocket.Channel = channel; |
|
|
|
|
|
else |
|
|
|
|
|
{ |
|
|
|
|
|
Logger.Warning("VOICE_STATE_UPDATE referenced an unknown channel, disconnecting."); |
|
|
|
|
|
await Disconnect().ConfigureAwait(false); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
break; |
|
|
|
|
|
case "VOICE_SERVER_UPDATE": |
|
|
|
|
|
{ |
|
|
|
|
|
var data = e.Payload.ToObject<VoiceServerUpdateEvent>(_serializer); |
|
|
|
|
|
if (data.GuildId == VoiceSocket.Server?.Id) |
|
|
|
|
|
{ |
|
|
|
|
|
var client = Service.Client; |
|
|
|
|
|
var id = client.CurrentUser?.Id; |
|
|
|
|
|
if (id != null) |
|
|
|
|
|
{ |
|
|
|
|
|
var host = "wss://" + e.Payload.Value<string>("endpoint").Split(':')[0]; |
|
|
|
|
|
await VoiceSocket.Connect(host, data.Token, id.Value, GatewaySocket.SessionId, _cancelToken).ConfigureAwait(false); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
break; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
catch (Exception ex) |
|
|
|
|
|
{ |
|
|
|
|
|
Logger.Error($"Error handling {e.Type} event", ex); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <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="count">Number of bytes in this frame. </param> |
|
|
|
|
|
public void Send(byte[] data, int offset, int count) |
|
|
|
|
|
{ |
|
|
|
|
|
if (data == null) throw new ArgumentException(nameof(data)); |
|
|
|
|
|
if (count < 0) throw new ArgumentOutOfRangeException(nameof(count)); |
|
|
|
|
|
if (offset < 0) throw new ArgumentOutOfRangeException(nameof(count)); |
|
|
|
|
|
if (VoiceSocket.Server == null) return; //Has been closed |
|
|
|
|
|
if (count == 0) return; |
|
|
|
|
|
|
|
|
|
|
|
VoiceSocket.SendPCMFrames(data, offset, count); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <summary> Clears the PCM buffer. </summary> |
|
|
|
|
|
public void Clear() |
|
|
|
|
|
{ |
|
|
|
|
|
if (VoiceSocket.Server == null) return; //Has been closed |
|
|
|
|
|
VoiceSocket.ClearPCMFrames(); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <summary> Returns a task that completes once the voice output buffer is empty. </summary> |
|
|
|
|
|
public void Wait() |
|
|
|
|
|
{ |
|
|
|
|
|
if (VoiceSocket.Server == null) return; //Has been closed |
|
|
|
|
|
VoiceSocket.WaitForQueue(); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
public void SendVoiceUpdate(ulong? serverId, ulong? channelId) |
|
|
|
|
|
{ |
|
|
|
|
|
GatewaySocket.SendUpdateVoice(serverId, channelId, |
|
|
|
|
|
(Service.Config.Mode | AudioMode.Outgoing) == 0, |
|
|
|
|
|
(Service.Config.Mode | AudioMode.Incoming) == 0); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|