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
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
- Slash Commands - Create voice control commands
- UI Components - Add playback control buttons
- Error Handling - Handle voice errors
