From 482044302fa23e9611de87bafda043bf060996e7 Mon Sep 17 00:00:00 2001 From: Imayhaveborkedit Date: Wed, 22 May 2024 16:47:57 -0400 Subject: [PATCH 1/2] Add VoiceClient.wait_until_done Adds a new function: `VoiceClient.wait_until_done`. As the name suggests, it waits until the player is done, similar to the other `wait_until_` functions. --- discord/player.py | 16 +++++++++++++++- discord/voice_client.py | 19 +++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/discord/player.py b/discord/player.py index 5b2c99dc0..488ddffdc 100644 --- a/discord/player.py +++ b/discord/player.py @@ -21,6 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ + from __future__ import annotations import threading @@ -717,6 +718,8 @@ class AudioPlayer(threading.Thread): self._current_error: Optional[Exception] = None self._lock: threading.Lock = threading.Lock() + self._end_async: asyncio.Event = asyncio.Event(loop=client.loop) + if after is not None and not callable(after): raise TypeError('Expected a callable for the "after" parameter.') @@ -774,7 +777,8 @@ class AudioPlayer(threading.Thread): self.stop() finally: self._call_after() - self.source.cleanup() + self._cleanup() + self.client.loop.call_soon_threadsafe(self._end_async.set) def _call_after(self) -> None: error = self._current_error @@ -788,6 +792,12 @@ class AudioPlayer(threading.Thread): elif error: _log.exception('Exception in voice thread %s', self.name, exc_info=error) + def _cleanup(self) -> None: + try: + self.source.cleanup() + except Exception: + _log.exception("Error cleaning up audio source %s", self.source) + def stop(self) -> None: self._end.set() self._resumed.set() @@ -817,6 +827,10 @@ class AudioPlayer(threading.Thread): self.source = source self.resume(update_speaking=False) + async def wait_async(self) -> Optional[Exception]: + await self._end_async.wait() + return self._current_error + def _speak(self, speaking: SpeakingState) -> None: try: asyncio.run_coroutine_threadsafe(self.client.ws.speak(speaking), self.client.client.loop) diff --git a/discord/voice_client.py b/discord/voice_client.py index 3e1c6a5ff..8bd1913cb 100644 --- a/discord/voice_client.py +++ b/discord/voice_client.py @@ -538,6 +538,25 @@ class VoiceClient(VoiceProtocol): self._player.set_source(value) + async def wait_until_done(self) -> Optional[Exception]: + """|coro| + + Waits for the audio player to finish playback and returns any encountered error. + + Returns + -------- + Optional[:class:`Exception`] + The exception the player encountered during playback, or ``None``. + """ + + if not self.is_connected(): + raise ClientException('Not connected to voice.') + + if self._player is None: + raise ValueError('Not playing anything.') + + return await self._player.wait_async() + def send_audio_packet(self, data: bytes, *, encode: bool = True) -> None: """Sends an audio packet composed of the data. From 6f74fb72136b7cb60f4175049c15ad8341ba7815 Mon Sep 17 00:00:00 2001 From: Imayhaveborkedit Date: Wed, 12 Jun 2024 23:36:30 -0400 Subject: [PATCH 2/2] Change Event to Future and add versionadded tag --- discord/player.py | 7 +++---- discord/voice_client.py | 2 ++ 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/discord/player.py b/discord/player.py index 488ddffdc..e1f7db9a5 100644 --- a/discord/player.py +++ b/discord/player.py @@ -718,7 +718,7 @@ class AudioPlayer(threading.Thread): self._current_error: Optional[Exception] = None self._lock: threading.Lock = threading.Lock() - self._end_async: asyncio.Event = asyncio.Event(loop=client.loop) + self._end_future = client.loop.create_future() if after is not None and not callable(after): raise TypeError('Expected a callable for the "after" parameter.') @@ -778,7 +778,7 @@ class AudioPlayer(threading.Thread): finally: self._call_after() self._cleanup() - self.client.loop.call_soon_threadsafe(self._end_async.set) + self.client.loop.call_soon_threadsafe(self._end_future.set_result, self._current_error) def _call_after(self) -> None: error = self._current_error @@ -828,8 +828,7 @@ class AudioPlayer(threading.Thread): self.resume(update_speaking=False) async def wait_async(self) -> Optional[Exception]: - await self._end_async.wait() - return self._current_error + return await self._end_future def _speak(self, speaking: SpeakingState) -> None: try: diff --git a/discord/voice_client.py b/discord/voice_client.py index 8bd1913cb..5ae153b99 100644 --- a/discord/voice_client.py +++ b/discord/voice_client.py @@ -543,6 +543,8 @@ class VoiceClient(VoiceProtocol): Waits for the audio player to finish playback and returns any encountered error. + .. versionadded:: 2.4 + Returns -------- Optional[:class:`Exception`]