Playing Tracks
This section demonstrates how to search for and play tracks using Salada. It covers the entire process from handling a user command to getting music playing in a voice channel.Complete Play Command Example
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 to play music!')
return
voice_channel = interaction.user.voice.channel
permissions = voice_channel.permissions_for(interaction.guild.me)
if not permissions.connect or not permissions.speak:
await interaction.followup.send('❌ I need Connect and Speak permissions in your voice channel!')
return
try:
player = bot.salad.getPlayer(interaction.guild.id)
if not player:
player = await bot.salad.createConnection({
'guildId': interaction.guild.id,
'voiceChannel': voice_channel.id,
'textChannel': interaction.channel.id
})
await voice_channel.connect()
await player.setVolume(65)
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 for your query.')
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(
title='📋 Playlist Added to Queue',
description=f'**{playlist_name}**',
color=discord.Color.green()
)
embed.add_field(name='Tracks', value=str(len(tracks)), inline=True)
embed.add_field(name='Requested by', value=interaction.user.display_name, inline=True)
await interaction.followup.send(embed=embed)
elif load_type in ('search', 'track'):
track = result['tracks'][0]
player.addToQueue(track)
embed = discord.Embed(
title='🎵 Track Added to Queue',
description=f'**{track.title}**\nBy {track.author}',
color=discord.Color.green()
)
embed.set_thumbnail(url=track.thumbnail)
embed.add_field(name='Duration', value=format_duration(track.duration), inline=True)
embed.add_field(name='Position in Queue', value=str(len(player.queue) + 1), inline=True)
embed.add_field(name='Requested by', value=interaction.user.display_name, inline=True)
await interaction.followup.send(embed=embed)
if not player.playing and not player.paused:
await player.play()
except Exception as error:
print(f'Play command error: {error}')
await interaction.followup.send('❌ An error occurred while processing your request.')
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}'
Search Methods
Salada supports multiple ways to search for tracks through theresolve() method.
Basic Search
result = await bot.salad.resolve('never gonna give you up', requester=interaction.user)
if result.get('tracks'):
track = result['tracks'][0]
player.addToQueue(track)
await player.play()
Platform-Specific Search
yt_result = await bot.salad.resolve('ytsearch:despacito', requester=interaction.user)
sc_result = await bot.salad.resolve('scsearch:lo-fi hip hop', requester=interaction.user)
sp_result = await bot.salad.resolve('spsearch:imagine dragons', requester=interaction.user)
url_result = await bot.salad.resolve('https://www.youtube.com/watch?v=dQw4w9WgXcQ', requester=interaction.user)
Advanced Search with Load Types
result = await bot.salad.resolve(query, requester=interaction.user)
load_type = result.get('loadType')
if load_type == 'error':
await interaction.reply('❌ Failed to load track!')
return
if load_type == 'empty':
await interaction.reply('❌ No tracks found!')
return
if load_type in ('track', 'search'):
track = result['tracks'][0]
player.addToQueue(track)
await player.play()
if load_type == 'playlist':
tracks = result.get('tracks', [])
for track in tracks:
player.addToQueue(track)
await interaction.reply(f'✅ Added {len(tracks)} tracks from playlist!')
await player.play()
Track Information
Accessing Track Properties
current_track = player.currentTrackObj
if current_track:
print('Title:', current_track.title)
print('Author:', current_track.author)
print('Duration:', current_track.duration)
print('URL:', current_track.uri)
print('Thumbnail:', current_track.thumbnail)
print('Encoded:', current_track.track)
print('Requester:', current_track.requester)
Creating Track Display
def create_track_embed(track, position: int = 1):
embed = discord.Embed(
title='🎵 Now Playing',
description=f'**{track.title}**',
color=0x1db954
)
embed.set_thumbnail(url=track.thumbnail)
embed.add_field(name='Artist', value=track.author, inline=True)
embed.add_field(name='Duration', value=format_duration(track.duration), inline=True)
embed.add_field(name='Requested by', value=track.requester.display_name, inline=True)
embed.add_field(name='Position', value=f'{position} of {len(player.queue) + 1}', inline=True)
embed.set_footer(text='Use /skip to skip this track')
return embed
Playlist Handling
Loading Playlists
async def handle_playlist_load(interaction, query):
result = await bot.salad.resolve(query, requester=interaction.user)
if result.get('loadType') == 'playlist':
playlist_info = result.get('playlistInfo', {})
tracks = result.get('tracks', [])
for track in tracks:
player.addToQueue(track)
total_duration = sum(track.duration for track in tracks if hasattr(track, 'duration'))
embed = discord.Embed(
title='📋 Playlist Added',
description=f'**{playlist_info.get("name", "Unknown")}**',
color=0x9932cc
)
embed.add_field(name='Tracks', value=str(len(tracks)), inline=True)
embed.add_field(name='Duration', value=format_duration(total_duration), inline=True)
embed.add_field(name='Added by', value=interaction.user.display_name, inline=True)
await interaction.followup.send(embed=embed)
if not player.playing and not player.paused:
await player.play()
Playlist Information
if result.get('playlistInfo'):
playlist = result['playlistInfo']
print('Playlist Name:', playlist.get('name'))
print('Track Count:', len(result.get('tracks', [])))
print('Selected Track:', playlist.get('selectedTrack', -1))
Queue Management
Adding Tracks to Queue
player.addToQueue(track)
for track in tracks:
player.addToQueue(track)
player.queue.add(track)
player.queue.insert(track, 0)
Queue Information
print('Queue length:', len(player.queue))
print('Current track:', player.currentTrackObj)
print('Is playing:', player.playing)
print('Is paused:', player.paused)
all_tracks = player.queue.getAll()
player.queue.clear()
player.queue.remove(0)
player.queue.shuffle()
Player Controls
Basic Playback Controls
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
Player State Checking
if player.playing:
print('Currently playing')
if player.paused:
print('Playback is paused')
if player.connected:
print('Connected to voice')
if player.destroyed:
print('Player has been destroyed')
Event Handling
Track Events
@bot.salad.event()
async def on_track_start(player, track):
channel = bot.get_channel(player.textChannel)
if channel:
embed = create_track_embed(track)
await channel.send(embed=embed)
@bot.salad.event()
async def on_track_end(player, track, reason):
if reason == 'finished':
if len(player.queue) > 0:
await player.play()
else:
await player.destroy()
@bot.salad.event()
async def on_track_exception(player, track, exception):
print(f'Track error: {track.title} - {exception}')
if len(player.queue) > 0:
await player.skip()
@bot.salad.event()
async def on_track_stuck(player, track, threshold):
print(f'Track stuck: {track.title}')
await player.skip()
Player Events
@bot.salad.event()
async def on_player_connect(player):
print(f'Player connected in guild {player.guildId}')
@bot.salad.event()
async def on_player_destroy(player, reason):
print(f'Player destroyed in guild {player.guildId}')
@bot.salad.event()
async def on_player_move(player, old_channel, new_channel):
print(f'Player moved from {old_channel} to {new_channel}')
@bot.salad.event()
async def on_queue_end(player):
print('Queue has ended')
await player.destroy()
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:
try:
result = await bot.salad.resolve(
attachment.url,
requester=message.author
)
if result.get('tracks'):
track = result['tracks'][0]
track.title = attachment.filename.rsplit('.', 1)[0]
track.author = 'Local File'
track.uri = attachment.url
player.addToQueue(track)
file_size_mb = attachment.size / (1024 * 1024)
await message.channel.send(
f'✅ Added local file: {attachment.filename} ({file_size_mb:.2f} MB)'
)
if not player.playing:
await player.play()
return True
except Exception as error:
print(f'Local file processing error: {error}')
return False
return False
Error Handling
Comprehensive Error Handling
async def play_track_safe(interaction, query):
try:
if not interaction.user.voice:
await interaction.followup.send('❌ You need to be in a voice channel!')
return
player = bot.salad.getPlayer(interaction.guild.id)
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()
result = await asyncio.wait_for(
bot.salad.resolve(query, requester=interaction.user),
timeout=10.0
)
if not result or not result.get('tracks'):
await interaction.followup.send('❌ No tracks found for your search.')
return
track = result['tracks'][0]
player.addToQueue(track)
await interaction.followup.send(f'✅ Added **{track.title}** to the queue!')
if not player.playing and not player.paused:
await player.play()
except asyncio.TimeoutError:
await interaction.followup.send('⏱️ Search timed out, please try again.')
except Exception as error:
print(f'Play command error: {error}')
await interaction.followup.send('❌ An error occurred while processing your request.')
Permission Validation
def validate_voice_permissions(voice_channel, bot_member):
permissions = voice_channel.permissions_for(bot_member)
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
Advanced Features
Auto-Play with Queue Management
@bot.salad.event()
async def on_track_end(player, track, reason):
if reason == 'finished':
if len(player.queue) > 0:
await player.play()
else:
await asyncio.sleep(300)
if len(player.queue) == 0 and not player.playing:
await player.destroy()
@bot.salad.event()
async def on_track_exception(player, track, exception):
print(f'Track failed: {track.title} - {exception}')
if len(player.queue) > 0:
await player.skip()
else:
channel = bot.get_channel(player.textChannel)
if channel:
await channel.send('❌ No more tracks to play!')
@bot.salad.event()
async def on_track_stuck(player, track, threshold):
print(f'Track stuck: {track.title}')
await player.skip()
Position Tracking
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')
Always validate user permissions and bot permissions before attempting to create connections or play tracks. This prevents common errors and improves user experience.
Use embeds to provide rich information about tracks and playlists. Users appreciate seeing thumbnails, duration, and other metadata when tracks are added to the queue.
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 to play music!')
return
voice_channel = interaction.user.voice.channel
permissions = voice_channel.permissions_for(interaction.guild.me)
if not permissions.connect or not permissions.speak:
await interaction.followup.send('❌ I need Connect and Speak permissions in your voice channel!')
return
try:
player = bot.salad.getPlayer(interaction.guild.id)
if not player:
player = await bot.salad.createConnection({
'guildId': interaction.guild.id,
'voiceChannel': voice_channel.id,
'textChannel': interaction.channel.id
})
await voice_channel.connect()
await player.setVolume(65)
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 for your query.')
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(
title='📋 Playlist Added to Queue',
description=f'**{playlist_name}**',
color=discord.Color.green()
)
embed.add_field(name='Tracks', value=str(len(tracks)), inline=True)
embed.add_field(name='Requested by', value=interaction.user.display_name, inline=True)
await interaction.followup.send(embed=embed)
elif load_type in ('search', 'track'):
track = result['tracks'][0]
player.addToQueue(track)
embed = discord.Embed(
title='🎵 Track Added to Queue',
description=f'**{track.title}**\nBy {track.author}',
color=discord.Color.green()
)
embed.set_thumbnail(url=track.thumbnail)
embed.add_field(name='Duration', value=format_duration(track.duration), inline=True)
embed.add_field(name='Position in Queue', value=str(len(player.queue) + 1), inline=True)
embed.add_field(name='Requested by', value=interaction.user.display_name, inline=True)
await interaction.followup.send(embed=embed)
if not player.playing and not player.paused:
await player.play()
except Exception as error:
print(f'Play command error: {error}')
await interaction.followup.send('❌ An error occurred while processing your request.')
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}'