# Voice Subsystem Keeper

> Work with DiscordPHP's voice subsystem — voice gateway protocol opcodes, encryption (VoiceGroupCrypto), voice packets, audio streaming, and Discord.php voice integration. Use when touching Voice/*, joinVoiceChannel, or voice encryption/packet logic.

- Skill: `discord-php/voice-subsystem-keeper` (Agent Skill)
- Install (CLI): `npx skillmds@latest add discord-php/voice-subsystem-keeper`
- Raw SKILL.md: https://api.skillmd.com/api/skills/discord-php/voice-subsystem-keeper/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- Author: discord-php (https://skillmd.com/u/discord-php)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/discord-php/voice-subsystem-keeper

---


# Skill: voice-subsystem-keeper

Use this skill when work touches `src/Discord/Voice/*`, `Discord::joinVoiceChannel()`, voice event handlers in `Discord.php`, or anything involving audio encryption and packets.

## Architecture overview

Voice support is split across three locations. Understand the boundary before touching any of them:

| Location | What lives here |
| --- | --- |
| `src/Discord/Voice/*` | Internal protocol types: opcodes (Hello, Ready, Speaking), session description, voice packets, encryption classes and traits. These are pure data/crypto — no audio I/O. |
| `src/Discord/Discord.php` | Runtime integration: `joinVoiceChannel()`, voice state update handlers, voice server update handlers. This is where the external voice client is wired to gateway events. |
| `discord-php-helpers/voice` (external package) | `Manager` and `VoiceClient`: actual audio I/O, Opus encoding, UDP transport, stream management. DiscordPHP delegates audio work here. |

**Rule:** do not blur these boundaries. Protocol types belong in `Voice/*`, audio I/O belongs in the external package, and wiring belongs in `Discord.php`.

## Read in this order

1. `src/Discord/Voice/VoiceGroupCrypto.php` — encryption/decryption base class
2. `src/Discord/Voice/VoiceGroupCryptoTrait.php` — mixin providing group-based AEAD crypto
3. `src/Discord/Voice/VoiceGroupCryptoInterface.php` — contract
4. `src/Discord/Voice/VoicePacket.php` — encrypted RTP packet encapsulation
5. `src/Discord/Voice/SessionDescription.php` — session key and mode negotiation
6. `src/Discord/Voice/Speaking.php`, `src/Discord/Voice/Hello.php`, `src/Discord/Voice/Ready.php`, `src/Discord/Voice/Resumed.php` — voice gateway opcodes
7. `src/Discord/Voice/Platform.php`, `src/Discord/Voice/Region.php` — enum helpers
8. `src/Discord/Discord.php` — search for `joinVoiceChannel`, `VOICE_STATE_UPDATE`, `VOICE_SERVER_UPDATE`
9. `src/Discord/Helpers/Buffer.php` — writable stream for audio buffering (extends EventEmitter)

## Core concepts

### Voice gateway protocol

Discord voice uses a separate WebSocket gateway from the main gateway. The handshake sequence is:

1. `Hello` — server sends heartbeat interval
2. `Identify` — client sends token + session
3. `Ready` — server sends UDP endpoint + SSRC
4. `Select Protocol` — client sends chosen encryption mode
5. `Session Description` — server sends secret key
6. `Speaking` — sent before/after transmitting audio

The classes in `src/Discord/Voice/` model these protocol steps as typed value objects.

### Encryption

`VoiceGroupCrypto` provides AEAD encryption for RTP packets. It depends on libsodium (`ext-sodium`). The trait `VoiceGroupCryptoTrait` provides the implementation; concrete classes select the cipher mode (e.g., `aead_xchacha20poly1305_ietf`).

- `LibSodiumNotFoundException` is thrown at runtime if the extension is absent — do not suppress it
- Cipher mode is negotiated via `SessionDescription`

### VoicePacket

`VoicePacket` encapsulates an encrypted RTP packet:
- SSRC identifies the audio source
- Sequence number and timestamp are required for RTP ordering
- The packet is encrypted before transmission using the session secret key

### Audio I/O (external package)

`discord-php-helpers/voice` owns all audio work:
- Opus codec encoding/decoding
- UDP socket management
- OGG/Opus stream handling
- FFmpeg process integration

**Do not replicate any of this in `src/Discord/Voice/`.** If you need to add audio capability, contribute to the external package or wrap it.

### Old* files

`OldVoiceClient.php`, `OldBuffer.php`, `OldOggStream.php`, `OldOggPage.php`, `OldOpusHead.php`, `OldOpusTags.php`, `OldReceiveStream.php` are legacy implementations. They are preserved for compatibility only.

**Do not extend, copy patterns from, or add new features to any `Old*` class.** Fix bugs in them only when the fix is isolated and does not require architectural change.

## Companion surfaces

When touching voice code, also inspect:

| Touching | Also inspect |
| --- | --- |
| `VoiceGroupCrypto` or crypto mode | `SessionDescription`, `VoicePacket`, `VoiceGroupCryptoInterface`, `LibSodiumNotFoundException` |
| `Speaking` or voice gateway opcode | `Hello`, `Ready`, `Resumed`, `SessionDescription` — full handshake chain |
| `Discord.php` voice handlers | Voice gateway opcodes, `Buffer`, external voice package `Manager` |
| `Buffer.php` | `Multipart.php` (similar streaming pattern), external voice package stream classes |
| Any new voice encryption mode | `VoiceGroupCryptoInterface`, crypto trait, `SessionDescription` mode list |

## Playbook: adding a new voice encryption mode

1. Add the mode constant to `SessionDescription`.
2. Implement the mode in a class using `VoiceGroupCryptoTrait` or extending `VoiceGroupCrypto`.
3. Register the mode in the external voice package's cipher negotiation if needed.
4. Update `VoiceGroupCryptoInterface` if the contract changes.
5. Verify libsodium function availability — throw `LibSodiumNotFoundException` if missing.
6. Add tests for encrypt/decrypt round-trip.

## Playbook: adding a voice gateway opcode

1. Create a typed value class under `src/Discord/Voice/` mirroring the Discord voice gateway docs.
2. Wire the opcode handler in `Discord.php` (find the voice WebSocket message handler).
3. Document the opcode sequence in the class docblock.
4. Do not put audio I/O logic in the opcode class — keep it as a typed payload.

## Design tripwires

- Adding audio codec, UDP, or FFmpeg logic inside `src/Discord/Voice/` — that belongs in the external voice package
- Extending any `Old*` class for new features
- Skipping libsodium availability check before using sodium functions
- Hard-coding a cipher mode instead of reading it from `SessionDescription`
- Blocking I/O inside voice packet or stream handlers — everything must be async/Promise-based
- Catching `LibSodiumNotFoundException` silently instead of surfacing it to the caller

## Reference files

- `src/Discord/Voice/VoiceGroupCrypto.php` — encryption base
- `src/Discord/Voice/VoicePacket.php` — RTP packet wrapper
- `src/Discord/Voice/SessionDescription.php` — session key/mode
- `src/Discord/Voice/Speaking.php` — voice speaking opcode
- `src/Discord/Helpers/Buffer.php` — writable stream helper
- `src/Discord/Exceptions/LibSodiumNotFoundException.php` — crypto dependency guard
- `src/Discord/Exceptions/OpusNotFoundException.php` — codec dependency guard
- `src/Discord/Exceptions/FFmpegNotFoundException.php` — audio tool dependency guard

