Skip to main content
Pycord provides powerful voice capabilities to connect to voice channels, play audio, and even record audio from users.

Prerequisites

Installing Dependencies

Voice support requires additional dependencies:
# Install PyNaCl for voice support
pip install py-cord[voice]

# Or install manually
pip install PyNaCl

FFmpeg Requirement

FFmpeg is required for audio playback:
# Download from https://ffmpeg.org/download.html
# Add to PATH

Opus Library

Opus encoding is required for voice:
import discord

# Load opus (usually automatic)
if not discord.opus.is_loaded():
    discord.opus.load_opus('opus')  # or path to opus library

Connecting to Voice

Basic Connection

import discord
from discord.ext import commands

intents = discord.Intents.default()
intents.message_content = True

bot = commands.Bot(command_prefix="!", intents=intents)

@bot.command()
async def join(ctx: commands.Context, *, channel: discord.VoiceChannel = None):
    """Join a voice channel."""
    if channel is None:
        # Join the author's voice channel
        if ctx.author.voice:
            channel = ctx.author.voice.channel
        else:
            await ctx.send("You are not in a voice channel!")
            return
    
    if ctx.voice_client is not None:
        # Already connected, move to new channel
        await ctx.voice_client.move_to(channel)
    else:
        # Connect to voice channel
        await channel.connect()
    
    await ctx.send(f"Connected to {channel.name}")

Disconnecting

@bot.command()
async def leave(ctx: commands.Context):
    """Disconnect from voice."""
    if ctx.voice_client:
        await ctx.voice_client.disconnect()
        await ctx.send("Disconnected from voice")
    else:
        await ctx.send("Not connected to any voice channel")

Auto-join

async def ensure_voice(ctx: commands.Context):
    """Ensure bot is in a voice channel."""
    if ctx.voice_client is None:
        if ctx.author.voice:
            await ctx.author.voice.channel.connect()
        else:
            await ctx.send("You are not connected to a voice channel.")
            raise commands.CommandError("Author not connected to a voice channel.")

Playing Audio

Play Local File

@bot.command()
async def play(ctx: commands.Context, *, query: str):
    """Play audio from a local file."""
    if not ctx.voice_client:
        await ensure_voice(ctx)
    
    # Create audio source
    source = discord.PCMVolumeTransformer(
        discord.FFmpegPCMAudio(query)
    )
    
    # Play audio
    ctx.voice_client.play(
        source,
        after=lambda e: print(f"Player error: {e}") if e else None
    )
    
    await ctx.send(f"Now playing: {query}")

Play from URL

import asyncio

class YTDLSource(discord.PCMVolumeTransformer):
    def __init__(self, source: discord.AudioSource, *, data: dict, volume: float = 0.5):
        super().__init__(source, volume)
        self.data = data
        self.title = data.get("title")
        self.url = data.get("url")
    
    @classmethod
    async def from_url(cls, url, *, loop=None, stream=False):
        import youtube_dl
        
        loop = loop or asyncio.get_event_loop()
        ytdl = youtube_dl.YoutubeDL({
            "format": "bestaudio/best",
            "outtmpl": "%(extractor)s-%(id)s-%(title)s.%(ext)s",
            "restrictfilenames": True,
            "noplaylist": True,
            "nocheckcertificate": True,
            "ignoreerrors": False,
            "logtostderr": False,
            "quiet": True,
            "no_warnings": True,
            "default_search": "auto",
            "source_address": "0.0.0.0",
        })
        
        data = await loop.run_in_executor(
            None, lambda: ytdl.extract_info(url, download=not stream)
        )
        
        if "entries" in data:
            data = data["entries"][0]
        
        filename = data["url"] if stream else ytdl.prepare_filename(data)
        ffmpeg_options = {"options": "-vn"}
        return cls(discord.FFmpegPCMAudio(filename, **ffmpeg_options), data=data)

@bot.command()
async def yt(ctx: commands.Context, *, url: str):
    """Play audio from YouTube."""
    await ensure_voice(ctx)
    
    async with ctx.typing():
        player = await YTDLSource.from_url(url, loop=bot.loop)
        ctx.voice_client.play(
            player,
            after=lambda e: print(f"Player error: {e}") if e else None
        )
    
    await ctx.send(f"Now playing: {player.title}")

Streaming Audio

@bot.command()
async def stream(ctx: commands.Context, *, url: str):
    """Stream audio without downloading."""
    await ensure_voice(ctx)
    
    async with ctx.typing():
        player = await YTDLSource.from_url(url, loop=bot.loop, stream=True)
        ctx.voice_client.play(
            player,
            after=lambda e: print(f"Player error: {e}") if e else None
        )
    
    await ctx.send(f"Now streaming: {player.title}")

Playback Control

Volume Control

@bot.command()
async def volume(ctx: commands.Context, volume: int):
    """Change the player's volume (0-100)."""
    if ctx.voice_client is None:
        return await ctx.send("Not connected to a voice channel.")
    
    if not 0 <= volume <= 100:
        return await ctx.send("Volume must be between 0 and 100")
    
    ctx.voice_client.source.volume = volume / 100
    await ctx.send(f"Changed volume to {volume}%")

Pause/Resume

@bot.command()
async def pause(ctx: commands.Context):
    """Pause audio playback."""
    if ctx.voice_client and ctx.voice_client.is_playing():
        ctx.voice_client.pause()
        await ctx.send("⏸️ Paused")
    else:
        await ctx.send("Not playing anything")

