Moving From Lavaplay.py

This guide is for migrating from Lavaplay.py to SonoLink.

It walks through the main differences between SonoLink and Lavaplay, so you can get a feel for what changes and what stays familiar.

If you are not migrating an existing Lavaplay bot, start with Getting Started instead.

What stays familiar

  • Lavalink remains the backend.

  • SonoLink supports discord.py, py-cord, disnake, and nextcord via a custom discord.VoiceProtocol for voice integration.

  • The main runtime objects are still a coordinator, nodes, players, tracks, playlists, and filters.

What changes

Lavaplay is library-independent, which means it does not integrate with discord.py’s voice connection cache — utilities such as Context.voice_client return None even when connected, and voice state updates must be forwarded manually. SonoLink is library-dependent and handles all of this automatically through discord.VoiceProtocol. It also adds a structured settings system, an automated queue with history and autoplay, and automatic node selection.

Concept mapping

Lavaplay

SonoLink

lavaplay.Lavalink

sonolink.Client

lavaplay.Node

sonolink.Node

lavaplay.Player

sonolink.Player

lavaplay.Node.search_youtube / lavaplay.Node.search_soundcloud / lavaplay.Node.search_youtube_music / lavaplay.Node.get_track / lavaplay.Node.auto_search_tracks

sonolink.Client.search_track() / sonolink.Node.search_track()

lavaplay.Track

sonolink.models.Playable

lavaplay.Playlist

sonolink.models.Playlist

lavaplay.TrackLoadFailed

sonolink.models.SearchResult.exception

lavaplay.Filters

sonolink.models.Filters

lavaplay.Player.filters

sonolink.Player.set_filters()

Connection lifecycle

In Lavaplay, you create a Lavalink instance, and then create a Node with Lavalink.create_node:

# Lavaplay.py
lavalink = Lavalink()
node = lavalink.create_node(
    host="127.0.0.1",
    port=2333,
    password="youshallnotpass",
    user_id=0,
)

# in any function, either a library's on_ready or just a set up:
node.connect()

In SonoLink, this is essentially the same, but with some changes:

# SonoLink
sl_client = sonolink.Client(bot)

sl_client.create_node(
    uri="http://127.0.0.1:2333",
    password="youshallnotpass",
    id="main",
)

async def setup_hook() -> None:
    await sl_client.start()

Note

sonolink.Client.start() should be called in discord.Client.setup_hook() (discord.py), discord.on_connect() (py-cord), disnake.on_connect() (disnake), or nextcord.on_connect() (nextcord) — not in on_ready.

Node management

Lavaplay only allows for one node to be created per process, so its management resides only in one Node class, which the players are bound to.

SonoLink handles all nodes internally, but still allows for node selection per player, exposing sonolink.Node.create_player(), similar to Lavaplay’s Node.create_player.

node = sl_client.get_node(id="main")
player = node.create_player(...)

For most bots, the automatic node selection SonoLink offers is sufficient and you will not need to call this directly.

Players and voice connection

Lavaplay does not directly expose a discord.VoiceProtocol, but instead relies on you providing the required data to connect and update the player, either via a custom discord.VoiceProtocol subclass, or receiving raw events.

# Lavaplay.py
player = lavalink.create_player(ctx.guild.id)
await ctx.guild.change_voice_state(
    channel=ctx.author.voice.channel,
)

async def on_voice_server_update(data):
    await player.raw_voice_server_update(data)

# other methods required, such as voice_state_update

In SonoLink, players are created through the standard Discord VoiceProtocol interface:

# SonoLink
player = await voice_channel.connect(cls=sonolink.Player)

If you need to pre-configure a player before connecting:

player = sonolink.Player(
    node=node,
    volume=100,
)

await voice_channel.connect(cls=player)

See Working With Players for the full player reference.

Searching and loading tracks

In Lavaplay.py, searching is done through any Node.search_X method, or Node.get_tracks, which returns a list of Tracks, a Playlist, or a TrackLoadFailed.

# Lavaplay.py
result = await node.get_tracks(query)

if not result or isinstance(result, TrackLoadFailed):
    return

if isinstance(result, PlayList):
    track = result.tracks[0]
else:
    track = result[0]

In SonoLink, searching is done through sonolink.Client.search_track() (or its equivalent on the node: sonolink.Node.search_track()). This method returns a sonolink.models.SearchResult wrapper. If you want to specify a source, you can either pass a prefix in query, or pass a sonolink.TrackSourceType enum member or string.

# SonoLink
result = await sl_client.search_track(query, source=sonolink.TrackSourceType.SOUNDCLOUD)
if result.is_error() or result.is_empty() or result.result is None:
    return

data = result.result

if isinstance(data, list):
    track = data[0]
elif isinstance(data, sonolink.models.Playlist):
    track = data.tracks[0]
else:
    track = data

sonolink.models.SearchResult covers all possible outcomes: a single track, a playlist, a search result list, an empty result, or an error.

Tracks

Lavaplay.py provides the lavaplay.Track model for individual tracks. The SonoLink equivalent is sonolink.models.Playable. SonoLink tracks additionally carry sonolink.models.Album and sonolink.models.Artist metadata when the source provides it.

Playback

