Talon 99937c9446 feat(ios): auto-reconnect + audio-device-change recovery
Network drops (e.g. Wi-Fi -> cellular) and audio-device plug/unplug (wired
headphones, AirPods) used to leave the iOS client in a dead/zombie state:
the engine went silent, no reconnect was attempted, and a live-session
disconnect waited 30-60 s for the C core's TCP keepalive/reaper timeout.

Reconnect (AppState.swift, SessionState.swift):
- Two-layer reconcile. Once SessionState overwrites client.onEvent at auth
  success, AppState.handleConnectEvent no longer sees live-session events.
  Added a weak SessionState.appState; SessionState.handleEvent .disconnected
  calls appState.onLiveSessionDisconnected after the cue -- the single path
  AppState learns a live session dropped. Shared teardownLiveSessionAndReconnect
  snapshots lastSession, stops audio, releases session/VoiceCatClient (io-
  thread join via vc_client_destroy), resets the backoff, and arms
  scheduleReconnect (exponential 1s -> 30s cap, indefinite, restored on auth
  success via existing TOFU_MATCHED auto-confirm + idempotent join_channel).
- NWPathMonitor now runs WHILE CONNECTED (not only mid-reconnect). On a Wi-Fi
  <-> cellular interface change or path .unsatisfied it calls
  proactiveReconnect: tearing the session down BEFORE the C core notices the
  dead socket collapses the 30-60 s reaper wait into ~1 s + first backoff
  tick. Same-interface refreshes (BSSID roams) are ignored via pathSignature.
  While mid-reconnect a .satisfied path resets the backoff for a fast retry.
- User-initiated disconnect()/cancelConnect() set userInitiatedDisconnect
  and cancel all reconnect state (task + monitor + lastSession + connectedServer).

Audio recovery (AudioSessionManager.swift, IOSVoiceProcessingEngine.swift):
- Intent-gated recoverAudio() replaces the narrow .oldDeviceUnavailable/
  .newDeviceAvailable route-change guard; fires on every externally-initiated
  route change reason except the ones we cause ourselves (.categoryChange/
  .routeConfigurationChange) to avoid a notification loop. Interruption-end
  now always recovers instead of only when .shouldResume is set.
- Added AVAudioEngineConfigurationChange observer on the engine so a system
  self-stop after our route-change handler wins the race is caught.
- IOSAudioEngine.rebuild() does a one-shot reactivation-retry on
  engine.start() failure (iOS sometimes refuses until the session is
  re-reactivated -- the silent-death case).

No C ABI / voicecat.h / proto / core changes. Swift-only. iOS sim build green
via scripts/build-ios-client.sh --no-configure (Xcode 26.5 / iOS 18.0 sim).
2026-06-25 14:57:13 +02:00

VoiceCat

Self-hosted, native voice & text chat in the spirit of classic TeamSpeak / Mumble — channel-based voice, channel + private text, one server you run yourself. Plain TCP (control) and UDP (media), no WebRTC. Encrypted by default. A shared C++ core (libvoicecat) drives native clients (Swift on macOS/iOS, C# on Windows) and the server.

Status: Design complete in docs/. M1M5 are implemented — real TLS control plane, encrypted UDP voice (Opus), multi-stream, TOFU identity pinning, channel tree, permissions, moderation, disconnect/keepalive/reaper. Windows WinForms C# client shipped (M4). macOS/iOS Swift client is next. See PROGRESS.md and docs/roadmap.md.

Read the design first

The docs/ folder is the source of truth. Start at docs/README.md, then architectureprotocolvoicesecuritytech-stackdeploymentroadmap.

Build

The default development preset is dev — it builds everything (server + tools + tests) with real vcpkg deps. It works on Windows, Linux, and macOS (vcpkg triplet auto-resolved).

# one-time vcpkg setup:
git clone https://github.com/microsoft/vcpkg && ./vcpkg/bootstrap-vcpkg.sh  # .bat on Windows
export VCPKG_ROOT=/path/to/vcpkg            # Linux/macOS; or $env:VCPKG_ROOT on PowerShell

# configure + build + test:
cmake --preset dev
cmake --build --preset dev
ctest --preset dev                          # 21 behavior tests

Artifacts land in build/dev/bin/ (voicecat-server, vccli, voicecat-admin).

The skeleton preset (no vcpkg deps, stubs only) is a fast smoke check that needs no third-party libraries:

cmake --preset skeleton && cmake --build --preset skeleton && ctest --preset skeleton

See docs/building.md for the full preset matrix (including release, server-release, windows-client, and Apple platform scaffolding).

Layout

docs/         design spec (read this)
core/         libvoicecat — the shared C++ core
  include/    voicecat.h  (the C ABI all clients call)
  proto/      voicecat.proto  (control-plane wire format, source of truth)
  src/        net/ crypto/ codec/ protocol/ session/ audio/  (stubs today)
server/       voicecat-server (headless; links the core)
tools/vccli/  headless test client — drives the protocol from M1 on
clients/      apple/ (Swift, M4)   windows/ (C#, M4)   — placeholders for now
tests/        CTest targets

License

Permissive-only dependencies (no GPL/LGPL) so the project can be redistributed freely, including closed-source. Project license: TBD (see docs/tech-stack.md §5).

Description
Native voice chat server and client
Readme 12 MiB
Languages
C++ 43.9%
Swift 30.6%
C# 17.5%
Shell 3.6%
CMake 2.1%
Other 2.3%