Browse Source

Added VoiceClient.latency and VoiceClient.average_latency

This also implements the heartbeating a bit more consistent to the
official Discord client.
pull/2634/head
Fwf 5 years ago
committed by Rapptz
parent
commit
fa34d357a1
  1. 26
      discord/gateway.py
  2. 18
      discord/voice_client.py

26
discord/gateway.py

@ -25,7 +25,7 @@ DEALINGS IN THE SOFTWARE.
""" """
import asyncio import asyncio
from collections import namedtuple from collections import namedtuple, deque
import concurrent.futures import concurrent.futures
import json import json
import logging import logging
@ -132,9 +132,10 @@ class KeepAliveHandler(threading.Thread):
class VoiceKeepAliveHandler(KeepAliveHandler): class VoiceKeepAliveHandler(KeepAliveHandler):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.recent_ack_latencies = deque(maxlen=20)
self.msg = 'Keeping voice websocket alive with timestamp %s.' self.msg = 'Keeping voice websocket alive with timestamp %s.'
self.block_msg = 'Voice heartbeat blocked for more than %s seconds' self.block_msg = 'Voice heartbeat blocked for more than %s seconds'
self.behind_msg = 'Can\'t keep up, voice websocket is %.1fs behind' self.behind_msg = 'High socket latency, heartbeat is %.1fs behind'
def get_payload(self): def get_payload(self):
return { return {
@ -142,6 +143,12 @@ class VoiceKeepAliveHandler(KeepAliveHandler):
'd': int(time.time() * 1000) 'd': int(time.time() * 1000)
} }
def ack(self):
ack_time = time.perf_counter()
self._last_ack = ack_time
self.latency = ack_time - self._last_send
self.recent_ack_latencies.append(self.latency)
class DiscordWebSocket(websockets.client.WebSocketClientProtocol): class DiscordWebSocket(websockets.client.WebSocketClientProtocol):
"""Implements a WebSocket for Discord's gateway v6. """Implements a WebSocket for Discord's gateway v6.
@ -702,7 +709,7 @@ class DiscordVoiceWebSocket(websockets.client.WebSocketClientProtocol):
await self.load_secret_key(data) await self.load_secret_key(data)
elif op == self.HELLO: elif op == self.HELLO:
interval = data['heartbeat_interval'] / 1000.0 interval = data['heartbeat_interval'] / 1000.0
self._keep_alive = VoiceKeepAliveHandler(ws=self, interval=interval) self._keep_alive = VoiceKeepAliveHandler(ws=self, interval=min(interval, 5.0))
self._keep_alive.start() self._keep_alive.start()
async def initial_connection(self, data): async def initial_connection(self, data):
@ -735,6 +742,19 @@ class DiscordVoiceWebSocket(websockets.client.WebSocketClientProtocol):
await self.client_connect() await self.client_connect()
@property
def latency(self):
""":class:`float`: Latency between a HEARTBEAT and its HEARTBEAT_ACK in seconds."""
heartbeat = self._keep_alive
return float('inf') if heartbeat is None else heartbeat.latency
@property
def average_latency(self):
""":class:`list`: Average of last 20 HEARTBEAT latencies."""
heartbeat = self._keep_alive
average_latency = sum(heartbeat.recent_ack_latencies)/len(heartbeat.recent_ack_latencies)
return float('inf') if heartbeat is None else average_latency
async def load_secret_key(self, data): async def load_secret_key(self, data):
log.info('received secret key for voice connection') log.info('received secret key for voice connection')
self._connection.secret_key = data.get('secret_key') self._connection.secret_key = data.get('secret_key')

18
discord/voice_client.py

@ -57,7 +57,6 @@ try:
except ImportError: except ImportError:
has_nacl = False has_nacl = False
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
class VoiceClient: class VoiceClient:
@ -207,6 +206,22 @@ class VoiceClient:
self._handshake_complete.set() self._handshake_complete.set()
@property
def latency(self):
""":class:`float`: Latency between a HEARTBEAT and a HEARTBEAT_ACK in seconds.
This could be referred to as the Discord Voice WebSocket latency and is
an analogue of user's voice latencies as seen in the Discord client.
"""
ws = self.ws
return float("inf") if not ws else ws.latency
@property
def average_latency(self):
""":class:`float`: Average of most recent 20 HEARTBEAT latencies in seconds."""
ws = self.ws
return float("inf") if not ws else ws.average_latency
async def connect(self, *, reconnect=True, _tries=0, do_handshake=True): async def connect(self, *, reconnect=True, _tries=0, do_handshake=True):
log.info('Connecting to voice...') log.info('Connecting to voice...')
try: try:
@ -342,7 +357,6 @@ class VoiceClient:
return header + box.encrypt(bytes(data), bytes(nonce)).ciphertext + nonce[:4] return header + box.encrypt(bytes(data), bytes(nonce)).ciphertext + nonce[:4]
def play(self, source, *, after=None): def play(self, source, *, after=None):
"""Plays an :class:`AudioSource`. """Plays an :class:`AudioSource`.

Loading…
Cancel
Save