sonolink.Player exposes almost the same methods as Lavaplay.py’s Player class, with the following main differences:

  • Unlike Lavaplay.py, sonolink.Player.queue is a sonolink.Queue object, which also provides history-backed navigation, autoplay integration, and additional configuration via AutoPlaySettings and HistorySettings. Tracks are added via put() rather than player.queue.append, and all queue-related methods live under sonolink.Player.queue.

  • Lavaplay.py offers player.play and player.play_playlist. The former has a direct SonoLink equivalent, play(), while the latter does not and requires manual iteration through the playlist tracks calling play on each.

  • In SonoLink, pausing and resuming are split into two separate methods: pause() and resume(), unlike Lavaplay.py where you called player.pause(True/False).

See Working With Players for the full playback reference.

Filters

In Lavaplay.py, filters are all encapsulated in the Filters class, offering methods such as Filters.equalizer or Filters.karaoke to update or set a filter, applied via player.filters(filters).

# Lavaplay.py
filters = lavaplay.Filters()
filters.equalizer([(0, 0.5)])  # set band 0 gain to 0.5

await player.filters(filters)

In SonoLink, the Filters class takes all filters you want to update as constructor arguments, then you call set_filters() to apply them. The seek parameter optionally restarts the current track position after applying:

# SonoLink
filters = sonolink.models.Filters(
    equalizer=[
        sonolink.models.Equalizer(
            band=0,
            gain=0.5,
        ),
    ],
)

await player.set_filters(filters)

See Filters And Playback State for the full filter reference.

Events

Lavaplay.py uses a @node.listener decorator on each Node instance to attach and handle events, passing the event model or event name:

# Lavaplay.py
@node.listener(lavaplay.TrackStartEvent)
async def on_track_start(event: lavaplay.TrackStartEvent) -> None:
    print("New track started playing:", event.track.title)

@node.listener("TrackEndEvent")
async def on_track_end(event: lavaplay.TrackEndEvent) -> None:
    print("Track finished playing:", event.track.title)

SonoLink instead dispatches events through your library’s client with the sonolink_ prefix, passing a typed payload object alongside the player. You register handlers as standard Discord event listeners:

# SonoLink
@bot.event
async def on_sonolink_track_start(player: sonolink.Player, payload: sonolink.gateway.TrackStartEvent) -> None:
    print("New track started playing:", payload.track.title)

@bot.event
async def on_sonolink_track_end(player: sonolink.Player, payload: sonolink.gateway.TrackEndEvent) -> None:
    print("Track finished playing:", payload.track.title)

The event name mapping is:

Lavaplay.py event class or name

SonoLink event

ReadyEvent

on_sonolink_node_ready(payload)

TrackStartEvent

on_sonolink_track_start(player, payload)

TrackEndEvent

on_sonolink_track_end(player, payload)

StatsUpdateEvent

(handled internally; you may access the updated data via stats )

TrackExceptionEvent

on_sonolink_track_exception(player, payload)

TrackStuckEvent

on_sonolink_track_stuck(player, payload)

WebSocketClosedEvent

on_sonolink_websocket_closed(player, payload)

PlayerUpdateEvent

on_sonolink_player_update(payload)

(no equivalent)

on_sonolink_player_disconnect(player, payload)

See Events for the full event reference and payload types.

Settings

SonoLink introduces a settings system with no Lavaplay.py equivalent. Settings are dataclass-like objects passed at node or player creation time, giving you structured control over behaviour that Lavaplay.py left to manual implementation in event handlers:

from sonolink.models import AutoPlaySettings, CacheSettings, HistorySettings, InactivitySettings
from sonolink.gateway import AutoPlayMode, InactivityMode

sl_client.create_node(
    uri="http://localhost:2333",
    password="youshallnotpass",
    inactivity_settings=InactivitySettings(
        timeout=300,
        mode=InactivityMode.ALL_BOTS,
    ),
    cache_settings=CacheSettings(
        enabled=True,
        max_items=1000,
    ),
)

Autoplay and track history

Lavaplay.py has no history or autoplay system — bots that want either must build them manually, although it provides helpful methods such as Node.auto_search_tracks. SonoLink introduces both features by default.

Autoplay is configured through sonolink.models.AutoPlaySettings at player creation time, and toggled via sonolink.Player.autoplay, which accepts an sonolink.AutoPlayMode value:

Track history is enabled via sonolink.models.HistorySettings and exposed through sonolink.Player.queue as a sonolink.History object. Autoplay uses history as its seed, so the two features work together.

Warning

Autoplay requires history to be enabled. If sonolink.models.HistorySettings is left at its default, autoplay will have no reference track to discover from.

Errors and exceptions

Lavaplay.py exposes different errors as Exception subclasses. SonoLink provides a subclass-based hierarchy for them:

Lavaplay.py

SonoLink

FiltersError and VolumeError

(no direct SonoLink equivalent)

NotConnectedError and ConnectedError

RuntimeError, raised for their corresponding methods (e.g. RuntimeError when calling Node.connect() means the node is already connected)

TrackLoadFailed

(surface via result.is_error() on sonolink.models.SearchResult )

requestFailed (HTTP failures)

sonolink.HTTPException

(no equivalent)

sonolink.QueueEmpty

(no equivalent)

sonolink.HistoryEmpty

(no equivalent)

sonolink.WebSocketError

(no equivalent)

sonolink.NodeURINotFound

See Exceptions for the full exception reference.

Useful references