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.Client attached 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:

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:

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.

Playing tracks

A common playback flow is:

  1. search for a track,

  2. add it to the queue,

  3. 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.