Round 6. Codex disagreed with two of my three round-5 claims and was right about both; I had independently refuted one of them with a sharper test. C2 REFUTED (by my own measurement): client.id is NOT an owner bridge. One gst-launch process doing capture+playback produced TWO client objects (209 input, 210 output), no link-group, same application.process.id 20172. So client.id bridges a *connection*, not an owner, and GStreamer — the same framework pixelpass uses — splits them by default. Replaced with a conservative union, strongest first: node.link-group, owned pulse.module.id, client.id, node application.process.id, else fail closed. With a trap Codex did not flag: application.process.id is pipewire-pulse's PID for module-created streams, so bridging on it would fuse every Pulse module's legs into one owner and mass-exclude tunnel/RTP/loopback audio the user may legitimately want shared. Never bridge on that key when it equals the pipewire-pulse PID; keys 1-2 already cover those precisely. PID thus returns to the design in the CORRELATION role while remaining unusable in the IDENTITY role — and in that role a wrong answer fails closed. C3 CONCEDED: taint must be STICKY. Current-topology taint forgets buffered audio — an app that reads a tainted monitor, buffers, then closes its input leg would be relinked while still emitting peerspeak audio from the buffer, and no graph event marks the drain. Taint now persists per owner until its nodes disappear. Added §6.1.4 quantifying the arrival-side window (~10.6-21.3 ms quantum plus scheduling) and noting it is zero when taint roots already exist, which is the common case. C1 SUSTAINED with Codex's caveat: node-granular traversal is free for the monitor boundary, but over-taints Audio/Duplex nodes. Fail-closed, accepted for v1, documented as a known contradiction of the "Firefox with a mic stays shareable" promise on duplex devices. S1: endpoint props demoted to an optimization; bind-LinkInfo fallback is the correctness path. S2: readiness epoch + revalidate before each link creation. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
PeerSpeak
Decentralized, peer-to-peer voice chat — full-mesh, NAT-traversing, with no central server. Built in Rust on iroh (QUIC), PipeWire audio, the Opus codec, and an iced GUI.
Create a room, share the join ticket, and talk. Everyone connects directly to everyone else; relays are only used to punch through NATs when a direct path isn't available.
Screenshots
| Launch screen | In a room |
|---|---|
![]() |
![]() |
| Settings |
|---|
![]() |
Features
Rooms & sessions
- Create a room → shareable join ticket; join by pasting a ticket.
- Full-mesh multi-peer rooms with live presence.
- Recent-rooms list to hop back into a room someone's still in.
- Remembered nickname and in-call duration timer.
Audio
- PipeWire capture/playback, selectable input and output devices, per-app gain.
- Opus codec (48 kHz mono, 20 ms frames) with an adaptive jitter buffer + packet-loss concealment.
- Noise gate with a draggable threshold on a live mic meter (test your mic off-call too).
- Mix-bus soft limiter and opt-in echo cancellation (PipeWire WebRTC AEC + noise suppression).
Voice controls
- Self-mute, deafen, and rebindable push-to-talk.
- Per-peer volume, local mute, and speaking indicators.
Text chat
- In-room text chat over the gossip plane, with clickable links and inline image/audio attachments.
- Drag-selectable, copyable messages; right-click context menu on all text fields.
Shared music listening
- Build a personal playlist of local audio files with a full transport (play/pause, seek, reorder).
- Let others tune in: peers stream your current track, timeline-synced and gapless, sitting under voice at their own volume.
Screen share (via pixelpass)
- Share your screen; peers click 👁 Watch to open the stream in mpv (vlc fallback).
- Live badges on sharing peers; per-app audio capture.
Recording & notifications
- Local call recording (mic + incoming mix → WAV in
~/peerspeak-recordings/). - Desktop notifications and event chimes with per-event custom sound overrides.
UI & networking
- Selectable room layouts (3-Column, Bottom Dock, Drawer) with draggable, persisted dividers.
- 10 built-in themes (Catppuccin, Dracula, Nord, Tokyo Night, Gruvbox, Solarized…), all WCAG-AA checked.
- Network mode picker (relay-no-discovery default, full n0, or direct-only); retained-address reconnect.
- Config, window size/position, and all preferences persisted to
~/.config/peerspeak/.
See docs/FEATURES.md for the full inventory and field-test status, and docs/ARCHITECTURE.md for internals.
Roadmap
- Contacts & invites — friends list with invite-notification one-click join (design in
docs/contacts-plan.md). - Spatial audio & per-peer EQ.
- Soundboard — play short clips into the call mix.
- Room persistence / invite links beyond the raw ticket.
- Windows support — cross-compiles and launches under Wine today; needs a real WASAPI audio pass (see
docs/WINDOWS.md).
Building
PeerSpeak builds with a stable Rust toolchain (edition 2024). Install the system dependencies below, then:
cargo build --release
./target/release/peerspeak
System dependencies
Arch Linux
sudo pacman -S --needed rust pipewire opus pkgconf git
Debian / Ubuntu
sudo apt install build-essential pkg-config clang libclang-dev \
libpipewire-0.3-dev libopus-dev libasound2-dev libxcb1-dev
Plus a Rust toolchain via rustup. clang/libclang are needed for the PipeWire bindings (bindgen).
At runtime you need a running PipeWire server. Screen sharing additionally requires pixelpass on your PATH, and mpv (or vlc) to watch a peer's share.
Packaging
- Arch:
cd packaging && makepkg -si(usespackaging/PKGBUILD). - Debian/Ubuntu:
.debis built withcargo-debfrom the[package.metadata.deb]block inCargo.toml. Build inside a Debian/Ubuntu environment so the binary links that distro's glibc. - Windows: see
docs/WINDOWS.md.
License
PeerSpeak is licensed under the MIT License, © 2026 mollusk.
Third-party components bundled with PeerSpeak (the Rust dependency tree, the
statically bundled Opus codec on some builds, and embedded fonts) are all under
permissive licenses; their texts and a full dependency manifest are collected in
THIRD_PARTY_LICENSES.