@bot.command()
async def resume(ctx: commands.Context):
    """Resume audio playback."""
    if ctx.voice_client and ctx.voice_client.is_paused():
        ctx.voice_client.resume()
        await ctx.send("▶️ Resumed")
    else:
        await ctx.send("Not paused")

Stop Playback

@bot.command()
async def stop(ctx: commands.Context):
    """Stop playing audio."""
    if ctx.voice_client:
        ctx.voice_client.stop()
        await ctx.send("⏹️ Stopped")
    else:
        await ctx.send("Not connected to voice")

Check Playback Status

@bot.command()
async def status(ctx: commands.Context):
    """Check voice client status."""
    if not ctx.voice_client:
        await ctx.send("Not connected to voice")
        return
    
    vc = ctx.voice_client
    status = "🔊 Playing" if vc.is_playing() else "⏸️ Paused" if vc.is_paused() else "⏹️ Stopped"
    
    embed = discord.Embed(title="Voice Status", color=discord.Color.blue())
    embed.add_field(name="Status", value=status)
    embed.add_field(name="Channel", value=vc.channel.name)
    embed.add_field(name="Latency", value=f"{round(vc.latency * 1000)}ms")
    
    await ctx.send(embed=embed)

Recording Audio

Basic Recording

import discord.sinks

@bot.command()
async def record(ctx: commands.Context):
    """Start recording audio."""
    if not ctx.voice_client:
        await ensure_voice(ctx)
    
    ctx.voice_client.start_recording(
        discord.sinks.WaveSink(),
        once_done,
        ctx.channel,
    )
    await ctx.send("Started recording!")

async def once_done(sink: discord.sinks.WaveSink, channel: discord.TextChannel):
    """Called when recording stops."""
    recorded_users = [
        f"<@{user_id}>"
        for user_id, audio in sink.audio_data.items()
    ]
    
    await channel.send(
        f"Finished recording for {', '.join(recorded_users)}"
    )
    
    # Save individual audio files
    for user_id, audio in sink.audio_data.items():
        filename = f"{user_id}.{sink.encoding}"
        with open(filename, "wb") as f:
            f.write(audio.file.getvalue())

@bot.command()
async def stop_record(ctx: commands.Context):
    """Stop recording."""
    if ctx.voice_client and ctx.voice_client.recording:
        ctx.voice_client.stop_recording()
        await ctx.send("Stopped recording")
    else:
        await ctx.send("Not recording")

Advanced Features

Wait for Song to Finish

@bot.command()
async def play_and_wait(ctx: commands.Context, file: str):
    """Play audio and wait for it to finish."""
    await ensure_voice(ctx)
    
    source = discord.FFmpegPCMAudio(file)
    future = ctx.voice_client.play(source, wait_finish=True)
    
    await ctx.send(f"Playing {file}...")
    
    # Wait for playback to finish
    error = await future
    if error:
        await ctx.send(f"Error: {error}")
    else:
        await ctx.send("Finished playing!")

Audio Filters

@bot.command()
async def nightcore(ctx: commands.Context, file: str):
    """Play audio with nightcore effect."""
    await ensure_voice(ctx)
    
    # Apply audio filter using FFmpeg
    source = discord.FFmpegPCMAudio(
        file,
        options="-af atempo=1.25,asetrate=48000*1.25"
    )
    
    ctx.voice_client.play(source)
    await ctx.send("Playing with nightcore effect!")

Custom Audio Source

class CustomAudioSource(discord.AudioSource):
    def __init__(self):
        # Initialize your audio source
        pass
    
    def read(self) -> bytes:
        # Return 20ms of audio data (3840 bytes for 48kHz stereo)
        # Return empty bytes when done
        return b"\x00" * 3840
    
    def is_opus(self) -> bool:
        return False  # Return True if providing Opus-encoded audio
    
    def cleanup(self):
        # Clean up resources
        pass

Voice Client Properties

Connection Info

vc = ctx.voice_client

# Channel
channel = vc.channel

# Guild
guild = vc.guild

# Latency
latency = vc.latency
average_latency = vc.average_latency

# Connection status
is_connected = vc.is_connected()
is_playing = vc.is_playing()
is_paused = vc.is_paused()

Best Practices

1
Check permissions
2
Ensure the bot has CONNECT and SPEAK permissions in the voice channel.
3
Handle errors
4
Always use error handlers for playback errors.
5
Clean up resources
6
Disconnect from voice when done to free resources.
7
Use volume control
8
Set appropriate volume levels (default 0.5 is often too loud).
9
Stream long audio
10
Use streaming for long audio files to save memory.
Common Issues:
  • PyNaCl not installed: pip install PyNaCl
  • FFmpeg not found: Install FFmpeg and add to PATH
  • Opus not loaded: Ensure opus library is available
  • Audio stuttering: Check CPU usage and network latency
  • “Already playing audio” error: Stop current audio before playing new

Troubleshooting

Check Voice Dependencies

import discord

print(f"discord.py version: {discord.__version__}")
print(f"Voice support: {discord.voice_client.has_nacl}")
print(f"Opus loaded: {discord.opus.is_loaded()}")

Debug Voice Connection

@bot.command()
async def debug_voice(ctx: commands.Context):
    if not ctx.voice_client:
        await ctx.send("Not connected to voice")
        return
    
    vc = ctx.voice_client
    info = f"""
    **Voice Debug Info**
    Channel: {vc.channel.name}
    Latency: {round(vc.latency * 1000)}ms
    Connected: {vc.is_connected()}
    Playing: {vc.is_playing()}
    Paused: {vc.is_paused()}
    """
    await ctx.send(info)

See Also

Build docs developers (and LLMs) love