From 6fec17d7d4e9de452cfabb1b7453f68bb4fcadc7 Mon Sep 17 00:00:00 2001 From: Rapptz Date: Sun, 25 Sep 2016 05:27:35 -0400 Subject: [PATCH] Catch Player errors and gracefully stop them. This also introduces the concept of the after function taking a single parameter, the current player. This is useful for error handling, e.g. checking Player.error. Fixes #291 --- discord/voice_client.py | 42 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/discord/voice_client.py b/discord/voice_client.py index 01826e210..f9645dd95 100644 --- a/discord/voice_client.py +++ b/discord/voice_client.py @@ -51,6 +51,7 @@ import shlex import functools import datetime import audioop +import inspect log = logging.getLogger(__name__) @@ -78,11 +79,12 @@ class StreamPlayer(threading.Thread): self.after = after self.delay = encoder.frame_length / 1000.0 self._volume = 1.0 + self._current_error = None if after is not None and not callable(after): raise TypeError('Expected a callable for the "after" parameter.') - def run(self): + def _do_run(self): self.loops = 0 self._start = time.time() while not self._end.is_set(): @@ -110,14 +112,34 @@ class StreamPlayer(threading.Thread): delay = max(0, self.delay + (next_time - time.time())) time.sleep(delay) + def run(self): + try: + self._do_run() + except Exception as e: + self._current_error = e + self.stop() + def stop(self): self._end.set() if self.after is not None: try: - self.after() + arg_count = len(inspect.signature(self.after).parameters) + except: + # if this ended up happening, a mistake was made. + arg_count = 0 + + try: + if arg_count == 0: + self.after() + else: + self.after(self) except: pass + @property + def error(self): + return self._current_error + @property def volume(self): return self._volume @@ -580,7 +602,8 @@ class VoiceClient: The stream player assumes that ``stream.read`` is a valid function that returns a *bytes-like* object. - The finalizer, ``after`` is called after the stream has been exhausted. + The finalizer, ``after`` is called after the stream has been exhausted + or an error occurred (see below). The following operations are valid on the ``StreamPlayer`` object: @@ -603,20 +626,29 @@ class VoiceClient: | | equivalent to 100% and 0.0 is equal to 0%. The | | | maximum the volume can be set to is 2.0 for 200%. | +---------------------+-----------------------------------------------------+ + | player.error | The exception that stopped the player. If no error | + | | happened, then this returns None. | + +---------------------+-----------------------------------------------------+ The stream must have the same sampling rate as the encoder and the same number of channels. The defaults are 48000 Hz and 2 channels. You could change the encoder options by using :meth:`encoder_options` but this must be called **before** this function. + If an error happens while the player is running, the exception is caught and + the player is then stopped. The caught exception could then be retrieved + via ``player.error``\. When the player is stopped in this matter, the + finalizer under ``after`` is called. + Parameters ----------- stream The stream object to read from. after The finalizer that is called after the stream is exhausted. - All exceptions it throws are silently discarded. It is called - without parameters. + All exceptions it throws are silently discarded. This function + can have either no parameters or a single parameter taking in the + current player. Returns --------