| name | telnyx-webrtc-client-android |
| description | Build VoIP calling apps on Android using Telnyx WebRTC SDK. Covers authentication, making/receiving calls, push notifications (FCM), call quality metrics, and AI Agent integration. Use when implementing real-time voice communication on Android. |
| metadata | {"author":"telnyx","product":"webrtc","language":"kotlin","platform":"android"} |
Telnyx WebRTC - Android SDK
Build real-time voice communication into Android applications using Telnyx WebRTC.
Prerequisites: Create WebRTC credentials and generate a login token using the Telnyx server-side SDK. See the telnyx-webrtc-* skill in your server language plugin (e.g., telnyx-python, telnyx-javascript).
Installation
Add JitPack repository to your project's build.gradle:
allprojects {
repositories {
maven { url 'https://jitpack.io' }
}
}
Add the dependency:
dependencies {
implementation 'com.github.team-telnyx:telnyx-webrtc-android:latest-version'
}
Required Permissions
Add to AndroidManifest.xml:
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_PHONE_CALL"/>
Authentication
Option 1: Credential-Based Login
val telnyxClient = TelnyxClient(context)
telnyxClient.connect()
val credentialConfig = CredentialConfig(
sipUser = "your_sip_username",
sipPassword = "your_sip_password",
sipCallerIDName = "Display Name",
sipCallerIDNumber = "+15551234567",
fcmToken = fcmToken,
logLevel = LogLevel.DEBUG,
autoReconnect = true
)
telnyxClient.credentialLogin(credentialConfig)
Option 2: Token-Based Login (JWT)
val tokenConfig = TokenConfig(
sipToken = "your_jwt_token",
sipCallerIDName = "Display Name",
sipCallerIDNumber = "+15551234567",
fcmToken = fcmToken,
logLevel = LogLevel.DEBUG,
autoReconnect = true
)
telnyxClient.tokenLogin(tokenConfig)
Configuration Options
| Parameter | Type | Description |
|---|
sipUser / sipToken | String | Credentials from Telnyx Portal |
sipCallerIDName | String? | Caller ID name displayed to recipients |
sipCallerIDNumber | String? | Caller ID number |
fcmToken | String? | Firebase Cloud Messaging token for push |
ringtone | Any? | Raw resource ID or URI for ringtone |
ringBackTone | Int? | Raw resource ID for ringback tone |
logLevel | LogLevel | NONE, ERROR, WARNING, DEBUG, INFO, ALL |
autoReconnect | Boolean | Auto-retry login on failure (3 attempts) |
region | Region | AUTO, US_EAST, US_WEST, EU_WEST |
Making Outbound Calls
telnyxClient.call.newInvite(
callerName = "John Doe",
callerNumber = "+15551234567",
destinationNumber = "+15559876543",
clientState = "my-custom-state"
)
Receiving Inbound Calls
Listen for socket events using SharedFlow (recommended):
lifecycleScope.launch {
telnyxClient.socketResponseFlow.collect { response ->
when (response.status) {
SocketStatus.ESTABLISHED -> {
}
SocketStatus.MESSAGERECEIVED -> {
response.data?.let { data ->
when (data.method) {
SocketMethod.CLIENT_READY.methodName -> {
}
SocketMethod.LOGIN.methodName -> {
}
SocketMethod.INVITE.methodName -> {
val invite = data.result as InviteResponse
telnyxClient.acceptCall(
invite.callId,
invite.callerIdNumber
)
}
SocketMethod.ANSWER.methodName -> {
}
SocketMethod.BYE.methodName -> {
}
SocketMethod.RINGING.methodName -> {
}
}
}
}
SocketStatus.ERROR -> {
}
SocketStatus.DISCONNECT -> {
}
}
}
}
Call Controls
val currentCall: Call? = telnyxClient.calls[callId]
currentCall?.endCall(callId)
currentCall?.onMuteUnmutePressed()
currentCall?.onHoldUnholdPressed(callId)
currentCall?.dtmf(callId, "1")
Handling Multiple Calls
val calls: Map<UUID, Call> = telnyxClient.calls
calls.forEach { (callId, call) ->
}
Push Notifications (FCM)
1. Setup Firebase
Add Firebase to your project and get an FCM token:
FirebaseMessaging.getInstance().token.addOnCompleteListener { task ->
if (task.isSuccessful) {
val fcmToken = task.result
}
}
2. Handle Incoming Push
In your FirebaseMessagingService:
class MyFirebaseService : FirebaseMessagingService() {
override fun onMessageReceived(remoteMessage: RemoteMessage) {
val params = remoteMessage.data
val metadata = JSONObject(params as Map<*, *>).getString("metadata")
if (params["message"] == "Missed call!") {
return
}
showIncomingCallNotification(metadata)
}
}
3. Decline Push Call (Simplified)
telnyxClient.connectWithDeclinePush(
txPushMetaData = pushMetaData,
credentialConfig = credentialConfig
)
Android 14+ Requirements
<service
android:name=".YourForegroundService"
android:foregroundServiceType="phoneCall"
android:exported="true" />
Call Quality Metrics
Enable metrics to monitor call quality in real-time:
val credentialConfig = CredentialConfig(
debug = true
)
lifecycleScope.launch {
currentCall?.callQualityFlow?.collect { metrics ->
println("MOS: ${metrics.mos}")
println("Jitter: ${metrics.jitter * 1000} ms")
println("RTT: ${metrics.rtt * 1000} ms")
println("Quality: ${metrics.quality}")
}
}
| Quality Level | MOS Range |
|---|
| EXCELLENT | > 4.2 |
| GOOD | 4.1 - 4.2 |
| FAIR | 3.7 - 4.0 |
| POOR | 3.1 - 3.6 |
| BAD | ≤ 3.0 |
AI Agent Integration
Connect to a Telnyx Voice AI Agent without traditional SIP credentials:
1. Anonymous Login
telnyxClient.connectAnonymously(
targetId = "your_ai_assistant_id",
targetType = "ai_assistant",
targetVersionId = "optional_version_id",
userVariables = mapOf("user_id" to "12345")
)
2. Start Conversation
telnyxClient.newInvite(
callerName = "User Name",
callerNumber = "+15551234567",
destinationNumber = "",
clientState = "state",
customHeaders = mapOf(
"X-Account-Number" to "123",
"X-User-Tier" to "premium"
)
)
3. Receive Transcripts
lifecycleScope.launch {
telnyxClient.transcriptUpdateFlow.collect { transcript ->
transcript.forEach { item ->
println("${item.role}: ${item.content}")
}
}
}
4. Send Text to AI Agent
telnyxClient.sendAIAssistantMessage("Hello, I need help with my account")
Custom Logging
Implement your own logger:
class MyLogger : TxLogger {
override fun log(level: LogLevel, tag: String?, message: String, throwable: Throwable?) {
MyAnalytics.log(level.name, tag ?: "Telnyx", message)
}
}
val config = CredentialConfig(
logLevel = LogLevel.ALL,
customLogger = MyLogger()
)
ProGuard Rules
If using code obfuscation, add to proguard-rules.pro:
-keep class com.telnyx.webrtc.** { *; }
-dontwarn kotlin.Experimental$Level
-dontwarn kotlin.Experimental
-dontwarn kotlinx.coroutines.scheduling.ExperimentalCoroutineDispatcher
Troubleshooting
| Issue | Solution |
|---|
| No audio | Check RECORD_AUDIO permission is granted |
| Push not received | Verify FCM token is passed in config |
| Login fails | Verify SIP credentials in Telnyx Portal |
| Call drops | Check network stability, enable autoReconnect |
| sender_id_mismatch (push) | FCM project mismatch - ensure app's google-services.json matches server credentials |
Resources