A robust and powerful, fully asynchronous Lavalink wrapper built for discord.py in Python.
Main audio player interface providing playback control, volume management, seeking, and voice channel operations with full discord.py VoiceProtocol integration. The Player class extends discord.VoiceProtocol to seamlessly integrate with Discord's voice system while providing advanced audio playback capabilities.
The Player class integrates with discord.py's voice system to provide audio playback in Discord voice channels.
class Player(discord.VoiceProtocol):
def __init__(self, client: discord.Client, channel: discord.VoiceChannel):
"""
Initialize a new Player instance.
Parameters:
- client: Discord client instance
- channel: Voice channel to connect to
"""
# Predefined queue instances
queue: Queue # Standard track queue
auto_queue: Queue # AutoPlay queue for recommendations
async def connect(
self,
channel: discord.VoiceChannel,
*,
self_deaf: bool = True,
**kwargs
) -> None:
"""
Connect to a Discord voice channel.
Parameters:
- channel: Voice channel to connect to
- self_deaf: Whether to self-deafen the bot
- **kwargs: Additional connection parameters
Raises:
InvalidChannelStateException: If channel is invalid or no permissions
ChannelTimeoutException: If connection times out
"""
async def move_to(self, channel: discord.VoiceChannel) -> None:
"""
Move to a different voice channel.
Parameters:
- channel: New voice channel to move to
"""
async def disconnect(self, **kwargs) -> None:
"""
Disconnect from the voice channel.
Parameters:
- **kwargs: Additional disconnection parameters
"""Core playback functionality including play, pause, seek, skip, and stop operations.
class Player(discord.VoiceProtocol):
async def play(
self,
track: Playable,
*,
replace: bool = True,
start: int = 0,
end: int | None = None,
volume: int | None = None,
paused: bool | None = None,
add_history: bool = True,
filters: Filters | None = None,
populate: bool = False,
max_populate: int = 5
) -> Playable:
"""
Play a track.
Parameters:
- track: The track to play
- replace: Whether to replace the current track
- start: Start position in milliseconds
- end: End position in milliseconds (None for full track)
- volume: Playback volume (0-100)
- paused: Whether to start paused
- add_history: Whether to add current track to history
- filters: Filters to apply when playing
- populate: Whether to populate AutoPlay queue
- max_populate: Maximum tracks to populate for AutoPlay
Returns:
Playable: The track that is now playing
"""
async def pause(self, value: bool) -> None:
"""
Pause or unpause playback.
Parameters:
- value: True to pause, False to unpause
"""
async def seek(self, position: int = 0) -> None:
"""
Seek to a specific position in the current track.
Parameters:
- position: Position to seek to in milliseconds
Raises:
ValueError: If track is not seekable
"""
async def stop(self, *, force: bool = True) -> Playable | None:
"""
Stop the current track.
Parameters:
- force: Whether to force stop immediately
Returns:
Playable | None: The stopped track, if any
"""
async def skip(self, *, force: bool = True) -> Playable | None:
"""
Skip to the next track in the queue.
Parameters:
- force: Whether to force skip immediately
Returns:
Playable | None: The skipped track, if any
"""Properties for monitoring and controlling player state, volume, and playback information.
class Player(discord.VoiceProtocol):
@property
def inactive_channel_tokens(self) -> int | None:
"""
Token limit for the amount of tracks to play before firing
the on_wavelink_inactive_player event when a channel is inactive.
Returns None if the check has been disabled. A channel is
considered inactive when no real members are in the voice channel.
Default value is 3. Setting to <= 0 or None disables the check.
"""
@inactive_channel_tokens.setter
def inactive_channel_tokens(self, value: int | None) -> None:
"""Set the inactive channel token limit."""
@property
def inactive_timeout(self) -> int | None:
"""
Time in seconds to wait before dispatching the
on_wavelink_inactive_player event for inactive players.
Returns None if no timeout is set. An inactive player is one
that has not been playing anything for the specified seconds.
The countdown starts when a track ends and cancels when a track starts.
"""
@inactive_timeout.setter
def inactive_timeout(self, value: int | None) -> None:
"""Set the inactive timeout in seconds."""
@property
def autoplay(self) -> AutoPlayMode:
"""Current AutoPlay mode setting."""
@autoplay.setter
def autoplay(self, value: AutoPlayMode) -> None:
"""Set the AutoPlay mode."""
@property
def node(self) -> Node:
"""The Lavalink node this player is connected to."""
@property
def guild(self) -> discord.Guild | None:
"""The Discord guild this player belongs to."""
@property
def connected(self) -> bool:
"""Whether the player is connected to a voice channel."""
@property
def current(self) -> Playable | None:
"""The currently playing track."""
@property
def volume(self) -> int:
"""Current player volume (0-100)."""
@property
def filters(self) -> Filters:
"""Currently applied audio filters."""
@property
def paused(self) -> bool:
"""Whether playback is currently paused."""
@property
def ping(self) -> int:
"""WebSocket ping to the Lavalink server in milliseconds."""
@property
def playing(self) -> bool:
"""Whether the player is currently playing a track."""
@property
def position(self) -> int:
"""Current playback position in milliseconds."""Methods for controlling audio volume and applying audio filters to enhance playback.
class Player(discord.VoiceProtocol):
async def set_volume(self, value: int = 100) -> None:
"""
Set the player volume.
Parameters:
- value: Volume level (0-100)
Raises:
ValueError: If volume is outside valid range
"""
async def set_filters(
self,
filters: Filters | None = None,
*,
seek: bool = False
) -> None:
"""
Apply audio filters to the player.
Parameters:
- filters: Filters object to apply (None to reset all filters)
- seek: Whether to seek to current position after applying filters
"""Event handler methods that can be overridden to respond to player events.
class Player(discord.VoiceProtocol):
async def on_voice_state_update(self, data) -> None:
"""
Handle voice state updates from Discord.
Parameters:
- data: Voice state update data
"""
async def on_voice_server_update(self, data) -> None:
"""
Handle voice server updates from Discord.
Parameters:
- data: Voice server update data
"""import discord
import wavelink
from discord.ext import commands
bot = commands.Bot(command_prefix='!', intents=discord.Intents.all())
@bot.command()
async def join(ctx):
"""Join the user's voice channel."""
if not ctx.author.voice:
return await ctx.send("You're not in a voice channel!")
channel = ctx.author.voice.channel
player = await channel.connect(cls=wavelink.Player)
await ctx.send(f"Connected to {channel.name}")
@bot.command()
async def leave(ctx):
"""Leave the voice channel."""
player = ctx.voice_client
if player:
await player.disconnect()
await ctx.send("Disconnected!")@bot.command()
async def play(ctx, *, query: str):
"""Play a track or add to queue."""
if not ctx.voice_client:
player = await ctx.author.voice.channel.connect(cls=wavelink.Player)
else:
player = ctx.voice_client
# Search for tracks
tracks = await wavelink.Pool.fetch_tracks(query)
if not tracks:
return await ctx.send("No tracks found!")
track = tracks[0] if isinstance(tracks, list) else tracks.tracks[0]
if player.playing:
# Add to queue if already playing
player.queue.put(track)
await ctx.send(f"Added to queue: {track.title}")
else:
# Start playing immediately
await player.play(track)
await ctx.send(f"Now playing: {track.title}")
@bot.command()
async def pause(ctx):
"""Pause or unpause playback."""
player = ctx.voice_client
if not player:
return await ctx.send("Not connected to a voice channel!")
await player.pause(not player.paused)
action = "Paused" if player.paused else "Resumed"
await ctx.send(f"{action} playback!")
@bot.command()
async def skip(ctx):
"""Skip the current track."""
player = ctx.voice_client
if not player or not player.playing:
return await ctx.send("Nothing is playing!")
skipped_track = await player.skip()
await ctx.send(f"Skipped: {skipped_track.title}")
@bot.command()
async def seek(ctx, seconds: int):
"""Seek to a position in the current track."""
player = ctx.voice_client
if not player or not player.current:
return await ctx.send("Nothing is playing!")
if not player.current.is_seekable:
return await ctx.send("This track is not seekable!")
position = seconds * 1000 # Convert to milliseconds
await player.seek(position)
await ctx.send(f"Seeked to {seconds} seconds")@bot.command()
async def volume(ctx, level: int = None):
"""Get or set the player volume."""
player = ctx.voice_client
if not player:
return await ctx.send("Not connected to a voice channel!")
if level is None:
await ctx.send(f"Current volume: {player.volume}%")
else:
if not 0 <= level <= 100:
return await ctx.send("Volume must be between 0 and 100!")
await player.set_volume(level)
await ctx.send(f"Volume set to {level}%")@bot.command()
async def autoplay(ctx, mode: str = None):
"""Configure AutoPlay mode."""
player = ctx.voice_client
if not player:
return await ctx.send("Not connected to a voice channel!")
if mode is None:
current = player.autoplay.name
await ctx.send(f"AutoPlay mode: {current}")
return
mode_map = {
'enabled': wavelink.AutoPlayMode.enabled,
'partial': wavelink.AutoPlayMode.partial,
'disabled': wavelink.AutoPlayMode.disabled
}
if mode.lower() not in mode_map:
return await ctx.send("Valid modes: enabled, partial, disabled")
player.autoplay = mode_map[mode.lower()]
await ctx.send(f"AutoPlay mode set to: {mode}")@bot.command()
async def now_playing(ctx):
"""Show information about the current track."""
player = ctx.voice_client
if not player or not player.current:
return await ctx.send("Nothing is playing!")
track = player.current
embed = discord.Embed(
title="Now Playing",
description=f"**{track.title}**\nby {track.author}",
color=discord.Color.blue()
)
embed.add_field(
name="Duration",
value=f"{track.length // 60000}:{(track.length // 1000) % 60:02d}",
inline=True
)
embed.add_field(
name="Position",
value=f"{player.position // 60000}:{(player.position // 1000) % 60:02d}",
inline=True
)
embed.add_field(name="Volume", value=f"{player.volume}%", inline=True)
embed.add_field(name="Source", value=track.source.name, inline=True)
embed.add_field(name="Paused", value=player.paused, inline=True)
embed.add_field(name="Queue Size", value=player.queue.count, inline=True)
if track.artwork:
embed.set_thumbnail(url=track.artwork)
await ctx.send(embed=embed)Install with Tessl CLI
npx tessl i tessl/pypi-wavelink