diff --git a/discord/player.py b/discord/player.py index bad6da88e..e299a802c 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 @@ -721,6 +722,8 @@ class AudioPlayer(threading.Thread): self._current_error: Optional[Exception] = None self._lock: threading.Lock = threading.Lock() + 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 +781,8 @@ class AudioPlayer(threading.Thread): self.stop() finally: self._call_after() - self.source.cleanup() + self._cleanup() + self.client.loop.call_soon_threadsafe(self._end_future.set_result, self._current_error) def _call_after(self) -> None: error = self._current_error @@ -792,6 +796,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() @@ -821,6 +831,9 @@ class AudioPlayer(threading.Thread): self.source = source self.resume(update_speaking=False) + async def wait_async(self) -> Optional[Exception]: + return await self._end_future + 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 795434e1e..95373f47f 100644 --- a/discord/voice_client.py +++ b/discord/voice_client.py @@ -557,6 +557,27 @@ 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. + + .. versionadded:: 2.4 + + 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.