aboutsummaryrefslogtreecommitdiff
path: root/discord/gateway.py
diff options
context:
space:
mode:
authorRapptz <[email protected]>2017-04-18 02:29:43 -0400
committerRapptz <[email protected]>2017-04-18 03:49:48 -0400
commit3b1b26ffb1c9a75ac9c3f958d6e134ccddd6be07 (patch)
treedcaac87b10f4d2a2bdf82f260a5738c1367f425f /discord/gateway.py
parentDon't clear state when READY is reached for auto sharded clients. (diff)
downloaddiscord.py-3b1b26ffb1c9a75ac9c3f958d6e134ccddd6be07.tar.xz
discord.py-3b1b26ffb1c9a75ac9c3f958d6e134ccddd6be07.zip
Re-implement voice sending.
This is a complete redesign of the old voice code. A list of major changes is as follows: * The voice websocket will now automatically reconnect with exponential back-off just like the regular Client does. * Removal of the stream player concept. * Audio now gracefully pauses and resumes when a disconnect is found. * Introduce a discord.AudioSource concept to abstract streams * Flatten previous stream player functionality with the VoiceClient, e.g. player.stop() is now voice_client.stop() * With the above re-coupling this means you no longer have to store players anywhere. * The after function now requires a single parameter, the error, if any existed. This will typically be None. A lot of this design is experimental.
Diffstat (limited to 'discord/gateway.py')
-rw-r--r--discord/gateway.py78
1 files changed, 56 insertions, 22 deletions
diff --git a/discord/gateway.py b/discord/gateway.py
index 9a6991e8..f8e50061 100644
--- a/discord/gateway.py
+++ b/discord/gateway.py
@@ -109,7 +109,6 @@ class VoiceKeepAliveHandler(KeepAliveHandler):
self.msg = 'Keeping voice websocket alive with timestamp {0[d]}'
def get_payload(self):
- self.ack()
return {
'op': self.ws.HEARTBEAT,
'd': int(time.time() * 1000)
@@ -481,12 +480,9 @@ class DiscordWebSocket(websockets.client.WebSocketClientProtocol):
}
}
+ log.debug('Updating our voice state to %s.', payload)
yield from self.send_as_json(payload)
- # we're leaving a voice channel so remove it from the client list
- if channel_id is None:
- self._connection._remove_voice_client(guild_id)
-
@asyncio.coroutine
def close_connection(self, force=False):
if self._keep_alive:
@@ -511,6 +507,14 @@ class DiscordVoiceWebSocket(websockets.client.WebSocketClientProtocol):
Receive only. Gives you the secret key required for voice.
SPEAKING
Send only. Notifies the client if you are currently speaking.
+ HEARTBEAT_ACK
+ Receive only. Tells you your heartbeat has been acknowledged.
+ RESUME
+ Sent only. Tells the client to resume its session.
+ HELLO
+ Receive only. Tells you that your websocket connection was acknowledged.
+ INVALIDATE_SESSION
+ Sent only. Tells you that your RESUME request has failed and to re-IDENTIFY.
"""
IDENTIFY = 0
@@ -519,6 +523,10 @@ class DiscordVoiceWebSocket(websockets.client.WebSocketClientProtocol):
HEARTBEAT = 3
SESSION_DESCRIPTION = 4
SPEAKING = 5
+ HEARTBEAT_ACK = 6
+ RESUME = 7
+ HELLO = 8
+ INVALIDATE_SESSION = 9
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -527,28 +535,50 @@ class DiscordVoiceWebSocket(websockets.client.WebSocketClientProtocol):
@asyncio.coroutine
def send_as_json(self, data):
+ log.debug('Sending voice websocket frame: %s.', data)
yield from self.send(utils.to_json(data))
+ @asyncio.coroutine
+ def resume(self):
+ state = self._connection
+ payload = {
+ 'op': self.RESUME,
+ 'd': {
+ 'token': state.token,
+ 'server_id': str(state.server_id),
+ 'session_id': state.session_id
+ }
+ }
+ yield from self.send_as_json(payload)
+
+ @asyncio.coroutine
+ def identify(self):
+ state = self._connection
+ payload = {
+ 'op': self.IDENTIFY,
+ 'd': {
+ 'server_id': str(state.server_id),
+ 'user_id': str(state.user.id),
+ 'session_id': state.session_id,
+ 'token': state.token
+ }
+ }
+ yield from self.send_as_json(payload)
+
@classmethod
@asyncio.coroutine
- def from_client(cls, client):
+ def from_client(cls, client, *, resume=False):
"""Creates a voice websocket for the :class:`VoiceClient`."""
- gateway = 'wss://' + client.endpoint
+ gateway = 'wss://' + client.endpoint + '/?v=3'
ws = yield from websockets.connect(gateway, loop=client.loop, klass=cls)
ws.gateway = gateway
ws._connection = client
- identify = {
- 'op': cls.IDENTIFY,
- 'd': {
- 'guild_id': client.guild_id,
- 'user_id': client.user.id,
- 'session_id': client.session_id,
- 'token': client.token
- }
- }
+ if resume:
+ yield from ws.resume()
+ else:
+ yield from ws.identify()
- yield from ws.send_as_json(identify)
return ws
@asyncio.coroutine
@@ -566,7 +596,6 @@ class DiscordVoiceWebSocket(websockets.client.WebSocketClientProtocol):
}
yield from self.send_as_json(payload)
- log.debug('Selected protocol as {}'.format(payload))
@asyncio.coroutine
def speak(self, is_speaking=True):
@@ -579,12 +608,11 @@ class DiscordVoiceWebSocket(websockets.client.WebSocketClientProtocol):
}
yield from self.send_as_json(payload)
- log.debug('Voice speaking now set to {}'.format(is_speaking))
@asyncio.coroutine
def received_message(self, msg):
log.debug('Voice websocket frame received: {}'.format(msg))
- op = msg.get('op')
+ op = msg['op']
data = msg.get('d')
if op == self.READY:
@@ -592,14 +620,20 @@ class DiscordVoiceWebSocket(websockets.client.WebSocketClientProtocol):
self._keep_alive = VoiceKeepAliveHandler(ws=self, interval=interval)
self._keep_alive.start()
yield from self.initial_connection(data)
+ elif op == self.HEARTBEAT_ACK:
+ self._keep_alive.ack()
+ elif op == self.INVALIDATE_SESSION:
+ log.info('Voice RESUME failed.')
+ yield from self.identify()
elif op == self.SESSION_DESCRIPTION:
yield from self.load_secret_key(data)
@asyncio.coroutine
def initial_connection(self, data):
state = self._connection
- state.ssrc = data.get('ssrc')
- state.voice_port = data.get('port')
+ state.ssrc = data['ssrc']
+ state.voice_port = data['port']
+
packet = bytearray(70)
struct.pack_into('>I', packet, 0, state.ssrc)
state.socket.sendto(packet, (state.endpoint_ip, state.voice_port))