gaato/discord/voice does not have a README file

AudioSource

pub(open) trait AudioSource {
async fn next_frame(Self) -> Bytes?
}

A source of pre-encoded 20 ms Opus frames. None marks end of stream.

DaveBackend

trait DaveBackend

The private seam between gateway orchestration and the official libdave wrapper. Keeping this package-private lets white-box tests model protocol outcomes without making an alternate crypto provider part of the API.

VoiceTransport

pub(open) trait VoiceTransport {
async fn recv(Self) -> VoiceFrame
async fn send_text(Self, String) -> Unit
async fn send_binary(Self, Bytes) -> Unit
async fn close(Self, code~ : Int) -> Unit
}

The network seam used by VoiceGateway. Tests can provide an in-memory implementation without opening a WebSocket.

VoiceUdp

pub(open) trait VoiceUdp {
async fn recv(Self, FixedArray[Byte]) -> Int
async fn send(Self, Bytes) -> Unit
async fn close(Self) -> Unit
}

UDP seam used by voice discovery and media transport.

DaveError

pub(all) suberror DaveError {
DaveInvalid(reason~ : String)
DaveUnavailable(reason~ : String)
DaveInternal(reason~ : String)
} derive(Eq,
Debug
)

Errors reported while coordinating Discord's DAVE protocol.

OggOpusError

pub(all) suberror OggOpusError {
InvalidOggPage(reason~ : String)
InvalidOpusStream(reason~ : String)
} derive(Eq,
Debug
)

Errors raised while parsing Ogg pages and Opus stream headers.

RtpError

pub(all) suberror RtpError {
InvalidRtpPacket(reason~ : String)
} derive(Eq,
Debug
)

Errors raised while validating an RTP packet.

VoiceCryptoError

pub(all) suberror VoiceCryptoError {
NoCompatibleMode(modes~ : Array[String])
ShimUnavailable(reason~ : String)
InvalidSecretKey(length~ : Int)
InvalidEncryptedPacket(reason~ : String)
AuthenticationFailed(reason~ : String)
AeadFailed(status~ : Int, reason~ : String)
} derive(Eq,
Debug
)

Errors raised by transport-mode negotiation and packet encryption.

VoiceEnvelopeError

pub(all) suberror VoiceEnvelopeError {
InvalidTextFrame(reason~ : String)
InvalidBinaryFrame(reason~ : String)
} derive(Eq,
Debug
)

A malformed JSON or binary voice gateway envelope.

VoiceError

pub(all) suberror VoiceError {
MissingIntent(intent~ : String)
JoinTimeout
ShimUnavailable(reason~ : String)
NoCompatibleMode(offered~ : Array[String])
GatewayClosed(code~ : Int?)
IpDiscoveryFailed(reason~ : String)
Disconnected
} derive(
Debug
)

Errors surfaced while joining or operating a voice connection.

VoiceTransportClosed

pub(all) suberror VoiceTransportClosed {
VoiceTransportClosed(code~ : Int?, reason~ : String)
} derive(
Debug
)

Raised when a voice transport is closed or cannot continue.

VoiceUdpError

pub(all) suberror VoiceUdpError {
InvalidDiscoveryPacket(reason~ : String)
IpDiscoveryFailed(attempts~ : Int, reason~ : String)
} derive(Eq,
Debug
)

Errors raised by voice UDP setup and IP discovery.

DaveAction

pub(all) enum DaveAction {
SendJson(Json)
SendBinary(op~ : Int, payload~ : Bytes)
SwitchMediaContext(protocol_version~ : Int)
} derive(Eq,
Debug
)

Side effects requested by the DAVE gateway/MLS orchestration state machine.

DaveCommitOutcome

type DaveCommitOutcome derive(Eq,
Debug
)

DaveMachine

pub struct DaveMachine {
// private fields
}

DAVE gateway/MLS orchestration. Networking remains outside this type.

DaveMachine::active_protocol_version

fn DaveMachine::active_protocol_version(self : DaveMachine) -> Int

Return the protocol version currently selected for outbound media.

DaveMachine::encrypt_opus_frame

fn DaveMachine::encrypt_opus_frame(self : DaveMachine, ssrc~ : UInt, frame : Bytes) -> Bytes raise DaveError

