Skip to content

fix: 4006 voice crashes and upgrade to voice v8 #2812

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
24 changes: 21 additions & 3 deletions discord/gateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import traceback
import zlib
from collections import deque, namedtuple
from typing import TYPE_CHECKING

import aiohttp

Expand Down Expand Up @@ -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)
Expand All @@ -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()
Expand Down Expand Up @@ -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

Expand All @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -860,14 +871,21 @@ 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)

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)
Expand Down
25 changes: 18 additions & 7 deletions discord/voice_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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,
Expand All @@ -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
Expand Down