Skip to main content

Playback Control

Control audio playback with play, pause, skip, volume, and seeking functions.

Basic Playback Controls

Play/Pause

await player.play()

await player.pause(False)

await player.pause(True)

await player.resume()

is_paused = player.paused
if is_paused:
    await player.resume()
else:
    await player.pause()

Stop & Skip

await player.stop()

await player.skip()

Volume Control

await player.setVolume(50)

await player.setVolume(100)

await player.setVolume(150)

print(f'Current volume: {player.volume}')

Seeking

await player.seek(60000)

await player.seek(120000)

async def seek_to_percentage(player, percentage: float):
    track = player.currentTrackObj
    if track and hasattr(track, 'duration'):
        position = int((track.duration * percentage) / 100)
        await player.seek(position)
        print(f'Seeked to {percentage}%')

await seek_to_percentage(player, 50)

Queue Management

Queue Operations

player.queue.add(track)

for track in tracks:
    player.queue.add(track)

player.addToQueue(track)

player.queue.remove(2)

player.queue.clear()

player.queue.shuffle()

print(f'Queue size: {len(player.queue)}')
print(f'Current track: {player.queue.current.title if player.queue.current else None}')

Loop Modes

from salada.enums import LoopMode

player.queue.setLoop(None)

player.queue.setLoop(LoopMode.TRACK)

player.queue.setLoop(LoopMode.QUEUE)

print(f'Loop mode: {player.queue.loop}')

Player State

Check Player Status

print(f'Playing: {player.playing}')
print(f'Paused: {player.paused}')
print(f'Connected: {player.connected}')
print(f'Destroyed: {player.destroyed}')
print(f'Position: {player.position}ms')
print(f'Volume: {player.volume}%')

Current Track Information

if player.currentTrackObj:
    track = player.currentTrackObj
    print(f'Title: {track.title}')
    print(f'Author: {track.author}')
    print(f'Duration: {track.duration}ms')
    print(f'Position: {player.position}ms')
    
    if hasattr(track, 'uri'):
        print(f'URL: {track.uri}')

Complete Playback 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 interaction.user.voice:
        await interaction.followup.send('❌ Join a voice channel first!')
        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 bot.salad.resolve(query, requester=interaction.user)
    tracks = result.get('tracks', [])
    
    if not tracks:
        await interaction.followup.send('❌ No tracks found!')
        return
    
    track = tracks[0]
    player.addToQueue(track)
    
    if not player.playing:
        await player.play()
        await interaction.followup.send(f'▶️ Now playing: **{track.title}**')
    else:
        await interaction.followup.send(f'➕ Added to queue: **{track.title}**')

@bot.tree.command(name='pause')
async def pause_command(interaction: discord.Interaction):
    await interaction.response.defer()
    
    player = bot.salad.getPlayer(interaction.guild.id)
    if not player:
        await interaction.followup.send('❌ No player found!')
        return
    
    if player.paused:
        await player.resume()
        await interaction.followup.send('▶️ Resumed!')
    else:
        await player.pause()
        await interaction.followup.send('⏸️ Paused!')

@bot.tree.command(name='skip')
async def skip_command(interaction: discord.Interaction):
    await interaction.response.defer()
    
    player = bot.salad.getPlayer(interaction.guild.id)
    if not player:
        await interaction.followup.send('❌ No player found!')
        return
    
    await player.skip()
    await interaction.followup.send('⏭️ Skipped!')

@bot.tree.command(name='stop')
async def stop_command(interaction: discord.Interaction):
    await interaction.response.defer()
    
    player = bot.salad.getPlayer(interaction.guild.id)
    if not player:
        await interaction.followup.send('❌ No player found!')
        return
    
    await player.stop()
    await interaction.followup.send('⏹️ Stopped and cleared queue!')

@bot.tree.command(name='volume')
@app_commands.describe(level='Volume level (0-1000)')
async def volume_command(interaction: discord.Interaction, level: int):
    await interaction.response.defer()
    
    if level < 0 or level > 1000:
        await interaction.followup.send('❌ Volume must be between 0 and 1000!')
        return
    
    player = bot.salad.getPlayer(interaction.guild.id)
    if not player:
        await interaction.followup.send('❌ No player found!')
        return
    
    await player.setVolume(level)
    await interaction.followup.send(f'🔊 Volume set to {level}%')

@bot.tree.command(name='seek')
@app_commands.describe(position='Position in seconds')
async def seek_command(interaction: discord.Interaction, position: int):
    await interaction.response.defer()
    
    player = bot.salad.getPlayer(interaction.guild.id)
    if not player or not player.playing:
        await interaction.followup.send('❌ Nothing is playing!')
        return
    
    position_ms = position * 1000
    await player.seek(position_ms)
    await interaction.followup.send(f'⏩ Seeked to {position} seconds!')