Encrypt one Opus frame. Passthrough is delegated to libdave as well, so the encryptor remains the sole owner of media-transition state.

DaveMachine::handle

fn DaveMachine::handle(self : DaveMachine, message : VoiceMessage) -> Array[DaveAction] raise DaveError

Handle one decoded voice-gateway message and return transport actions.

DaveMachine::new

fn DaveMachine::new(protocol_version~ : Int, self_user_id~ : String, channel_id~ : UInt64, roster? : Array[String]) -> DaveMachine raise DaveError

Create the production DAVE state machine backed by the official libdave wrapper. protocol_version is the version selected by the voice gateway.

DaveRosterChange

type DaveRosterChange derive(Eq,
Debug
)

DaveWelcomeOutcome

type DaveWelcomeOutcome derive(Eq,
Debug
)

EncryptionMode

pub(all) enum EncryptionMode {
AeadAes256GcmRtpsize
AeadXChaCha20Poly1305Rtpsize
} derive(Eq,
Debug
)

Discord voice transport encryption modes supported by this package.

EncryptionMode::negotiate

fn EncryptionMode::negotiate(modes : Array[String]) -> EncryptionMode raise VoiceCryptoError

Select the preferred common Discord transport encryption mode.

OggOpusSource

pub struct OggOpusSource {
// private fields
}

In-memory Ogg/Opus source. Opus packets must already represent 20 ms audio frames; this demuxer does not decode or resample them.

OggOpusSource::channels

fn OggOpusSource::channels(self : OggOpusSource) -> Int

The channel count declared by the stream's OpusHead header.

OggOpusSource::from_bytes

fn OggOpusSource::from_bytes(bytes : Bytes) -> OggOpusSource raise OggOpusError

Parse a complete in-memory Ogg Opus file into an audio source, validating its OpusHead/OpusTags headers.

OggOpusSource::from_reader

Open an Ogg/Opus source backed by an asynchronous reader. The constructor consumes and validates the OpusHead and OpusTags packets; audio packets are read lazily by AudioSource::next_frame.

Example

async test {
let file = @fs.open("voice.ogg")
defer file.close()
let source = @voice.OggOpusSource::from_reader(file)
let audio : &@voice.AudioSource = source
while audio.next_frame() is Some(frame) {
play_opus_frame(frame)
}
}

OggOpusSource::pre_skip

fn OggOpusSource::pre_skip(self : OggOpusSource) -> Int

Samples (at 48 kHz) to drop before playback, from the OpusHead header.

OggOpusWriter

pub struct OggOpusWriter {
// private fields
}

Pure in-memory Ogg/Opus muxer. Header pages are available immediately from take_output; audio pages become available as packet or lacing limits are reached, and finish emits the final EOS page.

OggOpusWriter::finish

fn OggOpusWriter::finish(self : OggOpusWriter) -> Unit

Flush remaining packets in a final EOS page. Repeated calls are no-ops.

OggOpusWriter::new

fn OggOpusWriter::new(channels? : Int, pre_skip? : Int, serial? : UInt, max_packets_per_page? : Int) -> OggOpusWriter

Create an Ogg/Opus muxer and queue its OpusHead and OpusTags pages.

Example

async test {
let writer = @voice.OggOpusWriter::new(channels=2, pre_skip=312)
for frame in recorded_opus_frames() {
writer.write_frame(frame)
}
writer.finish()
let file = @fs.create("recording.ogg")
defer file.close()
file.write(writer.take_output())
}

Panics

Panics if channels or pre_skip cannot be represented in an Opus header, or if max_packets_per_page is not positive.

OggOpusWriter::take_output

fn OggOpusWriter::take_output(self : OggOpusWriter) -> Bytes

Drain all complete Ogg pages generated so far.

OggOpusWriter::write_frame

fn OggOpusWriter::write_frame(self : OggOpusWriter, opus : Bytes, samples? : Int) -> Unit raise OggOpusError

Queue one complete Opus packet and advance the 48 kHz granule position.

OggPacketReader

pub struct OggPacketReader {
// private fields
}

Streaming Ogg packet reassembler. Feed arbitrary chunks with push, then pull complete packets with next_packet.

OggPacketReader::new

