Loading...
Loading...
Compare original and translation side by side
Prerequisites: Create WebRTC credentials and generate a login token using the Telnyx server-side SDK. See theskill in your server language plugin (e.g.,telnyx-webrtc-*,telnyx-python).telnyx-javascript
前置条件: 创建WebRTC凭证,并使用Telnyx服务端SDK生成登录令牌。可参考对应服务端语言插件中的技能(例如telnyx-webrtc-*、telnyx-python)。telnyx-javascript
build.gradleallprojects {
repositories {
maven { url 'https://jitpack.io' }
}
}dependencies {
implementation 'com.github.team-telnyx:telnyx-webrtc-android:latest-version'
}build.gradleallprojects {
repositories {
maven { url 'https://jitpack.io' }
}
}dependencies {
implementation 'com.github.team-telnyx:telnyx-webrtc-android:latest-version'
}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" />
<!-- For push notifications (Android 14+) -->
<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"/>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" />
<!-- 推送通知所需权限(Android 14及以上) -->
<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"/>val telnyxClient = TelnyxClient(context)
telnyxClient.connect()
val credentialConfig = CredentialConfig(
sipUser = "your_sip_username",
sipPassword = "your_sip_password",
sipCallerIDName = "Display Name",
sipCallerIDNumber = "+15551234567",
fcmToken = fcmToken, // Optional: for push notifications
logLevel = LogLevel.DEBUG,
autoReconnect = true
)
telnyxClient.credentialLogin(credentialConfig)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)val tokenConfig = TokenConfig(
sipToken = "your_jwt_token",
sipCallerIDName = "Display Name",
sipCallerIDNumber = "+15551234567",
fcmToken = fcmToken,
logLevel = LogLevel.DEBUG,
autoReconnect = true
)
telnyxClient.tokenLogin(tokenConfig)val tokenConfig = TokenConfig(
sipToken = "your_jwt_token",
sipCallerIDName = "Display Name",
sipCallerIDNumber = "+15551234567",
fcmToken = fcmToken,
logLevel = LogLevel.DEBUG,
autoReconnect = true
)
telnyxClient.tokenLogin(tokenConfig)| Parameter | Type | Description |
|---|---|---|
| String | Credentials from Telnyx Portal |
| String? | Caller ID name displayed to recipients |
| String? | Caller ID number |
| String? | Firebase Cloud Messaging token for push |
| Any? | Raw resource ID or URI for ringtone |
| Int? | Raw resource ID for ringback tone |
| LogLevel | NONE, ERROR, WARNING, DEBUG, INFO, ALL |
| Boolean | Auto-retry login on failure (3 attempts) |
| Region | AUTO, US_EAST, US_WEST, EU_WEST |
| 参数 | 类型 | 描述 |
|---|---|---|
| String | 来自Telnyx门户的凭证 |
| String? | 向接收方展示的来电显示名称 |
| String? | 来电显示号码 |
| String? | 用于推送的Firebase Cloud Messaging令牌 |
| Any? | 铃声的原生资源ID或URI |
| Int? | 回铃音的原生资源ID |
| LogLevel | NONE, ERROR, WARNING, DEBUG, INFO, ALL |
| Boolean | 登录失败时自动重试(最多3次) |
| Region | AUTO, US_EAST, US_WEST, EU_WEST |
// Create a new outbound call
telnyxClient.call.newInvite(
callerName = "John Doe",
callerNumber = "+15551234567",
destinationNumber = "+15559876543",
clientState = "my-custom-state"
)// 创建新的外呼通话
telnyxClient.call.newInvite(
callerName = "John Doe",
callerNumber = "+15551234567",
destinationNumber = "+15559876543",
clientState = "my-custom-state"
)lifecycleScope.launch {
telnyxClient.socketResponseFlow.collect { response ->
when (response.status) {
SocketStatus.ESTABLISHED -> {
// Socket connected
}
SocketStatus.MESSAGERECEIVED -> {
response.data?.let { data ->
when (data.method) {
SocketMethod.CLIENT_READY.methodName -> {
// Ready to make/receive calls
}
SocketMethod.LOGIN.methodName -> {
// Successfully logged in
}
SocketMethod.INVITE.methodName -> {
// Incoming call!
val invite = data.result as InviteResponse
// Show incoming call UI, then accept:
telnyxClient.acceptCall(
invite.callId,
invite.callerIdNumber
)
}
SocketMethod.ANSWER.methodName -> {
// Call was answered
}
SocketMethod.BYE.methodName -> {
// Call ended
}
SocketMethod.RINGING.methodName -> {
// Remote party is ringing
}
}
}
}
SocketStatus.ERROR -> {
// Handle error: response.errorCode
}
SocketStatus.DISCONNECT -> {
// Socket disconnected
}
}
}
}lifecycleScope.launch {
telnyxClient.socketResponseFlow.collect { response ->
when (response.status) {
SocketStatus.ESTABLISHED -> {
// Socket已连接
}
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 -> {
// 处理错误:response.errorCode
}
SocketStatus.DISCONNECT -> {
// Socket已断开连接
}
}
}
}// Get current call
val currentCall: Call? = telnyxClient.calls[callId]
// End call
currentCall?.endCall(callId)
// Mute/Unmute
currentCall?.onMuteUnmutePressed()
// Hold/Unhold
currentCall?.onHoldUnholdPressed(callId)
// Send DTMF tone
currentCall?.dtmf(callId, "1")// 获取当前通话
val currentCall: Call? = telnyxClient.calls[callId]
// 结束通话
currentCall?.endCall(callId)
// 静音/取消静音
currentCall?.onMuteUnmutePressed()
// 保持/取消保持
currentCall?.onHoldUnholdPressed(callId)
// 发送DTMF音
currentCall?.dtmf(callId, "1")// Get all active calls
val calls: Map<UUID, Call> = telnyxClient.calls
// Iterate through calls
calls.forEach { (callId, call) ->
// Handle each call
}// 获取所有活跃通话
val calls: Map<UUID, Call> = telnyxClient.calls
// 遍历所有通话
calls.forEach { (callId, call) ->
// 处理每一路通话
}FirebaseMessaging.getInstance().token.addOnCompleteListener { task ->
if (task.isSuccessful) {
val fcmToken = task.result
// Use this token in your login config
}
}FirebaseMessaging.getInstance().token.addOnCompleteListener { task ->
if (task.isSuccessful) {
val fcmToken = task.result
// 在登录配置中使用该令牌
}
}FirebaseMessagingServiceclass MyFirebaseService : FirebaseMessagingService() {
override fun onMessageReceived(remoteMessage: RemoteMessage) {
val params = remoteMessage.data
val metadata = JSONObject(params as Map<*, *>).getString("metadata")
// Check for missed call
if (params["message"] == "Missed call!") {
// Show missed call notification
return
}
// Show incoming call notification (use Foreground Service)
showIncomingCallNotification(metadata)
}
}FirebaseMessagingServiceclass 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)
}
}// The SDK now handles decline automatically
telnyxClient.connectWithDeclinePush(
txPushMetaData = pushMetaData,
credentialConfig = credentialConfig
)
// SDK connects, sends decline, and disconnects automatically// SDK现在会自动处理拒接逻辑
telnyxClient.connectWithDeclinePush(
txPushMetaData = pushMetaData,
credentialConfig = credentialConfig
)
// SDK会自动连接、发送拒接信号并断开连接<service
android:name=".YourForegroundService"
android:foregroundServiceType="phoneCall"
android:exported="true" /><service
android:name=".YourForegroundService"
android:foregroundServiceType="phoneCall"
android:exported="true" />val credentialConfig = CredentialConfig(
// ... other config
debug = true // Enables call quality metrics
)
// Listen for quality updates
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}") // EXCELLENT, GOOD, FAIR, POOR, BAD
}
}| 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 |
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}") // EXCELLENT, GOOD, FAIR, POOR, BAD
}
}| 质量等级 | MOS范围 |
|---|---|
| EXCELLENT | > 4.2 |
| GOOD | 4.1 - 4.2 |
| FAIR | 3.7 - 4.0 |
| POOR | 3.1 - 3.6 |
| BAD | ≤ 3.0 |
telnyxClient.connectAnonymously(
targetId = "your_ai_assistant_id",
targetType = "ai_assistant", // Default
targetVersionId = "optional_version_id",
userVariables = mapOf("user_id" to "12345")
)telnyxClient.connectAnonymously(
targetId = "your_ai_assistant_id",
targetType = "ai_assistant", // 默认值
targetVersionId = "optional_version_id",
userVariables = mapOf("user_id" to "12345")
)// After anonymous login, call the AI Agent
telnyxClient.newInvite(
callerName = "User Name",
callerNumber = "+15551234567",
destinationNumber = "", // Ignored for AI Agent
clientState = "state",
customHeaders = mapOf(
"X-Account-Number" to "123", // Maps to {{account_number}}
"X-User-Tier" to "premium" // Maps to {{user_tier}}
)
)// 匿名登录后,呼叫AI Agent
telnyxClient.newInvite(
callerName = "User Name",
callerNumber = "+15551234567",
destinationNumber = "", // AI Agent场景下会忽略该字段
clientState = "state",
customHeaders = mapOf(
"X-Account-Number" to "123", // 映射到 {{account_number}}
"X-User-Tier" to "premium" // 映射到 {{user_tier}}
)
)lifecycleScope.launch {
telnyxClient.transcriptUpdateFlow.collect { transcript ->
transcript.forEach { item ->
println("${item.role}: ${item.content}")
// role: "user" or "assistant"
}
}
}lifecycleScope.launch {
telnyxClient.transcriptUpdateFlow.collect { transcript ->
transcript.forEach { item ->
println("${item.role}: ${item.content}")
// role: "user" 或 "assistant"
}
}
}// Send text message during active call
telnyxClient.sendAIAssistantMessage("Hello, I need help with my account")// 在活跃通话过程中发送文本消息
telnyxClient.sendAIAssistantMessage("Hello, I need help with my account")class MyLogger : TxLogger {
override fun log(level: LogLevel, tag: String?, message: String, throwable: Throwable?) {
// Send to your logging service
MyAnalytics.log(level.name, tag ?: "Telnyx", message)
}
}
val config = CredentialConfig(
// ... other config
logLevel = LogLevel.ALL,
customLogger = MyLogger()
)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.pro-keep class com.telnyx.webrtc.** { *; }
-dontwarn kotlin.Experimental$Level
-dontwarn kotlin.Experimental
-dontwarn kotlinx.coroutines.scheduling.ExperimentalCoroutineDispatcherproguard-rules.pro-keep class com.telnyx.webrtc.** { *; }
-dontwarn kotlin.Experimental$Level
-dontwarn kotlin.Experimental
-dontwarn kotlinx.coroutines.scheduling.ExperimentalCoroutineDispatcher| 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 |
| sender_id_mismatch (push) | FCM project mismatch - ensure app's |
| 问题 | 解决方案 |
|---|---|
| 没有声音 | 检查RECORD_AUDIO权限已被授予 |
| 收不到推送 | 验证配置中传入了正确的FCM令牌 |
| 登录失败 | 验证Telnyx门户中的SIP凭证是否正确 |
| 通话掉线 | 检查网络稳定性,开启 |
| sender_id_mismatch(推送相关) | FCM项目不匹配 —— 确保应用的 |
TelnyxClientTelnyxClientnewInviteacceptCallendCallTxSocketListeneronOfferReceivedonAnswerReceivedonByeReceivedonErrorReceivedSharedFlowsocketResponseFlowLiveDatasocketResponseLiveDatanewInviteacceptCallendCallTxSocketListeneronOfferReceivedonAnswerReceivedonByeReceivedonErrorReceivedSharedFlowsocketResponseFlowLiveDatasocketResponseLiveDataTxSocketTxSocketListenerTelnyxClientonOfferReceived(jsonObject: JsonObject)onAnswerReceived(jsonObject: JsonObject)onByeReceived(jsonObject: JsonObject)jsonObjectcausecauseCodesipCodesipReasonCallState.DONECallTerminationReasononErrorReceived(jsonObject: JsonObject)onClientReady(jsonObject: JsonObject)onGatewayStateReceived(gatewayState: String, receivedSessionId: String?)CallTelnyxClientCallCallStateCallStateCallDROPPED(reason: CallNetworkChangeReason)RECONNECTING(reason: CallNetworkChangeReason)DONE(reason: CallTerminationReason?)socketResponseFlow: SharedFlow<SocketResponse<ReceivedMessageBody>>SocketResponseBYEReceivedMessageBodycom.telnyx.webrtc.sdk.verto.receive.ByeResponsesocketResponseLiveData: LiveData<SocketResponse<ReceivedMessageBody>>socketResponseFlowTxSocketTxSocketListenerTelnyxClientonOfferReceived(jsonObject: JsonObject)onAnswerReceived(jsonObject: JsonObject)onByeReceived(jsonObject: JsonObject)jsonObjectcausecauseCodesipCodesipReasonCallState.DONECallTerminationReasononErrorReceived(jsonObject: JsonObject)onClientReady(jsonObject: JsonObject)onGatewayStateReceived(gatewayState: String, receivedSessionId: String?)CallTelnyxClientCallCallStateCallCallStateDROPPED(reason: CallNetworkChangeReason)RECONNECTING(reason: CallNetworkChangeReason)DONE(reason: CallTerminationReason?)socketResponseFlow: SharedFlow<SocketResponse<ReceivedMessageBody>>SocketResponseBYEReceivedMessageBodycom.telnyx.webrtc.sdk.verto.receive.ByeResponsesocketResponseLiveData: LiveData<SocketResponse<ReceivedMessageBody>>socketResponseFlow// Initializing the client
val telnyxClient = TelnyxClient(context)
// Observing responses using SharedFlow (Recommended)
lifecycleScope.launch {
telnyxClient.socketResponseFlow.collect { response ->
when (response.status) {
SocketStatus.MESSAGERECEIVED -> {
response.data?.let {
when (it.method) {
SocketMethod.INVITE.methodName -> {
val invite = it.result as InviteResponse
// Handle incoming call invitation
}
SocketMethod.BYE.methodName -> {
val bye = it.result as com.telnyx.webrtc.sdk.verto.receive.ByeResponse
// Call ended by remote party, bye.cause, bye.sipCode etc. are available
Log.d("TelnyxClient", "Call ended: ${bye.callId}, Reason: ${bye.cause}")
}
// Handle other methods like ANSWER, RINGING, etc.
}
}
}
SocketStatus.ERROR -> {
// Handle errors
Log.e("TelnyxClient", "Error: ${response.errorMessage}")
}
// Handle other statuses: ESTABLISHED, LOADING, DISCONNECT
}
}
}@Deprecated("Use socketResponseFlow instead. LiveData is deprecated in favor of Kotlin Flows.")
// Observing responses (including errors and BYE messages)
telnyxClient.socketResponseLiveData.observe(lifecycleOwner, Observer { response ->
when (response.status) {
SocketStatus.MESSAGERECEIVED -> {
response.data?.let {
when (it.method) {
SocketMethod.INVITE.methodName -> {
val invite = it.result as InviteResponse
// Handle incoming call invitation
}
SocketMethod.BYE.methodName -> {
val bye = it.result as com.telnyx.webrtc.sdk.verto.receive.ByeResponse
// Call ended by remote party, bye.cause, bye.sipCode etc. are available
Log.d("TelnyxClient", "Call ended: ${bye.callId}, Reason: ${bye.cause}")
}
// Handle other methods like ANSWER, RINGING, etc.
}
}
}
SocketStatus.ERROR -> {
// Handle errors
Log.e("TelnyxClient", "Error: ${response.errorMessage}")
}
// Handle other statuses: ESTABLISHED, LOADING, DISCONNECT
}
})
// Connecting and Logging In (example with credentials)
telnyxClient.connect(
credentialConfig = CredentialConfig(
sipUser = "your_sip_username",
sipPassword = "your_sip_password",
// ... other config ...
)
)
// Making a call
val outgoingCall = telnyxClient.newInvite(
callerName = "My App",
callerNumber = "+11234567890",
destinationNumber = "+10987654321",
clientState = "some_state"
)
// Observing the specific call's state
outgoingCall.callStateFlow.collect { state ->
if (state is CallState.DONE) {
Log.d("TelnyxClient", "Outgoing call ended. Reason: ${state.reason?.cause}")
}
// Handle other states
}// 初始化客户端
val telnyxClient = TelnyxClient(context)
// 使用SharedFlow监听响应(推荐)
lifecycleScope.launch {
telnyxClient.socketResponseFlow.collect { response ->
when (response.status) {
SocketStatus.MESSAGERECEIVED -> {
response.data?.let {
when (it.method) {
SocketMethod.INVITE.methodName -> {
val invite = it.result as InviteResponse
// 处理来电邀请
}
SocketMethod.BYE.methodName -> {
val bye = it.result as com.telnyx.webrtc.sdk.verto.receive.ByeResponse
// 通话被对方结束,可访问bye.cause、bye.sipCode等属性
Log.d("TelnyxClient", "Call ended: ${bye.callId}, Reason: ${bye.cause}")
}
// 处理其他方法如ANSWER、RINGING等
}
}
}
SocketStatus.ERROR -> {
// 处理错误
Log.e("TelnyxClient", "Error: ${response.errorMessage}")
}
// 处理其他状态:ESTABLISHED、LOADING、DISCONNECT
}
}
}@Deprecated("Use socketResponseFlow instead. LiveData is deprecated in favor of Kotlin Flows.")
// 监听响应(包括错误和BYE消息)
telnyxClient.socketResponseLiveData.observe(lifecycleOwner, Observer { response ->
when (response.status) {
SocketStatus.MESSAGERECEIVED -> {
response.data?.let {
when (it.method) {
SocketMethod.INVITE.methodName -> {
val invite = it.result as InviteResponse
// 处理来电邀请
}
SocketMethod.BYE.methodName -> {
val bye = it.result as com.telnyx.webrtc.sdk.verto.receive.ByeResponse
// 通话被对方结束,可访问bye.cause、bye.sipCode等属性
Log.d("TelnyxClient", "Call ended: ${bye.callId}, Reason: ${bye.cause}")
}
// 处理其他方法如ANSWER、RINGING等
}
}
}
SocketStatus.ERROR -> {
// 处理错误
Log.e("TelnyxClient", "Error: ${response.errorMessage}")
}
// 处理其他状态:ESTABLISHED、LOADING、DISCONNECT
}
})
// 连接和登录(凭证登录示例)
telnyxClient.connect(
credentialConfig = CredentialConfig(
sipUser = "your_sip_username",
sipPassword = "your_sip_password",
// ... 其他配置 ...
)
)
// 发起通话
val outgoingCall = telnyxClient.newInvite(
callerName = "My App",
callerNumber = "+11234567890",
destinationNumber = "+10987654321",
clientState = "some_state"
)
// 监听特定通话的状态
outgoingCall.callStateFlow.collect { state ->
if (state is CallState.DONE) {
Log.d("TelnyxClient", "Outgoing call ended. Reason: ${state.reason?.cause}")
}
// 处理其他状态
} telnyxClient = TelnyxClient(context) telnyxClient = TelnyxClient(context)fun connect(
providedServerConfig: TxServerConfiguration = TxServerConfiguration(),
credentialConfig: CredentialConfig,
txPushMetaData: String? = null,
autoLogin: Boolean = true,
)fun connect(
providedServerConfig: TxServerConfiguration = TxServerConfiguration(),
credentialConfig: CredentialConfig,
txPushMetaData: String? = null,
autoLogin: Boolean = true,
)val socketResponseFlow: SharedFlow<SocketResponse<ReceivedMessageBody>>val socketResponseFlow: SharedFlow<SocketResponse<ReceivedMessageBody>> telnyxClient.call.newInvite(callerName, callerNumber, destinationNumber, clientState) telnyxClient.call.newInvite(callerName, callerNumber, destinationNumber, clientState) fun getSocketResponse(): LiveData<SocketResponse<ReceivedMessageBody>>? =
telnyxClient.getSocketResponse() fun getSocketResponse(): LiveData<SocketResponse<ReceivedMessageBody>>? =
telnyxClient.getSocketResponse()callId: UUIDsessionId: StringcallStateFlow: StateFlow<CallState>CallState.NEWCallState.CONNECTINGCallState.RINGINGCallState.ACTIVECallState.HELDCallState.DONE(reason: CallTerminationReason?)reasonCallTerminationReasoncausecauseCodesipCodesipReasonCallState.ERRORCallState.DROPPED(reason: CallNetworkChangeReason)reasonCallNetworkChangeReason.NETWORK_LOSTCallNetworkChangeReason.NETWORK_SWITCHCallState.RECONNECTING(reason: CallNetworkChangeReason)reasononCallQualityChange: ((CallQualityMetrics) -> Unit)?audioManager: AudioManagerAudioManagerpeerConnection: Peer?callId: UUIDsessionId: StringcallStateFlow: StateFlow<CallState>CallState.NEWCallState.CONNECTINGCallState.RINGINGCallState.ACTIVECallState.HELDCallState.DONE(reason: CallTerminationReason?)reasonCallTerminationReasoncausecauseCodesipCodesipReasonCallState.ERRORCallState.DROPPED(reason: CallNetworkChangeReason)reasonCallNetworkChangeReason.NETWORK_LOSTCallNetworkChangeReason.NETWORK_SWITCHCallState.RECONNECTING(reason: CallNetworkChangeReason)reasononCallQualityChange: ((CallQualityMetrics) -> Unit)?audioManager: AudioManagerAudioManagerpeerConnection: Peer?newInvite(...)TelnyxClientacceptCall(...)TelnyxClientendCall(callId: UUID)TelnyxClientCallonMuteUnmutePressed()onLoudSpeakerPressed()onHoldUnholdPressed(callId: UUID)dtmf(callId: UUID, tone: String)newInvite(...)TelnyxClientacceptCall(...)TelnyxClientendCall(callId: UUID)TelnyxClientCallonMuteUnmutePressed()onLoudSpeakerPressed()onHoldUnholdPressed(callId: UUID)dtmf(callId: UUID, tone: String)callStateFlowACTIVERECONNECTINGDONE// Example: Observing call state in a ViewModel or Composable
viewModelScope.launch {
myCall.callStateFlow.collect { state ->
when (state) {
is CallState.ACTIVE -> {
// Update UI to show active call controls
}
is CallState.DONE -> {
// Call has ended, update UI
// Access state.reason for termination details
val reasonDetails = state.reason?.let {
"Cause: ${it.cause}, SIP Code: ${it.sipCode}"
} ?: "No specific reason provided."
Log.d("Call Ended", "Reason: $reasonDetails")
}
is CallState.DROPPED -> {
// Call dropped, possibly show a message with state.reason.description
Log.d("Call Dropped", "Reason: ${state.callNetworkChangeReason.description}")
}
is CallState.RECONNECTING -> {
// Call is reconnecting, update UI
Log.d("Call Reconnecting", "Reason: ${state.callNetworkChangeReason.description}")
}
// Handle other states like NEW, CONNECTING, RINGING, HELD, ERROR
else -> { /* ... */ }
}
}
}TelnyxClientcallStateFlowACTIVERECONNECTINGDONE// 示例:在ViewModel或Composable中监听通话状态
viewModelScope.launch {
myCall.callStateFlow.collect { state ->
when (state) {
is CallState.ACTIVE -> {
// 更新UI以展示活跃通话控件
}
is CallState.DONE -> {
// 通话已结束,更新UI
// 访问state.reason获取终止详情
val reasonDetails = state.reason?.let {
"Cause: ${it.cause}, SIP Code: ${it.sipCode}"
} ?: "未提供具体原因。"
Log.d("Call Ended", "Reason: $reasonDetails")
}
is CallState.DROPPED -> {
// 通话已掉线,可展示包含state.reason.description的提示
Log.d("Call Dropped", "Reason: ${state.callNetworkChangeReason.description}")
}
is CallState.RECONNECTING -> {
// 通话正在重连,更新UI
Log.d("Call Reconnecting", "Reason: ${state.callNetworkChangeReason.description}")
}
// 处理其他状态如NEW、CONNECTING、RINGING、HELD、ERROR
else -> { /* ... */ }
}
}
}TelnyxClientdata class ReceivedMessageBody(val method: String, val result: ReceivedResult?)ReceivedResultdata class ReceivedMessageBody(val method: String, val result: ReceivedResult?)ReceivedResultdata class ReceivedMessageBody(
val method: String, // The Telnyx Message Method (e.g., "telnyx_rtc.invite", "telnyx_rtc.bye")
val result: ReceivedResult? // The content of the actual message
)method: StringSocketMethodSocketMethod.INVITESocketMethod.ANSWERSocketMethod.BYEwhenresultresult: ReceivedResult?ReceivedResultresultmethodmethodSocketMethod.LOGIN.methodNameresultLoginResponsemethodSocketMethod.INVITE.methodNameresultInviteResponsemethodSocketMethod.ANSWER.methodNameresultAnswerResponsemethodSocketMethod.BYE.methodNameresultcom.telnyx.webrtc.sdk.verto.receive.ByeResponseByeResponsecausecauseCodesipCodesipReasoncallIdReceivedResultRingingResponseMediaResponseDisablePushResponsedata class ReceivedMessageBody(
val method: String, // Telnyx消息方法(例如"telnyx_rtc.invite"、"telnyx_rtc.bye")
val result: ReceivedResult? // 实际消息的内容
)method: StringSocketMethodSocketMethod.INVITESocketMethod.ANSWERSocketMethod.BYEwhenresultresult: ReceivedResult?ReceivedResultresultmethodmethodSocketMethod.LOGIN.methodNameresultLoginResponsemethodSocketMethod.INVITE.methodNameresultInviteResponsemethodSocketMethod.ANSWER.methodNameresultAnswerResponsemethodSocketMethod.BYE.methodNameresultcom.telnyx.webrtc.sdk.verto.receive.ByeResponseByeResponsecallIdcausecauseCodesipCodesipReasonReceivedResultRingingResponseMediaResponseDisablePushResponseTelnyxClient.socketResponseLiveDataSocketResponse<ReceivedMessageBody>SocketStatus.MESSAGERECEIVEDdataSocketResponseReceivedMessageBodytelnyxClient.socketResponseLiveData.observe(this, Observer { response ->
if (response.status == SocketStatus.MESSAGERECEIVED) {
response.data?.let { receivedMessageBody ->
Log.d("SDK_APP", "Method: ${receivedMessageBody.method}")
when (receivedMessageBody.method) {
SocketMethod.LOGIN.methodName -> {
val loginResponse = receivedMessageBody.result as? LoginResponse
// Process login response
}
SocketMethod.INVITE.methodName -> {
val inviteResponse = receivedMessageBody.result as? InviteResponse
// Process incoming call invitation
}
SocketMethod.BYE.methodName -> {
val byeResponse = receivedMessageBody.result as? com.telnyx.webrtc.sdk.verto.receive.ByeResponse
byeResponse?.let {
// Process call termination, access it.cause, it.sipCode, etc.
Log.i("SDK_APP", "Call ${it.callId} ended. Reason: ${it.cause}, SIP Code: ${it.sipCode}")
}
}
// Handle other methods...
}
}
}
})methodresultTelnyxClient.socketResponseLiveDataSocketResponse<ReceivedMessageBody>SocketStatus.MESSAGERECEIVEDSocketResponsedataReceivedMessageBodytelnyxClient.socketResponseLiveData.observe(this, Observer { response ->
if (response.status == SocketStatus.MESSAGERECEIVED) {
response.data?.let { receivedMessageBody ->
Log.d("SDK_APP", "Method: ${receivedMessageBody.method}")
when (receivedMessageBody.method) {
SocketMethod.LOGIN.methodName -> {
val loginResponse = receivedMessageBody.result as? LoginResponse
// 处理登录响应
}
SocketMethod.INVITE.methodName -> {
val inviteResponse = receivedMessageBody.result as? InviteResponse
// 处理来电邀请
}
SocketMethod.BYE.methodName -> {
val byeResponse = receivedMessageBody.result as? com.telnyx.webrtc.sdk.verto.receive.ByeResponse
byeResponse?.let {
// 处理通话终止,访问it.cause、it.sipCode等属性
Log.i("SDK_APP", "Call ${it.callId} ended. Reason: ${it.cause}, SIP Code: ${it.sipCode}")
}
}
// 处理其他方法...
}
}
}
})methodresult