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:
- 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).
- 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.jsonly sets the flag forrole === 'admin'users today. - Until the API gating changes (replace
role === 'admin'with a per-userwebrtc_enabledcolumn), 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_Resumeduring PJSIP audio init. Mitigation: stay on WSS for those devices (the default). Investigated mitigations (packaging.jniLibs.pickFirstsforlibc++_shared.so, excluding openh264 from packaging) didn't resolve it becauselibpjsua2.soitself 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).
Related infrastructure¶
- WSS port (8089) is enabled per-environment in
/etc/asterisk/http.conf(the only Asterisk config the API does not manage). Seeastradial-platform/api/docs/SOFTPHONE_INTEGRATION.mdfor the full per-VPS enablement runbook. - Per-org
webrtc=yesenablement is patched intouserProvisioningService.js, gated onrole === '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 |