Create an empty reader; feed it Ogg data with push and drain packets with next_packet.

OggPacketReader::next_packet

fn OggPacketReader::next_packet(self : OggPacketReader) -> Bytes? raise OggOpusError

Return the next complete Ogg packet, or None when more input is needed.

OggPacketReader::push

fn OggPacketReader::push(self : OggPacketReader, bytes : Bytes) -> Unit

Add bytes to the streaming input without requiring a page boundary.

ReorderBuffer

pub struct ReorderBuffer[T] {
// private fields
}

A small RTP reorder window. The first packet establishes the expected sequence; later gaps are held until hold_ms elapses or eight packets are buffered.

ReorderBuffer::new

fn[T] ReorderBuffer::new(hold_ms? : Int) -> ReorderBuffer[T]

Create an empty window that holds packets behind a sequence gap for up to hold_ms milliseconds.

ReorderBuffer::push

fn[T] ReorderBuffer::push(self : ReorderBuffer[T], seq~ : Int, arrival_ms~ : Int64, packet : T) -> Array[ReorderOutput[T]]

Insert one RTP packet. Arrival time is supplied by the caller so timeout behavior is deterministic in tests.

ReorderOutput

pub(all) enum ReorderOutput[T] {
Deliver(T)
Lost(count~ : Int)
} derive(Eq,
Debug
)

Values produced while restoring RTP sequence order.

RtpPacket

pub(all) struct RtpPacket {
sequence : UInt16
timestamp : UInt
ssrc : UInt
header_len : Int
header : Bytes
payload : Bytes
} derive(Eq,
Debug
)

A parsed RTP packet with the rtpsize cleartext header separated.

TransportCipher

pub struct TransportCipher {
// private fields
}

Stateful voice transport cipher. The sender counter wraps modulo 2^32; receivers reconstruct nonces from the packet suffix and need no counter.

TransportCipher::new

fn TransportCipher::new(mode : EncryptionMode, secret_key : Bytes) -> TransportCipher raise VoiceCryptoError

Create a cipher for mode from the 32-byte session secret_key delivered by the voice gateway.

TransportCipher::open

fn TransportCipher::open(self : TransportCipher, packet : Bytes, header_len~ : Int) -> Bytes raise VoiceCryptoError

Authenticate and decrypt a packet, returning header || plaintext.

TransportCipher::seal

fn TransportCipher::seal(self : TransportCipher, header~ : Bytes, plaintext : Bytes) -> Bytes raise VoiceCryptoError

Encrypt an RTP payload and append the four-byte nonce suffix.

VoiceConnection

pub struct VoiceConnection {
// private fields
}

High-level owner of voice gateway, UDP media, DAVE, sender, and receiver tasks. All background work is attached to the task group passed to start.

VoiceConnection::disconnect

async fn VoiceConnection::disconnect(self : VoiceConnection) -> Unit

Leave the Discord voice state and tear down media transports. Repeated calls are harmless.

VoiceConnection::latency_ms

fn VoiceConnection::latency_ms(self : VoiceConnection) -> Int64?

Latest voice gateway heartbeat round-trip in milliseconds, if measured.

VoiceConnection::next_event

async fn VoiceConnection::next_event(self : VoiceConnection) -> VoiceEvent

Pull the next received media or voice membership event.

VoiceConnection::play

async fn VoiceConnection::play(self : VoiceConnection, source : &AudioSource) -> Unit

Replace the current source. The old sender is asked to stop and flush its five silence frames before the latest source begins.

VoiceConnection::set_speaking

async fn VoiceConnection::set_speaking(self : VoiceConnection, flags : Int) -> Unit

Send a Speaking (opcode 5) frame with the given flag bits. The playback loop manages this automatically; call it directly only for custom speaking indicators.

VoiceConnection::start

fn[X] VoiceConnection::start(group :
TaskGroup
[X], credentials : VoiceCredentials, user_id~ : String, channel_id~ : UInt64, leave~ : async () -> Unit, rejoin~ : async () -> VoiceCredentials, connector? : async (String) -> &VoiceTransport, udp_opener? : async (String, Int) -> &VoiceUdp, receive? : Bool, telemetry? : (VoiceTelemetry) -> Unit, sleeper? : async (Int) -> Unit, rand? :
Rand
) -> VoiceConnection

