Queue Basics
Accessing the Queue
Every player instance has a built-in queue system that manages tracks automatically.player = bot.salad.getPlayer(interaction.guild.id)
print(f'Queue length: {len(player.queue)}')
print(f'Current track: {player.queue.current}')
print(f'Queue tracks: {player.queue.getAll()}')
Player Creation and Management
Before working with queues, you need to create a player connection:player = await bot.salad.createConnection({
'guildId': interaction.guild.id,
'voiceChannel': interaction.user.voice.channel.id,
'textChannel': interaction.channel.id
})
await player.setVolume(65)
await interaction.user.voice.channel.connect()
player = bot.salad.getPlayer(interaction.guild.id)
Adding Tracks to Queue
The proper way to add tracks is through the resolve system:result = await bot.salad.resolve(
'never gonna give you up',
requester=interaction.user
)
load_type = result.get('loadType')
if load_type in ('track', 'search'):
track = result['tracks'][0]
player.addToQueue(track)
if not player.playing and not player.paused:
await player.play()
Handling Different Load Types
result = await bot.salad.resolve(query, requester=interaction.user)
load_type = result.get('loadType')
if load_type == 'playlist':
tracks = result.get('tracks', [])
for track in tracks:
player.addToQueue(track)
playlist_info = result.get('playlistInfo', {})
playlist_name = playlist_info.get('name', 'Unknown Playlist')
print(f'Added {len(tracks)} tracks from {playlist_name}')
elif load_type in ('search', 'track'):
track = result['tracks'][0]
player.addToQueue(track)
if not player.playing and not player.paused:
await player.play()
Queue Position Tracking
Getting Queue Position
def get_queue_position(player):
is_currently_playing = player.playing and player.currentTrackObj
position = len(player.queue) + (2 if is_currently_playing else 1)
return position
def get_position_suffix(num: int) -> str:
if num == 1:
return '1st'
elif num == 2:
return '2nd'
elif num == 3:
return '3rd'
else:
return f'{num}th'
position = get_queue_position(player)
position_text = get_position_suffix(position)
print(f'Track added to {position_text} position in queue')
File Upload Support
Handling Local Audio Files
SUPPORTED_AUDIO_FORMATS = [
'.mp3', '.wav', '.flac', '.aac', '.ogg',
'.wma', '.m4a', '.opus', '.mp4', '.avi',
'.mov', '.webm', '.mkv'
]
async def handle_file_upload(message, player):
if message.attachments:
attachment = message.attachments[0]
file_name = attachment.filename.lower()
has_valid_extension = any(
file_name.endswith(ext) for ext in SUPPORTED_AUDIO_FORMATS
)
if has_valid_extension:
local_file_data = {
'url': attachment.url,
'name': attachment.filename,
'size': attachment.size
}
try:
result = await bot.salad.resolve(
attachment.url,
requester=message.author
)
if result.get('tracks'):
track = result['tracks'][0]
track.title = local_file_data['name'].rsplit('.', 1)[0]
track.author = 'Local File'
track.uri = local_file_data['url']
player.addToQueue(track)
file_size_mb = local_file_data['size'] / (1024 * 1024)
print(f'Added local file: {local_file_data["name"]} ({file_size_mb:.2f} MB)')
return True
except Exception as error:
print(f'Local file processing error: {error}')
return False
return False
Player Connection Management
Reconnection and Error Handling
async def ensure_player_connection(interaction, bot):
player = bot.salad.getPlayer(interaction.guild.id)
guild = bot.get_guild(interaction.guild.id)
bot_voice_channel = guild.voice_client.channel if guild.voice_client else None
if not player:
player = await bot.salad.createConnection({
'guildId': interaction.guild.id,
'voiceChannel': interaction.user.voice.channel.id,
'textChannel': interaction.channel.id
})
await interaction.user.voice.channel.connect()
await player.setVolume(65)
else:
if not bot_voice_channel or not player.connected:
try:
player.voiceChannel = interaction.user.voice.channel.id
player.textChannel = interaction.channel.id
await player.connect()
await player.setVolume(65)
except Exception as error:
print(f'Error reconnecting player: {error}')
try:
await player.destroy()
player = await bot.salad.createConnection({
'guildId': interaction.guild.id,
'voiceChannel': interaction.user.voice.channel.id,
'textChannel': interaction.channel.id
})
await interaction.user.voice.channel.connect()
await player.setVolume(65)
except Exception as recreation_error:
print(f'Error recreating player: {recreation_error}')
raise Exception('Failed to establish voice connection')
return player
Queue Display and Information
Advanced Queue Display
import discord
import math
def create_queue_embed(player, page: int = 1):
tracks_per_page = 10
start_index = (page - 1) * tracks_per_page
end_index = start_index + tracks_per_page
total_pages = math.ceil(len(player.queue) / tracks_per_page)
queue_tracks = player.queue.getAll()[start_index:end_index]
embed = discord.Embed(
title='📋 Music Queue',
description=create_queue_display(queue_tracks, start_index),
color=discord.Color.green()
)
if player.currentTrackObj:
current = player.currentTrackObj
embed.add_field(
name='🎵 Now Playing',
value=f'**{current.title}** by {current.author}',
inline=False
)
total_duration = sum(
track.duration for track in player.queue.getAll()
if hasattr(track, 'duration')
)
embed.add_field(
name='Queue Statistics',
value=f'**{len(player.queue)}** tracks • **{format_duration(total_duration)}** total',
inline=True
)
embed.set_footer(text=f'Page {page} of {total_pages} • Use /queue <page> to view other pages')
return embed
def create_queue_display(tracks, start_index: int) -> str:
lines = []
for index, track in enumerate(tracks):
position = start_index + index + 1
duration = format_duration(track.duration) if hasattr(track, 'duration') else '0:00'
requester = getattr(track, 'requester', None)
requester_name = requester.display_name if requester else 'Unknown'
uri = getattr(track, 'uri', '#')
line = (
f'**{position}.** [{track.title}]({uri})\n'
f'└ {track.author} • `{duration}` • Requested by {requester_name}'
)
lines.append(line)
return '\n\n'.join(lines)
Database Integration
Saving Default Settings
import asyncio
from motor.motor_asyncio import AsyncIOMotorClient
mongo_client = AsyncIOMotorClient('mongodb://localhost:27017')
db = mongo_client['music_bot']
settings_collection = db['guild_settings']
async def get_default_volume(guild_id: int) -> int:
try:
settings = await settings_collection.find_one({'guildId': guild_id})
return settings.get('defaultVolume', 65) if settings else 65
except Exception as error:
print(f'[MUSIC] Error fetching default volume: {error}')
return 65
async def get_default_source(guild_id: int) -> str:
try:
settings = await settings_collection.find_one({'guildId': guild_id})
return settings.get('source', 'ytsearch') if settings else 'ytsearch'
except Exception as error:
print(f'[MUSIC] Error fetching default source: {error}')
return 'ytsearch'
async def save_default_volume(guild_id: int, volume: int):
await settings_collection.update_one(
{'guildId': guild_id},
{'$set': {'defaultVolume': volume}},
upsert=True
)
Error Handling Best Practices
Comprehensive Error Handling
async def safe_player_operation(operation, error_message: str = "An error occurred"):
try:
return await operation()
except Exception as error:
print(f'[MUSIC] {error_message}: {error}')
error_str = str(error)
if 'timeout' in error_str.lower():
raise Exception('Search timed out. Please try again.')
if 'failed to resolve' in error_str.lower():
raise Exception('Could not find the requested track.')
raise Exception(error_message)
try:
result = await safe_player_operation(
lambda: bot.salad.resolve(query, requester=interaction.user),
'Failed to search for track'
)
except Exception as error:
await interaction.followup.send(f'❌ {str(error)}')
Message Management
Auto-Deleting Messages
import asyncio
async def send_temporary_message(channel, embed, delete_after: int = 8):
try:
msg = await channel.send(embed=embed)
await asyncio.sleep(delete_after)
await msg.delete()
return msg
except Exception as error:
print(f'Failed to send temporary message: {error}')
success_embed = discord.Embed(
description='✅ Track added to queue successfully',
color=discord.Color.green()
)
await send_temporary_message(interaction.channel, success_embed)
try:
await interaction.message.delete()
except:
pass
Player State Management
Basic Player Controls
if not player.playing and not player.paused:
await player.play()
await player.skip()
await player.stop()
await player.pause()
await player.resume()
await player.setVolume(75)
await player.seek(30000)
position = player.position
Utility Functions
Essential Helper Functions
def format_duration(ms: int) -> str:
if not ms or ms == 0:
return '0:00'
seconds = ms // 1000
minutes = seconds // 60
hours = minutes // 60
if hours > 0:
return f'{hours}:{(minutes % 60):02d}:{(seconds % 60):02d}'
return f'{minutes}:{(seconds % 60):02d}'
def is_url(string: str) -> bool:
return string.startswith('http') or string.startswith('www.')
def validate_voice_permissions(member, bot, voice_channel):
permissions = voice_channel.permissions_for(bot)
if not permissions.connect:
raise Exception('Missing permission to connect to voice channel')
if not permissions.speak:
raise Exception('Missing permission to speak in voice channel')
return True
Complete Play Command Pattern
from discord import app_commands
import discord
@bot.tree.command(name='play')
@app_commands.describe(query='Song name or URL')
async def play_command(interaction: discord.Interaction, query: str):
await interaction.response.defer()
if not bot.salad:
await interaction.followup.send('❌ Music system is not ready')
return
if not interaction.user.voice:
await interaction.followup.send('❌ You must be in a voice channel')
return
try:
player = await ensure_player_connection(interaction, bot)
result = await bot.salad.resolve(query, requester=interaction.user)
load_type = result.get('loadType')
if load_type == 'empty':
await interaction.followup.send('❌ No tracks found')
return
if load_type == 'error':
await interaction.followup.send('❌ An error occurred while searching')
return
if load_type == 'playlist':
tracks = result.get('tracks', [])
for track in tracks:
player.addToQueue(track)
playlist_info = result.get('playlistInfo', {})
playlist_name = playlist_info.get('name', 'Unknown Playlist')
embed = discord.Embed(
description=f'✅ Added **{len(tracks)}** tracks from **{playlist_name}**',
color=discord.Color.green()
)
await interaction.followup.send(embed=embed)
elif load_type in ('search', 'track'):
track = result['tracks'][0]
position = get_queue_position(player)
player.addToQueue(track)
embed = discord.Embed(
description=f'✅ Added **{track.title}** to queue position {get_position_suffix(position)}',
color=discord.Color.green()
)
await interaction.followup.send(embed=embed)
if not player.playing and not player.paused:
await player.play()
except Exception as error:
await interaction.followup.send(f'❌ Error: {str(error)}')
Advanced Queue Commands
Queue Command with Pagination
@bot.tree.command(name='queue')
@app_commands.describe(page='Page number to view')
async def queue_command(interaction: discord.Interaction, page: int = 1):
await interaction.response.defer()
player = bot.salad.getPlayer(interaction.guild.id)
if not player:
await interaction.followup.send('❌ No active player')
return
if len(player.queue) == 0:
await interaction.followup.send('📭 Queue is empty')
return
embed = create_queue_embed(player, page)
await interaction.followup.send(embed=embed)
@bot.tree.command(name='remove')
@app_commands.describe(position='Track position to remove')
async def remove_command(interaction: discord.Interaction, position: int):
await interaction.response.defer()
player = bot.salad.getPlayer(interaction.guild.id)
if not player:
await interaction.followup.send('❌ No active player')
return
if position < 1 or position > len(player.queue):
await interaction.followup.send('❌ Invalid position')
return
removed = player.queue.remove(position - 1)
if removed:
await interaction.followup.send(f'✅ Removed **{removed.title}** from queue')
else:
await interaction.followup.send('❌ Failed to remove track')
@bot.tree.command(name='shuffle')
async def shuffle_command(interaction: discord.Interaction):
await interaction.response.defer()
player = bot.salad.getPlayer(interaction.guild.id)
if not player:
await interaction.followup.send('❌ No active player')
return
if len(player.queue) == 0:
await interaction.followup.send('❌ Queue is empty')
return
player.queue.shuffle()
await interaction.followup.send('🔀 Queue shuffled!')
@bot.tree.command(name='clear')
async def clear_command(interaction: discord.Interaction):
await interaction.response.defer()
player = bot.salad.getPlayer(interaction.guild.id)
if not player:
await interaction.followup.send('❌ No active player')
return
player.queue.clear()
await interaction.followup.send('🗑️ Queue cleared!')
The queue automatically handles track progression and loop modes. You don’t need to manually manage track transitions.
Always check if a player exists before accessing its queue. A destroyed player will have a null queue.