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); } }