Skip to content

Commit 11a56bc

Browse files
authored
Fix user status update when speaking (#3204)
1 parent e8c5436 commit 11a56bc

File tree

5 files changed

+122
-16
lines changed

5 files changed

+122
-16
lines changed

src/Discord.Net.WebSocket/API/Voice/SpeakingParams.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@ namespace Discord.API.Voice
55
internal class SpeakingParams
66
{
77
[JsonProperty("speaking")]
8-
public bool IsSpeaking { get; set; }
8+
public int Speaking { get; set; }
99
[JsonProperty("delay")]
1010
public int Delay { get; set; }
11+
[JsonProperty("ssrc")]
12+
public uint Ssrc { get; set; }
1113
}
1214
}

src/Discord.Net.WebSocket/Audio/AudioClient.cs

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using Discord.API.Voice;
22
using Discord.Audio.Streams;
33
using Discord.Logging;
4-
using Discord.Net;
54
using Discord.Net.Converters;
65
using Discord.WebSocket;
76
using Newtonsoft.Json;
@@ -45,7 +44,7 @@ public StreamPair(AudioInStream reader, AudioOutStream writer)
4544
private readonly SemaphoreSlim _stateLock;
4645
private readonly ConcurrentQueue<long> _heartbeatTimes;
4746
private readonly ConcurrentQueue<KeyValuePair<ulong, int>> _keepaliveTimes;
48-
private readonly ConcurrentDictionary<uint, ulong> _ssrcMap;
47+
private readonly SsrcMap _ssrcMap;
4948
private readonly ConcurrentDictionary<ulong, StreamPair> _streams;
5049

5150
private Task _heartbeatTask, _keepaliveTask;
@@ -54,7 +53,7 @@ public StreamPair(AudioInStream reader, AudioOutStream writer)
5453
private string _url, _sessionId, _token;
5554
private ulong _userId;
5655
private uint _ssrc;
57-
private bool _isSpeaking;
56+
private bool? _isSpeaking;
5857
private StopReason _stopReason;
5958
private bool _resuming;
6059

@@ -90,7 +89,7 @@ internal AudioClient(SocketGuild guild, int clientId, ulong channelId)
9089
_connection.Disconnected += (exception, _) => _disconnectedEvent.InvokeAsync(exception);
9190
_heartbeatTimes = new ConcurrentQueue<long>();
9291
_keepaliveTimes = new ConcurrentQueue<KeyValuePair<ulong, int>>();
93-
_ssrcMap = new ConcurrentDictionary<uint, ulong>();
92+
_ssrcMap = new SsrcMap();
9493
_streams = new ConcurrentDictionary<ulong, StreamPair>();
9594

9695
_serializer = new JsonSerializer { ContractResolver = new DiscordContractResolver() };
@@ -146,12 +145,15 @@ private async Task OnConnectingAsync()
146145

