Using @remedia/client
Create a client, add embedded or remote nodes, join a Discord voice channel, play audio, and clean up.
Use @remedia/client to control embedded engines and remote servers through the same Discord session API. Start with one node, then add a pool when you need to distribute guilds. Node.js 22.18 or newer is required.
For installation from scratch, follow the embedded or remote quickstart.
Install
npm install @remedia/clientRemote nodes need nothing else on the bot machine. An embedded node also needs @remedia/node and
FFmpeg 9:
npm install @remedia/nodeCreate a client
A client needs a list of nodes, a function that sends Discord Gateway payloads, and your bot's user ID. Add whichever nodes you use:
import { RemediaClient } from '@remedia/client';
const remedia = await RemediaClient.create({
botUserId: discord.user.id,
nodes: [
// Runs the engine inside this process.
{ type: 'embedded', id: 'local' },
// Runs on a remedia-server process.
{
type: 'remote',
id: 'media-1',
url: 'remedia://127.0.0.1:8787',
auth: { username: 'media-bot', password: process.env.REMEDIA_RPC_PASSWORD! },
},
],
send(guildId, payload) {
discord.guilds.cache.get(guildId)?.shard.send(payload);
},
});
discord.on('raw', (packet) => remedia.receive(packet));You keep your own Discord Gateway connection. Remedia calls send to ask Discord for a voice
connection and expects every raw Gateway packet through remedia.receive(). botUserId is required
when any node is remote. It is sent to the server so several bots can share one server account.
Use remedia:// for ws:// and remedias:// for wss://. With more than one node, see
Nodes and routing for how guilds are assigned.
Join a channel and play
const voice = remedia.createDiscordSession({
guildId: '123456789012345678',
channelId: '234567890123456789',
userId: discord.user.id,
});
await voice.connect();
const info = await voice.play({ url: 'https://media.example.com/audio.mp3' });
console.log(info.codec, info.durationMs);
console.log(await voice.wait()); // 'completed', 'stopped' or 'failed'connect()resolves once the voice connection is ready, and rejects on timeout or failure.play()resolves with stream details as soon as playback starts. It rejects if the source cannot be opened.wait()resolves when the current track reaches a terminal state.- Snowflakes (guild, channel, user IDs) are decimal strings.
Sources can be a URL, a file path, or (on embedded nodes) a Buffer or Readable. See
Media sources and probing.
Control playback
await voice.pause();
await voice.resume();
await voice.seek(30_000); // milliseconds into the source
await voice.setVolume(0.8); // 0 to 2, default 1
await voice.filters.set('bass=gain=6'); // FFmpeg audio filtergraph
await voice.stop(); // stop the track, stay in the channelstate, position and durationMs are updated from events. Call await voice.refresh() for a fresh
snapshot. The position is where the engine is in the source, not proof of what listeners have heard.
Close resources
await voice.disconnect(); // leave the channel; the session can connect again
await voice.dispose(); // leave and release the session
await remedia.close(); // dispose all sessions and close all nodesCall remedia.close() when your process shuts down. A client created without calling close()
keeps WebSocket connections and native workers alive.
Handling errors
Failures are typed. The ones you will meet most often:
| Error | Meaning |
|---|---|
RemediaRpcError | A remote node rejected a command. Check error.code, for example invalid_request or plugin_host_unavailable. |
NodeUnavailableError | The node is not connected. The client reconnects in the background. |
NodeSelectionError | No suitable node could be chosen, or a pinned node is full or unhealthy. |
UnsupportedMediaSourceError | The node cannot accept that source type, for example a Buffer on a remote node. |
SessionLostError | The server no longer has your session. See Session lifecycle. |
Messages never include passwords, tokens, or media URLs from other users.
Where to go next
- Nodes and routing: several nodes, guild affinity, migration.
- Session lifecycle and recovery: reconnection, channel moves, voice server changes.
- Events and hooks: react to track endings, attach your own metadata.
- Media sources and probing, Crossfading.
- Executing plugin operations from your application.
@remedia/clientreference: options, members, and errors.