Queue Commands

@bot.tree.command(name='queue')
async def queue_command(interaction: discord.Interaction):
    await interaction.response.defer()
    
    player = bot.salad.getPlayer(interaction.guild.id)
    if not player:
        await interaction.followup.send('❌ No player found!')
        return
    
    if len(player.queue) == 0:
        await interaction.followup.send('📭 Queue is empty!')
        return
    
    queue_list = []
    for i, track in enumerate(player.queue.getAll()[:10]):
        queue_list.append(f'{i+1}. {track.title}')
    
    embed = discord.Embed(
        title='📃 Queue',
        description='\n'.join(queue_list),
        color=discord.Color.blue()
    )
    
    if len(player.queue) > 10:
        embed.set_footer(text=f'And {len(player.queue) - 10} more...')
    
    await interaction.followup.send(embed=embed)

@bot.tree.command(name='nowplaying')
async def nowplaying_command(interaction: discord.Interaction):
    await interaction.response.defer()
    
    player = bot.salad.getPlayer(interaction.guild.id)
    if not player or not player.currentTrackObj:
        await interaction.followup.send('❌ Nothing is playing!')
        return
    
    track = player.currentTrackObj
    duration = track.duration if hasattr(track, 'duration') else 0
    position = player.position
    
    duration_str = f'{duration // 60000}:{(duration // 1000) % 60:02d}'
    position_str = f'{position // 60000}:{(position // 1000) % 60:02d}'
    
    embed = discord.Embed(
        title='🎵 Now Playing',
        description=f'**{track.title}**\nBy {track.author}',
        color=discord.Color.green()
    )
    embed.add_field(name='Progress', value=f'{position_str} / {duration_str}')
    embed.add_field(name='Volume', value=f'{player.volume}%')
    
    if player.paused:
        embed.add_field(name='Status', value='⏸️ Paused')
    
    await interaction.followup.send(embed=embed)

@bot.tree.command(name='loop')
@app_commands.describe(mode='Loop mode: none, track, or queue')
async def loop_command(interaction: discord.Interaction, mode: str):
    await interaction.response.defer()
    
    player = bot.salad.getPlayer(interaction.guild.id)
    if not player:
        await interaction.followup.send('❌ No player found!')
        return
    
    from salada.enums import LoopMode
    
    if mode.lower() == 'none':
        player.queue.setLoop(None)
        await interaction.followup.send('🔁 Loop disabled')
    elif mode.lower() == 'track':
        player.queue.setLoop(LoopMode.TRACK)
        await interaction.followup.send('🔂 Looping current track')
    elif mode.lower() == 'queue':
        player.queue.setLoop(LoopMode.QUEUE)
        await interaction.followup.send('🔁 Looping queue')
    else:
        await interaction.followup.send('❌ Invalid mode! Use: none, track, or queue')

@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 player found!')
        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 player found!')
        return
    
    player.queue.clear()
    await interaction.followup.send('🗑️ Queue cleared!')

Advanced Usage

Auto-disconnect on Queue End

@bot.salad.on('queueEnd')
async def on_queue_end(player):
    channel = bot.get_channel(player.textChannel)
    if channel:
        await channel.send('📭 Queue ended. Disconnecting in 5 minutes...')
    
    await asyncio.sleep(300)
    
    if len(player.queue) == 0 and not player.playing:
        await player.destroy()
        
        guild = bot.get_guild(player.guildId)
        if guild and guild.voice_client:
            await guild.voice_client.disconnect()

Progress Bar

def create_progress_bar(position: int, duration: int, length: int = 20) -> str:
    if duration == 0:
        return '─' * length
    
    filled = int((position / duration) * length)
    bar = '━' * filled + '○' + '─' * (length - filled - 1)
    return bar

@bot.tree.command(name='progress')
async def progress_command(interaction: discord.Interaction):
    await interaction.response.defer()
    
    player = bot.salad.getPlayer(interaction.guild.id)
    if not player or not player.currentTrackObj:
        await interaction.followup.send('❌ Nothing is playing!')
        return
    
    track = player.currentTrackObj
    duration = track.duration if hasattr(track, 'duration') else 0
    position = player.position
    
    bar = create_progress_bar(position, duration)
    
    duration_str = f'{duration // 60000}:{(duration // 1000) % 60:02d}'
    position_str = f'{position // 60000}:{(position // 1000) % 60:02d}'
    
    await interaction.followup.send(f'`{position_str}` {bar} `{duration_str}`')
All playback control methods are asynchronous and should be awaited. The player automatically handles queue management and track progression.
Always check if a player exists before attempting to control it. Use bot.salad.getPlayer(guild_id) to retrieve the player instance.