private async Task HandleDispatch(JObject jo) { var opc = (int)jo["op"]; var opp = jo["d"] as JObject; switch (opc) { case 2: Discord.DebugLogger.LogMessage(LogLevel.Debug, "VoiceNext", "OP2 received", DateTime.Now); var vrp = opp.ToObject <VoiceReadyPayload>(); SSRC = vrp.SSRC; ConnectionEndpoint = new ConnectionEndpoint { Hostname = ConnectionEndpoint.Hostname, Port = vrp.Port }; HeartbeatInterval = vrp.HeartbeatInterval; HeartbeatTask = Task.Run(Heartbeat); await Stage1().ConfigureAwait(false); break; case 4: Discord.DebugLogger.LogMessage(LogLevel.Debug, "VoiceNext", "OP4 received", DateTime.Now); var vsd = opp.ToObject <VoiceSessionDescriptionPayload>(); Key = vsd.SecretKey; await Stage2().ConfigureAwait(false); break; case 5: // Don't spam OP5 //this.Discord.DebugLogger.LogMessage(LogLevel.Debug, "VoiceNext", "OP5 received", DateTime.Now); var spd = opp.ToObject <VoiceSpeakingPayload>(); var spk = new UserSpeakingEventArgs(Discord) { Speaking = spd.Speaking, SSRC = spd.SSRC.Value, User = Discord.InternalGetCachedUser(spd.UserId.Value) }; if (!SSRCMap.ContainsKey(spk.SSRC)) { SSRCMap.AddOrUpdate(spk.SSRC, spk.User.Id, (k, v) => spk.User.Id); } await _userSpeaking.InvokeAsync(spk).ConfigureAwait(false); break; case 6: var dt = DateTime.Now; var ping = (int)(dt - LastHeartbeat).TotalMilliseconds; Volatile.Write(ref _ping, ping); Discord.DebugLogger.LogMessage(LogLevel.Debug, "VoiceNext", $"Received voice heartbeat ACK, ping {ping.ToString("#,##0", CultureInfo.InvariantCulture)}ms", dt); LastHeartbeat = dt; break; case 8: // this sends a heartbeat interval that appears to be consistent with regular GW hello // however opcodes don't match (8 != 10) // so we suppress it so that users are not alerted // HELLO break; case 9: Discord.DebugLogger.LogMessage(LogLevel.Debug, "VoiceNext", "OP9 received", DateTime.Now); HeartbeatTask = Task.Run(Heartbeat); break; case 13: var ulpd = opp.ToObject <VoiceUserLeavePayload>(); var usr = await Discord.GetUserAsync(ulpd.UserId).ConfigureAwait(false); var ssrc = SSRCMap.FirstOrDefault(x => x.Value == ulpd.UserId); if (ssrc.Value != 0) { SSRCMap.TryRemove(ssrc.Key, out _); } Discord.DebugLogger.LogMessage(LogLevel.Debug, "VoiceNext", $"User '{usr.Username}#{usr.Discriminator}' ({ulpd.UserId.ToString(CultureInfo.InvariantCulture)}) left voice chat in '{Channel.Guild.Name}' ({Channel.Guild.Id.ToString(CultureInfo.InvariantCulture)})", DateTime.Now); await _userLeft.InvokeAsync(new VoiceUserLeaveEventArgs(Discord) { User = usr }).ConfigureAwait(false); break; default: Discord.DebugLogger.LogMessage(LogLevel.Warning, "VoiceNext", $"Unknown opcode received: {opc.ToString(CultureInfo.InvariantCulture)}", DateTime.Now); break; } }
private async Task VoiceReceiverTask() { var token = ReceiverToken; var client = UdpClient; while (!token.IsCancellationRequested) { if (client.DataAvailable <= 0) { continue; } byte[] data = null, header = null; ushort seq = 0; uint ts = 0, ssrc = 0; try { data = await client.ReceiveAsync().ConfigureAwait(false); header = new byte[RtpCodec.SIZE_HEADER]; data = Rtp.Decode(data, header); var nonce = Rtp.MakeNonce(header); data = Sodium.Decode(data, nonce, Key); // following is thanks to code from Eris // https://github.com/abalabahaha/eris/blob/master/lib/voice/VoiceConnection.js#L623 var doff = 0; Rtp.Decode(header, out seq, out ts, out ssrc, out var has_ext); if (has_ext) { if (data[0] == 0xBE && data[1] == 0xDE) { // RFC 5285, 4.2 One-Byte header // http://www.rfcreader.com/#rfc5285_line186 var hlen = data[2] << 8 | data[3]; var i = 4; for (; i < hlen + 4; i++) { var b = data[i]; // This is unused(?) //var id = (b >> 4) & 0x0F; var len = (b & 0x0F) + 1; i += len; } while (data[i] == 0) { i++; } doff = i; } // TODO: consider implementing RFC 5285, 4.3. Two-Byte Header } data = Opus.Decode(data, doff, data.Length - doff); } catch { continue; } // TODO: wait for ssrc map? DiscordUser user = null; if (SSRCMap.ContainsKey(ssrc)) { var id = SSRCMap[ssrc]; if (Guild != null) { user = Guild._members.FirstOrDefault(xm => xm.Id == id) ?? await Guild.GetMemberAsync(id).ConfigureAwait(false); } if (user == null) { user = Discord.InternalGetCachedUser(id); } if (user == null) { user = new DiscordUser { Discord = Discord, Id = id } } ; } await _voiceReceived.InvokeAsync(new VoiceReceiveEventArgs(Discord) { SSRC = ssrc, Voice = new ReadOnlyCollection <byte>(data), VoiceLength = 20, User = user }).ConfigureAwait(false); } }