Start a voice connection and attach all of its tasks to group.

VoiceConnection::state

The current lifecycle state of this voice connection.

VoiceConnection::stop

async fn VoiceConnection::stop(self : VoiceConnection) -> Unit

Stop the active source; the send loop emits five silence frames and clears the speaking flag.

VoiceConnection::subscribe

fn VoiceConnection::subscribe(self : VoiceConnection, user_id~ : String, end_after_silence_ms? : Int?) -> VoiceReceiveStream

Subscribe to received Opus packets for one Discord user. With end_after_silence_ms, the first frame waits indefinitely and subsequent reads end the stream after the requested silence interval.

Example

async test {
// Echo one speaker until they are silent for one second.
connection.play(
connection.subscribe(user_id="1234", end_after_silence_ms=Some(1000)),
)

// Or record the same stream into an Ogg/Opus file.
let stream = connection.subscribe(user_id="1234")
let audio : &@voice.AudioSource = stream
let writer = @voice.OggOpusWriter::new(channels=2)
while audio.next_frame() is Some(frame) {
writer.write_frame(frame)
}
writer.finish()
}

VoiceConnection::wait_ready

async fn VoiceConnection::wait_ready(self : VoiceConnection, timeout_ms? : Int) -> Unit raise VoiceError

Wait until the current connection handshake reaches Ready.

VoiceConnectionState

pub(all) enum VoiceConnectionState {
Connecting
Ready
Reconnecting
Rejoining
Closed(code~ : Int?)
} derive(Eq,
Debug
)

Public voice connection lifecycle.

VoiceCredentials

pub(all) struct VoiceCredentials {
server_id : String
session_id : String
token : String
endpoint : String
} derive(Eq,
Debug
)

Credentials supplied by the main Discord gateway voice-state handshake.

VoiceEvent

pub(all) enum VoiceEvent {
OpusReceived(user_id~ : String?, ssrc~ : UInt, sequence~ : Int, timestamp~ : UInt, opus~ : Bytes)
PacketsLost(user_id~ : String?, ssrc~ : UInt, count~ : Int)
SpeakingChanged(user_id~ : String, ssrc~ : UInt, flags~ : Int)
UserConnected(user_id~ : String)
UserDisconnected(user_id~ : String)
ConnectionReady
ConnectionResumed
} derive(Eq,
Debug
)

Events emitted by a live voice connection.

VoiceFrame

pub(all) enum VoiceFrame {
Text(String)
Binary(Bytes)
} derive(Eq,
Debug
)

A complete WebSocket message received from the voice gateway.

VoiceGateway

pub struct VoiceGateway {
// private fields
}

Voice gateway v8 driver. The caller-owned task group controls its lifetime; cancelling the group tears down connection and heartbeat tasks.

VoiceGateway::close

async fn VoiceGateway::close(self : VoiceGateway) -> Unit noraise

Request graceful shutdown of the voice WebSocket.

VoiceGateway::latency_ms

fn VoiceGateway::latency_ms(self : VoiceGateway) -> Int64?

Latest matched heartbeat round-trip time in milliseconds.

VoiceGateway::next

async fn VoiceGateway::next(self : VoiceGateway) -> VoiceGatewayEvent

Pull the next voice gateway event. Blocks until one is available.

VoiceGateway::send_binary

async fn VoiceGateway::send_binary(self : VoiceGateway, op~ : Int, payload : Bytes) -> Unit

Send a client-to-server DAVE binary envelope.

VoiceGateway::send_json

async fn VoiceGateway::send_json(self : VoiceGateway, payload : Json) -> Unit

Send an arbitrary JSON voice gateway envelope.

VoiceGateway::start

fn[X] VoiceGateway::start(group :
TaskGroup
[X], server_id~ : String, user_id~ : String, session_id~ : String, token~ : String, endpoint~ : String, max_dave_protocol_version? : UInt16, select_protocol~ : async (UInt, String, Int, Array[String]) -> (String, Int, String), connector? : async (String) -> &VoiceTransport, queue_capacity? : Int, telemetry? : (VoiceGatewayEvent) -> Unit, sleeper? : async (Int) -> Unit, rand? :
Rand
) -> VoiceGateway

