Voice and audio
Play audio in a server voice channel with the same room and moderation controls as people.
Before you join
- Approve View Channel, Connect and Speak on the application and its server installation.
- Allow those permissions in the target voice channel. Existing installs must approve newly requested voice permissions.
- Install FFmpeg with libopus and SRTP support on the machine running the bot.
- Use a trusted local audio file or a direct HTTP(S) audio URL. A video website page is not a direct audio source.
Voice is send-only and self-deafened. Bots cannot receive or record the room, join DM calls or share screens through this API. Text bots do not need FFmpeg.
Join the person using /play
Interactions include voiceChannelId in JavaScript/TypeScript and voice_channel_id in Python. Pass that ID to joinVoice / join_voice to follow the caller instead of configuring a fixed room. The ID is a delivery-time snapshot, limited to the same server and a connected room visible to both caller and bot. A disconnected user, DM call, different server or inaccessible room returns null / None.
This handler uses the bot client from Your first bot. Keep the audio source in your environment; do not accept arbitrary file paths or URLs from chat. Add play to the full command list you register.
The field requires the updated API and SDK 0.2.3. It does not grant permission: Connect and Speak are still checked when joining. The packaged voice.js / music.py examples include startup and shutdown.
const source = process.env.RISCORD_AUDIO_SOURCE;
if (!source) throw new Error('Set RISCORD_AUDIO_SOURCE');
bot.command('play', async interaction => {
if (!interaction.voiceChannelId) {
return interaction.reply('Join a voice channel I can see in this server first.', { private: true });
}
await interaction.defer({ private: true });
const voice = await bot.joinVoice(interaction.voiceChannelId);
await interaction.reply('Playing in your voice channel.');
voice.play(source).catch(bot.onError);
});
await bot.registerCommands([
{ name: 'play', description: 'Play the configured audio in your voice channel', contexts: ['server'] },
]);Join, play and leave
This standalone example plays one source and exits. Set RISCORD_VOICE_CHANNEL_ID and RISCORD_AUDIO_SOURCE alongside your token. JavaScript waits for ready before joining; the guard prevents a reconnect from replaying the same track.
import { Client } from '@riscord/bot';
const bot = new Client({
token: process.env.RISCORD_BOT_TOKEN,
ffmpegPath: process.env.FFMPEG_PATH || 'ffmpeg',
});
let started = false;
bot.on('ready', async () => {
if (started) return;
started = true;
try {
const voice = await bot.joinVoice(process.env.RISCORD_VOICE_CHANNEL_ID);
try { await voice.play(process.env.RISCORD_AUDIO_SOURCE); }
finally { await voice.leave(); }
} finally { bot.close(); }
});
process.once('SIGINT', () => bot.close());
process.once('SIGTERM', () => bot.close());
await bot.connect();Keep playback out of command handlers
play waits for the track to finish or stop. After replying or deferring, start playback in a background promise or asyncio task so other gateway events can keep being handled. Catch failures and keep a reference to Python tasks so you can cancel them during shutdown.
stop ends the current track and keeps the bot in voice. leave also releases channel membership. A new play replaces the previous track; with SDK 0.4.2 and the updated API, a new join replaces only that server’s voice connection. Other servers keep their own connection and independent playback. Keep one VoiceConnection per server in your command state; closing the client or losing its gateway releases all of them. Older APIs keep the single-connection limit.
The SDK renews a short server lease and closes its encoder when ownership is lost. With SDK0.4.1 and the updated API, authorized moves keep the connection and the current play task alive while rebuilding audio for the destination. Seekable tracks resume near their playback position; live or non-seekable sources depend on FFmpeg/source support. A moderator disconnect still ends the connection. Server mute and deafen keep audio paused. Network reconnects still require an explicit rejoin.
Do not accept arbitrary member-supplied paths or URLs. FFmpeg opens media on the bot’s own machine. Never log media encryption keys or private source URLs.
Choose an FFmpeg executable
JavaScript configures ffmpegPath in the Client constructor. Python configures ffmpeg on voice.play. There is no ffmpeg_path option on join_voice. Both default to ffmpeg on PATH. The SDK does not download or bundle an encoder.