|
41 | 41 | from .exceptions import (
|
42 | 42 | ChannelTimeoutException,
|
43 | 43 | InvalidChannelStateException,
|
| 44 | + InvalidNodeException, |
44 | 45 | LavalinkException,
|
45 | 46 | LavalinkLoadException,
|
46 | 47 | QueueEmpty,
|
|
73 | 74 | TrackStartEventPayload,
|
74 | 75 | )
|
75 | 76 | from .types.request import Request as RequestPayload
|
76 |
| - from .types.state import PlayerVoiceState, VoiceState |
| 77 | + from .types.state import PlayerBasicState, PlayerVoiceState, VoiceState |
77 | 78 |
|
78 | 79 | VocalGuildChannel = discord.VoiceChannel | discord.StageChannel
|
79 | 80 |
|
@@ -445,6 +446,89 @@ async def _search(query: str | None) -> T_a:
|
445 | 446 | logger.info('Player "%s" could not load any songs via AutoPlay.', self.guild.id)
|
446 | 447 | self._inactivity_start()
|
447 | 448 |
|
| 449 | + @property |
| 450 | + def state(self) -> PlayerBasicState: |
| 451 | + """Property returning a dict of the current basic state of the player. |
| 452 | +
|
| 453 | + This property includes the ``voice_state`` received via Discord. |
| 454 | +
|
| 455 | + Returns |
| 456 | + ------- |
| 457 | + PlayerBasicState |
| 458 | +
|
| 459 | + .. versionadded:: 3.5.0 |
| 460 | + """ |
| 461 | + data: PlayerBasicState = { |
| 462 | + "voice_state": self._voice_state.copy(), |
| 463 | + "position": self.position, |
| 464 | + "connected": self.connected, |
| 465 | + "current": self.current, |
| 466 | + "paused": self.paused, |
| 467 | + "volume": self.volume, |
| 468 | + "filters": self.filters, |
| 469 | + } |
| 470 | + return data |
| 471 | + |
| 472 | + async def switch_node(self, new_node: wavelink.Node, /) -> None: |
| 473 | + """Method which attempts to switch the current node of the player. |
| 474 | +
|
| 475 | + This method initiates a live switch, and all player state will be moved from the current node to the provided |
| 476 | + node. |
| 477 | +
|
| 478 | + .. warning:: |
| 479 | +
|
| 480 | + Caution should be used when using this method. If this method fails, your player might be left in a stale |
| 481 | + state. Consider handling cases where the player is unable to connect to the new node. To avoid stale state |
| 482 | + in both wavelink and discord.py, it is recommended to disconnect the player when a RuntimeError occurs. |
| 483 | +
|
| 484 | + Parameters |
| 485 | + ---------- |
| 486 | + new_node: :class:`wavelink.Node` |
| 487 | + A positional only argument of a :class:`wavelink.Node`, which is the new node the player will attempt to |
| 488 | + switch to. This must not be the same as the current node. |
| 489 | +
|
| 490 | + Raises |
| 491 | + ------ |
| 492 | + InvalidNodeException |
| 493 | + The provided node was identical to the players current node. |
| 494 | + RuntimeError |
| 495 | + The player was unable to connect properly to the new node. At this point your player might be in a stale |
| 496 | + state. Consider trying another node, or :meth:`disconnect` the player. |
| 497 | +
|
| 498 | +
|
| 499 | + .. versionadded:: 3.5.0 |
| 500 | + """ |
| 501 | + assert self._guild |
| 502 | + |
| 503 | + if new_node.identifier == self.node.identifier: |
| 504 | + msg: str = f"Player '{self._guild.id}' current node is identical to the passed node: {new_node!r}" |
| 505 | + raise InvalidNodeException(msg) |
| 506 | + |
| 507 | + await self._destroy(with_invalidate=False) |
| 508 | + self._node = new_node |
| 509 | + |
| 510 | + await self._dispatch_voice_update() |
| 511 | + if not self.connected: |
| 512 | + raise RuntimeError(f"Switching Node on player '{self._guild.id}' failed. Failed to switch voice_state.") |
| 513 | + |
| 514 | + self.node._players[self._guild.id] = self |
| 515 | + |
| 516 | + if not self._current: |
| 517 | + await self.set_filters(self.filters) |
| 518 | + await self.set_volume(self.volume) |
| 519 | + await self.pause(self.paused) |
| 520 | + return |
| 521 | + |
| 522 | + await self.play( |
| 523 | + self._current, |
| 524 | + replace=True, |
| 525 | + start=self.position, |
| 526 | + volume=self.volume, |
| 527 | + filters=self.filters, |
| 528 | + paused=self.paused, |
| 529 | + ) |
| 530 | + logger.debug("Switching nodes for player: '%s' was successful. New Node: %r", self._guild.id, self.node) |
| 531 | + |
448 | 532 | @property
|
449 | 533 | def inactive_channel_tokens(self) -> int | None:
|
450 | 534 | """A settable property which returns the token limit as an ``int`` of the amount of tracks to play before firing
|
@@ -1128,17 +1212,19 @@ def _invalidate(self) -> None:
|
1128 | 1212 | except (AttributeError, KeyError):
|
1129 | 1213 | pass
|
1130 | 1214 |
|
1131 |
| - async def _destroy(self) -> None: |
| 1215 | + async def _destroy(self, with_invalidate: bool = True) -> None: |
1132 | 1216 | assert self.guild
|
1133 | 1217 |
|
1134 |
| - self._invalidate() |
| 1218 | + if with_invalidate: |
| 1219 | + self._invalidate() |
| 1220 | + |
1135 | 1221 | player: Player | None = self.node._players.pop(self.guild.id, None)
|
1136 | 1222 |
|
1137 | 1223 | if player:
|
1138 | 1224 | try:
|
1139 | 1225 | await self.node._destroy_player(self.guild.id)
|
1140 |
| - except LavalinkException: |
1141 |
| - pass |
| 1226 | + except Exception as e: |
| 1227 | + logger.debug("Disregarding. Failed to send 'destroy_player' payload to Lavalink: %s", e) |
1142 | 1228 |
|
1143 | 1229 | def _add_to_previous_seeds(self, seed: str) -> None:
|
1144 | 1230 | # Helper method to manage previous seeds.
|
|
0 commit comments