|
33 | 33 | import warnings |
34 | 34 |
|
35 | 35 | from discord import opus |
| 36 | +from discord.enums import SpeakingState, try_enum |
36 | 37 | from discord.errors import ClientException |
37 | 38 | from discord.player import AudioPlayer, AudioSource |
38 | 39 | from discord.sinks.core import Sink |
|
42 | 43 | from ._types import VoiceProtocol |
43 | 44 | from .receive import AudioReader |
44 | 45 | from .state import VoiceConnectionState |
| 46 | +from .enums import OpCodes |
45 | 47 |
|
46 | 48 | if TYPE_CHECKING: |
47 | 49 | from typing_extensions import ParamSpec |
@@ -202,6 +204,68 @@ def checked_add(self, attr: str, value: int, limit: int) -> None: |
202 | 204 | def create_connection_state(self) -> VoiceConnectionState: |
203 | 205 | return VoiceConnectionState(self, hook=self._recv_hook) |
204 | 206 |
|
| 207 | + async def _recv_hook(self, ws: VoiceWebSocket, msg: dict[str, Any]) -> None: |
| 208 | + op = msg["op"] |
| 209 | + data = msg.get("d", {}) |
| 210 | + |
| 211 | + if op == OpCodes.ready: |
| 212 | + self._add_ssrc(self.guild.me.id, data["ssrc"]) |
| 213 | + elif op == OpCodes.speaking: |
| 214 | + uid = int(data["user_id"]) |
| 215 | + ssrc = data["ssrc"] |
| 216 | + |
| 217 | + self._add_ssrc(uid, ssrc) |
| 218 | + |
| 219 | + member = self.guild.get_member(uid) |
| 220 | + state = try_enum(SpeakingState, data["speaking"]) |
| 221 | + self.dispatch("member_speaking_state_update", member, ssrc, state) |
| 222 | + elif op == OpCodes.clients_connect: |
| 223 | + uids = list(map(int, data["user_ids"])) |
| 224 | + |
| 225 | + for uid in uids: |
| 226 | + member = self.guild.get_member(uid) |
| 227 | + if not member: |
| 228 | + _log.warning("Skipping member referencing ID %d on member_connect", uid) |
| 229 | + continue |
| 230 | + self.dispatch("member_connect", member) |
| 231 | + elif op == OpCodes.client_disconnect: |
| 232 | + uid = int(data["user_id"]) |
| 233 | + ssrc = self._id_to_ssrc.get(uid) |
| 234 | + |
| 235 | + if self._reader and ssrc is not None: |
| 236 | + _log.debug("Destroying decoder for user %d, ssrc=%d", uid, ssrc) |
| 237 | + self._reader.packet_router.destroy_decoder(ssrc) |
| 238 | + |
| 239 | + self._remove_ssrc(user_id=uid) |
| 240 | + member = self.guild.get_member(uid) |
| 241 | + self.dispatch("member_disconnect", member, ssrc) |
| 242 | + |
| 243 | + # maybe handle video and such things? |
| 244 | + |
| 245 | + async def _run_event(self, coro, event_name: str, *args: Any, **kwargs: Any) -> None: |
| 246 | + try: |
| 247 | + await coro(*args, **kwargs) |
| 248 | + except asyncio.CancelledError: |
| 249 | + pass |
| 250 | + except Exception: |
| 251 | + _log.exception("Error calling %s", event_name) |
| 252 | + |
| 253 | + def _schedule_event(self, coro, event_name: str, *args: Any, **kwargs: Any) -> asyncio.Task: |
| 254 | + wrapped = self._run_event(coro, event_name, *args, **kwargs) |
| 255 | + return self.client.loop.create_task(wrapped, name=f"voice-receiver-event-dispatch: {event_name}") |
| 256 | + |
| 257 | + def dispatch(self, event: str, /, *args: Any, **kwargs: Any) -> None: |
| 258 | + _log.debug("Dispatching voice_client event %s", event) |
| 259 | + |
| 260 | + event_name = f"on_{event}" |
| 261 | + for coro in self._event_listeners.get(event_name, []): |
| 262 | + task = self._schedule_event(coro, event_name, *args, **kwargs) |
| 263 | + self._connection._dispatch_task_set.add(task) |
| 264 | + task.add_done_callback(self._connection._dispatch_task_set.discard) |
| 265 | + |
| 266 | + self._dispatch_sink(event, *args, **kwargs) |
| 267 | + self.client.dispatch(event, *args, **kwargs) |
| 268 | + |
205 | 269 | async def on_voice_state_update(self, data: RawVoiceStateUpdateEvent) -> None: |
206 | 270 | old_channel_id = self.channel.id if self.channel else None |
207 | 271 | await self._connection.voice_state_update(data) |
|
0 commit comments