Table of Contents

Voice Events

VoiceConnection exposes async events for user activity, audio frames, packet diagnostics, and DAVE state.

Event List

Event Args Purpose
UserSpeaking UserSpeakingEventArgs Discord speaking flag updates (OP5)
UserJoined VoiceUserJoinEventArgs User SSRC/user binding joined
UserLeft VoiceUserLeaveEventArgs User SSRC/user binding left
VoiceReceived VoiceReceiveEventArgs Decoded PCM (and source Opus when available)
VoicePacketDropped VoicePacketDroppedEventArgs Inbound packet drop classification
DaveStateChanged DaveStateChangedEventArgs Public DAVE FSM state transitions
DaveOpcodeObserved DaveOpcodeEventArgs DAVE opcode send/receive diagnostics
VoiceSocketErrored SocketErrorEventArgs Voice WebSocket exception path

Subscribe and Unsubscribe

using DisCatSharp.EventArgs;
using DisCatSharp.Voice;
using DisCatSharp.Voice.EventArgs;

public static void WireVoiceEvents(VoiceConnection connection)
{
    connection.UserSpeaking += OnUserSpeaking;
    connection.UserJoined += OnUserJoined;
    connection.UserLeft += OnUserLeft;
    connection.VoiceReceived += OnVoiceReceived;
    connection.VoicePacketDropped += OnVoicePacketDropped;
    connection.DaveStateChanged += OnDaveStateChanged;
    connection.DaveOpcodeObserved += OnDaveOpcodeObserved;
    connection.VoiceSocketErrored += OnVoiceSocketErrored;
}

public static void UnwireVoiceEvents(VoiceConnection connection)
{
    connection.UserSpeaking -= OnUserSpeaking;
    connection.UserJoined -= OnUserJoined;
    connection.UserLeft -= OnUserLeft;
    connection.VoiceReceived -= OnVoiceReceived;
    connection.VoicePacketDropped -= OnVoicePacketDropped;
    connection.DaveStateChanged -= OnDaveStateChanged;
    connection.DaveOpcodeObserved -= OnDaveOpcodeObserved;
    connection.VoiceSocketErrored -= OnVoiceSocketErrored;
}

private static Task OnUserSpeaking(VoiceConnection _, UserSpeakingEventArgs e)
{
    Console.WriteLine($"[SPEAK] user={e.User?.Id} ssrc={e.Ssrc} flags={e.Speaking}");
    return Task.CompletedTask;
}

private static Task OnUserJoined(VoiceConnection _, VoiceUserJoinEventArgs e)
{
    Console.WriteLine($"[JOIN] user={e.User.Id} ssrc={e.Ssrc}");
    return Task.CompletedTask;
}

private static Task OnUserLeft(VoiceConnection _, VoiceUserLeaveEventArgs e)
{
    Console.WriteLine($"[LEAVE] user={e.User.Id} ssrc={e.Ssrc}");
    return Task.CompletedTask;
}

private static Task OnVoiceReceived(VoiceConnection _, VoiceReceiveEventArgs e)
{
    Console.WriteLine($"[RX] user={e.User?.Id} seq={e.Sequence} pcm={e.PcmData.Length} missing={e.MissingFrames} conceal={e.IsConcealmentFrame}");
    return Task.CompletedTask;
}

private static Task OnVoicePacketDropped(VoiceConnection _, VoicePacketDroppedEventArgs e)
{
    Console.WriteLine($"[DROP] reason={e.Reason} user={e.User?.Id} ssrc={e.Ssrc} seq={e.Sequence} detail={e.Detail}");
    return Task.CompletedTask;
}

private static Task OnDaveStateChanged(VoiceConnection _, DaveStateChangedEventArgs e)
{
    Console.WriteLine($"[DAVE FSM] {e.OldState} -> {e.NewState} via {e.Handler} ({e.Reason})");
    return Task.CompletedTask;
}

private static Task OnDaveOpcodeObserved(VoiceConnection _, DaveOpcodeEventArgs e)
{
    Console.WriteLine($"[DAVE FLOW] {e.Direction} OP{e.Opcode} len={e.PayloadLength} seq={e.Sequence} binary={e.IsBinary}");
    return Task.CompletedTask;
}

private static Task OnVoiceSocketErrored(VoiceConnection _, SocketErrorEventArgs e)
{
    Console.WriteLine($"[VOICE WS ERROR] {e.Exception.Message}");
    return Task.CompletedTask;
}

DAVE Readiness

For DAVE-gated use cases, combine event monitoring with an explicit wait:

bool daveReady = await connection.WaitForDaveActiveAsync(TimeSpan.FromSeconds(5));
if (!daveReady)
{
    // Decide whether to skip playback, pass through, or fail based on your policy.
}

Related configuration:

  • VoiceConfiguration.MaxDaveProtocolVersion
  • VoiceConfiguration.DavePendingAudioBehavior
  • VoiceConfiguration.EnableDebugLogging
  • VoiceConnection.EnableDebugLogging

Runtime Logging Control

If you only want deep diagnostics for one problematic call, toggle logging on that connection instead of globally:

connection.EnableDebugLogging = true;
// reproduce issue, inspect logs
connection.EnableDebugLogging = false;