Added VoiceClient.latency and VoiceClient.average_latency
This also implements the heartbeating a bit more consistent to the official Discord client.
This commit is contained in:
parent
1b0e806245
commit
fa34d357a1
@ -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')
|
||||||
|
@ -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…
x
Reference in New Issue
Block a user