Working With Players¶
Connecting a player¶
For most bots, the simplest way to create a player is to pass sonolink.Player
to your Discord client when connecting to a voice channel:
player = await voice_channel.connect(cls=sonolink.Player)
When you connect this way, the player will:
use the
sonolink.Clientattached to your Discord client,choose the best available connected node,
complete the Discord voice handshake for you.
This is usually the easiest place to start because it keeps player creation short and predictable.
If you need to configure the player before connecting, create it first, either by using an
instance or by using sonolink.Node.create_player(), and then pass the configured instance
as cls to:
discord.abc.Connectable.connect()(discord.py)discord.VoiceChannel.connect()(py-cord)disnake.VoiceChannel.connect()(disnake)nextcord.VoiceChannel.connect()(nextcord)
from sonolink.models import Karaoke, Filters
player = sonolink.Player(
node=node,
volume=100,
filters=Filters(
karaoke=Karaoke(level=0.5),
),
)
# or
player = node.create_player(
volume=100,
filters=Filters(
karaoke=Karaoke(level=0.5),
),
)
await voice_channel.connect(cls=player)
Reusing an existing player¶
In command handlers, the active voice connection is available via ctx.voice_client —
see:
discord.ext.commands.Context.voice_client(discord.py)nextcord.ext.commands.Context.voice_client(nextcord)
player = ctx.voice_client
if not isinstance(player, sonolink.Player):
player = await ctx.author.voice.channel.connect(cls=sonolink.Player)
Subclassing a player¶
If you need to extend or override player behaviour, subclass sonolink.Player directly.
The framework adapter is injected automatically at class-creation time, so your subclass
works as a drop-in replacement anywhere sonolink.Player is accepted.
class MyPlayer(sonolink.Player):
async def stop(
self,
/,
*,
clear_queue: bool = False,
clear_history: bool = False,
) -> None:
print("Method stop is overridden.")
await super().stop(clear_queue=clear_queue, clear_history=clear_history)
Pass your subclass the same way you would pass sonolink.Player:
player = await voice_channel.connect(cls=MyPlayer)
Warning
Your subclass must be defined after constructing sonolink.Client, otherwise
the framework adapter may not be resolved correctly. Alternatively, set the
SONOLINK_FRAMEWORK environment variable before any imports to force a specific
framework ahead of time.
Playback controls¶
The main playback methods on sonolink.Player are:
All of these methods act on the current player attached to a guild. In practice, that means they are the methods you will call from command handlers, button callbacks, or playback services.
sonolink.Player.play()¶
Use sonolink.Player.play() to start a specific track right away.
track = result.result[0]
await player.play(track)
play starts the track you pass to it immediately. It does not automatically
pick the next track from the queue for you.
It is the direct “play this now” call, and it also accepts optional keyword
arguments such as start, end, volume, and paused when you need
more control over playback.
sonolink.Player.stop()¶
Use sonolink.Player.stop() to end the current track and clear the active
playback state.
await player.stop()
This is useful when you want playback to end without immediately starting another track. By default, this stops the current track but leaves the queue and history intact.
If you want a more complete reset, you can also clear queued and historical tracks:
await player.stop(clear_queue=True, clear_history=True)
This is a good fit for commands such as stop, leave, clear, or admin-side
“reset player” actions.
sonolink.Player.pause() and sonolink.Player.resume()¶
Use sonolink.Player.pause() to pause playback and
sonolink.Player.resume() to continue it later.
await player.pause()
await player.resume()
These methods simply change the state of the current track. The paused state is
also available through sonolink.Player.paused if you want to show playback
status in an embed or avoid repeating a pause or resume action.
sonolink.Player.skip()¶
Use sonolink.Player.skip() to move forward to the next track.
next_track = await player.skip()
skip is queue-aware:
if there is another track in the queue, it starts that track,
if autoplay is enabled, autoplay may provide the next track,
if neither applies, playback stops.
This makes it a better choice than manually calling stop() when your intent is
“go to whatever should play next”.
sonolink.Player.previous()¶
Use sonolink.Player.previous() to return to the most recent track in history.
previous_track = await player.previous()
This is meant to behave like a real “back” button, not just replay the current song. When possible, it pulls the last track from history, pushes the current track back to the front of the queue, and starts the previous track.
That means previous depends on queue history being available. If there is no playback
history yet, it will fail rather than guessing what “previous” should mean.
sonolink.Player.seek()¶
Use sonolink.Player.seek() to jump to a specific position in the current track.
The position is expressed in milliseconds.
await player.seek(60_000) # 1 minute
seek also matters outside dedicated seek commands, because some Lavalink
behavior, especially filter application, may only become audible after playback
position changes.
sonolink.Player.set_volume()¶
Use sonolink.Player.set_volume() to change the player’s output volume.
await player.set_volume(75)
SonoLink volume values use the Lavalink range of 0 to 1000:
100is the normal default volume,values below
100reduce volume,values above
100amplify audio and may sound harsh or clip at higher levels.
sonolink.Player.set_filters()¶
Use sonolink.Player.set_filters() to apply Lavalink filters such as karaoke,
timescale, equalizer, tremolo, or rotation.
from sonolink.models import Filters, Timescale
filters = Filters(timescale=Timescale(speed=1.1))
await player.set_filters(filters, seek=True)
Filters are grouped into a single sonolink.models.Filters object and then
applied in one call.
The seek=True part is often important. Some filters are not immediately
audible until playback position changes, so seeking to the current position
makes the effect audible right away.
For more information on filters, see Filters And Playback State.
Playing tracks¶
A common playback flow is:
search for a track,
add it to the queue,
start playback only if nothing is currently playing.
result = await sl_client.search_track("never gonna give you up")
if result.is_error() or result.is_empty() or result.result is None:
return
data = result.result
rest = []
if isinstance(data, list):
play_track = data[0]
elif isinstance(data, sonolink.models.Playlist):
play_track = data.tracks[0]
rest = data.tracks[1:]
else:
play_track = data
if not player.current:
await player.play(play_track)
else:
rest = [play_track, *rest]
for track in rest:
await player.queue.put_wait(track)
Moving between nodes¶
If you run multiple Lavalink nodes, you can migrate a player with:
await player.move_to(other_node)
This is mainly useful for operational workflows such as:
draining a busy or unhealthy node,
moving guilds away from a node before maintenance,
redistributing load across multiple Lavalink instances.
You probably will not need this in day-to-day command logic, but it becomes very useful in larger bots and multi-node deployments.
Disconnecting cleanly¶
Use sonolink.Player.disconnect() instead of only disconnecting the Discord voice client.
This ensures the Lavalink-side player is destroyed and SonoLink can clean up its internal state
for that guild.
In practice, this is as simple as:
vc = ctx.voice_client
await vc.disconnect()
If vc is a sonolink.Player, this will call the player implementation and perform
the full cleanup. This is the recommended way to handle leave or disconnect commands.