Skip to content

Commit 57158cc

Browse files
authored
- Added better logic around voice connection attempts because oftentimes there is a timeout when waiting for a discovery reply (#15)
1 parent 2975695 commit 57158cc

File tree

2 files changed

+64
-5
lines changed

2 files changed

+64
-5
lines changed

discord/gateway.py

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
import traceback
3636
import zlib
3737
from collections import deque, namedtuple
38-
from typing import TYPE_CHECKING
38+
from typing import TYPE_CHECKING, Dict, Any
3939

4040
import aiohttp
4141

@@ -924,7 +924,7 @@ async def received_message(self, msg):
924924

925925
await self._hook(self, msg)
926926

927-
async def initial_connection(self, data):
927+
async def initial_connection_v1(self, data):
928928
state = self._connection
929929
state.ssrc = data["ssrc"]
930930
state.voice_port = data["port"]
@@ -967,6 +967,63 @@ async def initial_connection(self, data):
967967
await self.select_protocol(state.ip, state.port, mode)
968968
_log.info("selected the voice protocol for use (%s)", mode)
969969

970+
async def initial_connection(self, data: Dict[str, Any]) -> None:
971+
state = self._connection
972+
state.ssrc = data['ssrc']
973+
state.voice_port = data['port']
974+
state.endpoint_ip = data['ip']
975+
976+
_log.debug('Connecting to voice socket')
977+
await self.loop.sock_connect(state.socket, (state.endpoint_ip, state.voice_port))
978+
979+
state.ip, state.port = await self.discover_ip(state)
980+
# there *should* always be at least one supported mode (xsalsa20_poly1305)
981+
modes = [mode for mode in data['modes'] if mode in self._connection.supported_modes]
982+
_log.debug('received supported encryption modes: %s', ', '.join(modes))
983+
984+
mode = modes[0]
985+
await self.select_protocol(state.ip, state.port, mode)
986+
_log.debug('selected the voice protocol for use (%s)', mode)
987+
988+
async def discover_ip(self, state, max_retries: int = 2, base_timeout: float = 3.0):
989+
"""Send a UDP discovery packet and wait for the correct 74-byte response."""
990+
packet = bytearray(74)
991+
struct.pack_into(">H", packet, 0, 1)
992+
struct.pack_into(">H", packet, 2, 70)
993+
struct.pack_into(">I", packet, 4, state.ssrc)
994+
995+
for attempt in range(max_retries):
996+
increment = 2.0
997+
attempt_timeout = base_timeout + (attempt * increment)
998+
999+
await self.loop.sock_sendall(state.socket, packet)
1000+
1001+
try:
1002+
# wait for discovery reply with timeout
1003+
data = await asyncio.wait_for(self.loop.sock_recv(state.socket, 74), timeout=attempt_timeout)
1004+
1005+
# validate packet
1006+
if len(data) == 74 and data[1] == 0x02:
1007+
ip_start = 8
1008+
ip_end = data.index(0, ip_start)
1009+
ip = data[ip_start:ip_end].decode("ascii")
1010+
port = struct.unpack_from(">H", data, len(data) - 2)[0]
1011+
_log.debug("detected ip: %s port: %s", ip, port)
1012+
return ip, port
1013+
1014+
# wrong packet type → keep waiting
1015+
_log.debug("Ignored non-discovery packet during handshake.")
1016+
continue
1017+
1018+
except asyncio.TimeoutError:
1019+
if attempt + 1 < max_retries:
1020+
_log.warning("No discovery reply, retrying (%d/%d)...", attempt + 1, max_retries)
1021+
await asyncio.sleep(0.5)
1022+
continue
1023+
else:
1024+
_log.error("UDP discovery timed out after %d attempts.", max_retries)
1025+
raise
1026+
9701027
@property
9711028
def latency(self) -> float:
9721029
"""Latency between a HEARTBEAT and its HEARTBEAT_ACK in seconds."""

discord/voice_client.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -375,11 +375,13 @@ async def connect_websocket(self) -> DiscordVoiceWebSocket:
375375
self._connected.set()
376376
return ws
377377

378-
async def connect(self, *, reconnect: bool, timeout: float) -> None:
378+
async def connect(self, *, reconnect: bool, attempting_reconnect=False, timeout: float) -> None:
379379
_log.info("Connecting to voice...")
380380
self.timeout = timeout
381381

382-
for i in range(5):
382+
max_attempts = 2 if not attempting_reconnect else 5
383+
384+
for i in range(max_attempts):
383385
self.prepare_handshake()
384386

385387
# This has to be created before we start the flow.
@@ -520,7 +522,7 @@ async def poll_voice_ws(self, reconnect: bool) -> None:
520522
await asyncio.sleep(retry)
521523
await self.voice_disconnect()
522524
try:
523-
await self.connect(reconnect=True, timeout=self.timeout)
525+
await self.connect(reconnect=True, attempting_reconnect=True, timeout=self.timeout)
524526
except asyncio.TimeoutError:
525527
# at this point we've retried 5 times... let's continue the loop.
526528
_log.warning("Could not connect to voice... Retrying...")

0 commit comments

Comments
 (0)