diff options
| author | Rapptz <[email protected]> | 2016-09-25 05:27:35 -0400 |
|---|---|---|
| committer | Rapptz <[email protected]> | 2016-09-25 05:27:35 -0400 |
| commit | 6fec17d7d4e9de452cfabb1b7453f68bb4fcadc7 (patch) | |
| tree | d2cb4f21e9229a15ac47af03bb6de11438243274 | |
| parent | Add Client.get_user_info to retrieve a User from ID. (diff) | |
| download | discord.py-6fec17d7d4e9de452cfabb1b7453f68bb4fcadc7.tar.xz discord.py-6fec17d7d4e9de452cfabb1b7453f68bb4fcadc7.zip | |
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
| -rw-r--r-- | discord/voice_client.py | 42 |
1 files changed, 37 insertions, 5 deletions
diff --git a/discord/voice_client.py b/discord/voice_client.py index 01826e21..f9645dd9 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,15 +112,35 @@ 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 -------- |