Embedded Discord sessions
Connect an existing bot Gateway to the embedded engine and manage voice sessions.
Create a Discord session when audio should reach a voice channel. The application supplies the authenticated Gateway connection.
Discord Gateway adapter
The application still owns its Discord Gateway client and decides when to
join a channel. Supply the adapter with the existing client: supply a function that sends a
Gateway payload for a guild, then forward raw Gateway packets to
remedia.receive(). Remedia sends OP 4, collects the matching
VOICE_STATE_UPDATE and VOICE_SERVER_UPDATE in either order, and calls the
native transport only after both are valid. connect() resolves after the
voice WebSocket, UDP transport, encryption, and required DAVE setup are ready.
import { Remedia } from '@remedia/node';
const remedia = await Remedia.create({
send(guildId, payload) {
return sendToShard(guildId, payload);
},
});
discord.on('raw', (packet) => remedia.receive(packet));
const voice = remedia.createDiscordSession({
guildId: '123456789012345678',
channelId: '234567890123456789',
userId: '345678901234567890',
});
try {
await voice.connect();
await voice.play({ url: 'https://example.org/audio.mp3' });
console.log(voice.connectionState, await voice.wait());
await voice.stop(); // leaves the voice connection open
} finally {
await voice.disconnect();
await remedia.close();
}Connection timeout and cancellation
Snowflakes remain decimal strings in JavaScript and are validated before any
Gateway request. connect() defaults to a 45-second timeout and accepts
{ timeoutMs, signal } for per-attempt timeout and AbortSignal cancellation. An interrupted connection sends OP 4 with channel_id: null and discards the
pending credentials. disconnect() also sends this leave request, stops active
playback, closes the native transport, and leaves the wrapper reusable for a
later connect() with fresh Gateway events.
dispose() disconnects and removes
the session from its Remedia instance. Only one live wrapper per guild is
allowed in an instance. Gateway responses are not persisted or logged. The
server token and endpoint are discarded after native setup, and pending data is
reset before each new exchange and on disconnect.
An abort or timeout rejects the pending connect() and requests a Gateway
leave. If native voice setup has already started, its network handshake cannot
be interrupted mid-operation; disconnect() waits for the transport's bounded
timeouts and disposes any late native connection result.
Automatic transport recovery
Keep forwarding raw Gateway packets after connection. receive() ignores unrelated guilds, users, channels, and packet types. Changed voice credentials trigger transport replacement on the existing playback session. Recovery preserves the source and paused state and may create a brief audio gap. See session recovery and movement for control behavior, retries, and failure handling.
The adapter correlates fresh dispatch sequences and discards stale credentials. If native setup finishes after a newer attempt or disconnect, that late native result is disposed. The Rust voice reference describes packet routing and DAVE state.
Session configuration and channel movement
A client owns one session per guild. Separate clients (including separate bot users) can use the
same guild and channel with independent playback, transport generations, and events. userId is
the Discord bot user ID, not a Remedia client session ID. Channel IDs are mutable destinations.
Duplicate guild registration on the same client throws. Dispose the old session before replacing it.
const session = remedia.createDiscordSession({
guildId,
channelId,
userId,
selfDeaf: true,
selfMute: false,
volume: 0.75,
filterGraph: 'bass=gain=3',
});
await session.connect();
await session.move(otherChannelId);
await session.setSelfDeaf(false);
await session.setSelfMute(true);
await session.dispose();selfDeaf defaults to true, selfMute to false, gain to 1, and the filtergraph to null. Values are validated before playback and retained across tracks. Keep send(guildId, payload) and receive(packet) wired to the same bot throughout the session.
move(channelId) keeps the decoder and requests credentials for the new destination. A new destination supersedes an older pending move. channelId exposes the latest requested destination, including after failure. To recover from a failed move, retry connect(), move elsewhere, or disconnect. See move concurrency and failure for the shared Node/client contract.
Use direct @remedia/node when the engine should stay inside one process. Use the client node pool for remote nodes and explicit cross-node migration.