import { VoiceConnection } from "@discordjs/voice"; import { VoiceChannel } from "discord.js"; import { Readable } from "stream"; import { EventEmitter } from "events"; import { IDiscordBot } from "../../models/DiscordBot"; import { ILogger } from "tsdatautils-core"; /** * SoundService - A robust service for playing audio in Discord voice channels * * This service provides a comprehensive solution for Discord bot audio playback with: * - Robust error handling and detailed logging * - Automatic connection management and cleanup * - Support for both file paths and readable streams as audio input * - Proper resource cleanup to prevent memory leaks * - Graceful handling of voice connection interruptions * - Event emission for all stages of sound playback * * @fires SoundService#connecting When starting to connect to a voice channel * @fires SoundService#connected When successfully connected to a voice channel * @fires SoundService#resource-created When an audio resource is successfully created * @fires SoundService#buffering When the audio player starts buffering * @fires SoundService#playing When audio playback begins * @fires SoundService#finished When audio playback completes successfully * @fires SoundService#error When an error occurs during any stage * @fires SoundService#cleanup When resources are being cleaned up * * @example * ```typescript * // Create service with logger (recommended) * const soundService = new SoundService(bot.logger); * * // Listen to events * soundService.on('connecting', (guildId, channelId) => { * console.log(`Connecting to voice channel ${channelId} in guild ${guildId}`); * }); * soundService.on('playing', (guildId, channelId) => { * console.log(`Started playing audio in ${channelId}`); * }); * soundService.on('finished', (guildId, channelId) => { * console.log(`Finished playing audio in ${channelId}`); * }); * soundService.on('error', (guildId, channelId, error) => { * console.error(`Audio error in ${channelId}:`, error.message); * }); * * // Play a sound file * await soundService.playSoundInChannel(bot, voiceChannel, './audio/sound.mp3'); * * // Play from a stream * const stream = fs.createReadStream('./audio/music.ogg'); * await soundService.playSoundInChannel(bot, voiceChannel, stream); * * // Check if bot is connected to voice * const isConnected = soundService.isInVoiceChannel(bot, guildId); * ``` * * @since 1.3.44 */ export interface SoundServiceEvents { 'connecting': (guildId: string, channelId: string) => void; 'connected': (guildId: string, channelId: string) => void; 'resource-created': (guildId: string, channelId: string) => void; 'buffering': (guildId: string, channelId: string) => void; 'playing': (guildId: string, channelId: string) => void; 'finished': (guildId: string, channelId: string) => void; 'error': (guildId: string, channelId: string, error: Error) => void; 'cleanup': (guildId: string, channelId: string) => void; } export declare class SoundService extends EventEmitter { private logger; /** * Creates a new SoundService instance * * @param logger Optional logger instance for detailed logging. If not provided, * the service will fall back to console logging. */ constructor(logger?: ILogger); /** * Logs error messages with consistent formatting * @private */ private logError; /** * Logs warning messages with consistent formatting * @private */ private logWarn; /** * Logs info messages with consistent formatting * @private */ private logInfo; /** * Logs debug messages with consistent formatting * @private */ private logDebug; /** * Retrieves the current voice connection for a bot in a specific guild * * @param bot The Discord bot instance * @param guildId The ID of the guild to check for voice connection * @param destroyIfDisconnected Whether to destroy disconnected connections (default: true) * @returns The active voice connection, or null if none exists or parameters are invalid * * @example * ```typescript * const connection = soundService.getVoiceConnection(bot, '123456789', true); * if (connection) { * console.log('Bot is connected to voice'); * } * ``` */ getVoiceConnection(bot: IDiscordBot, guildId: string, destroyIfDisconnected?: boolean): VoiceConnection; /** * Checks if the bot is currently connected to a voice channel in the specified guild * * @param bot The Discord bot instance * @param guildId The ID of the guild to check * @returns true if bot is connected to voice, false otherwise * * @example * ```typescript * if (soundService.isInVoiceChannel(bot, message.guild.id)) { * await soundService.playSoundInChannel(bot, voiceChannel, audioFile); * } else { * console.log('Bot is not in a voice channel'); * } * ``` */ isInVoiceChannel(bot: IDiscordBot, guildId: string): boolean; /** * Plays audio in a Discord voice channel with comprehensive error handling * * This method handles the complete audio playback lifecycle: * - Validates all input parameters * - Creates audio resources from files or streams * - Manages voice connection establishment * - Monitors playback status with timeouts * - Cleans up resources after completion or on error * * @param bot The Discord bot instance * @param voiceChannel The voice channel to play audio in * @param audioInput Either a file path (string) or a readable stream containing audio data * * @throws {Error} When any parameter is null/undefined * @throws {Error} When audio resource creation fails * @throws {Error} When voice adapter creator is unavailable * @throws {Error} When voice connection or audio playback fails * * @example * ```typescript * try { * // Play from file path * await soundService.playSoundInChannel(bot, voiceChannel, './sounds/notification.mp3'); * * // Play from stream * const audioStream = fs.createReadStream('./music/song.ogg'); * await soundService.playSoundInChannel(bot, voiceChannel, audioStream); * } catch (error) { * console.error('Failed to play sound:', error.message); * } * ``` */ playSoundInChannel(bot: IDiscordBot, voiceChannel: VoiceChannel, audioInput: string | Readable): Promise; /** * Internal method that handles the complex voice connection and audio playback logic * * This method manages the low-level Discord voice API operations: * - Establishes voice channel connection with timeout handling * - Creates and configures audio player with event monitoring * - Handles voice disconnection/reconnection scenarios * - Manages audio playback lifecycle with comprehensive error handling * - Ensures proper cleanup of all resources * * @private * @param groupId Bot identifier for voice connection grouping * @param guildId Discord guild (server) ID * @param channelId Discord voice channel ID * @param adapterCreator Voice adapter creator function from Discord.js * @param audioResource Pre-created audio resource to play * * @throws {Error} For any voice connection, audio player, or playback failures */ private playSoundInChannelInternal; }