Skip to main content

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.