Skip to content

Astradial Softphone App

Flutter-based mobile softphone (Android first, iOS deferred) that registers against the Astradial Asterisk PBX. Source repo: call_dial_app (in-repo progress log: PROGRESS.md).

What it is

A SIP softphone in the MicroSIP / Zoiper class, built specifically for Astradial PBX users. Place outbound, receive inbound, full call controls. Two interchangeable SIP backends share a single SipService Dart interface; swapping is a one-line toggle.

Backends

Backend Library Transport Default? Notes
WSS sip_ua ^1.1.0 (cloudwebrtc) over flutter_webrtc WebSocket Secure to :8089/ws ✅ Yes Pure-Dart, works on every Android phone (no native crash on 32-bit ARM). Requires the user's PJSIP endpoint to have webrtc=yes set on Asterisk.
PJSIP UDP VoiSmart pjsip-android v2.13.0 (vendored as a local Gradle module at android/sipservice/) UDP / TCP / TLS to :5080 ❌ No Native PJSUA2. Works for any extension regardless of webrtc=yes. Crashes on 32-bit-OS Android phones — VoiSmart's armeabi-v7a build of libopenh264.so has a buggy embedded unwinder that SIGSEGVs during PJSIP audio init.

The toggle lives at lib/sip/sip_service_provider.dart as a top-level const kUseNativePjsip. Stays false (WSS) by default — see Why default to WSS below.

QR-based credential provisioning

The editor's user-management page (Users → SIP icon) renders a QR that contains the user's SIP credentials in Zoiper provisioning format:

server=stagesip.astradial.com
port=5080
username=org_<orgid>_<extension>
password=<secret>
transport=udp
protocol=sip
wss_port=8089
wss_path=/ws

The wss_port and wss_path fields are an Astradial-specific extension to let WebRTC clients (the mobile app) read their connection details from the same QR that Zoiper-style softphones scan. Other softphones ignore unknown fields, so the QR works for both audiences.

Editor source: astradial-platform/editor/components/users/SipQrDialog.tsx. Defaults 8089 / /ws; override per-environment with NEXT_PUBLIC_WSS_PORT and NEXT_PUBLIC_WSS_PATH.

App side: lib/sip/qr_provisioning.dart parses the payload; lib/android/qr_scanner_page.dart is the fullscreen camera scan UI. Settings page has a "Scan QR" button that fills the form automatically. The port chosen depends on which backend toggle is active.

Why default to WSS

kUseNativePjsip = false is deliberate, even though PJSIP is the more universally compatible protocol choice:

  1. Hardware compatibility. WSS uses Dart's WebSocket libs — no native code that crashes on 32-bit ARM. Tested on a Redmi 9A (Android 10, 32-bit OS).
  2. Audience. The users who can't use WSS are non-admin extensions whose Asterisk endpoint lacks webrtc=yes. Those users are blocked at the API layer regardless of the toggle — userProvisioningService.js only sets the flag for role === 'admin' users today.
  3. Until the API gating changes (replace role === 'admin' with a per-user webrtc_enabled column), every onboarded user of the app is an admin and WSS serves them all without involving native code.

When the gating change lands, revisit the toggle and consider runtime ABI / account detection.

What's validated

  • ✅ WSS REGISTER + outbound + inbound + audio on a real 32-bit Android phone (Redmi 9A, Android 10).
  • ✅ PJSIP UDP REGISTER on the x86_64 Android emulator (Android 14) against stagesip.astradial.com:5080 — note the emulator's NAT doesn't forward DNS to native libs, so the IP must be entered directly. Outbound INVITE / signaling works; RTP audio is blocked by emulator double-NAT (known limitation, real devices unaffected).
  • ✅ QR scanner end-to-end against stageeditor.astradial.com.

Known limitations

  • PJSIP audio path on 32-bit ARM: SIGSEGV inside libopenh264.so __gnu_Unwind_Resume during PJSIP audio init. Mitigation: stay on WSS for those devices (the default). Investigated mitigations (packaging.jniLibs.pickFirsts for libc++_shared.so, excluding openh264 from packaging) didn't resolve it because libpjsua2.so itself dynamically links openh264 at the ELF level.
  • Inbound calls on PJSIP: not yet validated end-to-end — needs a 64-bit-OS test phone (emulator can't do bidirectional UDP RTP, and the Redmi 9A hits the openh264 bug). The wiring is in place; just no real-device confirmation yet.
  • iOS: deferred. Will need CallKit + PushKit + a server-side push-INVITE bridge for true push (Astradial doesn't currently push to mobile clients).
  • WSS port (8089) is enabled per-environment in /etc/asterisk/http.conf (the only Asterisk config the API does not manage). See astradial-platform/api/docs/SOFTPHONE_INTEGRATION.md for the full per-VPS enablement runbook.
  • Per-org webrtc=yes enablement is patched into userProvisioningService.js, gated on role === 'admin' so plain UDP softphones (Zoiper) keep working for non-admin users.

Environment cheat sheet

Field Staging
Server stagesip.astradial.com
Port (WSS) 8089
Port (UDP) 5080
Path (WSS) /ws
Username org_<orgid>_<extension> (admin role for WSS)
Password from the API, or auto-filled by scanning the QR