Skip to content
This repository was archived by the owner on Apr 5, 2025. It is now read-only.

Commit e43fe1c

Browse files
committed
Add switch_node method to Player.
1 parent 040af86 commit e43fe1c

File tree

2 files changed

+124
-5
lines changed

2 files changed

+124
-5
lines changed

wavelink/player.py

+91-5
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
from .exceptions import (
4242
ChannelTimeoutException,
4343
InvalidChannelStateException,
44+
InvalidNodeException,
4445
LavalinkException,
4546
LavalinkLoadException,
4647
QueueEmpty,
@@ -73,7 +74,7 @@
7374
TrackStartEventPayload,
7475
)
7576
from .types.request import Request as RequestPayload
76-
from .types.state import PlayerVoiceState, VoiceState
77+
from .types.state import PlayerBasicState, PlayerVoiceState, VoiceState
7778

7879
VocalGuildChannel = discord.VoiceChannel | discord.StageChannel
7980

@@ -445,6 +446,89 @@ async def _search(query: str | None) -> T_a:
445446
logger.info('Player "%s" could not load any songs via AutoPlay.', self.guild.id)
446447
self._inactivity_start()
447448

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+
448532
@property
449533
def inactive_channel_tokens(self) -> int | None:
450534
"""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:
11281212
except (AttributeError, KeyError):
11291213
pass
11301214

1131-
async def _destroy(self) -> None:
1215+
async def _destroy(self, with_invalidate: bool = True) -> None:
11321216
assert self.guild
11331217

1134-
self._invalidate()
1218+
if with_invalidate:
1219+
self._invalidate()
1220+
11351221
player: Player | None = self.node._players.pop(self.guild.id, None)
11361222

11371223
if player:
11381224
try:
11391225
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)
11421228

11431229
def _add_to_previous_seeds(self, seed: str) -> None:
11441230
# Helper method to manage previous seeds.

wavelink/types/state.py

+33
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@
2626

2727
from typing_extensions import NotRequired
2828

29+
from ..filters import Filters
30+
from ..tracks import Playable
31+
2932

3033
class PlayerState(TypedDict):
3134
time: int
@@ -45,3 +48,33 @@ class PlayerVoiceState(TypedDict):
4548
channel_id: NotRequired[str]
4649
track: NotRequired[str]
4750
position: NotRequired[int]
51+
52+
53+
class PlayerBasicState(TypedDict):
54+
"""A dictionary of basic state for the Player.
55+
56+
Attributes
57+
----------
58+
voice_state: :class:`PlayerVoiceState`
59+
The voice state received via Discord. Includes the voice connection ``token``, ``endpoint`` and ``session_id``.
60+
position: int
61+
The player position.
62+
connected: bool
63+
Whether the player is currently connected to a channel.
64+
current: :class:`~wavelink.Playable` | None
65+
The currently playing track or `None` if no track is playing.
66+
paused: bool
67+
The players paused state.
68+
volume: int
69+
The players current volume.
70+
filters: :class:`~wavelink.Filters`
71+
The filters currently assigned to the Player.
72+
"""
73+
74+
voice_state: PlayerVoiceState
75+
position: int
76+
connected: bool
77+
current: Playable | None
78+
paused: bool
79+
volume: int
80+
filters: Filters

0 commit comments

Comments
 (0)