> ## Documentation Index
> Fetch the complete documentation index at: https://salad-ffed6391.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Playing Tracks

> Learn how to search for and play tracks with Salada

## 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

```python theme={null}
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 the `resolve()` method.

### Basic Search

```python theme={null}
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

```python theme={null}
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

```python theme={null}
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

```python theme={null}
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

```python theme={null}
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

```python theme={null}
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

```python theme={null}
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

```python theme={null}
player.addToQueue(track)

for track in tracks:
    player.addToQueue(track)

player.queue.add(track)

player.queue.insert(track, 0)
```

### Queue Information

```python theme={null}
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

```python theme={null}
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

```python theme={null}
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

```python theme={null}
@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

```python theme={null}
@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

```python theme={null}
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

```python theme={null}
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

```python theme={null}
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

```python theme={null}
@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

```python theme={null}
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')
```

<Warning>
  Always validate user permissions and bot permissions before attempting to create connections or play tracks. This prevents common errors and improves user experience.
</Warning>

<Tip>
  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.
</Tip>

```python theme={null}
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}'
```
