Skip to content

Commit fbab980

Browse files
committed
fix voice connection, now fix voice recording 😭
1 parent fe88ee8 commit fbab980

File tree

15 files changed

+308
-1107
lines changed

15 files changed

+308
-1107
lines changed

discord/abc.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
from .role import Role
5656
from .scheduled_events import ScheduledEvent
5757
from .sticker import GuildSticker, StickerItem
58-
from .voice_client import VoiceClient, VoiceProtocol
58+
from .voice import VoiceClient, VoiceProtocol
5959

6060
__all__ = (
6161
"Snowflake",

discord/client.py

Lines changed: 29 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -61,19 +61,19 @@
6161
from .ui.view import View
6262
from .user import ClientUser, User
6363
from .utils import MISSING
64-
from .voice_client import VoiceClient
64+
from .voice import VoiceClient
6565
from .webhook import Webhook
6666
from .widget import Widget
6767

6868
if TYPE_CHECKING:
6969
from .abc import GuildChannel, PrivateChannel, Snowflake, SnowflakeTime
7070
from .channel import DMChannel
71-
from .interaction import Interaction
71+
from .interactions import Interaction
7272
from .member import Member
7373
from .message import Message
7474
from .poll import Poll
7575
from .ui.item import Item
76-
from .voice_client import VoiceProtocol
76+
from .voice import VoiceProtocol
7777

7878
__all__ = ("Client",)
7979

@@ -467,7 +467,7 @@ def _schedule_event(
467467
return task
468468

469469
def dispatch(self, event: str, *args: Any, **kwargs: Any) -> None:
470-
_log.debug("Dispatching event %s", event)
470+
logging.getLogger('discord.state').debug("Dispatching event %s", event)
471471
method = f"on_{event}"
472472

473473
listeners = self._listeners.get(event)
@@ -789,7 +789,16 @@ async def start(self, token: str, *, reconnect: bool = True) -> None:
789789
await self.login(token)
790790
await self.connect(reconnect=reconnect)
791791

792-
def run(self, *args: Any, **kwargs: Any) -> None:
792+
def run(
793+
self,
794+
token: str,
795+
*,
796+
reconnect: bool = True,
797+
log_handler: logging.Handler | None = MISSING,
798+
log_formatter: logging.Formatter = MISSING,
799+
log_level: int = MISSING,
800+
root_logger: bool = False,
801+
) -> None:
793802
"""A blocking call that abstracts away the event loop
794803
initialisation from you.
795804
@@ -815,39 +824,25 @@ def run(self, *args: Any, **kwargs: Any) -> None:
815824
"""
816825
loop = self.loop
817826

818-
try:
819-
loop.add_signal_handler(signal.SIGINT, loop.stop)
820-
loop.add_signal_handler(signal.SIGTERM, loop.stop)
821-
except (NotImplementedError, RuntimeError):
822-
pass
823-
824827
async def runner():
825-
try:
826-
await self.start(*args, **kwargs)
827-
finally:
828-
if not self.is_closed():
829-
await self.close()
830-
831-
def stop_loop_on_completion(f):
832-
loop.stop()
828+
async with self:
829+
await self.start(token, reconnect=reconnect)
830+
831+
if log_handler is not None:
832+
utils.setup_logging(
833+
handler=log_handler,
834+
formatter=log_formatter,
835+
level=log_level,
836+
root=root_logger,
837+
)
833838

834-
future = asyncio.ensure_future(runner(), loop=loop)
835-
future.add_done_callback(stop_loop_on_completion)
836839
try:
837-
loop.run_forever()
840+
asyncio.run(runner())
838841
except KeyboardInterrupt:
839-
_log.info("Received signal to terminate bot and event loop.")
840-
finally:
841-
future.remove_done_callback(stop_loop_on_completion)
842-
_log.info("Cleaning up tasks.")
843-
_cleanup_loop(loop)
844-
845-
if not future.cancelled():
846-
try:
847-
return future.result()
848-
except KeyboardInterrupt:
849-
# I am unsure why this gets raised here but suppress it anyway
850-
return None
842+
# nothing to do here
843+
# `asyncio.run` handles the loop cleanup
844+
# and `self.start` closes all sockets and the HTTPClient instance.
845+
return
851846

852847
# properties
853848

discord/commands/context.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
from ..permissions import Permissions
4949
from ..state import ConnectionState
5050
from ..user import User
51-
from ..voice_client import VoiceClient
51+
from ..voice import VoiceClient
5252
from ..webhook import WebhookMessage
5353
from .core import ApplicationCommand, Option
5454

discord/ext/commands/context.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
from discord.member import Member
4242
from discord.state import ConnectionState
4343
from discord.user import ClientUser, User
44-
from discord.voice_client import VoiceProtocol
44+
from discord.voice import VoiceProtocol
4545

4646
from .bot import AutoShardedBot, Bot
4747
from .cog import Cog

discord/guild.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,8 @@
111111
from .types.guild import GuildFeature, MFALevel
112112
from .types.member import Member as MemberPayload
113113
from .types.threads import Thread as ThreadPayload
114-
from .types.voice import GuildVoiceState
115-
from .voice_client import VoiceClient
114+
from .types.voice import VoiceState as GuildVoiceState
115+
from .voice import VoiceClient
116116
from .webhook import Webhook
117117

118118
VocalGuildChannel = Union[VoiceChannel, StageChannel]

discord/player.py

Lines changed: 40 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,13 @@
4141
from typing import IO, TYPE_CHECKING, Any, Callable, Generic, TypeVar
4242

4343
from .errors import ClientException
44+
from .enums import SpeakingState
4445
from .oggparse import OggStream
45-
from .opus import Encoder as OpusEncoder
46+
from .opus import Encoder as OpusEncoder, OPUS_SILENCE
4647
from .utils import MISSING
4748

4849
if TYPE_CHECKING:
49-
from .voice_client import VoiceClient
50+
from .voice import VoiceClient
5051

5152

5253
AT = TypeVar("AT", bound="AudioSource")
@@ -732,7 +733,6 @@ def __init__(self, source: AudioSource, client: VoiceClient, *, after=None):
732733
self._resumed: threading.Event = threading.Event()
733734
self._resumed.set() # we are not paused
734735
self._current_error: Exception | None = None
735-
self._connected: threading.Event = client._connected
736736
self._lock: threading.Lock = threading.Lock()
737737
self._played_frames_offset: int = 0
738738

@@ -742,58 +742,60 @@ def __init__(self, source: AudioSource, client: VoiceClient, *, after=None):
742742
def _do_run(self) -> None:
743743
# attempt to read first audio segment from source before starting
744744
# some sources can take a few seconds and may cause problems
745-
first_data = self.source.read()
746745
self.loops = 0
747746
self._start = time.perf_counter()
748747

749748
# getattr lookup speed ups
750-
play_audio = self.client.send_audio_packet
751-
self._speak(True)
749+
client = self.client
750+
play_audio = client.send_audio_packet
751+
self._speak(SpeakingState.voice)
752752

753753
while not self._end.is_set():
754754
# are we paused?
755755
if not self._resumed.is_set():
756+
self.send_silence()
756757
# wait until we aren't
757758
self._resumed.wait()
758759
continue
759760

760-
# are we disconnected from voice?
761-
if not self._connected.is_set():
762-
# wait until we are connected
763-
self._connected.wait()
764-
# reset our internal data
765-
self._played_frames_offset += self.loops
766-
self.loops = 0
767-
self._start = time.perf_counter()
768-
769-
self.loops += 1
770-
771-
# Send the data read from the start of the function if it is not None
772-
if first_data is not None:
773-
data = first_data
774-
first_data = None
775-
# Else read the next bit from the source
776-
else:
777-
data = self.source.read()
761+
data = self.source.read()
778762

779763
if not data:
780764
self.stop()
781765
break
782766

767+
# are we disconnected from voice?
768+
if not client.is_connected():
769+
_log.debug('Not connected, waiting for %ss...', client.timeout)
770+
# wait until we are connected, but not forever
771+
connected = client.wait_until_connected(client.timeout)
772+
if self._end.is_set() or not connected:
773+
_log.debug('Aborting playback')
774+
return
775+
_log.debug('Reconnected, resuming playback')
776+
self._speak(SpeakingState.voice)
777+
# reset our internal data
778+
self.loops = 0
779+
self._start = time.perf_counter()
780+
783781
play_audio(data, encode=not self.source.is_opus())
782+
self.loops += 1
784783
next_time = self._start + self.DELAY * self.loops
785784
delay = max(0, self.DELAY + (next_time - time.perf_counter()))
786785
time.sleep(delay)
787786

787+
if client.is_connected():
788+
self.send_silence()
789+
788790
def run(self) -> None:
789791
try:
790792
self._do_run()
791793
except Exception as exc:
792794
self._current_error = exc
793795
self.stop()
794796
finally:
795-
self.source.cleanup()
796797
self._call_after()
798+
self.source.cleanup()
797799

798800
def _call_after(self) -> None:
799801
error = self._current_error
@@ -802,32 +804,29 @@ def _call_after(self) -> None:
802804
try:
803805
self.after(error)
804806
except Exception as exc:
805-
_log.exception("Calling the after function failed.")
806807
exc.__context__ = error
807-
traceback.print_exception(type(exc), exc, exc.__traceback__)
808+
_log.exception("Calling the after function failed.", exc_info=exc)
808809
elif error:
809810
msg = f"Exception in voice thread {self.name}"
810811
_log.exception(msg, exc_info=error)
811-
print(msg, file=sys.stderr)
812-
traceback.print_exception(type(error), error, error.__traceback__)
813812

814813
def stop(self) -> None:
815814
self._end.set()
816815
self._resumed.set()
817-
self._speak(False)
816+
self._speak(SpeakingState.none)
818817

819818
def pause(self, *, update_speaking: bool = True) -> None:
820819
self._resumed.clear()
821820
if update_speaking:
822-
self._speak(False)
821+
self._speak(SpeakingState.none)
823822

824823
def resume(self, *, update_speaking: bool = True) -> None:
825824
self._played_frames_offset += self.loops
826825
self.loops = 0
827826
self._start = time.perf_counter()
828827
self._resumed.set()
829828
if update_speaking:
830-
self._speak(True)
829+
self._speak(SpeakingState.voice)
831830

832831
def is_playing(self) -> bool:
833832
return self._resumed.is_set() and not self._end.is_set()
@@ -841,14 +840,21 @@ def _set_source(self, source: AudioSource) -> None:
841840
self.source = source
842841
self.resume(update_speaking=False)
843842

844-
def _speak(self, speaking: bool) -> None:
843+
def _speak(self, state: SpeakingState) -> None:
845844
try:
846845
asyncio.run_coroutine_threadsafe(
847-
self.client.ws.speak(speaking), self.client.loop
846+
self.client.ws.speak(state), self.client.loop
848847
)
849848
except Exception as e:
850849
_log.info("Speaking call in player failed: %s", e)
851850

852851
def played_frames(self) -> int:
853852
"""Gets the number of 20ms frames played since the start of the audio file."""
854853
return self._played_frames_offset + self.loops
854+
855+
def send_silence(self, count: int = 5) -> None:
856+
try:
857+
for n in range(count):
858+
self.client.send_audio_packet(OPUS_SILENCE, encode=False)
859+
except Exception:
860+
pass

discord/raw_models.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -972,7 +972,6 @@ class RawVoiceStateUpdateEvent(_PayloadLike):
972972
"suppress",
973973
"requested_to_speak_at",
974974
"afk",
975-
"channel",
976975
"guild_id",
977976
"channel_id",
978977
"_state",

discord/state.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@
8888
from .types.poll import Poll as PollPayload
8989
from .types.sticker import GuildSticker as GuildStickerPayload
9090
from .types.user import User as UserPayload
91-
from .voice_client import VoiceClient
91+
from .voice import VoiceClient
9292

9393
T = TypeVar("T")
9494
CS = TypeVar("CS", bound="ConnectionState")

0 commit comments

Comments
 (0)