Skip to main content

A modern async Lavalink client library for Python Discord music bots.

Project description

FluxWave

CI Python License: MIT Docs Typed

FluxWave is a typed, async Lavalink v4 client for Python Discord music bots. It provides the Lavalink REST and websocket client, Discord voice integration, node pooling, queueing, filters, events, plugin helpers, autoplay, persistence, and production-oriented recovery tools.

FluxWave is 0.2.2 (beta). It is usable in real bots today; being pre-1.0, some public APIs may still change before 1.0.

Features

  • Async Lavalink v4 REST + websocket client, fully typed (py.typed).
  • Library-agnostic — works with discord.py, py-cord, nextcord, or disnake; depends only on aiohttp.
  • Multi-node pooling (NodePool / Pool) with health-aware selection, blacklist/cooldown, automatic failover and return-to-home, hot-node draining, and parallel multi-node search.
  • Players — full playback controls, voice-state recovery, a tagged filter stack, opt-in crossfade (smooth volume fades between tracks), and a voice watchdog for stalled-playback recovery.
  • Queue — history, loop modes, shuffle/move/dedupe, capacity limits, and serialization.
  • Filters — equalizer, karaoke, timescale, a 30+ effect preset library (nightcore, 8D, slowed + reverb, lo-fi, genre EQs…), interpolation, and set_filters(..., seek=True).
  • Searchytsearch:/ytmsearch:/URLs/playlists, default sources, LFU caching, and in-flight request coalescing.
  • Events — typed payloads with fluxwave_* and Wavelink-style aliases, plus a raw-websocket event for debugging.
  • Plugins — LavaSrc, LavaLyrics, SponsorBlock, and custom REST routes.
  • Lyrics — fetch full/plain lyrics, plus live_lyrics() to stream synced lines as the track plays for karaoke-style now-playing messages.
  • Autoplay — recommendation providers with duplicate filtering.
  • Persistence & observability — in-memory and crash-safe on-disk state stores, metrics, and structured tracing.

Documentation

Full documentation is hosted at fluxwave.readthedocs.io (source in docs/). Good starting points:

The docs/guide/ folder covers players, nodes & pools, search & autoplay, queues & filters, events, plugins, persistence & observability, and troubleshooting.

Requirements

  • Python 3.11 or newer.
  • One supported Discord library, which you install yourself: discord.py 2.4+, py-cord 2.4+, nextcord 2.6+, or disnake 2.9+. Like mafic and lavalink.py, FluxWave does not depend on a specific one — it detects whichever is installed.
  • aiohttp.
  • A Lavalink v4 server.

Installation

FluxWave needs two things: FluxWave itself, plus any one Discord library (discord.py, py-cord, nextcord, or disnake — your choice). FluxWave auto-detects whichever one is installed, so there is nothing to configure.

If you already have a Discord library installed, just install FluxWave:

python -m pip install fluxwave

If you are starting fresh, install FluxWave and a Discord library together (discord.py shown here — swap in py-cord, nextcord, or disnake if you prefer):

python -m pip install fluxwave discord.py

Or pull a Discord library in automatically with an extra (discordpy, pycord, nextcord, or disnake):

python -m pip install "fluxwave[discordpy]"

That is the whole setup. Once a Discord library is importable, import fluxwave just works.

Why isn't the Discord library bundled? FluxWave itself depends only on aiohttp. It deliberately does not install a Discord library for you, because discord.py and py-cord share the same discord import name — bundling one would overwrite and break users of the other. Bringing your own library (the same approach as mafic and lavalink.py) keeps every install clash-free.

If you happen to have more than one supported library installed at once, tell FluxWave which to use with the FLUXWAVE_DISCORD_LIBRARY environment variable (discord, nextcord, or disnake).

For local development:

git clone https://github.com/FluxWaveProject/fluxwave.git
cd fluxwave
python -m pip install -e ".[dev,docs]"

Quick Start

Create a Lavalink node and use FluxPlayer as the Discord voice client:

import os

import discord
from discord.ext import commands

import fluxwave

bot = commands.Bot(command_prefix="!", intents=discord.Intents.default())


@bot.event
async def setup_hook() -> None:
    node = fluxwave.Node(  # describe one Lavalink server
        uri=os.getenv("LAVALINK_URI", "http://127.0.0.1:2333"),
        password=os.getenv("LAVALINK_PASSWORD", "youshallnotpass"),
        user_id=bot.user.id,  # the bot's own Discord user id
        identifier="main",
    )
    await fluxwave.Pool.connect(nodes=[node], cache_capacity=256)  # register + connect the node(s)


