Helper utilities for state management, dependency reporting, stream probing, gateway adapter integration, and common operations that support the core voice functionality.
Utility for waiting for specific states in voice connections and audio players.
/**
* Wait for a voice connection to enter a specific state
* @param target - VoiceConnection to monitor
* @param status - Status to wait for
* @param timeoutOrSignal - Timeout in ms or AbortSignal
* @returns Promise that resolves when target reaches the desired state
*/
function entersState(
target: VoiceConnection,
status: VoiceConnectionStatus,
timeoutOrSignal: AbortSignal | number
): Promise<VoiceConnection>;
/**
* Wait for an audio player to enter a specific state
* @param target - AudioPlayer to monitor
* @param status - Status to wait for
* @param timeoutOrSignal - Timeout in ms or AbortSignal
* @returns Promise that resolves when target reaches the desired state
*/
function entersState(
target: AudioPlayer,
status: AudioPlayerStatus,
timeoutOrSignal: AbortSignal | number
): Promise<AudioPlayer>;
/**
* Generic state waiting function
* @param target - Object with state property to monitor
* @param status - Status to wait for
* @param timeoutOrSignal - Timeout in ms or AbortSignal
* @returns Promise that resolves when target reaches the desired state
*/
function entersState<T extends AudioPlayer | VoiceConnection>(
target: T,
status: AudioPlayerStatus | VoiceConnectionStatus,
timeoutOrSignal: AbortSignal | number
): Promise<T>;Usage Examples:
import {
entersState,
VoiceConnectionStatus,
AudioPlayerStatus,
joinVoiceChannel,
createAudioPlayer
} from "@discordjs/voice";
// Wait for voice connection to be ready
const connection = joinVoiceChannel({
channelId: "123456789012345678",
guildId: "987654321098765432",
adapterCreator: guild.voiceAdapterCreator,
});
try {
await entersState(connection, VoiceConnectionStatus.Ready, 30_000);
console.log("Connection is ready!");
} catch (error) {
console.error("Connection failed to become ready within 30 seconds");
}
// Wait for audio player to start playing
const player = createAudioPlayer();
const resource = createAudioResource("./music.mp3");
player.play(resource);
try {
await entersState(player, AudioPlayerStatus.Playing, 5_000);
console.log("Audio started playing!");
} catch (error) {
console.error("Audio failed to start within 5 seconds");
}
// Using AbortSignal for cancellation
const controller = new AbortController();
// Cancel after 15 seconds
setTimeout(() => controller.abort(), 15_000);
try {
await entersState(connection, VoiceConnectionStatus.Ready, controller.signal);
console.log("Connection ready before timeout");
} catch (error) {
if (error.name === "AbortError") {
console.log("State waiting was aborted");
} else {
console.error("State waiting failed:", error);
}
}Generate comprehensive reports of audio processing dependencies and their versions.
/**
* Generate a report of dependencies used by @discordjs/voice
* Useful for debugging audio processing capabilities and issues
* @returns String report containing version information and capabilities
*/
function generateDependencyReport(): string;Usage Example:
import { generateDependencyReport } from "@discordjs/voice";
// Generate and log dependency report
const report = generateDependencyReport();
console.log("@discordjs/voice Dependency Report:");
console.log(report);
// Example output:
// @discordjs/voice: 0.19.0
// prism-media: 1.3.5
// @discordjs/opus: 0.9.0 (optional)
// @noble/ciphers: 1.3.0 (for DAVE encryption)
// Node.js: v22.12.0
// FFmpeg: 4.4.2 (detected)
// Available Opus libraries: @discordjs/opus, node-opus
// Available encryption libraries: @noble/ciphers
// Encryption modes: aead_aes256_gcm_rtpsize, aead_xchacha20_poly1305_rtpsizeProbe audio streams to determine format and Discord compatibility.
/**
* Attempt to probe a readable stream to determine if it can be demuxed
* @param stream - Readable stream to probe
* @param probeSize - Number of bytes to read for probing (default: 1024)
* @param validator - Function to validate Opus head (default: validateDiscordOpusHead)
* @returns Promise resolving to probe information
*/
function demuxProbe(
stream: Readable,
probeSize?: number,
validator?: (opusHead: Buffer) => boolean
): Promise<ProbeInfo>;
/**
* Validate if an Opus head is suitable for Discord voice channels
* @param opusHead - Opus header buffer to validate
* @returns true if suitable for Discord (2 channels, 48kHz), false otherwise
*/
function validateDiscordOpusHead(opusHead: Buffer): boolean;
interface ProbeInfo {
/** Processed readable stream to use (may differ from input) */
stream: Readable;
/** Recommended stream type for this audio */
type: StreamType;
}Usage Examples:
import {
demuxProbe,
validateDiscordOpusHead,
createAudioResource,
StreamType
} from "@discordjs/voice";
import { createReadStream } from "fs";
// Probe unknown audio format
const inputStream = createReadStream("unknown-audio-file.audio");
try {
const probeInfo = await demuxProbe(inputStream);
console.log("Detected stream type:", probeInfo.type);
// Use probed stream
const resource = createAudioResource(probeInfo.stream, {
inputType: probeInfo.type,
});
player.play(resource);
} catch (error) {
console.error("Failed to probe stream:", error);
// Fallback to arbitrary type
const resource = createAudioResource(inputStream, {
inputType: StreamType.Arbitrary,
});
}
// Custom Opus validation
const customValidator = (opusHead) => {
const channels = opusHead.readUInt8(9);
const sampleRate = opusHead.readUInt32LE(12);
console.log(`Opus: ${channels} channels, ${sampleRate}Hz`);
// Discord requirements: 2 channels, 48kHz
return channels === 2 && sampleRate === 48000;
};
const probeInfo = await demuxProbe(stream, 2048, customValidator);Types and interfaces for integrating with Discord gateway adapters.
/**
* Function type for creating Discord gateway adapters
* @param methods - Methods provided by the voice library
* @returns Methods that the adapter implementer provides
*/
type DiscordGatewayAdapterCreator = (
methods: DiscordGatewayAdapterLibraryMethods
) => DiscordGatewayAdapterImplementerMethods;
/**
* Methods provided by @discordjs/voice to adapter implementations
*/
interface DiscordGatewayAdapterLibraryMethods {
/** Call when adapter can no longer be used */
destroy(): void;
/**
* Call when VOICE_SERVER_UPDATE is received
* @param data - Inner data of VOICE_SERVER_UPDATE payload
*/
onVoiceServerUpdate(data: GatewayVoiceServerUpdateDispatchData): void;
/**
* Call when VOICE_STATE_UPDATE is received
* @param data - Inner data of VOICE_STATE_UPDATE payload
*/
onVoiceStateUpdate(data: GatewayVoiceStateUpdateDispatchData): void;
}
/**
* Methods that adapter implementers must provide
*/
interface DiscordGatewayAdapterImplementerMethods {
/** Called when adapter can safely be destroyed */
destroy(): void;
/**
* Send payload to main Discord gateway connection
* @param payload - Payload to send to gateway
* @returns false if payload definitely failed to send
*/
sendPayload(payload: any): boolean;
}Usage Example:
import { DiscordGatewayAdapterCreator } from "@discordjs/voice";
// Example adapter implementation for discord.js
function createDiscordJSAdapter(guild) {
return (methods) => {
// Store library methods
const libraryMethods = methods;
// Listen for voice events from guild
guild.client.on('voiceServerUpdate', (update) => {
if (update.guild?.id === guild.id) {
libraryMethods.onVoiceServerUpdate(update);
}
});
guild.client.on('voiceStateUpdate', (oldState, newState) => {
if (newState.guild?.id === guild.id && newState.member?.id === guild.client.user?.id) {
libraryMethods.onVoiceStateUpdate(newState);
}
});
// Return implementer methods
return {
sendPayload: (payload) => {
try {
guild.shard.send(payload);
return true;
} catch (error) {
console.error('Failed to send payload:', error);
return false;
}
},
destroy: () => {
// Cleanup listeners if needed
},
};
};
}
// Use the adapter
const connection = joinVoiceChannel({
channelId: "123456789012345678",
guildId: "987654321098765432",
adapterCreator: createDiscordJSAdapter(guild),
});Internal utility for creating abort signals with timeouts.
/**
* Create an AbortController and signal that aborts after specified time
* @param delay - Delay in milliseconds before aborting
* @returns Tuple of [AbortController, AbortSignal]
*/
function abortAfter(delay: number): [AbortController, AbortSignal];Usage Example:
import { abortAfter } from "@discordjs/voice";
// Create timeout signal
const [controller, signal] = abortAfter(10_000);
// Use with entersState
try {
await entersState(connection, VoiceConnectionStatus.Ready, signal);
} catch (error) {
if (error.name === "AbortError") {
console.log("Operation timed out after 10 seconds");
}
}
// Manual cancellation
setTimeout(() => {
controller.abort(); // Cancel early if needed
}, 5_000);Internal utilities for managing voice connection data and payloads.
/**
* Create a voice state update payload for the Discord gateway
* @param config - Join configuration
* @returns Gateway payload object
*/
function createJoinVoiceChannelPayload(config: JoinConfig): any;
interface JoinConfig {
/** Channel ID to join (null to leave) */
channelId: string | null;
/** Connection group name */
group: string;
/** Guild ID */
guildId: string;
/** Whether to join deafened */
selfDeaf: boolean;
/** Whether to join muted */
selfMute: boolean;
}Create custom state monitoring utilities:
import { entersState, VoiceConnectionStatus } from "@discordjs/voice";
async function waitForMultipleStates(targets, status, timeout = 30_000) {
const promises = targets.map(target =>
entersState(target, status, timeout)
);
try {
const results = await Promise.allSettled(promises);
const successful = results.filter(r => r.status === 'fulfilled');
const failed = results.filter(r => r.status === 'rejected');
console.log(`${successful.length}/${targets.length} connections reached ${status}`);
return { successful: successful.map(r => r.value), failed };
} catch (error) {
console.error("Error waiting for states:", error);
throw error;
}
}
// Usage
const connections = [connection1, connection2, connection3];
const { successful, failed } = await waitForMultipleStates(
connections,
VoiceConnectionStatus.Ready
);Combine probing with resource creation:
import { demuxProbe, createAudioResource, StreamType } from "@discordjs/voice";
async function createSmartAudioResource(input, options = {}) {
if (typeof input === 'string' || options.inputType) {
// File path or type hint provided
return createAudioResource(input, options);
}
try {
// Probe stream to detect format
const probeInfo = await demuxProbe(input);
return createAudioResource(probeInfo.stream, {
...options,
inputType: probeInfo.type,
});
} catch (error) {
console.warn("Stream probing failed, using arbitrary type:", error.message);
return createAudioResource(input, {
...options,
inputType: StreamType.Arbitrary,
});
}
}
// Usage
const resource = await createSmartAudioResource(someStream, {
metadata: { title: "Unknown Format Audio" },
inlineVolume: true,
});Use utilities for debugging voice issues:
import { generateDependencyReport, validateDiscordOpusHead } from "@discordjs/voice";
function diagnoseVoiceIssues(connection, player) {
console.log("=== Voice Diagnostics ===");
// Dependency report
console.log("\nDependency Report:");
console.log(generateDependencyReport());
// Connection state
console.log("\nConnection State:");
console.log(`Status: ${connection.state.status}`);
if (connection.state.status === 'ready') {
console.log(`Networking: ${connection.state.networking.state.code}`);
}
// Player state
console.log("\nPlayer State:");
console.log(`Status: ${player.state.status}`);
if (player.state.status === 'playing' || player.state.status === 'paused') {
console.log(`Playback Duration: ${player.state.playbackDuration}ms`);
console.log(`Resource: ${player.state.resource.constructor.name}`);
}
console.log("=== End Diagnostics ===");
}
// Usage
diagnoseVoiceIssues(connection, player);