discord-php-helpers/voice
Discord voice support for DiscordPHP: join voice channels, send and receive audio, with DAVE end-to-end encryption, gateway opcode handling, and FFI-based libdave integration. Requires PHP 8.3+, ext-json, and ext-ffi with libdave installed.
Architecture fit. Despite being catalogued as a Laravel/PHP package, discord-php-helpers/voice is not a Laravel library — it is a DiscordPHP extension that adds voice-gateway connectivity, audio playback, and recording. It has no service provider, no config publishing, no facade, and no Laravel integration surface. Architecturally it is a long-running daemon component: it runs on a ReactPHP event loop and assumes a persistent process, which is fundamentally incompatible with the php-fpm request/response lifecycle. In a Laravel estate it belongs in a supervisor-managed worker container, exchanging work with the application through a queue (Redis/SQS) or an internal API, not in web requests. Confirming that boundary is the first architectural decision, because it determines whether this is "a Composer package" or "a new deployable service."
Integration feasibility. The hard dependency is DiscordPHP itself — if we are not already running a DiscordPHP bot, this package is unreachable and the real scope is "adopt DiscordPHP + add voice." Beyond that the dependency chain is unusually heavy for a PHP package: PHP 8.3+, ext-json, ext-ffi, libdave as a platform shared library, plus ffmpeg, libsodium, and Opus for the codec paths. Because Discord mandated DAVE end-to-end encryption for all voice connections in March 2026, there is no unencrypted fallback: Manager::__construct() throws LibDaveNotFoundException immediately if ext-ffi or libdave cannot be loaded. This is not a runtime degradation, it is a hard boot failure — which is good for fail-fast clarity and bad for deploy portability. Managed PHP hosts and most shared hosting will not support FFI, so this must be containerised with a controlled base image.
Technical risk. Four risks dominate:
ext-ffi + a specific libdave build + header macro compatibility is the classic source of "works on my machine" breakage. v8.3.1 pins the setup script to libdave 1.2.1 and accepts its namespaced opaque-handle macros, which tells us that upstream ABI/header churn is active. Any libdave upgrade is a re-certification event, not a routine bump.Key questions to resolve before committing:
setup-libdave.sh release asset?RecordingFormat::WAV writes per-user files trivially, which makes it easy to accidentally create a compliance surface.Stack fit. The package fits a Laravel estate only as a sidecar worker: Laravel owns HTTP, auth, persistence, and job dispatch; a separate voice-worker container (built from a PHP 8.3 image with ext-ffi, ext-json, ext-gmp, libdave, ffmpeg, libsodium, Opus baked in) owns the Discord connection. Communication is one-directional by default — Laravel pushes playback/recording jobs onto a queue, the worker consumes and executes, and results are written back via the same Laravel API or database. Do not attempt to host the Discord client inside the app container or reuse the web container image; the native library set would bloat and harden the web tier for no benefit.
Migration path. This is greenfield rather than migration — there is no prior voice implementation to move. The path is: (1) a two-to-three day spike in a throwaway repo proving composer install + setup-libdave.sh + joinChannel + playFile in a container on our target platform; (2) harden that container into a versioned base image we own; (3) build the queue contract; (4) build the worker; (5) integrate into the existing bot process if one exists. The spike is not optional and should gate the estimate — the native dependency is where the schedule risk lives, and it is cheap to discover early.
Compatibility. PHP 8.3+ aligns with Laravel 11/12, so framework compatibility is a non-issue. The real compatibility matrix is OS/architecture × libdave version × FFmpeg build. Prefer Linux x64 with a pinned libdave asset and DISCORDPHP_DAVE_LIBRARY set explicitly in the image's environment, rather than relying on the setup script at deploy time — fetch the library at image build, never at runtime. Pin the package to a minor line (8.3.x) and treat every libdave or DiscordPHP major as a deliberate, tested upgrade. Note that the package version tracks DiscordPHP's major, so DiscordPHP upgrades are coupled.
Sequencing. Deliver in this order: container/spike → playback-only proof → queue integration → live-guild acceptance test → recording (if in scope) → hardening and runbooks. Recording should trail playback by a full cycle: it introduces file handling, per-user fan-out, and the compliance question, and stopRecording() finalisation is a state-machine edge worth proving separately. Keep the feature behind a flag until a staging guild has run clean for at least a week.
Maintenance. Ongoing maintenance is concentrated in the container and native library, not the PHP code. Expect a recurring, non-trivial chore: refresh the libdave build, rebuild the image, re-run the live voice smoke test. Budget roughly one upgrade cycle per quarter plus ad-hoc response to Discord protocol changes. Because the package moves fast (v8.0.0 through v8.3.1 with RC churn), read changelogs rather than assuming patch releases are inert — v8.3.1's one-argument joinChannel($channel) default is a behaviour change that affects how the manager is wired.
Support. Upstream support is a Discord guild and GitHub issues from a very small contributor base — adequate for a hobby bot, thin for a production dependency. Internal support capability must therefore be built: at least two engineers who can read the DAVE and audio-pipeline docs and debug a stuck voice session. The VoiceException marker interface is genuinely useful here — a single catch-all handler around joinChannel() covers LibDaveNotFoundException, EnterChannelDeniedException, CantSpeakInChannelException, FFmpegNotFoundException, and LibSodiumNotFoundException, so failure is at least classifiable and alertable rather than opaque.
Scaling. Scale is bounded by Discord's per-token voice session limits and by the worker's CPU/bandwidth, not by anything in Laravel. Each concurrent voice connection is a long-lived process holding a decoded PCM pipeline; audio transcoding via ffmpeg is CPU-bound and will be the first bottleneck. Plan horizontal scaling by running N worker instances with partitioned guild/channel assignments, and cap concurrency per worker explicitly rather than discovering the ceiling via saturation.
Failure modes. Enumerate and alert on: LibDaveNotFoundException at boot (image drift — should be caught by a startup healthcheck, not by traffic); missing ffmpeg/libsodium at playback time; gateway disconnects and reconnect storms; stalled playback with a pending promise after stop() (called out in the v8.3.1 release notes); partially written WAV files if the process is killed mid-recording; and silent audio with a healthy socket, which is the hardest to detect. Mitigations: startup self-check that constructs the Manager and fails the probe, supervisor-level restart policy, structured logging around session lifecycle, and a heartbeat that asserts audio is actually flowing rather than merely connected.
Ramp-up. Realistic onboarding is 1–2 weeks for an engineer already fluent in async PHP; 3–4 weeks for one who is not, because event loops and promise semantics cannot be absorbed while also learning the DAVE protocol. Assign the async-experienced engineer to the spike and treat the knowledge transfer to a second engineer as a deliverable. Given the low star count and thin dependents, the team should also document the fork escape hatch — where we would diverge, and what we would have to maintain — while the context is fresh, even if we never use it.
How can I help you explore Laravel packages today?