@bot.command()
async def play(ctx: commands.Context, *, query: str) -> None:
    if ctx.author.voice is None or ctx.author.voice.channel is None:  # caller must be in a voice channel
        await ctx.reply("Join a voice channel first.")
        return

    player = ctx.voice_client  # existing voice connection, if any
    if not isinstance(player, fluxwave.FluxPlayer):
        player = await ctx.author.voice.channel.connect(cls=fluxwave.FluxPlayer)  # join voice as a FluxPlayer

    result = await player.enqueue(query)  # search and add tracks to the queue
    if result.added == 0:  # nothing matched the query
        await ctx.reply("No tracks found.")
        return

    if player.current is None:  # nothing playing yet
        await player.skip(force=True)  # start the first queued track

    await ctx.reply(result.message)  # human-friendly summary of what was added


bot.run(os.environ["DISCORD_TOKEN"])

See examples/basic_bot.py for a fuller command set. See examples/advanced_bot.py for autoplay, filters, lyrics, persistence, metrics, and watchdog usage.

Core Concepts

Node

Node represents one Lavalink server. It owns REST, websocket, authentication, session resume, route planner helpers, known Lavalink players, live Discord players, and optional node-local search caching.

Read more: Nodes and Pools, API Reference: Node API.

node = fluxwave.Node(  # describe one Lavalink server
    uri="http://127.0.0.1:2333",
    password="youshallnotpass",
    user_id=1234567890,  # the bot's own Discord user id
    identifier="main",
    search_cache_capacity=256,  # per-node search result cache size
)
await node.connect()  # open REST + websocket to Lavalink

Pool

NodePool is instance-based. Pool is the global facade for bot code that prefers Wavelink-style helpers.

Read more: Nodes and Pools, API Reference: Node API.

await fluxwave.Pool.connect(nodes=[node], cache_capacity=512)  # register + connect the node(s)
tracks = await fluxwave.Pool.search("never gonna give you up")  # search the default source

For multiple nodes:

results = await fluxwave.Pool.search_all("ytsearch:lofi")  # search every node in parallel
degraded = fluxwave.Pool.get_degraded_nodes()  # nodes currently unhealthy
await fluxwave.Pool.drain(old_node, cooldown=300)  # stop using a node, cooldown in seconds

Player

FluxPlayer is a Discord voice protocol (compatible with discord.py, py-cord, nextcord, and disnake). It handles Discord voice updates, sends voice state to Lavalink, owns queues, controls playback, dispatches public events, and can persist/restore playback state.

Read more: Player Guide, API Reference: Player API.

player = await voice_channel.connect(cls=fluxwave.FluxPlayer)  # join voice as a FluxPlayer
await player.enqueue("ytsearch:lofi beats")  # search and add tracks to the queue
await player.skip(force=True)  # advance to the next track now
await player.set_volume(80)  # volume as a percentage
await player.set_filters(fluxwave.Filters().nightcore(), seek=True)  # nightcore effect, re-seek so it applies now

Queue

Read more: Queues and Filters, API Reference: Queue API.

player.queue.put(track)  # add a track to the end of the queue
player.queue.move(4, 0)  # move the track at index 4 to the front
player.queue.dedupe()  # remove duplicate tracks
next_ten = player.queue.clear_next(10)  # remove and return the next 10 tracks
duration_ms = player.queue.total_duration  # total queue length in milliseconds
next_track = player.queue.get(bypass_loop=True)  # pop next track, ignoring loop mode
snapshot = player.queue.to_raw_data()  # serialize the queue for persistence

Events

FluxWave dispatches both FluxWave-prefixed and Wavelink-style Discord event names for easier migration:

Read more: Events, API Reference: Events.

@bot.event
async def on_fluxwave_track_start(event: fluxwave.TrackStartEvent) -> None:
    print(f"Started {event.track.title} in guild {event.guild_id}")


@fluxwave.listen("track_end")
async def on_track_end(event: fluxwave.TrackEndEvent) -> None:
    print(event.reason)

Persistence

Read more: Persistence and Observability.

store = fluxwave.MemoryStore()  # in-memory state store
state = player.save_state(extra={"source": "shutdown"})  # snapshot player + queue
await store.save(player.guild.id, state)  # persist keyed by guild id

restored = await store.load(player.guild.id)  # load any saved snapshot
if restored is not None:
    await player.restore_state(restored)  # rebuild queue and playback

