Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/disgoorg/disgo/llms.txt

Use this file to discover all available pages before exploring further.

DisGo provides a comprehensive voice system for connecting to Discord voice channels, sending and receiving audio, and integrating with the DAVE (Discord Audio Visual Experience) protocol for end-to-end encryption.

Setting up voice

To use voice features, you need to enable the voice manager when creating your bot client and request the required gateway intents.
import (
    "github.com/disgoorg/disgo"
    "github.com/disgoorg/disgo/bot"
    "github.com/disgoorg/disgo/gateway"
    "github.com/disgoorg/disgo/voice"
)

client, err := disgo.New(token,
    bot.WithGatewayConfigOpts(
        gateway.WithIntents(gateway.IntentGuildVoiceStates),
    ),
    bot.WithVoiceManagerConfigOpts(
        voice.WithLogger(logger),
    ),
)
The IntentGuildVoiceStates intent is required to receive voice state updates from Discord.

Connecting to a voice channel

To connect to a voice channel, you create a voice connection through the voice manager and open it with a channel ID.
1

Create a voice connection

Use the voice manager to create a connection for a specific guild:
conn := client.VoiceManager.CreateConn(guildID)
2

Open the connection

Open the connection to a voice channel with options for muting and deafening:
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

err := conn.Open(ctx, channelID, false, false)
if err != nil {
    // Handle error
}
The parameters are:
  • channelID - The voice channel to join
  • selfMute - Whether the bot should be muted
  • selfDeaf - Whether the bot should be deafened
3

Close when done

Always close the connection when you’re finished:
defer func() {
    closeCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()
    conn.Close(closeCtx)
}()

Sending audio

DisGo supports sending Opus-encoded audio frames to Discord. You can either write raw Opus frames or use the built-in frame provider system.

Sending raw Opus frames

The UDP connection implements io.Writer, allowing you to write Opus frames directly:
// Set speaking flag before sending audio
if err := conn.SetSpeaking(ctx, voice.SpeakingFlagMicrophone); err != nil {
    panic(err)
}

// Write Opus frames to the connection
opusFrame := []byte{...} // Your Opus-encoded audio
_, err := conn.UDP().Write(opusFrame)

Using OpusReader

For reading from a file or stream, use the OpusReader to provide frames automatically:
file, err := os.Open("audio.dca")
if err != nil {
    panic(err)
}
defer file.Close()

reader := voice.NewOpusReader(file)
conn.SetOpusFrameProvider(reader)
The DCA (Discord Channel Audio) format is a simple container for Opus frames. Each frame is prefixed with a 4-byte little-endian length.

Custom frame providers

You can implement custom audio sources by implementing the OpusFrameProvider interface:
type OpusFrameProvider interface {
    ProvideOpusFrame() ([]byte, error)
    Close()
}

type CustomAudioSource struct {
    // Your implementation
}

func (s *CustomAudioSource) ProvideOpusFrame() ([]byte, error) {
    // Return next Opus frame
    return frame, nil
}

func (s *CustomAudioSource) Close() {
    // Cleanup
}

// Use it
conn.SetOpusFrameProvider(&CustomAudioSource{})

Receiving audio

You can receive audio from other users in the voice channel by reading packets from the UDP connection.

Reading raw packets

go func() {
    for {
        packet, err := conn.UDP().ReadPacket()
        if err != nil {
            slog.Error("error reading packet", slog.Any("err", err))
            return
        }
        
        // packet.Opus contains the Opus-encoded audio
        // packet.SSRC identifies the audio source
        userID := conn.UserIDBySSRC(packet.SSRC)
        
        // Process audio...
    }
}()

Using OpusWriter

To write received audio to a file or stream:
file, err := os.Create("recording.dca")
if err != nil {
    panic(err)
}
defer file.Close()

// Optional: filter specific users
userFilter := func(userID snowflake.ID) bool {
    return userID == targetUserID
}

writer := voice.NewOpusWriter(file, userFilter)
conn.SetOpusFrameReceiver(writer)

Custom frame receivers

type OpusFrameReceiver interface {
    ReceiveOpusFrame(userID snowflake.ID, packet *Packet) error
    CleanupUser(userID snowflake.ID)
    Close()
}

Voice gateway events