Spawn a voice gateway driver into group and return its handle. max_dave_protocol_version is advertised in Identify. It defaults to zero so low-level callers do not negotiate DAVE without an active backend; pass a supported maximum explicitly to enable it.

VoiceGateway::state

Current voice gateway lifecycle state.

VoiceGatewayEvent

pub(all) enum VoiceGatewayEvent {
ReadyReceived
SessionEstablished(mode~ : String, secret_key~ : Bytes, dave_protocol_version~ : Int)
Message(VoiceMessage)
Connected(resumed~ : Bool)
Disconnected(code~ : Int?, resuming~ : Bool)
ConnectFailed(reason~ : String)
NeedsRejoin(code~ : Int?)
FatallyClosed(code~ : Int)
} derive(Eq,
Debug
)

Events surfaced by VoiceGateway::next.

VoiceGatewayState

pub(all) enum VoiceGatewayState {
Disconnected(reconnect_attempts~ : Int)
Connecting
Identifying
SelectingProtocol
Active
Resuming
FatallyClosed(code~ : Int)
} derive(Eq,
Debug
)

Voice gateway lifecycle state.

VoiceMessage

pub(all) enum VoiceMessage {
Ready(ssrc~ : UInt, ip~ : String, port~ : Int, modes~ : Array[String])
Hello(heartbeat_interval~ : Int)
SessionDescription(mode~ : String, secret_key~ : Bytes, dave_protocol_version~ : Int)
HeartbeatAck(nonce~ : Int64)
Speaking(ssrc~ : UInt, user_id~ : String, flags~ : Int)
ClientsConnect(user_ids~ : Array[String])
ClientDisconnect(user_id~ : String)
Resumed
DavePrepareTransition(transition_id~ : Int, protocol_version~ : Int)
DaveExecuteTransition(transition_id~ : Int)
DavePrepareEpoch(epoch~ : Int, protocol_version~ : Int)
DaveMlsExternalSender(payload~ : Bytes)
DaveMlsProposals(payload~ : Bytes)
DaveMlsAnnounceCommitTransition(transition_id~ : Int, commit~ : Bytes)
DaveMlsWelcome(transition_id~ : Int, welcome~ : Bytes)
Unknown(op~ : Int)
} derive(Eq,
Debug
)

A decoded voice gateway v8 message.

VoiceReceiveStream

pub struct VoiceReceiveStream {
// private fields
}

A per-user stream of received Opus packets. Streams are created with VoiceConnection::subscribe and can be passed directly to play or read through the AudioSource trait.

VoiceReceiveStream::close

fn VoiceReceiveStream::close(self : VoiceReceiveStream) -> Unit

End this stream and unregister it from its connection. Repeated calls are harmless, and a blocked next_frame wakes with None.

VoiceReconnectPolicy

pub(all) enum VoiceReconnectPolicy {
Resume
Rejoin
Fatal
} derive(Eq,
Debug
)

Action after a voice WebSocket close: resume the session, rejoin from scratch, or give up because reconnecting cannot succeed.

VoiceTelemetry

pub(all) enum VoiceTelemetry {
GatewayEvent(VoiceGatewayEvent)
DaveMediaContextActivated(protocol_version~ : Int)
DaveBinaryControlSent(opcode~ : Int, payload_bytes~ : Int)
PacketDropped(reason~ : String)
DaveEncryptDropped(reason~ : String)
DaveDecryptDropped(user_id~ : String?, ssrc~ : UInt, count~ : Int, reason~ : String)
} derive(Eq,
Debug
)

Non-fatal diagnostics from the connection's gateway and media paths.

SILENCE_FRAME

let SILENCE_FRAME : Bytes

Discord's canonical Opus silence frame.

build_ip_discovery_request

fn build_ip_discovery_request(ssrc : UInt) -> Bytes

Build Discord's 74-byte voice IP discovery request.

build_rtp_header

fn build_rtp_header(sequence~ : UInt16, timestamp~ : UInt, ssrc~ : UInt) -> Bytes

Build Discord's RTP header for a 20 ms Opus frame.

connect_voice_websocket

async fn connect_voice_websocket(url : String) -> &VoiceTransport

