Remedia

Embedded quickstart

Install FFmpeg 9 and @remedia/node, then play audio into a Discord voice channel from your Node.js bot.

Run the engine inside your bot's Node.js process. You need Node.js 22.18 or newer, a Discord bot token, and permission to join and speak in a test voice channel.

1. Install FFmpeg 9

The engine needs FFmpeg 9 shared libraries on this machine. The official installer is the quickest way and needs no environment variables afterwards:

curl -fsSL https://remedia.twlite.dev/install.sh | sh -s -- --ffmpeg-only

On Windows PowerShell, and for other options, see Install FFmpeg 9.

2. Install the package

npm install @remedia/node discord.js

3. Check that the engine starts

node --input-type=module -e "import { Remedia } from '@remedia/node'; const engine = await Remedia.create(); await engine.close(); console.log('Remedia native runtime OK')"

If this prints Remedia native runtime OK, FFmpeg was found and the addon loaded. Otherwise the error lists every location that was tried. See native library troubleshooting.

4. Play into a voice channel

Your bot keeps its own Gateway connection. Give Remedia a function that sends a Gateway payload for a guild, and forward every raw packet to remedia.receive(). This example uses discord.js:

import { Client, Events, GatewayIntentBits } from 'discord.js';
import { Remedia } from '@remedia/node';

const discord = new Client({
  intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildVoiceStates],
});

const remedia = await Remedia.create({
  send(guildId, payload) {
    discord.guilds.cache.get(guildId)?.shard.send(payload);
  },
});

discord.on(Events.Raw, (packet) => remedia.receive(packet));

discord.once(Events.ClientReady, async (ready) => {
  const voice = remedia.createDiscordSession({
    guildId: '123456789012345678',
    channelId: '234567890123456789',
    userId: ready.user.id,
  });

  try {
    await voice.connect();
    await voice.play({ url: 'https://example.com/audio.mp3' });
    await voice.wait();
  } finally {
    await remedia.close();
    discord.destroy();
  }
});

await discord.login(process.env.DISCORD_TOKEN);

Export DISCORD_TOKEN before running the example. It plays one source, then closes the engine and Gateway client. In a long-running bot, keep both clients open until application shutdown.

connect() resolves once the voice connection is ready. play() resolves once the audio stream is open and playback has started, and wait() resolves when it ends.

Snowflake IDs are always strings. Replace the guild and channel IDs with real ones, and use a URL or file path your bot may read.

5. Control playback

await voice.pause();
await voice.resume();
await voice.seek(30_000); // milliseconds
await voice.setVolume(0.8); // 0 to 2, default 1
await voice.filters.set('bass=gain=6'); // FFmpeg audio filtergraph
await voice.stop(); // stops playback and stays in the channel
await voice.disconnect(); // leaves the channel

Always call await remedia.close() when your process shuts down. It stops playback and releases native resources.

Playback without Discord is silent

remedia.createPlaybackSession() decodes audio into a null output. It is useful for tests and for checking that a file decodes, but it does not play through speakers. Audio is only audible through a Discord voice session.

Next steps

On this page