15 KiB
WebRTC Calling Service
Table of Contents
- Overview
- Call State Machine
- Android Implementation
- Desktop Implementation
- Common Call API
- IncomingCallAlertView
- Source Files
Executive Summary
WebRTC calling in SimpleX Chat operates over SMP (SimpleX Messaging Protocol) for signaling, with platform-specific WebRTC media implementations. Android uses a WebView-based approach with a dedicated CallActivity and foreground CallService, while Desktop opens the system browser and communicates via a NanoWSD WebSocket server on localhost. Both platforms share a common CallManager for call lifecycle and a CallState enum for state tracking. Call commands and responses are serialized as JSON and exchanged between the native layer and the WebRTC JavaScript layer.
1. Overview
Call signaling uses the same SMP protocol on all platforms -- call invitations, offers, answers, ICE candidates, and status updates flow through the chat backend via API commands. The WebRTC media plane, however, is implemented differently per platform:
- Android: WebView loads
call.htmlfrom bundled assets; a@JavascriptInterfacebridge (WebRTCInterface) forwards JSON messages between Kotlin and JavaScript. - Desktop: The system browser opens
http://localhost:50395/simplex/call/; a NanoWSD HTTP+WebSocket server servescall.htmlfrom classpath resources and relays JSON commands/responses over WebSocket.
Both platforms share the CallManager class (119 lines), which orchestrates incoming call acceptance, call ending, and notification management.
2. Call State Machine
Defined in WebRTC.kt:
enum class CallState {
WaitCapabilities, // Call initiated, waiting for local WebRTC capabilities
InvitationSent, // Invitation sent to peer via SMP
InvitationAccepted, // Peer's invitation accepted locally
OfferSent, // SDP offer sent to peer
OfferReceived, // SDP offer received from peer
AnswerReceived, // SDP answer received from peer
Negotiated, // ICE negotiation in progress
Connected, // Media flowing
Ended; // Call terminated
}
Outgoing call flow: WaitCapabilities -> InvitationSent -> OfferSent -> AnswerReceived -> Negotiated -> Connected -> Ended
Incoming call flow: InvitationAccepted -> OfferReceived -> Negotiated -> Connected -> Ended
State transitions are driven by WCallResponse messages from the WebRTC layer. Each transition typically triggers a corresponding API command (e.g., apiSendCallInvitation, apiSendCallOffer).
3. Android Implementation
3.1 CallActivity.kt (464 lines)
A dedicated ComponentActivity that hosts the call UI. Key responsibilities:
- Intent handling (line 64): On
AcceptCallActionintent, looks up the matchingRcvCallInvitationand callscallManager.acceptIncomingCall(). - Lock screen support (line 160):
unlockForIncomingCall()usessetShowWhenLocked(true)/setTurnScreenOn(true)on API 27+, falls back to window flags on older versions.lockAfterIncomingCall()reverses these settings. - Picture-in-Picture (line 99):
setPipParams()configures PiP aspect ratio and source rect hint. On Android 12+ (Build.VERSION_CODES.S), auto-enter PiP is enabled for video calls.onPictureInPictureModeChangedtogglesactiveCallViewIsCollapsedand sends aWCallCommand.Layoutcommand. - Permission checks (line 122): Checks
RECORD_AUDIOand conditionallyCAMERApermissions. - Service binding (line 181): Binds to
CallServiceas a workaround for Android 12 background activity launch restrictions. - CallActivityView composable (line 208): Renders
ActiveCallView()when permissions are granted and a call is active. ShowsCallPermissionsViewwhen permissions are needed. ShowsIncomingCallLockScreenAlertfor incoming calls on the lock screen.
3.2 CallService.kt (207 lines)
An Android foreground Service that keeps the call alive when the app is backgrounded:
- Foreground notification (line 131): Shows contact name (respecting
NotificationPreviewMode), call type (audio/video), a chronometer when connected, and an "End call" action button. - WakeLock (line 66): Acquires
PARTIAL_WAKE_LOCKto prevent CPU sleep during calls. - Notification channel (line 121): Creates
CALL_NOTIFICATION_CHANNEL_IDwithIMPORTANCE_DEFAULT. - Foreground service type (line 100): Uses
MEDIA_PLAYBACK | MICROPHONE(+CAMERAfor video) on API 30+,REMOTE_MESSAGINGon API 34+ when no active call. - Binder (line 158):
CallServiceBinderallowsCallActivityto callupdateNotification()when call state changes. - CallActionReceiver (line 170):
BroadcastReceiverthat handles theEndCallActionfrom the notification.
3.3 CallView.android.kt (891 lines)
The actual platform implementation of ActiveCallView() and supporting composables:
- ActiveCallState (line 74): Manages proximity lock (screen-off wake lock),
CallAudioDeviceManagerfor audio routing (earpiece/speaker/bluetooth),CallSoundsPlayerfor ringtones and vibration. ImplementsCloseableto clean up resources on call end. - ActiveCallView (line 114): Renders
WebRTCViewplusActiveCallOverlay. HandlesWCallResponsemessages and dispatches corresponding API calls. Manages volume control stream (STREAM_VOICE_CALL), screen keep-on, and call command lifecycle. - WebRTCView (line 691): Creates/reuses a static
WebViewviaAndroidView. ConfiguresWebViewAssetLoaderfor local asset loading. Sets upWebRTCInterfaceJavaScript bridge. Loadsfile:android_asset/www/android/call.html. ProcessesWCallCommandqueue by evaluatingprocessCommand()JavaScript. - ActiveCallOverlayLayout (line 329): Full overlay with mic toggle, speaker/device selector, end call, video toggle, and camera flip buttons. Adapts layout for video vs audio calls.
- CallPermissionsView (line 569): Handles runtime permission requests for microphone and camera with a fallback to settings if the system dialog is not shown.
3.4 ActiveCallState
ActiveCallState (line 74 of CallView.android.kt):
| Component | Purpose |
|---|---|
proximityLock |
PROXIMITY_SCREEN_OFF_WAKE_LOCK -- turns screen off when phone is held to ear |
callAudioDeviceManager |
Manages audio routing between earpiece, speaker, Bluetooth, wired headset |
CallSoundsPlayer |
Plays connecting/ringing sounds and vibration patterns |
wasConnected |
Tracks if call ever connected (for end-of-call vibration) |
close() |
Stops sounds, vibrates on disconnect, releases proximity lock, clears audio manager overrides |
4. Desktop Implementation
4.1 CallView.desktop.kt (263 lines)
Desktop calls run WebRTC in the system browser, not an embedded WebView:
- NanoWSD server (line 209):
startServer()creates aNanoWSDinstance bound tolocalhost:50395. The server servescall.htmlfrom JAR resources at/assets/www/desktop/call.htmlfor the path/simplex/call/. All other paths serve resources from/assets/www/. - WebSocket communication (line 238):
MyWebSockethandles WebSocket frames from the browser.onMessagedeserializes JSON intoWVAPIMessageand forwards to the response handler.onClosetriggersWCallResponse.End. - WebRTCController (line 153): Opens
http://localhost:50395/simplex/call/viaLocalUriHandler. ProcessesWCallCommandqueue by sending JSON over WebSocket to all active connections. On dispose, sendsWCallCommand.Endand stops the server. - SendStateUpdates (line 137): Sends
WCallCommand.Descriptionwith call state and encryption info text to the browser for display. - ActiveCallView (line 28): Handles
WCallResponsemessages identically to Android (same state machine), plus aWCallCommand.Permissionmessage onCapabilitieserror for browser permission denial guidance.
5. Common Call API
Defined in SimpleXAPI.kt:
| Function | Line | Description |
|---|---|---|
apiGetCallInvitations |
L1842 | Retrieve pending call invitations from the backend |
apiSendCallInvitation |
L1849 | Send call invitation to a contact with CallType |
apiRejectCall |
L1854 | Reject an incoming call |
apiSendCallOffer |
L1859 | Send SDP offer with ICE candidates and capabilities |
apiSendCallAnswer |
L1866 | Send SDP answer with ICE candidates |
apiSendCallExtraInfo |
L1872 | Send additional ICE candidates discovered after initial exchange |
apiEndCall |
L1878 | Terminate a call |
apiCallStatus |
L1883 | Report WebRTC connection status to the backend |
All functions send commands via sendCmd() to the chat core and return Boolean success status (except apiGetCallInvitations which returns List<RcvCallInvitation>).
6. IncomingCallAlertView
IncomingCallAlertView.kt (128 lines)
An in-app notification banner shown when a call invitation arrives while the app is in the foreground:
- IncomingCallAlertView (line 27): Starts
SoundPlayerfor the ringtone (suppressed if already in a call view). ShowsIncomingCallAlertLayout. - IncomingCallAlertLayout (line 49): Colored banner with
ProfilePreviewof the caller, call type icon (audio/video), and three action buttons: Reject (red), Ignore (primary), Accept (green). - IncomingCallInfo (line 74): Shows the user profile image (for multi-user), call media type icon, and call type text (encrypted/unencrypted audio/video).
7. Source Files
| File | Path | Lines | Description |
|---|---|---|---|
CallView.kt |
common/src/commonMain/.../views/call/CallView.kt |
28 | expect fun ActiveCallView(), delivery receipt waiting |
CallView.android.kt |
common/src/androidMain/.../views/call/CallView.android.kt |
891 | Android WebView WebRTC, overlay, permissions |
CallView.desktop.kt |
common/src/desktopMain/.../views/call/CallView.desktop.kt |
263 | Desktop browser WebRTC via NanoWSD |
CallActivity.kt |
android/src/main/java/.../views/call/CallActivity.kt |
464 | Android call Activity, PiP, lock screen |
CallService.kt |
android/src/main/java/.../CallService.kt |
207 | Android foreground service for calls |
CallManager.kt |
common/src/commonMain/.../views/call/CallManager.kt |
119 | Call lifecycle management |
WebRTC.kt |
common/src/commonMain/.../views/call/WebRTC.kt |
-- | CallState enum, WCallCommand, WCallResponse types |
IncomingCallAlertView.kt |
common/src/commonMain/.../views/call/IncomingCallAlertView.kt |
128 | In-app incoming call notification banner |
SimpleXAPI.kt |
common/src/commonMain/.../model/SimpleXAPI.kt |
-- | Call API commands (L1837--L1881) |