Audio Playback & Queuing
Relevant source files
The following files were used as context for generating this wiki page:The Audio Playback and Queuing system is responsible for playing translated audio back to users in a Discord voice channel. It is designed to handle multiple audio clips in an orderly fashion, preventing them from overlapping. This is achieved by maintaining a dedicated queue for each voice channel and processing audio files sequentially.
The system is built around two main classes: AudioQueue and PlayerPool. PlayerPool manages a collection of AudioPlayer instances, ensuring one player per guild, while AudioQueue orchestrates the queuing and playback logic for each channel. The entire process is initiated when the bot joins a voice channel and is terminated when it leaves. The system relies on the @discordjs/voice library for its core voice functionalities.
Sources: src/utils/AudioQueue.ts, src/utils/PlayerPool.ts, package.json:28
Core Components
The system is composed of two primary utility classes that work together to manage audio playback.
PlayerPool
The PlayerPool class is a simple manager for AudioPlayer instances. Its purpose is to create and retrieve a unique AudioPlayer for each guild (server). This prevents resource conflicts and ensures that each guild has its own dedicated player.
get(guildId: string): Retrieves an existingAudioPlayerfor the givenguildId. If one does not exist, it creates a new player, stores it in an internalMap, and returns it.stop(guildId: string): Stops the player associated with theguildIdand removes it from the pool.
Sources: src/utils/PlayerPool.ts:3-20
AudioQueue
AudioQueue is the central component for managing the playback of audio files. It maintains a queue of audio file paths for each voice channel and uses a PlayerPool to get the appropriate AudioPlayer for playback. It is implemented as a singleton and exported as audioQueue.
The class uses three main properties:
players: An instance ofPlayerPool.queues: AMapwhere the key is the channel ID and the value is an array of audio file paths (strings).playingIn: ASetof channel IDs where playback is currently active, used to prevent starting multiple concurrent playback loops in the same channel.
Sources: src/utils/AudioQueue.ts:5-7, src/utils/AudioQueue.ts:100
The relationship between AudioQueue and PlayerPool is straightforward: AudioQueue contains and uses an instance of PlayerPool.
Key Methods
| Method | Description |
|---|---|
init(connection) | Gets a player from the pool for the connection's guild and subscribes the connection to it. Called when the bot joins a channel. |
add(channelId, audioUrl) | Adds an audio file URL to the queue for a specific channel. Creates a new queue if one doesn't exist. |
play(connection, audioUrl) | A public method that first adds the audio to the queue via add() and then starts the playback loop (playFromQueue) if not already running for that channel. |
playFromQueue(connection) | The core private playback loop. It extracts the next audio file from the queue, plays it, and sets up a listener to call itself again once playback is idle. |
clear(connection) | Clears the audio queue for a channel, removes the channel from the playingIn set, and pauses the player. |
stop(connection) | Calls clear() and then fully stops the player for the guild. |
Sources: src/utils/AudioQueue.ts:9-98
Architecture and Data Flow
The audio playback process is the final step in the voice translation pipeline. It begins after a user's speech has been recorded, transcribed, translated, and converted back into an audio file via Text-to-Speech (TTS).
Playback Initiation and Queuing
The following sequence diagram illustrates the flow from the TTS process to the audio being played in the voice channel.
This diagram shows how the readText processor calls audioQueue.play(). This adds the audio file to the queue and, if nothing is currently playing in that channel, kicks off the playFromQueue loop.
Sources: src/processors/readText.ts:19, src/utils/AudioQueue.ts:77-83
The Playback Loop
The playFromQueue method is a recursive function that ensures audio files are played one after another.
This flow ensures that as long as there are items in a channel's queue, the player will pull and play the next one as soon as the current one finishes. When the queue is empty, the loop terminates.
Sources: src/utils/AudioQueue.ts:46-63
Lifecycle Management
The audio playback system is managed through bot commands, primarily /join and /leave.
Initialization
When a user invokes the /join command, the bot joins the user's voice channel. During this process, audioQueue.init(connection) is called. This step is crucial as it retrieves or creates an AudioPlayer for the guild and subscribes the voice connection to it, making playback possible. A "connect.mp3" sound is played to confirm the connection.
Sources: src/commands/join.ts:86-87, src/utils/AudioQueue.ts:9-13
Termination
When the /leave command is used, the system performs a clean shutdown. It first plays a "disconnect.mp3" sound. Once that sound finishes playing (on the AudioPlayerStatus.Idle event), the audioQueue.stop(connection) method is called. This method clears any remaining items in the queue for that channel and stops the guild's audio player. Finally, the voice connection itself is destroyed.
Sources: src/commands/leave.ts:38-44, src/utils/AudioQueue.ts:94-97