You can listen to voice gateway events by setting an event handler:
conn.SetEventHandlerFunc(func(gateway voice.Gateway, op voice.Opcode, seq int, data voice.GatewayMessageData) {
    switch d := data.(type) {
    case voice.GatewayMessageDataSpeaking:
        slog.Info("user started speaking", slog.Any("user_id", d.UserID))
    case voice.GatewayMessageDataClientDisconnect:
        slog.Info("user disconnected", slog.Any("user_id", d.UserID))
    }
})

DAVE protocol integration

DisGo supports the DAVE (Discord Audio Visual Experience) protocol for end-to-end encrypted voice. To enable DAVE, provide a session create function:
import "github.com/disgoorg/godave/golibdave"

client, err := disgo.New(token,
    bot.WithVoiceManagerConfigOpts(
        voice.WithDaveSessionCreateFunc(golibdave.NewSession),
    ),
)
DAVE support requires the godave library and native dependencies. See the godave repository for installation instructions.

Complete example

Here’s a complete example that joins a voice channel and plays audio:
package main

import (
    "context"
    "log/slog"
    "os"
    "time"

    "github.com/disgoorg/disgo"
    "github.com/disgoorg/disgo/bot"
    "github.com/disgoorg/disgo/events"
    "github.com/disgoorg/disgo/gateway"
    "github.com/disgoorg/disgo/voice"
    "github.com/disgoorg/snowflake/v2"
)

func main() {
    client, err := disgo.New(os.Getenv("TOKEN"),
        bot.WithGatewayConfigOpts(
            gateway.WithIntents(gateway.IntentGuildVoiceStates),
        ),
        bot.WithEventListenerFunc(func(e *events.Ready) {
            go playAudio(e.Client())
        }),
    )
    if err != nil {
        panic(err)
    }
    defer client.Close(context.TODO())

    if err = client.OpenGateway(context.TODO()); err != nil {
        panic(err)
    }

    // Keep alive
    select {}
}

func playAudio(client *bot.Client) {
    guildID := snowflake.MustParse(os.Getenv("GUILD_ID"))
    channelID := snowflake.MustParse(os.Getenv("CHANNEL_ID"))

    conn := client.VoiceManager.CreateConn(guildID)

    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()

    if err := conn.Open(ctx, channelID, false, false); err != nil {
        slog.Error("failed to open connection", slog.Any("err", err))
        return
    }
    defer conn.Close(context.Background())

    if err := conn.SetSpeaking(ctx, voice.SpeakingFlagMicrophone); err != nil {
        slog.Error("failed to set speaking", slog.Any("err", err))
        return
    }

    file, err := os.Open("audio.dca")
    if err != nil {
        slog.Error("failed to open audio file", slog.Any("err", err))
        return
    }
    defer file.Close()

    reader := voice.NewOpusReader(file)
    conn.SetOpusFrameProvider(reader)
}

Configuration options

Voice manager options

  • WithLogger(*slog.Logger) - Set custom logger
  • WithConnCreateFunc(ConnCreateFunc) - Custom connection factory
  • WithConnConfigOpts(...ConnConfigOpt) - Configure all connections
  • WithDaveSessionCreateFunc(godave.SessionCreateFunc) - Enable DAVE encryption
  • WithDaveSessionLogger(*slog.Logger) - Logger for DAVE sessions

Connection options

  • WithConnLogger(*slog.Logger) - Connection-specific logger
  • WithConnGatewayConfigOpts(...GatewayConfigOpt) - Configure voice gateway
  • WithUDPConnConfigOpts(...UDPConnConfigOpt) - Configure UDP connection
  • WithConnEventHandlerFunc(EventHandlerFunc) - Handle voice gateway events
  • WithConnAudioSenderCreateFunc(AudioSenderCreateFunc) - Custom audio sender
  • WithConnAudioReceiverCreateFunc(AudioReceiverCreateFunc) - Custom audio receiver

Best practices

Voice operations can hang if Discord doesn’t respond. Always use context timeouts:
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
Users can disconnect or network issues can occur. Always handle errors when reading packets:
packet, err := conn.UDP().ReadPacket()
if err != nil {
    if errors.Is(err, io.EOF) {
        // Connection closed
        return
    }
    // Handle other errors
}
Discord uses speaking flags to optimize audio transmission. Set them before sending audio:
conn.SetSpeaking(ctx, voice.SpeakingFlagMicrophone)
Always close voice connections when done to free resources:
defer func() {
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()
    conn.Close(ctx)
}()

Build docs developers (and LLMs) love