diff --git a/CHANGELOG.md b/CHANGELOG.md index fdfdc6d896..400e3606d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -119,6 +119,8 @@ These changes are available on the `master` branch, but have not yet been releas ([#2781](https://github.com/Pycord-Development/pycord/pull/2781)) - Fixed `VoiceClient` crashing randomly while receiving audio ([#2800](https://github.com/Pycord-Development/pycord/pull/2800)) +- Fixed `VoiceClient.connect` failing to do initial connection. + ([#2812](https://github.com/Pycord-Development/pycord/pull/2812)) ### Changed diff --git a/discord/gateway.py b/discord/gateway.py index 4af59f3864..e968bc9858 100644 --- a/discord/gateway.py +++ b/discord/gateway.py @@ -35,6 +35,7 @@ import traceback import zlib from collections import deque, namedtuple +from typing import TYPE_CHECKING import aiohttp @@ -208,6 +209,9 @@ def ack(self): class VoiceKeepAliveHandler(KeepAliveHandler): + if TYPE_CHECKING: + ws: DiscordVoiceWebSocket + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.recent_ack_latencies = deque(maxlen=20) @@ -216,7 +220,10 @@ def __init__(self, *args, **kwargs): self.behind_msg = "High socket latency, shard ID %s heartbeat is %.1fs behind" def get_payload(self): - return {"op": self.ws.HEARTBEAT, "d": int(time.time() * 1000)} + return { + "op": self.ws.HEARTBEAT, + "d": {"t": int(time.time() * 1000), "seq_ack": self.ws.seq_ack}, + } def ack(self): ack_time = time.perf_counter() @@ -784,6 +791,7 @@ def __init__(self, socket, loop, *, hook=None): self._close_code = None self.secret_key = None self.ssrc_map = {} + self.seq_ack: int = -1 if hook: self._hook = hook @@ -804,6 +812,9 @@ async def resume(self): "token": state.token, "server_id": str(state.server_id), "session_id": state.session_id, + # this seq_ack will allow for us to do buffered resume, which is, receive the + # lost voice packets while trying to resume the reconnection + "seq_ack": self.seq_ack, }, } await self.send_as_json(payload) @@ -824,7 +835,7 @@ async def identify(self): @classmethod async def from_client(cls, client, *, resume=False, hook=None): """Creates a voice websocket for the :class:`VoiceClient`.""" - gateway = f"wss://{client.endpoint}/?v=4" + gateway = f"wss://{client.endpoint}/?v=8" http = client._state.http socket = await http.ws_connect(gateway, compress=15) ws = cls(socket, loop=client.loop, hook=hook) @@ -860,7 +871,13 @@ async def client_connect(self): await self.send_as_json(payload) async def speak(self, state=SpeakingState.voice): - payload = {"op": self.SPEAKING, "d": {"speaking": int(state), "delay": 0}} + payload = { + "op": self.SPEAKING, + "d": { + "speaking": int(state), + "delay": 0, + }, + } await self.send_as_json(payload) @@ -868,6 +885,7 @@ async def received_message(self, msg): _log.debug("Voice websocket frame received: %s", msg) op = msg["op"] data = msg.get("d") + self.seq_ack = data.get("seq", self.seq_ack) if op == self.READY: await self.initial_connection(data) diff --git a/discord/voice_client.py b/discord/voice_client.py index 75902ecbe2..3ed983e2b6 100644 --- a/discord/voice_client.py +++ b/discord/voice_client.py @@ -325,11 +325,7 @@ async def on_voice_server_update(self, data: VoiceServerUpdatePayload) -> None: ) return - self.endpoint, _, _ = endpoint.rpartition(":") - if self.endpoint.startswith("wss://"): - # Just in case, strip it off since we're going to add it later - self.endpoint = self.endpoint[6:] - + self.endpoint = endpoint.removeprefix("wss://") # This gets set later self.endpoint_ip = MISSING @@ -471,8 +467,8 @@ async def poll_voice_ws(self, reconnect: bool) -> None: # The following close codes are undocumented, so I will document them here. # 1000 - normal closure (obviously) # 4014 - voice channel has been deleted. - # 4015 - voice server has crashed - if exc.code in (1000, 4015): + # 4015 - voice server has crashed, we should resume + if exc.code == 1000: _log.info( "Disconnecting from voice normally, close code %d.", exc.code, @@ -494,6 +490,21 @@ async def poll_voice_ws(self, reconnect: bool) -> None: ) await self.disconnect() break + if exc.code == 4015: + _log.info("Disconnected from voice, trying to resume...") + + try: + await self.ws.resume() + except asyncio.TimeoutError: + _log.info( + "Could not resume the voice connection... Disconnection..." + ) + if self._connected.is_set(): + await self.disconnect(force=True) + else: + _log.info("Successfully resumed voice connection") + continue + if not reconnect: await self.disconnect() raise