| @@ -0,0 +1,18 @@ | |||||
| namespace Discord | |||||
| { | |||||
| /// <summary> | |||||
| /// Provides properties that are used to modify an <see cref="IAudioChannel" /> with the specified changes. | |||||
| /// </summary> | |||||
| public class AudioChannelProperties | |||||
| { | |||||
| /// <summary> | |||||
| /// Sets whether the user should be muted. | |||||
| /// </summary> | |||||
| public Optional<bool> SelfMute { get; set; } | |||||
| /// <summary> | |||||
| /// Sets whether the user should be deafened. | |||||
| /// </summary> | |||||
| public Optional<bool> SelfDeaf { get; set; } | |||||
| } | |||||
| } | |||||
| @@ -1,4 +1,5 @@ | |||||
| using Discord.Audio; | using Discord.Audio; | ||||
| using System; | |||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| namespace Discord | namespace Discord | ||||
| @@ -27,5 +28,16 @@ namespace Discord | |||||
| /// A task representing the asynchronous operation for disconnecting from the audio channel. | /// A task representing the asynchronous operation for disconnecting from the audio channel. | ||||
| /// </returns> | /// </returns> | ||||
| Task DisconnectAsync(); | Task DisconnectAsync(); | ||||
| /// <summary> | |||||
| /// Modifies this audio channel. | |||||
| /// </summary> | |||||
| /// <param name="func">The properties to modify the channel with.</param> | |||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| /// <returns> | |||||
| /// A task that represents the asynchronous modification operation. | |||||
| /// </returns> | |||||
| /// <seealso cref="AudioChannelProperties"/> | |||||
| Task ModifyAsync(Action<AudioChannelProperties> func, RequestOptions options = null); | |||||
| } | } | ||||
| } | } | ||||
| @@ -196,6 +196,7 @@ namespace Discord.Rest | |||||
| /// <exception cref="NotSupportedException">Connecting to a group channel is not supported.</exception> | /// <exception cref="NotSupportedException">Connecting to a group channel is not supported.</exception> | ||||
| Task<IAudioClient> IAudioChannel.ConnectAsync(bool selfDeaf, bool selfMute, bool external) { throw new NotSupportedException(); } | Task<IAudioClient> IAudioChannel.ConnectAsync(bool selfDeaf, bool selfMute, bool external) { throw new NotSupportedException(); } | ||||
| Task IAudioChannel.DisconnectAsync() { throw new NotSupportedException(); } | Task IAudioChannel.DisconnectAsync() { throw new NotSupportedException(); } | ||||
| Task IAudioChannel.ModifyAsync(Action<AudioChannelProperties> func, RequestOptions options) { throw new NotSupportedException(); } | |||||
| //IChannel | //IChannel | ||||
| Task<IUser> IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) | Task<IUser> IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) | ||||
| @@ -76,6 +76,7 @@ namespace Discord.Rest | |||||
| /// <exception cref="NotSupportedException">Connecting to a REST-based channel is not supported.</exception> | /// <exception cref="NotSupportedException">Connecting to a REST-based channel is not supported.</exception> | ||||
| Task<IAudioClient> IAudioChannel.ConnectAsync(bool selfDeaf, bool selfMute, bool external) { throw new NotSupportedException(); } | Task<IAudioClient> IAudioChannel.ConnectAsync(bool selfDeaf, bool selfMute, bool external) { throw new NotSupportedException(); } | ||||
| Task IAudioChannel.DisconnectAsync() { throw new NotSupportedException(); } | Task IAudioChannel.DisconnectAsync() { throw new NotSupportedException(); } | ||||
| Task IAudioChannel.ModifyAsync(Action<AudioChannelProperties> func, RequestOptions options) { throw new NotSupportedException(); } | |||||
| //IGuildChannel | //IGuildChannel | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| @@ -284,7 +284,6 @@ namespace Discord.API | |||||
| } | } | ||||
| public async Task SendVoiceStateUpdateAsync(ulong guildId, ulong? channelId, bool selfDeaf, bool selfMute, RequestOptions options = null) | public async Task SendVoiceStateUpdateAsync(ulong guildId, ulong? channelId, bool selfDeaf, bool selfMute, RequestOptions options = null) | ||||
| { | { | ||||
| options = RequestOptions.CreateOrClone(options); | |||||
| var payload = new VoiceStateUpdateParams | var payload = new VoiceStateUpdateParams | ||||
| { | { | ||||
| GuildId = guildId, | GuildId = guildId, | ||||
| @@ -294,6 +293,11 @@ namespace Discord.API | |||||
| }; | }; | ||||
| await SendGatewayAsync(GatewayOpCode.VoiceStateUpdate, payload, options: options).ConfigureAwait(false); | await SendGatewayAsync(GatewayOpCode.VoiceStateUpdate, payload, options: options).ConfigureAwait(false); | ||||
| } | } | ||||
| public async Task SendVoiceStateUpdateAsync(VoiceStateUpdateParams payload, RequestOptions options = null) | |||||
| { | |||||
| options = RequestOptions.CreateOrClone(options); | |||||
| await SendGatewayAsync(GatewayOpCode.VoiceStateUpdate, payload, options: options).ConfigureAwait(false); | |||||
| } | |||||
| public async Task SendGuildSyncAsync(IEnumerable<ulong> guildIds, RequestOptions options = null) | public async Task SendGuildSyncAsync(IEnumerable<ulong> guildIds, RequestOptions options = null) | ||||
| { | { | ||||
| options = RequestOptions.CreateOrClone(options); | options = RequestOptions.CreateOrClone(options); | ||||
| @@ -311,6 +311,7 @@ namespace Discord.WebSocket | |||||
| /// <exception cref="NotSupportedException">Connecting to a group channel is not supported.</exception> | /// <exception cref="NotSupportedException">Connecting to a group channel is not supported.</exception> | ||||
| Task<IAudioClient> IAudioChannel.ConnectAsync(bool selfDeaf, bool selfMute, bool external) { throw new NotSupportedException(); } | Task<IAudioClient> IAudioChannel.ConnectAsync(bool selfDeaf, bool selfMute, bool external) { throw new NotSupportedException(); } | ||||
| Task IAudioChannel.DisconnectAsync() { throw new NotSupportedException(); } | Task IAudioChannel.DisconnectAsync() { throw new NotSupportedException(); } | ||||
| Task IAudioChannel.ModifyAsync(Action<AudioChannelProperties> func, RequestOptions options) { throw new NotSupportedException(); } | |||||
| //IChannel | //IChannel | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| @@ -76,6 +76,12 @@ namespace Discord.WebSocket | |||||
| public async Task DisconnectAsync() | public async Task DisconnectAsync() | ||||
| => await Guild.DisconnectAudioAsync(); | => await Guild.DisconnectAudioAsync(); | ||||
| /// <inheritdoc /> | |||||
| public async Task ModifyAsync(Action<AudioChannelProperties> func, RequestOptions options = null) | |||||
| { | |||||
| await Guild.ModifyAudioAsync(Id, func, options).ConfigureAwait(false); | |||||
| } | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public override SocketGuildUser GetUser(ulong id) | public override SocketGuildUser GetUser(ulong id) | ||||
| { | { | ||||
| @@ -1,3 +1,4 @@ | |||||
| using Discord.API.Gateway; | |||||
| using Discord.Audio; | using Discord.Audio; | ||||
| using Discord.Rest; | using Discord.Rest; | ||||
| using System; | using System; | ||||
| @@ -39,6 +40,7 @@ namespace Discord.WebSocket | |||||
| private ImmutableArray<GuildEmote> _emotes; | private ImmutableArray<GuildEmote> _emotes; | ||||
| private ImmutableArray<string> _features; | private ImmutableArray<string> _features; | ||||
| private AudioClient _audioClient; | private AudioClient _audioClient; | ||||
| private VoiceStateUpdateParams _voiceStateUpdateParams; | |||||
| #pragma warning restore IDISP002, IDISP006 | #pragma warning restore IDISP002, IDISP006 | ||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| @@ -1054,11 +1056,19 @@ namespace Discord.WebSocket | |||||
| promise = new TaskCompletionSource<AudioClient>(); | promise = new TaskCompletionSource<AudioClient>(); | ||||
| _audioConnectPromise = promise; | _audioConnectPromise = promise; | ||||
| _voiceStateUpdateParams = new VoiceStateUpdateParams | |||||
| { | |||||
| GuildId = Id, | |||||
| ChannelId = channelId, | |||||
| SelfDeaf = selfDeaf, | |||||
| SelfMute = selfMute | |||||
| }; | |||||
| if (external) | if (external) | ||||
| { | { | ||||
| #pragma warning disable IDISP001 | #pragma warning disable IDISP001 | ||||
| var _ = promise.TrySetResultAsync(null); | var _ = promise.TrySetResultAsync(null); | ||||
| await Discord.ApiClient.SendVoiceStateUpdateAsync(Id, channelId, selfDeaf, selfMute).ConfigureAwait(false); | |||||
| await Discord.ApiClient.SendVoiceStateUpdateAsync(_voiceStateUpdateParams).ConfigureAwait(false); | |||||
| return null; | return null; | ||||
| #pragma warning restore IDISP001 | #pragma warning restore IDISP001 | ||||
| } | } | ||||
| @@ -1093,7 +1103,7 @@ namespace Discord.WebSocket | |||||
| #pragma warning restore IDISP003 | #pragma warning restore IDISP003 | ||||
| } | } | ||||
| await Discord.ApiClient.SendVoiceStateUpdateAsync(Id, channelId, selfDeaf, selfMute).ConfigureAwait(false); | |||||
| await Discord.ApiClient.SendVoiceStateUpdateAsync(_voiceStateUpdateParams).ConfigureAwait(false); | |||||
| } | } | ||||
| catch | catch | ||||
| { | { | ||||
| @@ -1140,7 +1150,38 @@ namespace Discord.WebSocket | |||||
| await Discord.ApiClient.SendVoiceStateUpdateAsync(Id, null, false, false).ConfigureAwait(false); | await Discord.ApiClient.SendVoiceStateUpdateAsync(Id, null, false, false).ConfigureAwait(false); | ||||
| _audioClient?.Dispose(); | _audioClient?.Dispose(); | ||||
| _audioClient = null; | _audioClient = null; | ||||
| _voiceStateUpdateParams = null; | |||||
| } | } | ||||
| internal async Task ModifyAudioAsync(ulong channelId, Action<AudioChannelProperties> func, RequestOptions options) | |||||
| { | |||||
| await _audioLock.WaitAsync().ConfigureAwait(false); | |||||
| try | |||||
| { | |||||
| await ModifyAudioInternalAsync(channelId, func, options).ConfigureAwait(false); | |||||
| } | |||||
| finally | |||||
| { | |||||
| _audioLock.Release(); | |||||
| } | |||||
| } | |||||
| private async Task ModifyAudioInternalAsync(ulong channelId, Action<AudioChannelProperties> func, RequestOptions options) | |||||
| { | |||||
| if (_voiceStateUpdateParams == null || _voiceStateUpdateParams.ChannelId != channelId) | |||||
| throw new InvalidOperationException("Cannot modify properties of not connected audio channel"); | |||||
| var props = new AudioChannelProperties(); | |||||
| func(props); | |||||
| if (props.SelfDeaf.IsSpecified) | |||||
| _voiceStateUpdateParams.SelfDeaf = props.SelfDeaf.Value; | |||||
| if (props.SelfMute.IsSpecified) | |||||
| _voiceStateUpdateParams.SelfMute = props.SelfMute.Value; | |||||
| await Discord.ApiClient.SendVoiceStateUpdateAsync(_voiceStateUpdateParams, options).ConfigureAwait(false); | |||||
| } | |||||
| internal async Task FinishConnectAudio(string url, string token) | internal async Task FinishConnectAudio(string url, string token) | ||||
| { | { | ||||
| //TODO: Mem Leak: Disconnected/Connected handlers arent cleaned up | //TODO: Mem Leak: Disconnected/Connected handlers arent cleaned up | ||||
| @@ -41,6 +41,11 @@ namespace Discord | |||||
| throw new NotImplementedException(); | throw new NotImplementedException(); | ||||
| } | } | ||||
| public Task ModifyAsync(Action<AudioChannelProperties> func, RequestOptions options) | |||||
| { | |||||
| throw new NotImplementedException(); | |||||
| } | |||||
| public IDisposable EnterTypingState(RequestOptions options = null) | public IDisposable EnterTypingState(RequestOptions options = null) | ||||
| { | { | ||||
| throw new NotImplementedException(); | throw new NotImplementedException(); | ||||
| @@ -58,6 +58,11 @@ namespace Discord | |||||
| throw new NotImplementedException(); | throw new NotImplementedException(); | ||||
| } | } | ||||
| public Task ModifyAsync(Action<AudioChannelProperties> func, RequestOptions options) | |||||
| { | |||||
| throw new NotImplementedException(); | |||||
| } | |||||
| public Task<ICategoryChannel> GetCategoryAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null) | public Task<ICategoryChannel> GetCategoryAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null) | ||||
| { | { | ||||
| throw new NotImplementedException(); | throw new NotImplementedException(); | ||||