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
src/Discord/Voice/VoiceGroupCrypto.php — encryption/decryption base class
src/Discord/Voice/VoiceGroupCryptoTrait.php — mixin providing group-based AEAD crypto
src/Discord/Voice/VoiceGroupCryptoInterface.php — contract
src/Discord/Voice/VoicePacket.php — encrypted RTP packet encapsulation
src/Discord/Voice/SessionDescription.php — session key and mode negotiation
src/Discord/Voice/Speaking.php, src/Discord/Voice/Hello.php, src/Discord/Voice/Ready.php, src/Discord/Voice/Resumed.php — voice gateway opcodes
src/Discord/Voice/Platform.php, src/Discord/Voice/Region.php — enum helpers
src/Discord/Discord.php — search for joinVoiceChannel, VOICE_STATE_UPDATE, VOICE_SERVER_UPDATE
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:
Hello — server sends heartbeat interval
Identify — client sends token + session
Ready — server sends UDP endpoint + SSRC
Select Protocol — client sends chosen encryption mode
Session Description — server sends secret key
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
- Add the mode constant to
SessionDescription.
- Implement the mode in a class using
VoiceGroupCryptoTrait or extending VoiceGroupCrypto.
- Register the mode in the external voice package's cipher negotiation if needed.
- Update
VoiceGroupCryptoInterface if the contract changes.
- Verify libsodium function availability — throw
LibSodiumNotFoundException if missing.
- Add tests for encrypt/decrypt round-trip.
Playbook: adding a voice gateway opcode
- Create a typed value class under
src/Discord/Voice/ mirroring the Discord voice gateway docs.
- Wire the opcode handler in
Discord.php (find the voice WebSocket message handler).
- Document the opcode sequence in the class docblock.
- 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
1---2name: voice-subsystem-keeper3description: 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.4---56# Skill: voice-subsystem-keeper78Use this skill when work touches `src/Discord/Voice/*`, `Discord::joinVoiceChannel()`, voice event handlers in `Discord.php`, or anything involving audio encryption and packets.910## Architecture overview1112Voice support is split across three locations. Understand the boundary before touching any of them:1314| Location | What lives here |15| --- | --- |16| `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. |17| `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. |18| `discord-php-helpers/voice` (external package) | `Manager` and `VoiceClient`: actual audio I/O, Opus encoding, UDP transport, stream management. DiscordPHP delegates audio work here. |1920**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`.2122## Read in this order23241. `src/Discord/Voice/VoiceGroupCrypto.php` — encryption/decryption base class252. `src/Discord/Voice/VoiceGroupCryptoTrait.php` — mixin providing group-based AEAD crypto263. `src/Discord/Voice/VoiceGroupCryptoInterface.php` — contract274. `src/Discord/Voice/VoicePacket.php` — encrypted RTP packet encapsulation285. `src/Discord/Voice/SessionDescription.php` — session key and mode negotiation296. `src/Discord/Voice/Speaking.php`, `src/Discord/Voice/Hello.php`, `src/Discord/Voice/Ready.php`, `src/Discord/Voice/Resumed.php` — voice gateway opcodes307. `src/Discord/Voice/Platform.php`, `src/Discord/Voice/Region.php` — enum helpers318. `src/Discord/Discord.php` — search for `joinVoiceChannel`, `VOICE_STATE_UPDATE`, `VOICE_SERVER_UPDATE`329. `src/Discord/Helpers/Buffer.php` — writable stream for audio buffering (extends EventEmitter)3334## Core concepts3536### Voice gateway protocol3738Discord voice uses a separate WebSocket gateway from the main gateway. The handshake sequence is:39401. `Hello` — server sends heartbeat interval412. `Identify` — client sends token + session423. `Ready` — server sends UDP endpoint + SSRC434. `Select Protocol` — client sends chosen encryption mode445. `Session Description` — server sends secret key456. `Speaking` — sent before/after transmitting audio4647The classes in `src/Discord/Voice/` model these protocol steps as typed value objects.4849### Encryption5051`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`).5253- `LibSodiumNotFoundException` is thrown at runtime if the extension is absent — do not suppress it54- Cipher mode is negotiated via `SessionDescription`5556### VoicePacket5758`VoicePacket` encapsulates an encrypted RTP packet:59- SSRC identifies the audio source60- Sequence number and timestamp are required for RTP ordering61- The packet is encrypted before transmission using the session secret key6263### Audio I/O (external package)6465`discord-php-helpers/voice` owns all audio work:66- Opus codec encoding/decoding67- UDP socket management68- OGG/Opus stream handling69- FFmpeg process integration7071**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.7273### Old* files7475`OldVoiceClient.php`, `OldBuffer.php`, `OldOggStream.php`, `OldOggPage.php`, `OldOpusHead.php`, `OldOpusTags.php`, `OldReceiveStream.php` are legacy implementations. They are preserved for compatibility only.7677**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.7879## Companion surfaces8081When touching voice code, also inspect:8283| Touching | Also inspect |84| --- | --- |85| `VoiceGroupCrypto` or crypto mode | `SessionDescription`, `VoicePacket`, `VoiceGroupCryptoInterface`, `LibSodiumNotFoundException` |86| `Speaking` or voice gateway opcode | `Hello`, `Ready`, `Resumed`, `SessionDescription` — full handshake chain |87| `Discord.php` voice handlers | Voice gateway opcodes, `Buffer`, external voice package `Manager` |88| `Buffer.php` | `Multipart.php` (similar streaming pattern), external voice package stream classes |89| Any new voice encryption mode | `VoiceGroupCryptoInterface`, crypto trait, `SessionDescription` mode list |9091## Playbook: adding a new voice encryption mode92931. Add the mode constant to `SessionDescription`.942. Implement the mode in a class using `VoiceGroupCryptoTrait` or extending `VoiceGroupCrypto`.953. Register the mode in the external voice package's cipher negotiation if needed.964. Update `VoiceGroupCryptoInterface` if the contract changes.975. Verify libsodium function availability — throw `LibSodiumNotFoundException` if missing.986. Add tests for encrypt/decrypt round-trip.99100## Playbook: adding a voice gateway opcode1011021. Create a typed value class under `src/Discord/Voice/` mirroring the Discord voice gateway docs.1032. Wire the opcode handler in `Discord.php` (find the voice WebSocket message handler).1043. Document the opcode sequence in the class docblock.1054. Do not put audio I/O logic in the opcode class — keep it as a typed payload.106107## Design tripwires108109- Adding audio codec, UDP, or FFmpeg logic inside `src/Discord/Voice/` — that belongs in the external voice package110- Extending any `Old*` class for new features111- Skipping libsodium availability check before using sodium functions112- Hard-coding a cipher mode instead of reading it from `SessionDescription`113- Blocking I/O inside voice packet or stream handlers — everything must be async/Promise-based114- Catching `LibSodiumNotFoundException` silently instead of surfacing it to the caller115116## Reference files117118- `src/Discord/Voice/VoiceGroupCrypto.php` — encryption base119- `src/Discord/Voice/VoicePacket.php` — RTP packet wrapper120- `src/Discord/Voice/SessionDescription.php` — session key/mode121- `src/Discord/Voice/Speaking.php` — voice speaking opcode122- `src/Discord/Helpers/Buffer.php` — writable stream helper123- `src/Discord/Exceptions/LibSodiumNotFoundException.php` — crypto dependency guard124- `src/Discord/Exceptions/OpusNotFoundException.php` — codec dependency guard125- `src/Discord/Exceptions/FFmpegNotFoundException.php` — audio tool dependency guard