Connect to a voice-gateway WebSocket. The caller supplies the complete v8 URL, normally wss://<endpoint>?v=8.

discover_external_address

async fn discover_external_address(udp : &VoiceUdp, ssrc : UInt, retries? : Int, timeout_ms? : Int) -> (String, Int)

Run voice IP discovery, retrying only receive timeouts.

encode_binary_client_frame

fn encode_binary_client_frame(op : Int, payload : Bytes) -> Bytes

Prefix a client-to-server binary DAVE payload with its one-byte opcode.

encode_heartbeat

fn encode_heartbeat(t~ : Int64, seq_ack~ : Int) -> Json

Voice gateway Heartbeat (opcode 3) payload carrying the nonce and last acknowledged server sequence.

encode_identify

fn encode_identify(server_id~ : String, user_id~ : String, session_id~ : String, token~ : String, max_dave_protocol_version? : UInt16) -> Json

Voice gateway Identify (opcode 0) payload, advertising the selected maximum DAVE protocol version. It defaults to zero so low-level callers do not advertise DAVE without an active backend; pass a supported maximum explicitly to enable negotiation.

encode_invalid_commit_welcome

fn encode_invalid_commit_welcome(transition_id~ : Int) -> Json

DAVE Invalid Commit/Welcome (opcode 31) payload asking the server to reset the group after a rejected MLS message.

encode_resume

fn encode_resume(server_id~ : String, session_id~ : String, token~ : String, seq_ack~ : Int) -> Json

Voice gateway Resume (opcode 7) payload for reattaching to an existing session.

encode_select_protocol

fn encode_select_protocol(address~ : String, port~ : Int, mode~ : String) -> Json

Voice gateway Select Protocol (opcode 1) payload announcing the discovered UDP address and chosen encryption mode.

encode_speaking

fn encode_speaking(ssrc~ : UInt, flags~ : Int, delay? : Int) -> Json

Voice gateway Speaking (opcode 5) payload for the sender's SSRC.

encode_transition_ready

fn encode_transition_ready(transition_id~ : Int) -> Json

DAVE Transition Ready (opcode 23) payload acknowledging an announced protocol transition.

open_voice_udp

async fn open_voice_udp(ip : String, port : Int) -> &VoiceUdp

Open a connected UDP socket for a Discord voice server.

parse_binary_frame

fn parse_binary_frame(bytes : Bytes) -> (VoiceMessage, Int?) raise VoiceEnvelopeError

Decode a server-to-client binary DAVE frame. Its first two bytes are the big-endian v8 sequence number and the third byte is the opcode.

parse_ip_discovery_response

fn parse_ip_discovery_response(bytes : Bytes, expected_ssrc~ : UInt) -> (String, Int) raise VoiceUdpError

Parse Discord's 74-byte voice IP discovery response.

parse_rtp_packet

fn parse_rtp_packet(bytes : Bytes) -> RtpPacket raise RtpError

Parse an RTP v2 packet and retain extension data in payload.

parse_text_frame

fn parse_text_frame(text : String) -> (VoiceMessage, Int?) raise VoiceEnvelopeError

Decode a JSON voice gateway message and return its optional top-level buffered-resume sequence number.

rtpsize_header_len

fn rtpsize_header_len(bytes : Bytes) -> Int

Return the cleartext RTP header length used as AEAD associated data.

run_send_loop

async fn run_send_loop(source : &AudioSource, cipher : TransportCipher, udp : &VoiceUdp, gateway : VoiceGateway, ssrc~ : UInt, initial_sequence? : UInt16, initial_timestamp? : UInt, stop? :
Ref
[Bool], frame_duration_ms? : Int, dave? : DaveMachine, on_dave_error? : (String) -> Unit) -> Unit

Internal paced sender used by the M4 connection driver.

shim_available

fn shim_available() -> Bool

Whether a compatible transport-only ABI v3 voice shim is available.

shim_unavailable_reason

fn shim_unavailable_reason() -> String?

Explain why the native voice shim could not be loaded.

voice_on_close

fn voice_on_close(code : Int?, has_session : Bool) -> VoiceReconnectPolicy

Classify a voice WebSocket close code. A resumable transport failure still requires a fresh join when no established voice session exists.