Watchdog

Read more: Persistence and Observability.

watchdog = player.start_watchdog(  # monitor for stalled playback
    fluxwave.WatchdogConfig(check_interval=5.0, stagnation_threshold=12.0)  # poll every 5s, recover after 12s stalled
)

Common Bot Commands

FluxWave is not a command framework, but it is designed to make common commands simple:

await player.enqueue("song name")  # search and add to the queue
await player.play_next("song name")  # queue a track to play next
await player.skip(force=True)  # advance to the next track now
await player.stop(force=True)  # stop and clear the queue
await player.stop(clear_queue=False)  # stop current track but keep queue
await player.set_filters(fluxwave.Filters().bass_boost(), seek=True)  # bass boost, re-seek so it applies now
lyrics = await player.current_lyrics()  # fetch lyrics for the current track
state = player.save_state()  # snapshot player + queue

Security Notes

  • Never hardcode Discord bot tokens in examples or committed code.
  • Read tokens/passwords from environment variables or a secret manager.
  • Treat Lavalink plugin endpoints as trusted-server APIs; validate user input before passing it to custom routes.

Lavalink Configuration

FluxWave expects a Lavalink v4 server reachable over HTTP or HTTPS.

Common environment variables used by examples and integration tests:

export DISCORD_TOKEN="your bot token"
export LAVALINK_URI="http://127.0.0.1:2333"
export LAVALINK_HOST="127.0.0.1"
export LAVALINK_PORT="2333"
export LAVALINK_PASSWORD="youshallnotpass"
export LAVALINK_SECURE="false"

Development

python -m pip install -e ".[dev,docs]"
.venv/bin/python -m ruff check .
.venv/bin/python -m ruff format --check .
.venv/bin/python -m mypy
.venv/bin/python -m pytest

Run integration tests against your own Lavalink server:

LAVALINK_HOST=127.0.0.1 \
LAVALINK_PORT=2333 \
LAVALINK_PASSWORD=youshallnotpass \
LAVALINK_SECURE=false \
.venv/bin/python -m pytest -m integration tests/test_integration_lavalink.py

Build docs locally:

.venv/bin/python -m sphinx -b html docs docs/_build/html

Build a wheel/sdist locally:

.venv/bin/python -m pip install build
.venv/bin/python -m build

Project Status

FluxWave is beta-quality and pre-1.0. On top of a heavy unit-test suite, it has been validated end-to-end against live Discord and Lavalink — playback, multi-node failover, every plugin integration, and an extended stress soak. Public APIs may still change before 1.0.

Support

Contributing

Contributions are welcome — see CONTRIBUTING.md for the dev setup and the lint/type/test checks. Security issues: see SECURITY.md.

License

FluxWave is distributed under the MIT License. See LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

fluxwave-0.2.2.tar.gz (152.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

fluxwave-0.2.2-py3-none-any.whl (93.8 kB view details)

Uploaded Python 3

File details

Details for the file fluxwave-0.2.2.tar.gz.

File metadata

  • Download URL: fluxwave-0.2.2.tar.gz
  • Upload date:
  • Size: 152.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for fluxwave-0.2.2.tar.gz
Algorithm Hash digest
SHA256 877d53b2ee26ae5861577a308c9a7fca01a4b773acac1b20d6e5adb7d7500b2e
MD5 d4a4e3757d53f59d436c0e51635efc8b
BLAKE2b-256 f08ad6685ffcc37bf634d6099f44c05bf1fd98d20c42ad7f2f118e190f6cc7dc

See more details on using hashes here.

Provenance

The following attestation bundles were made for fluxwave-0.2.2.tar.gz:

Publisher: release.yml on FluxWaveProject/fluxwave

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file fluxwave-0.2.2-py3-none-any.whl.

File metadata

  • Download URL: fluxwave-0.2.2-py3-none-any.whl
  • Upload date:
  • Size: 93.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for fluxwave-0.2.2-py3-none-any.whl
Algorithm Hash digest
SHA256 67a66b7168a1730199a8be12e27a6d603ae6e22e9ca41335cd63f82a1b55b4b0
MD5 b64bf6d770884a01cda7872b77c87264
BLAKE2b-256 91ef5cf33216b69a8394fe2d7049d36fee290708e16600b6182637d93ef9fa3d

See more details on using hashes here.

Provenance

The following attestation bundles were made for fluxwave-0.2.2-py3-none-any.whl:

Publisher: release.yml on FluxWaveProject/fluxwave

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page