147146
//Wait for READY
148147
await _connection.WaitAsync().ConfigureAwait(false);
148+
_ssrcMap.UserSpeakingChanged += OnUserSpeakingChanged;
149149
}
150150
private async Task OnDisconnectingAsync(Exception ex)
151151
{
152152
await _audioLogger.DebugAsync("Disconnecting ApiClient").ConfigureAwait(false);
153153
await ApiClient.DisconnectAsync().ConfigureAwait(false);
154154

155+
_ssrcMap.UserSpeakingChanged -= OnUserSpeakingChanged;
156+
155157
if (_stopReason == StopReason.Unknown && ex.InnerException is WebSocketException exception)
156158
{
157159
await _audioLogger.WarningAsync(
@@ -207,6 +209,11 @@ private async Task FinishDisconnect(Exception ex, bool wontTryReconnect)
207209
}
208210
}
209211

212+
private async void OnUserSpeakingChanged(ulong userId, bool isSpeaking)
213+
{
214+
await _speakingUpdatedEvent.InvokeAsync(userId, isSpeaking);
215+
}
216+
210217
private async Task ClearHeartBeaters()
211218
{
212219
//Wait for tasks to complete
@@ -332,8 +339,7 @@ private async Task ProcessMessageAsync(VoiceOpCode opCode, object payload)
332339
throw new InvalidOperationException($"Discord selected an unexpected mode: {data.Mode}");
333340

334341
SecretKey = data.SecretKey;
335-
_isSpeaking = false;
336-
await ApiClient.SendSetSpeaking(_isSpeaking).ConfigureAwait(false);
342+
await SetSpeakingAsync(false);
337343
_keepaliveTask = RunKeepaliveAsync(_connection.CancelToken);
338344

339345
_ = _connection.CompleteAsync();
@@ -366,7 +372,7 @@ private async Task ProcessMessageAsync(VoiceOpCode opCode, object payload)
366372
await _audioLogger.DebugAsync("Received Speaking").ConfigureAwait(false);
367373

368374
var data = (payload as JToken).ToObject<SpeakingEvent>(_serializer);
369-
_ssrcMap[data.Ssrc] = data.UserId;
375+
_ssrcMap.AddClient(data.Ssrc, data.UserId, data.Speaking);
370376

371377
await _speakingUpdatedEvent.InvokeAsync(data.UserId, data.Speaking);
372378
}
@@ -468,7 +474,7 @@ private async Task ProcessPacketAsync(byte[] packet)
468474
{
469475
await _audioLogger.DebugAsync("Malformed Frame").ConfigureAwait(false);
470476
}
471-
else if (!_ssrcMap.TryGetValue(ssrc, out ulong userId))
477+
else if (!_ssrcMap.TryUpdateUser(ssrc, out ulong userId))
472478
{
473479
await _audioLogger.DebugAsync($"Unknown SSRC {ssrc}").ConfigureAwait(false);
474480
}
@@ -582,7 +588,7 @@ public async Task SetSpeakingAsync(bool value)
582588
if (_isSpeaking != value)
583589
{
584590
_isSpeaking = value;
585-
await ApiClient.SendSetSpeaking(value).ConfigureAwait(false);
591+
await ApiClient.SendSetSpeaking(value, _ssrc).ConfigureAwait(false);
586592
}
587593
}
588594

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
using System;
2+
using System.Collections.Concurrent;
3+
using System.Timers;
4+
5+
namespace Discord.Audio
6+
{
7+
internal class SsrcMap
8+
{
9+
// The delay after a packet is received from a user until he is marked as not speaking anymore.
10+
public static readonly TimeSpan Delay = TimeSpan.FromMilliseconds(100);
11+
12+
private readonly ConcurrentDictionary<uint, ClientData> _clients;
13+
14+
public event Action<ulong, bool> UserSpeakingChanged;
15+
16+
public SsrcMap()
17+
{
18+
_clients = new ConcurrentDictionary<uint, ClientData>();
19+
}
20+
21+
public void AddClient(uint ssrc, ulong userId, bool isSpeaking)
22+
{
23+
if (_clients.TryGetValue(ssrc, out ClientData client))
24+
{
25+
client.SpeakingChanged -= OnUserSpeakingChanged;
26+
}
27+
28+
client = new ClientData(userId, isSpeaking);
29+
client.SpeakingChanged += OnUserSpeakingChanged;
30+
_clients[ssrc] = client;
31+
}
32+
33+
public bool TryUpdateUser(uint ssrc, out ulong userId)
34+
{
35+
bool exists = false;
36+
userId = 0;
37+
38+
if (_clients.TryGetValue(ssrc, out ClientData client))
39+
{
40+
exists = true;
41+
userId = client.UserId;
42+
client.ActivateSpeaking();
43+
}
44+
45+
return exists;
46+
}
47+
48+
private void OnUserSpeakingChanged(ClientData client)
49+
{
50+
UserSpeakingChanged?.Invoke(client.UserId, client.IsSpeaking);
51+
}
52+
53+
public void Clear()
54+
{
55+
_clients.Clear();
56+
}
57+
58+
private class ClientData
59+
{
60+
public ulong UserId { get; }
61+
public Timer Timer { get; }
62+
public bool IsSpeaking { get; private set; }
63+
64+
public event Action<ClientData> SpeakingChanged;
65+
66+
public ClientData(ulong userId, bool isSpeaking)
67+
{
68+
UserId = userId;
69+
Timer = new Timer(Delay);
70+
Timer.AutoReset = false;
71+
Timer.Elapsed += OnTimerElapsed;
72+
IsSpeaking = isSpeaking;
73+
74+
if (IsSpeaking) Timer.Start();
75+
}
76+
77+
private void OnTimerElapsed(object sender, ElapsedEventArgs e)
78+
{
79+
if (IsSpeaking)
80+
{
81+
IsSpeaking = false;
82+
SpeakingChanged?.Invoke(this);
83+
}
84+
}
85+
86+
public void ActivateSpeaking()
87+
{
88+
if (!IsSpeaking)
89+
{
90+
IsSpeaking = true;
91+
SpeakingChanged?.Invoke(this);
92+
}
93+
94+
// Restart timer
95+
Timer.Stop();
96+
Timer.Start();
97+
}
98+
}
99+
}
100+
}

src/Discord.Net.WebSocket/Audio/Streams/RTPReadStream.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ namespace Discord.Audio.Streams
88
public class RTPReadStream : AudioOutStream
99
{
1010
private readonly AudioStream _next;
11-
private readonly byte[] _buffer, _nonce;
1211

1312
public override bool CanRead => true;
1413
public override bool CanSeek => false;
@@ -17,8 +16,6 @@ public class RTPReadStream : AudioOutStream
1716
public RTPReadStream(AudioStream next, int bufferSize = 4000)
1817
{
1918
_next = next;
20-
_buffer = new byte[bufferSize];
21-
_nonce = new byte[24];
2219
}
2320

2421
/// <exception cref="OperationCanceledException">The token has had cancellation requested.</exception>

src/Discord.Net.WebSocket/DiscordVoiceApiClient.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -164,12 +164,13 @@ public Task SendSelectProtocol(string externalIp)
164164
});
165165
}
166166

167-
public Task SendSetSpeaking(bool value)
167+
public Task SendSetSpeaking(bool value, uint ssrc)
168168
{
169169
return SendAsync(VoiceOpCode.Speaking, new SpeakingParams
170170
{
171-
IsSpeaking = value,
172-
Delay = 0
171+
Speaking = value ? 1 : 0,
172+
Delay = 0,
173+
Ssrc = ssrc
173174
});
174175
}
175176

0 commit comments

Comments
 (0)