WebRTC Integration for Calls in Mobile Application
WebRTC — open standard for P2P real-time communication. Google maintains native SDK for Android and iOS, there are Flutter wrappers. Choosing WebRTC over Twilio/Vonage means more infrastructure control and lower operational costs on scale but requires independent signaling implementation, ICE management and TURN/STUN server deployment.
How WebRTC Call Works
Connection setup — multi-step process via ICE (Interactive Connectivity Establishment):
- Caller creates
PeerConnection, generatesoffer(SDP — Session Description Protocol) -
offertransmitted to peer via signaling channel (WebSocket) - Peer creates
PeerConnection, appliesoffer, generatesanswer -
answerreturns via signaling channel - Both clients exchange ICE candidates — potential network paths
- ICE agent selects best path and establishes P2P connection
ICE candidates three types: host (local IP), srflx (via STUN — public IP), relay (via TURN). Direct P2P (host/srflx) works in 70–80% cases. In corporate networks behind symmetric NAT need relay via TURN.
Native Android Implementation
Google maintains WebRTC Android SDK — io.getstream:stream-webrtc-android or directly from webrtc.org.
// Initialization
PeerConnectionFactory.initialize(
PeerConnectionFactory.InitializationOptions.builder(context)
.createInitializationOptions()
)
val factory = PeerConnectionFactory.builder()
.setAudioDeviceModule(JavaAudioDeviceModule.builder(context).createAudioDeviceModule())
.createPeerConnectionFactory()
// ICE configuration
val config = PeerConnection.RTCConfiguration(
listOf(
PeerConnection.IceServer.builder("stun:stun.l.google.com:19302").createIceServer(),
PeerConnection.IceServer.builder("turn:your-turn.example.com:3478")
.setUsername("user").setPassword("pass").createIceServer()
)
)
val peerConnection = factory.createPeerConnection(config, peerConnectionObserver)
// Audio track
val audioSource = factory.createAudioSource(MediaConstraints())
val audioTrack = factory.createAudioTrack("audio0", audioSource)
val localStream = factory.createLocalMediaStream("stream0")
localStream.addTrack(audioTrack)
peerConnection?.addStream(localStream)
Video added similarly via VideoCapturer — Camera2Capturer for native camera.
iOS: RTCPeerConnection
On iOS use same Google WebRTC SDK via CocoaPods (pod 'GoogleWebRTC') or Swift Package (google/webrtc).
let config = RTCConfiguration()
config.iceServers = [
RTCIceServer(urlStrings: ["stun:stun.l.google.com:19302"]),
RTCIceServer(urlStrings: ["turn:your-turn.example.com:3478"],
username: "user", credential: "pass")
]
config.sdpSemantics = .unifiedPlan
let constraints = RTCMediaConstraints(
mandatoryConstraints: nil,
optionalConstraints: ["DtlsSrtpKeyAgreement": "true"]
)
let peerConnection = factory.peerConnection(
with: config, constraints: constraints, delegate: self
)
CallKit integration mandatory for iOS — without it call doesn't get audio session priority. Details in VoIP calls article.
TURN Server: Why It's Not Optional
Without TURN server WebRTC fails behind corporate firewalls and symmetric NAT. This ~20–30% real users. Deploy coturn:
# /etc/turnserver.conf
listening-port=3478
listening-ip=0.0.0.0
relay-ip=YOUR_PUBLIC_IP
external-ip=YOUR_PUBLIC_IP
realm=your-domain.com
user=webrtc:strongpassword
lt-cred-mech
Public STUN from Google (stun.l.google.com) free but doesn't provide TURN. Need own or paid service (Twilio Network Traversal Service, Xirsys).
Signaling Protocol
WebRTC doesn't define signaling — developer responsibility. Minimum: WebSocket channel for SDP offer/answer and ICE candidates transmission.
// Send offer via WebSocket
fun createOffer() {
val constraints = MediaConstraints().apply {
mandatory.add(MediaConstraints.KeyValuePair("OfferToReceiveAudio", "true"))
}
peerConnection?.createOffer(object : SdpObserver {
override fun onCreateSuccess(sdp: SessionDescription) {
peerConnection?.setLocalDescription(this, sdp)
signalingChannel.send(json { "type" to "offer"; "sdp" to sdp.description })
}
// ...
}, constraints)
}
Session state: new → connecting → connected → disconnected → failed. Handle failed — try restartIce() or connection re-establishment. Without handler user sees hung call with no feedback.
Audio Quality
WebRTC includes Opus codec, echo cancellation (AEC), noise suppression (NS) and automatic gain control (AGC) by default. For real-time quality monitoring — WebRTC stats API:
peerConnection?.getStats { report ->
val inboundAudio = report.statsMap.values
.filterIsInstance<RTCInboundRtpStreamStats>()
.firstOrNull { it.kind == "audio" }
val packetsLost = inboundAudio?.packetsLost ?: 0
val jitter = inboundAudio?.jitter ?: 0.0
}
Jitter > 30 ms and loss > 5% — threshold for noticeable audio quality degradation.
Flutter
flutter_webrtc package wraps native WebRTC SDK. API similar to native but with extra layer. Production experience: package stable but updates lag behind native SDK — for critical vulnerabilities in WebRTC sometimes wait weeks for package update.
What's Included
Deploy TURN/STUN infrastructure, implement signaling server (WebSocket), integrate WebRTC SDK on mobile platforms, connect CallKit (iOS) / ConnectionService (Android), setup connection quality monitoring.
Timeline: 3–6 weeks for audio/video calls with infrastructure and system call API integration.







