DiscordVoice
Duplex Discord voice-channel provider ('input' + 'output').
Defined in: src/providers/io/discord/DiscordVoice.ts:425
Duplex Discord voice-channel provider ('input' + 'output').
Remarks
DiscordVoice captures speech from users in a Discord voice channel and plays synthesized speech back into it, implementing both AudioInputProvider and AudioOutputProvider in one class.
Input: the connection’s receiver notifies when a user starts speaking (receiver.speaking.on('start')); the provider subscribes to that user’s Opus packet stream (ending after DiscordVoiceConfig.silenceDurationMs of silence), decodes it with prism-media (rate: 48000, channels: 2, frameSize: 960), downmixes interleaved stereo to mono by averaging each left/right pair, and emits linear16 mono 48 kHz AudioChunks.
Output: TTS chunks are queued by enqueue(). flush() concatenates the queue, converts it to 48 kHz stereo s16le PCM (linear-interpolation resampling for other rates, mono duplicated into both channels), wraps it in an audio resource with StreamType.Raw, and plays it via a shared AudioPlayer. flush() resolves when the player transitions back to AudioPlayerStatus.Idle. Because the audio is fed as raw PCM, no ffmpeg is required.
Duplex stop() semantics: a duplex provider has a single stop() serving both role interfaces. DiscordVoice.stop() implements the output contract (barge-in): it clears queued audio and calls player.stop(true), leaving capture running so the interrupting user is still heard. To end capture, call stopCapture(), detach(), or dispose(). Likewise pause()/resume() primarily gate input emission (the turn-taking system uses them to mute capture during agent speech); pause() only pauses the player when audio is actually playing.
Data-flow diagram:
speaking('start', userId)
│
▼
receiver.subscribe(userId) ──Opus──> prism opus.Decoder ──PCM 48k/2ch──┐
│
onAudio(chunk: linear16 48k mono) <──stereo→mono downmix──┘
enqueue(chunk) ─> queue ─flush()─> 48k stereo s16le ─> createAudioResource(Raw)
│
player.play(resource) <─────────┘
│
AudioPlayerStatus.Idle ─> flush() resolves, onPlaybackEnd
Example
import { DiscordVoice } from 'composite-voice';
import { joinVoiceChannel } from '@discordjs/voice';
const discord = new DiscordVoice({ debug: true });
await discord.initialize();
discord.attach(joinVoiceChannel({
channelId, guildId, adapterCreator: guild.voiceAdapterCreator,
selfDeaf: false,
}));
discord.onAudio((chunk) => {
console.log(`captured ${chunk.data.byteLength} bytes`);
});
discord.start();
See
- DiscordVoiceConfig for configuration options
- AudioInputProvider and AudioOutputProvider for the contracts
Implements
Constructors
Constructor
new DiscordVoice(config?): DiscordVoice;
Defined in: src/providers/io/discord/DiscordVoice.ts:530
Creates a new DiscordVoice instance.
Parameters
| Parameter | Type | Description |
|---|---|---|
config | DiscordVoiceConfig | Provider configuration. All fields are optional; a connection can be supplied later via attach(). |
Returns
DiscordVoice
Remarks
Construction is side-effect free. The @discordjs/voice and prism-media peer dependencies are only imported when initialize() is called.
Example
const discord = new DiscordVoice({ silenceDurationMs: 800 });
Properties
| Property | Modifier | Type | Default value | Description | Defined in |
|---|---|---|---|---|---|
roles | readonly | readonly ProviderRole[] | undefined | Pipeline roles covered by this provider. Remarks DiscordVoice is a duplex provider covering both the 'input' and 'output' slots. Separate STT, LLM, and TTS providers are still required. | src/providers/io/discord/DiscordVoice.ts:442 |
type | readonly | ProviderType | 'websocket' | Communication type for this provider. Remarks 'websocket' — the provider operates over Discord’s persistent voice gateway connection (owned by the attached VoiceConnection). | src/providers/io/discord/DiscordVoice.ts:433 |
Methods
attach()
attach(connection): void;
Defined in: src/providers/io/discord/DiscordVoice.ts:649
Attach a Discord VoiceConnection to the provider.
Parameters
| Parameter | Type | Description |
|---|---|---|
connection | DiscordVoiceConnection | The voice connection to attach. |
Returns
void
Remarks
The application creates the connection with joinVoiceChannel() from @discordjs/voice (join with selfDeaf: false, or the bot receives no audio). Attaching subscribes the internal audio player to the connection and registers the speaking listener on its receiver. Calling attach() again with a different connection detaches the previous one first.
May be called before initialize(); the connection is then wired during initialization.
Example
const connection = joinVoiceChannel({
channelId: channel.id,
guildId: guild.id,
adapterCreator: guild.voiceAdapterCreator,
selfDeaf: false,
});
discord.attach(connection);
configure()
configure(metadata): void;
Defined in: src/providers/io/discord/DiscordVoice.ts:865
Configure the output with the audio format the TTS will produce.
Parameters
| Parameter | Type | Description |
|---|---|---|
metadata | AudioMetadata | Format description of the incoming TTS audio. |
Returns
void
Remarks
DiscordVoice accepts linear16 PCM only (mono or stereo, any sample rate) — the provider resamples to 48 kHz and expands mono to stereo itself. Compressed formats (mp3, opus, mulaw, alaw) are rejected because decoding them server-side would require ffmpeg.
Throws
ConfigurationError if metadata.encoding is not 'linear16' — configure your TTS accordingly, e.g. new DeepgramTTS({ encoding: 'linear16', sampleRate: 24000 }).
Implementation of
detach()
detach(): void;
Defined in: src/providers/io/discord/DiscordVoice.ts:676
Detach the current voice connection, if any.
Returns
void
Remarks
Destroys all active receive streams, removes the speaking listener, and unsubscribes the audio player from the connection. The provider can be re-attached to the same or a different connection afterwards. The connection itself is left open — destroying it is the application’s job.
dispose()
dispose(): Promise<void>;
Defined in: src/providers/io/discord/DiscordVoice.ts:590
Dispose of the provider and release all resources.
Returns
Promise<void>
Remarks
Detaches the connection (destroying receive streams and unsubscribing the player), stops playback, settles any pending flush promises, and clears all state. The instance can be re-initialized afterwards.
Implementation of
enqueue()
enqueue(chunk): void;
Defined in: src/providers/io/discord/DiscordVoice.ts:884
Enqueue a TTS audio chunk for the next flush().
Parameters
| Parameter | Type | Description |
|---|---|---|
chunk | AudioChunk | Audio data to queue for playback. |
Returns
void
Remarks
Chunks are buffered until flush() builds a single playback resource from them. A per-chunk AudioChunk.metadata is honored as a fallback when configure() was not called.
Implementation of
flush()
flush(): Promise<void>;
Defined in: src/providers/io/discord/DiscordVoice.ts:905
Play all queued audio into the channel and wait for playback to finish.
Returns
Promise<void>
Remarks
Concatenates the queued chunks, converts them to 48 kHz stereo s16le PCM (linear-interpolation resampling via resamplePcm for other rates; mono duplicated into both channels), wraps the result in a Readable played as StreamType.Raw, and resolves once the player transitions to AudioPlayerStatus.Idle. Resolves immediately when nothing is queued. A stop() (barge-in) also settles the promise, because player.stop(true) forces the Idle transition.
Throws
ConfigurationError if no format is known (neither configure() nor per-chunk metadata) or the format is not linear16.
Throws
ProviderInitializationError via initialize rules if the provider is not initialized.
Implementation of
getMetadata()
getMetadata(): AudioMetadata;
Defined in: src/providers/io/discord/DiscordVoice.ts:839
Get the audio format metadata for captured audio.
Returns
{ encoding: 'linear16', sampleRate: 48000, channels: 1, bitDepth: 16 }
Remarks
Discord voice is Opus at 48 kHz stereo on the wire; after decoding and downmixing, the provider emits linear16 mono at 48 kHz. The pipeline uses this to auto-configure the STT provider.
Implementation of
AudioInputProvider.getMetadata
initialize()
initialize(): Promise<void>;
Defined in: src/providers/io/discord/DiscordVoice.ts:555
Initialize the provider: load peer dependencies and create the player.
Returns
Promise<void>
Remarks
Dynamically imports @discordjs/voice and prism-media, creates the shared AudioPlayer, wires its status events, and attaches the connection from DiscordVoiceConfig.connection (or one supplied earlier via attach()). Safe to call again after dispose(). If already initialized, this is a no-op.
Throws
ProviderInitializationError if @discordjs/voice or prism-media is not installed (with install instructions).
Implementation of
AudioOutputProvider.initialize
isActive()
isActive(): boolean;
Defined in: src/providers/io/discord/DiscordVoice.ts:810
Check whether input capture is active.
Returns
boolean
true when started and not paused.
Implementation of
isPlaying()
isPlaying(): boolean;
Defined in: src/providers/io/discord/DiscordVoice.ts:953
Check whether audio is currently being played into the channel.
Returns
boolean
true between the player’s Playing and Idle transitions.
Implementation of
isReady()
isReady(): boolean;
Defined in: src/providers/io/discord/DiscordVoice.ts:617
Check whether the provider has been initialized.
Returns
boolean
true when initialize has completed and dispose has not yet been called.
Implementation of
onAudio()
onAudio(callback): void;
Defined in: src/providers/io/discord/DiscordVoice.ts:825
Register a callback to receive captured audio chunks.
Parameters
| Parameter | Type | Description |
|---|---|---|
callback | (chunk) => void | Function invoked with each AudioChunk (linear16, 48 kHz, mono) while capture is active. |
Returns
void
Remarks
Only one callback can be registered at a time; subsequent calls replace the previous callback. Must be called before start().
Implementation of
onPlaybackEnd()
onPlaybackEnd(callback): void;
Defined in: src/providers/io/discord/DiscordVoice.ts:973
Register a callback fired when the queue fully drains.
Parameters
| Parameter | Type | Description |
|---|---|---|
callback | () => void | Invoked when the player returns to Idle after having played audio. |
Returns
void
Implementation of
AudioOutputProvider.onPlaybackEnd
onPlaybackError()
onPlaybackError(callback): void;
Defined in: src/providers/io/discord/DiscordVoice.ts:982
Register a callback fired when the player reports an error.
Parameters
| Parameter | Type | Description |
|---|---|---|
callback | (error) => void | Invoked with the underlying error. |
Returns
void
Implementation of
AudioOutputProvider.onPlaybackError
onPlaybackStart()
onPlaybackStart(callback): void;
Defined in: src/providers/io/discord/DiscordVoice.ts:963
Register a callback fired when audio delivery begins after being idle.
Parameters
| Parameter | Type | Description |
|---|---|---|
callback | () => void | Invoked on the player’s Idle/Buffering → Playing transition. |
Returns
void
Implementation of
AudioOutputProvider.onPlaybackStart
pause()
pause(): void;
Defined in: src/providers/io/discord/DiscordVoice.ts:781
Pause audio processing.
Returns
void
Remarks
Gates input emission (decoded chunks are silently dropped) — the turn-taking system calls this to mute capture while the agent speaks. If audio is currently playing, the player is paused as well. Resume with resume().
Implementation of
resume()
resume(): void;
Defined in: src/providers/io/discord/DiscordVoice.ts:798
Resume audio processing after a pause.
Returns
void
Remarks
Re-opens the input emission gate and unpauses the player.
See
Implementation of
start()
start(): void;
Defined in: src/providers/io/discord/DiscordVoice.ts:719
Start capturing audio from users speaking in the channel.
Returns
void
Remarks
After calling start(), each speaking('start') event triggers a per-user Opus subscription and decoded chunks are delivered to the callback registered with onAudio(). Requires an attached connection to have any effect.
Implementation of
stop()
stop(): void;
Defined in: src/providers/io/discord/DiscordVoice.ts:744
Stop the provider — barge-in while the agent is speaking, capture halt otherwise.
Returns
void
Remarks
A duplex provider exposes a single stop() for both role interfaces, so DiscordVoice dispatches on playback state:
- Barge-in (
output.stop()) — while audio is queued or the player is delivering, the pending output queue is cleared andplayer.stop(true)forces the player toIdle, settling any in-flight flush(). Capture deliberately survives: the orchestrator invokes this synchronously when a user interrupts the agent, and the interrupting speech must be transcribed. - Stop listening (
input.stop()) — with nothing queued or playing, behaves as stopCapture().
detach() and dispose() stop both sides unconditionally.
Implementation of
stopCapture()
stopCapture(): void;
Defined in: src/providers/io/discord/DiscordVoice.ts:701
Stop audio capture without touching playback.
Returns
void
Remarks
The input-role counterpart to the barge-in stop(): destroys all active receive streams and closes the emission gate, while the speaking listener stays attached so start() can restart capture at any time.
See
stop for the playback (barge-in) stop
Implementation of
AudioInputProvider.stopCapture
stopPlayback()
stopPlayback(): void;
Defined in: src/providers/io/discord/DiscordVoice.ts:766
Stop playback, leaving capture running.
Returns
void
Remarks
The pipeline calls this for barge-in, so the provider never has to infer which side was meant. Capture stays open deliberately: barge-in fires because someone started talking, and that speech has to reach STT.
Safe when nothing is playing — barge-in is also raised while the agent is still thinking, and there is simply no audio to drop yet.