@tencent-rtc/mcp 1.4.1 → 1.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/package.json +1 -1
  2. package/resource/doc/callkit/core-sdk/android/answer-call.md +852 -0
  3. package/resource/doc/callkit/core-sdk/android/make-call.md +954 -0
  4. package/resource/doc/callkit/core-sdk/android/offline-push.md +361 -0
  5. package/resource/doc/callkit/core-sdk/flutter/answer-call.md +1160 -0
  6. package/resource/doc/callkit/core-sdk/flutter/make-call.md +1205 -0
  7. package/resource/doc/callkit/core-sdk/flutter/offline-push.md +398 -0
  8. package/resource/doc/callkit/core-sdk/ios/answer-call.md +1090 -0
  9. package/resource/doc/callkit/core-sdk/ios/make-call.md +1248 -0
  10. package/resource/doc/callkit/core-sdk/ios/offline-push.md +485 -0
  11. package/resource/doc/callkit/core-sdk/uni-app/answer-call.md +950 -0
  12. package/resource/doc/callkit/core-sdk/uni-app/make-call.md +1052 -0
  13. package/resource/doc/callkit/preset/android/integration.md +1 -1
  14. package/resource/doc/callkit/preset/flutter/integration.md +1 -1
  15. package/resource/doc/callkit/preset/ios/integration.md +1 -1
  16. package/resource/doc/callkit/preset/react/api.md +1 -1
  17. package/resource/doc/callkit/preset/vue/api.md +1 -1
  18. package/server.js +1 -1
  19. package/tools/call/get_native_callkit_core_integration.js +67 -0
  20. package/tools/call/get_native_callkit_core_integration.js.map +1 -0
  21. package/tools/common/present_framework_choice.js +1 -1
  22. package/tools/common/present_framework_choice.js.map +1 -1
  23. package/tools/common/record_skill_session.js +17 -6
  24. package/tools/common/record_skill_session.js.map +1 -1
  25. package/tools/definitions/get_native_call_uikit_integration_def.js +9 -3
  26. package/tools/definitions/get_native_call_uikit_integration_def.js.map +1 -1
  27. package/tools/definitions/get_native_callkit_core_integration_def.js +63 -0
  28. package/tools/definitions/get_native_callkit_core_integration_def.js.map +1 -0
  29. package/tools/index.js +2 -0
  30. package/tools/index.js.map +1 -1
  31. package/utils/report-cls-client.js +1 -1
  32. package/utils/report-es-client.js +1 -1
  33. package/utils/report-runtime-error-cls-client.js +91 -0
  34. package/utils/report-runtime-error-cls-client.js.map +1 -0
@@ -0,0 +1,852 @@
1
+ This guide walks you through implementing the "answer call" feature using the **AtomicXCore SDK**, leveraging its **DeviceStore**, **CallStore**, and the core UI component **CallCoreView**.
2
+
3
+ ## Core Features
4
+
5
+ To build multi-party audio/video call features with **AtomicXCore**, you'll use the following core modules:
6
+
7
+ | **Module** | **Description** |
8
+ | --- | --- |
9
+ | [CallCoreView](https://liteav.sdk.cloudcachetci.com/doc/product/tuikit/atomic-x/android/en/-atomic-x%20-core%20-a-p-i/io.trtc.tuikit.atomicxcore.api.view/-call-core-view/index.html) | Core call view component. Automatically observes CallStore data and renders video, while supporting UI customization such as layout switching, avatar and icon configuration. |
10
+ | [CallStore](https://liteav.sdk.cloudcachetci.com/doc/product/tuikit/atomic-x/android/en/-atomic-x%20-core%20-a-p-i/io.trtc.tuikit.atomicxcore.api.call/-call-store/index.html) | Manages the call lifecycle: initiate, answer, reject, hang up. Provides real-time access to participant audio/video status, call timer, call records, and more. |
11
+ | [DeviceStore](https://liteav.sdk.cloudcachetci.com/doc/product/tuikit/atomic-x/android/en/-atomic-x%20-core%20-a-p-i/io.trtc.tuikit.atomicxcore.api.device/-device-store/index.html) | Controls audio/video devices: microphone (toggle/volume), camera (toggle/switch/quality), screen sharing, and real-time device status monitoring. |
12
+
13
+ ## Getting Started
14
+
15
+ ### Step 1: Activate the Service
16
+
17
+ Go to Activate the Service to obtain either the trial or paid version of the SDK.
18
+
19
+ ### Step 2: Integrate the SDK
20
+
21
+ **Install Components:** Add `api "io.trtc.uikit:atomicx-core:latest.release"` and `api "com.tencent.imsdk:imsdk-plus:8.7.7201"` to your **build.gradle** file, then run **Gradle Sync**.
22
+
23
+ ```groovy
24
+ dependencies {
25
+ api 'io.trtc.uikit:atomicx-core:latest.release'
26
+ api "com.tencent.imsdk:imsdk-plus:8.7.7201"
27
+ // other dependencies...
28
+ }
29
+ ```
30
+
31
+ ### Step 3: Initialize and Log In
32
+
33
+ To enable the call service, initialize CallStore and log in the user in sequence. CallStore will automatically sync user information after a successful login, entering the ready state. See the sample code below:
34
+
35
+ ```kotlin
36
+ class MainActivity : ComponentActivity() {
37
+ override fun onCreate(savedInstanceState: Bundle?) {
38
+ super.onCreate(savedInstanceState)
39
+ // Initialize CallStore
40
+ CallStore.shared
41
+
42
+ val sdkAppId = 1400000001 // Replace with your SDKAppID
43
+ val userId = "test_001" // Replace with your UserID
44
+ val userSig = "xxxxxxxxxxx" // Use a temporary userSig during development; when MCP is configured with sdkAppId and sdkSecretKey, the tool can issue it for you
45
+
46
+ LoginStore.shared.login(
47
+ this,
48
+ sdkAppId,
49
+ userId,
50
+ userSig,
51
+ object : CompletionHandler {
52
+ override fun onSuccess() {
53
+ // Complete TUICallEngine initialization
54
+ TUICallEngine.createInstance(this@MainActivity).init(sdkAppId, userId, userSig, null)
55
+ // Handle login success
56
+ Log.d("Login", "login success")
57
+ }
58
+
59
+ override fun onFailure(code: Int, desc: String) {
60
+ // Handle login failure
61
+ Log.e("Login", "login failed, code: $code, error: $desc")
62
+ }
63
+ }
64
+ )
65
+ }
66
+ }
67
+ ```
68
+
69
+ **Login parameters:**
70
+
71
+ | **Parameter** | **Type** | **Description** |
72
+ | --- | --- | --- |
73
+ | `userId` | `String` | Unique identifier for the current user. Only English letters, numbers, hyphens, and underscores are allowed. Avoid simple IDs (e.g., `1`, `123`) to prevent multi-device login conflicts. |
74
+ | `sdkAppId` | `int` | Obtain from the Tencent RTC console; typically a 10-digit integer starting with `140` or `160`. |
75
+ | `userSig` | `String` | Authentication token for TRTC. **Development**: locally generate a temporary `userSig` using [GenerateTestUserSig.genTestUserSig](https://github.com/Tencent-RTC/TUIKit_Android/blob/main/application/debug/src/main/java/com/tencent/qcloud/tuikit/debug/GenerateTestUserSig.java). When **Tencent RTC MCP Server** is configured with both `sdkAppId` and `sdkSecretKey`, the tool can issue a `userSig` for you automatically. **Production**: always generate `userSig` server-side with the SDKSecretKey; never embed or distribute the SDKSecretKey in the client. |
76
+
77
+ ## Implement Call Answering
78
+
79
+ > **Ensure the user is logged in before answering calls**. Follow these 6 steps to answer incoming calls.
80
+
81
+ ### Step 1: Create Call Page
82
+
83
+ Create a call page that launches when an incoming call is received.
84
+
85
+ 1. **Create the call page:** Implement a new Activity to serve as the call interface, triggered upon incoming call.
86
+
87
+ 2. **Bind the call page to `CallCoreView`:** The `CallCoreView` component automatically observes `CallStore` data and renders video, supporting UI customization like layout switching, avatar, and icon configuration.
88
+
89
+ ```kotlin
90
+ import io.trtc.tuikit.atomicxcore.api.view.CallCoreView
91
+
92
+ class CallActivity : AppCompatActivity() {
93
+ private var callCoreView: CallCoreView? = null
94
+
95
+ // 1. Create the call page container
96
+ override fun onCreate(savedInstanceState: Bundle?) {
97
+ super.onCreate(savedInstanceState)
98
+ // 2. Bind CallCoreView to the call page
99
+ callCoreView = CallCoreView(this)
100
+ setContentView(callCoreView)
101
+ }
102
+ }
103
+ ```
104
+
105
+ **CallCoreView Component Features:**
106
+
107
+ | **Feature** | **Description** | **Reference** |
108
+ | --- | --- | --- |
109
+ | **Set Layout Mode** | Switches layout modes flexibly. If not set, adapts layout automatically based on participant count. | Layout mode switching |
110
+ | **Set Avatar** | Allows custom avatars for users by providing avatar resource paths. | Custom default avatar |
111
+ | **Set Volume Indicator Icon** | Supports custom volume indicator icons for different volume levels. | Custom volume indicator icon |
112
+ | **Set Network Indicator Icon** | Configures network status indicator icons based on real-time network quality. | Custom network indicator icon |
113
+ | **Set Waiting Animation for Users** | In multi-party calls, supports GIF animations for users waiting to answer. | Custom loading animation |
114
+
115
+ ### Step 2: Add Answer and Reject Buttons
116
+
117
+ Use `DeviceStore` and `CallStore` APIs to customize your buttons.
118
+
119
+ **DeviceStore:** Controls microphone (toggle/volume), camera (toggle/switch/quality), screen sharing, and monitors device status. Bind methods to button click events and update UI in real time by observing device status changes.
120
+
121
+ **CallStore:** Provides core call controls (answer, hang up, reject). Bind methods to button click events and observe call status to keep button visibility in sync with the call phase.
122
+
123
+ **Icon Resources:** Download button icons from GitHub. These icons are designed for TUICallKit and are copyright-free.
124
+
125
+ | **Resource** | **Description** | **Download** |
126
+ | --- | --- | --- |
127
+ | `callview_ic_dialing.png` | Answer/accept call icon. | [GitHub](https://github.com/Tencent-RTC/TUIKit_Android/blob/main/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_dialing.png) |
128
+ | `callview_ic_hangup.png` | Hang up / reject call icon. | [GitHub](https://github.com/Tencent-RTC/TUIKit_Android/blob/main/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_hangup.png) |
129
+ | `callview_ic_camera_disable.png` | Camera off icon. | [GitHub](https://github.com/Tencent-RTC/TUIKit_Android/blob/main/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_camera_disable.png) |
130
+ | `callview_ic_camera_enable.png` | Camera on icon. | [GitHub](https://github.com/Tencent-RTC/TUIKit_Android/blob/main/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_camera_enable.png) |
131
+ | `callview_ic_mic_unmute.png` | Microphone unmuted icon. | [GitHub](https://github.com/Tencent-RTC/TUIKit_Android/blob/main/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_mic_unmute.png) |
132
+ | `callview_ic_mic_mute.png` | Microphone muted icon. | [GitHub](https://github.com/Tencent-RTC/TUIKit_Android/blob/main/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_mic_mute.png) |
133
+ | `callview_ic_handsfree_disable.png` | Handsfree off icon. | [GitHub](https://github.com/Tencent-RTC/TUIKit_Android/blob/main/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_handsfree_disable.png) |
134
+ | `callview_ic_handsfree_enable.png` | Handsfree on icon. | [GitHub](https://github.com/Tencent-RTC/TUIKit_Android/blob/main/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_handsfree_enable.png) |
135
+
136
+ **Implementation for adding "Answer" and "Reject" buttons:**
137
+
138
+ 1.1 **Add Answer and Reject Buttons:** Create a bottom button bar container and add "Answer" and "Reject" buttons, binding their click events to the `accept` and `reject` methods.
139
+
140
+ ```kotlin
141
+ import io.trtc.tuikit.atomicxcore.api.device.DeviceStore
142
+ import io.trtc.tuikit.atomicxcore.api.call.CallStore
143
+
144
+ class CallActivity : AppCompatActivity() {
145
+ private var buttonContainer: LinearLayout? = null
146
+ private var buttonAccept: Button? = null
147
+ private var buttonReject: Button? = null
148
+
149
+ override fun onCreate(savedInstanceState: Bundle?) {
150
+ super.onCreate(savedInstanceState)
151
+ // 1. Create the bottom button bar container
152
+ createButtonContainer()
153
+ // 2. Add "Answer" and "Reject" buttons
154
+ addRejectAndAcceptButtons()
155
+ setContentView(buttonContainer)
156
+ }
157
+
158
+ // Create the bottom button bar container
159
+ private fun createButtonContainer() {
160
+ buttonContainer = LinearLayout(this).apply {
161
+ orientation = LinearLayout.HORIZONTAL
162
+ gravity = Gravity.CENTER
163
+ layoutParams = FrameLayout.LayoutParams(
164
+ FrameLayout.LayoutParams.MATCH_PARENT,
165
+ FrameLayout.LayoutParams.WRAP_CONTENT
166
+ ).apply {
167
+ gravity = Gravity.BOTTOM
168
+ bottomMargin = dpToPx(80)
169
+ }
170
+ }
171
+ }
172
+
173
+ // Add "Answer" and "Reject" buttons
174
+ private fun addRejectAndAcceptButtons() {
175
+ createAcceptButton()
176
+ createRejectButton()
177
+ buttonContainer?.addView(buttonAccept)
178
+ buttonContainer?.addView(buttonReject)
179
+ }
180
+
181
+ // Create the Answer button
182
+ private fun createAcceptButton() {
183
+ buttonAccept = Button(this).apply {
184
+ text = "Answer"
185
+ layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f).apply {
186
+ marginEnd = dpToPx(8)
187
+ }
188
+ setOnClickListener {
189
+ // 3. Bind accept to Answer button click event
190
+ CallStore.shared.accept(null)
191
+ }
192
+ }
193
+ }
194
+
195
+ // Create the Reject button
196
+ private fun createRejectButton() {
197
+ buttonReject = Button(this).apply {
198
+ text = "Reject"
199
+ layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f).apply {
200
+ marginStart = dpToPx(8)
201
+ }
202
+ setOnClickListener {
203
+ // 3. Bind reject to Reject button click event
204
+ CallStore.shared.reject(null)
205
+ }
206
+ }
207
+ }
208
+ }
209
+ ```
210
+
211
+ 1.2 **Destroy the interface when the caller cancels or you reject:** If the caller cancels or the callee rejects, the `onCallEnded` event is triggered. Listen for this event to promptly close (destroy) the call interface when the call ends.
212
+
213
+ ```kotlin
214
+ import io.trtc.tuikit.atomicxcore.api.call.CallEndReason
215
+ import io.trtc.tuikit.atomicxcore.api.call.CallListener
216
+ import io.trtc.tuikit.atomicxcore.api.call.CallMediaType
217
+ import io.trtc.tuikit.atomicxcore.api.call.CallStore
218
+
219
+ class CallActivity : AppCompatActivity() {
220
+ private var callListener: CallListener? = null
221
+
222
+ override fun onCreate(savedInstanceState: Bundle?) {
223
+ super.onCreate(savedInstanceState)
224
+ // ... other initialization code
225
+ // 1. Listen for call ended events
226
+ addListener()
227
+ }
228
+
229
+ private fun addListener() {
230
+ callListener = object : CallListener() {
231
+ override fun onCallEnded(callId: String, mediaType: CallMediaType, reason: CallEndReason, userId: String) {
232
+ // 2. Close the page when the call ends
233
+ finish()
234
+ }
235
+ }
236
+ callListener?.let { CallStore.shared.addListener(it) }
237
+ }
238
+ }
239
+ ```
240
+
241
+ **onCallEnded Event Parameters:**
242
+
243
+ | **Parameter** | **Type** | **Description** |
244
+ | --- | --- | --- |
245
+ | `callId` | `String` | Unique identifier for this call. |
246
+ | `mediaType` | [CallMediaType](https://liteav.sdk.cloudcachetci.com/doc/product/tuikit/atomic-x/android/en/-atomic-x%20-core%20-a-p-i/io.trtc.tuikit.atomicxcore.api.call/-call-media-type/index.html) | Specifies whether it's an audio or video call.<br>- `CallMediaType.Video`: Video call.<br>- `CallMediaType.Audio`: Audio call. |
247
+ | `reason` | [CallEndReason](https://liteav.sdk.cloudcachetci.com/doc/product/tuikit/atomic-x/android/en/-atomic-x%20-core%20-a-p-i/io.trtc.tuikit.atomicxcore.api.call/-call-end-reason/index.html) | Reason for call ending.<br>- `Unknown`: Unknown reason.<br>- `Hangup`: User hung up.<br>- `Reject`: Callee rejected.<br>- `NoResponse`: Callee did not answer in time.<br>- `Offline`: Other party offline.<br>- `LineBusy`: Other party busy.<br>- `Canceled`: Caller canceled before callee answered.<br>- `OtherDeviceAccepted`: Answered on another device.<br>- `OtherDeviceReject`: Rejected on another device.<br>- `EndByServer`: Ended by server. |
248
+ | `userId` | `String` | User ID that triggered the end. |
249
+
250
+ ### Step 3: Request Audio/Video Permissions
251
+
252
+ Check audio/video permissions before connecting the call. If permissions are missing, prompt the user to grant them dynamically.
253
+
254
+ 1. **Declare permissions:** Add camera and microphone permissions in your `AndroidManifest.xml`.
255
+
256
+ ```xml
257
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
258
+ <!-- Microphone permission -->
259
+ <uses-permission android:name="android.permission.RECORD_AUDIO" />
260
+ <!-- Camera permission -->
261
+ <uses-permission android:name="android.permission.CAMERA" />
262
+
263
+ <application>
264
+ <!-- ... -->
265
+ <activity
266
+ android:name=".CallActivity"
267
+ android:exported="false"
268
+ android:screenOrientation="portrait" />
269
+ </application>
270
+ </manifest>
271
+ ```
272
+
273
+ 2. **Request permissions dynamically:** Request audio/video permissions when an incoming call is received or as needed in your app logic.
274
+
275
+ ```kotlin
276
+ // Dynamically request permissions
277
+ private fun requestAllCallPermissions() {
278
+ val allPermissions = arrayOf(
279
+ Manifest.permission.CAMERA,
280
+ Manifest.permission.RECORD_AUDIO
281
+ )
282
+ if (!checkPermissions(allPermissions)) {
283
+ ActivityCompat.requestPermissions(this, allPermissions, PERMISSION_REQUEST_CODE)
284
+ }
285
+ }
286
+
287
+ // Handle permission request result
288
+ override fun onRequestPermissionsResult(
289
+ requestCode: Int,
290
+ permissions: Array<out String>,
291
+ grantResults: IntArray
292
+ ) {
293
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults)
294
+
295
+ if (requestCode == PERMISSION_REQUEST_CODE) {
296
+ // Check if all permissions are granted
297
+ var allGranted = true
298
+ for (result in grantResults) {
299
+ if (result != PackageManager.PERMISSION_GRANTED) {
300
+ allGranted = false
301
+ break
302
+ }
303
+ }
304
+ if (allGranted) {
305
+ Log.d("MainActivity", "Permission granted")
306
+ } else {
307
+ Log.w("MainActivity", "Some permissions denied")
308
+ }
309
+ }
310
+ }
311
+ ```
312
+
313
+ ### Step 4: Incoming Call Notification
314
+
315
+ Monitor the user's call status and play a ringtone or vibration when an incoming call arrives. Stop the notification after the call is answered or hung up.
316
+
317
+ 1. **Subscribe to data layer:** Listen to `CallStore.observerState.selfInfo` for the current user's status.
318
+
319
+ 2. **Play/stop notification:** If `SelfInfo.Status` is `CallParticipantStatus.Waiting`, play ringtone/vibration. If status is `CallParticipantStatus.Accept`, stop the notification.
320
+
321
+ ```kotlin
322
+ import kotlinx.coroutines.CoroutineScope
323
+ import kotlinx.coroutines.Dispatchers
324
+ import kotlinx.coroutines.Job
325
+ import kotlinx.coroutines.launch
326
+
327
+ class MainActivity : AppCompatActivity() {
328
+ private var stateJob: Job? = null
329
+
330
+ override fun onCreate(savedInstanceState: Bundle?) {
331
+ super.onCreate(savedInstanceState)
332
+ // 1. Monitor current user's call status
333
+ observeSelfStatus()
334
+ }
335
+
336
+ // Monitor current user's call status
337
+ private fun observeSelfStatus() {
338
+ stateJob = CoroutineScope(Dispatchers.Main).launch {
339
+ CallStore.shared.observerState.selfInfo.collect { selfInfo ->
340
+ // 2. Play/stop incoming call notification
341
+ if (selfInfo.status == CallParticipantStatus.Waiting) {
342
+ // Start playing incoming call notification
343
+ }
344
+ if (selfInfo.status == CallParticipantStatus.Accept) {
345
+ // Stop playing incoming call notification
346
+ }
347
+ }
348
+ }
349
+ }
350
+ }
351
+ ```
352
+
353
+ ### Step 5: Open Media Devices on Incoming Call
354
+
355
+ When an incoming call is received, use the `onCallReceived` event to determine the media type. For a better experience, pre-open the relevant devices when the call interface is triggered.
356
+
357
+ 1. **Listen for incoming call event:** Subscribe to the `onCallReceived` event.
358
+
359
+ 2. **Open devices based on media type:** For audio calls, open the microphone. For video calls, open both microphone and camera.
360
+
361
+ ```kotlin
362
+ import io.trtc.tuikit.atomicxcore.api.device.DeviceStore
363
+ import io.trtc.tuikit.atomicxcore.api.call.CallMediaType
364
+ import io.trtc.tuikit.atomicxcore.api.call.*
365
+
366
+ class MainActivity : AppCompatActivity() {
367
+ private var callListener: CallListener? = null
368
+
369
+ override fun onCreate(savedInstanceState: Bundle?) {
370
+ super.onCreate(savedInstanceState)
371
+ // ... other initialization code
372
+ // 1. Listen for incoming call event
373
+ addListener()
374
+ }
375
+
376
+ private fun addListener() {
377
+ callListener = object : CallListener() {
378
+ override fun onCallReceived(callId: String, mediaType: CallMediaType, userData: String) {
379
+ super.onCallReceived(callId, mediaType, userData)
380
+ // 2. Open devices based on media type
381
+ openDeviceForMediaType(mediaType)
382
+ }
383
+ }
384
+ callListener?.let { CallStore.shared.addListener(it) }
385
+ }
386
+
387
+ private fun openDeviceForMediaType(mediaType: CallMediaType?) {
388
+ mediaType?.let {
389
+ DeviceStore.shared().openLocalMicrophone(null)
390
+ if (mediaType == CallMediaType.Video) {
391
+ val isFrontCamera = true
392
+ DeviceStore.shared().openLocalCamera(isFrontCamera, null)
393
+ }
394
+ }
395
+ }
396
+ }
397
+ ```
398
+
399
+ **onCallReceived Event Parameters:**
400
+
401
+ | **Parameter** | **Type** | **Description** |
402
+ | --- | --- | --- |
403
+ | `callId` | `String` | Unique identifier for this call. |
404
+ | `mediaType` | [CallMediaType](https://liteav.sdk.cloudcachetci.com/doc/product/tuikit/atomic-x/android/en/-atomic-x%20-core%20-a-p-i/io.trtc.tuikit.atomicxcore.api.call/-call-media-type/index.html) | Specifies whether it's an audio or video call.<br>- `CallMediaType.Video`: Video call.<br>- `CallMediaType.Audio`: Audio call. |
405
+
406
+ **openLocalCamera API Parameters:**
407
+
408
+ | **Parameter** | **Type** | **Required** | **Description** |
409
+ | --- | --- | --- | --- |
410
+ | `isFront` | `Boolean` | Yes | Whether to open the front camera. `true`: front camera. `false`: rear camera. |
411
+ | `completion` | `CompletionHandler` | No | Callback for operation result; returns error code/message if failed. |
412
+
413
+ **openLocalMicrophone API Parameters:**
414
+
415
+ | **Parameter** | **Type** | **Required** | **Description** |
416
+ | --- | --- | --- | --- |
417
+ | `completion` | `CompletionHandler` | No | Callback for operation result; returns error code/message if failed. |
418
+
419
+ ### Step 6: Trigger Call Interface on Incoming Call
420
+
421
+ Since you already subscribed to the `onCallReceived` event in Step 5, launch the call page in this event:
422
+
423
+ ```kotlin
424
+ private fun addListener() {
425
+ callListener = object : CallListener() {
426
+ override fun onCallReceived(callId: String, mediaType: CallMediaType, userData: String) {
427
+ super.onCallReceived(callId, mediaType, userData)
428
+ // Trigger call page
429
+ val intent = Intent(this@MainActivity, CallActivity::class.java)
430
+ startActivity(intent)
431
+ }
432
+ }
433
+ callListener?.let { CallStore.shared.addListener(it) }
434
+ }
435
+ ```
436
+
437
+ ### Demo Effect
438
+
439
+ After completing the above 6 steps, you should have a complete answer-call flow working in your app.
440
+
441
+ ## Customize Pages
442
+
443
+ `CallCoreView` supports extensive UI customization, including flexible avatar and volume indicator icon replacement. Download these icons directly from GitHub. All icons are designed for TUICallKit and are copyright-free.
444
+
445
+ ### Custom Volume Indicator Icons
446
+
447
+ Use the `setVolumeLevelIcons` method in `CallCoreView` to set custom volume indicator icons.
448
+
449
+ **setVolumeLevelIcons Sample Code:**
450
+
451
+ ```kotlin
452
+ private fun setIconResourcePath() {
453
+ val volumeLevelIcons = mapOf(VolumeLevel.Mute to "icon resource path")
454
+ val callCoreView = CallCoreView(context)
455
+ callCoreView.setVolumeLevelIcons(volumeLevelIcons)
456
+ }
457
+ ```
458
+
459
+ **setVolumeLevelIcons API Parameters:**
460
+
461
+ | **Parameter** | **Type** | **Required** | **Description** |
462
+ | --- | --- | --- | --- |
463
+ | `icons` | `Map` | Yes | Mapping of volume levels to icon resources.<br>**Key (`VolumeLevel`):**<br>- `VolumeLevel.Mute`: Microphone muted.<br>- `VolumeLevel.Low`: Volume (0-25].<br>- `VolumeLevel.Medium`: Volume (25-50].<br>- `VolumeLevel.High`: Volume (50-75].<br>- `VolumeLevel.Peak`: Volume (75-100].<br>**Value (`String`):** Icon resource path. |
464
+
465
+ **Volume Indicator Icons:**
466
+
467
+ | **Resource** | **Description** | **Download** |
468
+ | --- | --- | --- |
469
+ | `callview_ic_audio_input.png` | Volume indicator icon. Recommended for `VolumeLevel.Low` or `VolumeLevel.Medium`. | [GitHub](https://github.com/Tencent-RTC/TUIKit_Android/blob/main/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_audio_input.png) |
470
+ | `callview_ic_self_mute.png` | Mute icon. Recommended for `VolumeLevel.Mute`. | [GitHub](https://github.com/Tencent-RTC/TUIKit_Android/blob/main/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_self_mute.png) |
471
+
472
+ ### Custom Network Indicator Icons
473
+
474
+ Use the `setNetworkQualityIcons` method in `CallCoreView` to set custom network status icons.
475
+
476
+ **setNetworkQualityIcons Sample Code:**
477
+
478
+ ```kotlin
479
+ private fun setNetworkQualityIcons() {
480
+ val volumeLevelIcons = mapOf(NetworkQuality.BAD to "icon path")
481
+ val callCoreView = CallCoreView(context)
482
+ callCoreView.setNetworkQualityIcons(volumeLevelIcons)
483
+ }
484
+ ```
485
+
486
+ **setNetworkQualityIcons API Parameters:**
487
+
488
+ | **Parameter** | **Type** | **Required** | **Description** |
489
+ | --- | --- | --- | --- |
490
+ | `icons` | `Map` | Yes | Mapping of network quality levels to icon resources.<br>**Key (`NetworkQuality`):**<br>- `NetworkQuality.UNKNOWN`: Unknown.<br>- `NetworkQuality.EXCELLENT`: Excellent.<br>- `NetworkQuality.GOOD`: Good.<br>- `NetworkQuality.POOR`: Poor.<br>- `NetworkQuality.BAD`: Bad.<br>- `NetworkQuality.VERY_BAD`: Very bad.<br>- `NetworkQuality.DOWN`: Disconnected.<br>**Value (`String`):** Icon resource path. |
491
+
492
+ **Poor Network Indicator Icon:**
493
+
494
+ | **Resource** | **Description** | **Download** |
495
+ | --- | --- | --- |
496
+ | `callview_ic_network_bad.png` | Poor network indicator icon. Recommended for `NetworkQuality.BAD`, `NetworkQuality.VERY_BAD`, or `NetworkQuality.DOWN`. | [GitHub](https://github.com/Tencent-RTC/TUIKit_Android/blob/main/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_network_bad.png) |
497
+
498
+ ### Custom Default Avatar
499
+
500
+ Use the `setParticipantAvatars` method in `CallCoreView` to set user avatars. Listen to `allParticipants` in `CallStore`: set and display avatars when available; show the default avatar if unavailable or failed to load.
501
+
502
+ **setParticipantAvatars Sample Code:**
503
+
504
+ ```kotlin
505
+ private fun setParticipantAvatars() {
506
+ val avatars: MutableMap<String, ParticipantAvatarInfo> = mutableMapOf()
507
+ val userId = "" // User ID
508
+ val avatarPath = "" // Default avatar resource path
509
+ avatars[userId] = avatarPath
510
+ val callCoreView = CallCoreView(context)
511
+ callCoreView.setParticipantAvatars(avatars)
512
+ }
513
+ ```
514
+
515
+ **setParticipantAvatars API Parameters:**
516
+
517
+ | **Parameter** | **Type** | **Required** | **Description** |
518
+ | --- | --- | --- | --- |
519
+ | `icons` | `Map` | Yes | Mapping of user IDs to avatar resource paths.<br>- **Key:** `userID`.<br>- **Value:** Absolute path to the avatar resource. |
520
+
521
+ **Default Avatar Resource:**
522
+
523
+ | **Resource** | **Description** | **Download** |
524
+ | --- | --- | --- |
525
+ | `callview_ic_avatar.png` | Default avatar. Recommended when the user's avatar is missing or fails to load. | [GitHub](https://github.com/Tencent-RTC/TUIKit_Android/blob/main/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_avatar.png) |
526
+
527
+ ### Custom Loading Animation
528
+
529
+ Use the `setWaitingAnimation` method in `CallCoreView` to set custom waiting animations for users.
530
+
531
+ **setWaitingAnimation Sample Code:**
532
+
533
+ ```kotlin
534
+ private fun setWaitingAnimation() {
535
+ val waitingAnimationPath = "" // Path to the waiting animation GIF resource
536
+ val callCoreView = CallCoreView(context)
537
+ callCoreView.setWaitingAnimation(waitingAnimationPath)
538
+ }
539
+ ```
540
+
541
+ **setWaitingAnimation API Parameters:**
542
+
543
+ | **Parameter** | **Type** | **Required** | **Description** |
544
+ | --- | --- | --- | --- |
545
+ | `path` | `String` | Yes | Absolute path to the GIF resource. |
546
+
547
+ **Waiting Animation:**
548
+
549
+ | **Resource** | **Description** | **Download** |
550
+ | --- | --- | --- |
551
+ | `callview_ic_loading.gif` | Waiting animation for users. Recommended for group calls when a user is waiting to answer. | [GitHub](https://github.com/Tencent-RTC/TUIKit_Android/blob/main/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_loading.gif) |
552
+
553
+ ### Add Call Timer
554
+
555
+ Get call duration in real time from the `duration` field in `activeCall`.
556
+
557
+ 1. **Subscribe to data layer:** Listen to `CallStore.observerState.activeCall` for the current active call.
558
+
559
+ 2. **Bind call timer data:** Bind `activeCall.duration` to your UI control. This field updates reactively—no need to manually manage a timer.
560
+
561
+ ```kotlin
562
+ import android.content.Context
563
+ import androidx.appcompat.widget.AppCompatTextView
564
+ import androidx.core.content.ContextCompat
565
+ import com.tencent.qcloud.tuicore.util.DateTimeUtil
566
+ import io.trtc.tuikit.atomicx.R
567
+ import io.trtc.tuikit.atomicxcore.api.call.CallStore
568
+ import io.trtc.tuikit.atomicxcore.api.call.CallParticipantStatus
569
+ import kotlinx.coroutines.CoroutineScope
570
+ import kotlinx.coroutines.Dispatchers
571
+ import kotlinx.coroutines.Job
572
+ import kotlinx.coroutines.launch
573
+
574
+ class TimerView(context: Context) : AppCompatTextView(context) {
575
+ private var subscribeStateJob: Job? = null
576
+
577
+ override fun onAttachedToWindow() {
578
+ super.onAttachedToWindow()
579
+ // 1. Subscribe to activeCall in the data layer
580
+ registerActiveCallObserver()
581
+ }
582
+
583
+ override fun onDetachedFromWindow() {
584
+ super.onDetachedFromWindow()
585
+ subscribeStateJob?.cancel()
586
+ }
587
+
588
+ private fun registerActiveCallObserver() {
589
+ subscribeStateJob = CoroutineScope(Dispatchers.Main).launch {
590
+ CallStore.shared.observerState.activeCall.collect { activeCall ->
591
+ // 2. Bind call timer data, update call duration
592
+ updateDurationView(activeCall)
593
+ }
594
+ }
595
+ }
596
+
597
+ private fun updateDurationView(activeCall: CallInfo) {
598
+ val currentDuration = activeCall.duration
599
+ text = DateTimeUtil.formatSecondsTo00(currentDuration.toInt())
600
+ }
601
+ }
602
+ ```
603
+
604
+ > **Note:**
605
+ >
606
+ > For more info on reactive call state data, see CallState.
607
+
608
+ ## More Features
609
+
610
+ ### Set Avatar and Nickname
611
+
612
+ Before starting a call, use `setSelfInfo` to set your nickname and avatar.
613
+
614
+ **setSelfInfo Sample Code:**
615
+
616
+ ```kotlin
617
+ val user = UserProfile()
618
+ user.userID = "" // Your userId
619
+ user.avatarURL = "" // Avatar url
620
+ user.nickname = "" // Nickname to set
621
+
622
+ LoginStore.shared.setSelfInfo(user, object : CompletionHandler {
623
+ override fun onSuccess() {
624
+ // Success callback
625
+ }
626
+
627
+ override fun onFailure(code: Int, desc: String) {
628
+ // Failure callback
629
+ }
630
+ })
631
+ ```
632
+
633
+ **setSelfInfo API Parameters:**
634
+
635
+ | **Parameter** | **Type** | **Required** | **Description** |
636
+ | --- | --- | --- | --- |
637
+ | `userProfile` | [UserProfile](https://liteav.sdk.cloudcachetci.com/doc/product/tuikit/atomic-x/android/en/-atomic-x%20-core%20-a-p-i/io.trtc.tuikit.atomicxcore.api.login/-user-profile/index.html) | Yes | User information struct.<br>- `userID` (`String`): User ID.<br>- `avatarURL` (`String`): Avatar URL.<br>- `nickname` (`String`): User nickname.<br>See [UserProfile](https://liteav.sdk.cloudcachetci.com/doc/product/tuikit/atomic-x/android/en/-atomic-x%20-core%20-a-p-i/io.trtc.tuikit.atomicxcore.api.login/-user-profile/index.html) for more fields. |
638
+ | `completion` | `CompletionHandler` | No | Callback for operation result. |
639
+
640
+ ### Switch Layout Modes
641
+
642
+ Use `setLayoutTemplate` to switch layout modes. If not set, `CallCoreView` adapts automatically: **Float** mode for 1v1 calls, **Grid** mode for multi-party calls.
643
+
644
+ | **Mode** | **Layout** | **Interaction** |
645
+ | --- | --- | --- |
646
+ | **Float Mode** | Full screen shows your own video while waiting; after answering, full screen shows the other party's video, and your own video floats as a small window. | Supports dragging and tapping to swap big/small windows. |
647
+ | **Grid Mode** | All participant videos are tiled in a grid. Suitable for 2+ participants. | Tap a participant to enlarge their video. |
648
+ | **PIP Mode** | In 1v1, always shows the other party's video; in multi-party calls, uses active speaker strategy with full-screen current speaker. | Shows your own video while waiting; displays the call timer after answering. |
649
+
650
+ **setLayoutTemplate Sample Code:**
651
+
652
+ ```kotlin
653
+ private fun setLayoutTemplate() {
654
+ val callCoreView = CallCoreView()
655
+ val template = CallLayoutTemplate.Grid
656
+ // Set layout mode
657
+ callCoreView.setLayoutTemplate(template)
658
+ setContentView(callCoreView)
659
+ }
660
+ ```
661
+
662
+ **setLayoutTemplate API Parameters:**
663
+
664
+ | **Parameter** | **Type** | **Required** | **Description** |
665
+ | --- | --- | --- | --- |
666
+ | `template` | [CallLayoutTemplate](https://liteav.sdk.cloudcachetci.com/doc/product/tuikit/atomic-x/android/en/-atomic-x%20-core%20-a-p-i/io.trtc.tuikit.atomicxcore.api.view/-call-layout-template/index.html) | Yes | `CallCoreView` layout mode.<br>- `CallLayoutTemplate.Float`: While waiting, display your own video full screen; after answering, show the remote video full screen and your own video as a floating window.<br>- `CallLayoutTemplate.Grid`: All participant videos are tiled in a grid; best for 2+ participants. Tap to enlarge a video.<br>- `CallLayoutTemplate.Pip`: In 1v1, remote video is fixed; in multi-party calls, the active speaker is shown full screen. |
667
+
668
+ ### Entering Picture-in-Picture Mode
669
+
670
+ Implementing Picture-in-Picture (PiP) mode requires Android 8.0 (API level 26) or higher. For an optimal user experience, it is recommended to switch your layout to a PiP template when entering this mode.
671
+
672
+ **Entering Picture-in-Picture Mode:** To initiate PiP, call the system method `enterPictureInPictureMode`.
673
+
674
+ ```kotlin
675
+ private fun enterPictureInPictureModeWithBuild() {
676
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && hasPipModePermission()) {
677
+ val pictureInPictureParams: PictureInPictureParams.Builder = PictureInPictureParams.Builder()
678
+ val floatViewWidth = resources.getDimensionPixelSize(R.dimen.callkit_video_small_view_width)
679
+ val floatViewHeight = resources.getDimensionPixelSize(R.dimen.callkit_video_small_view_height)
680
+ val aspectRatio = Rational(floatViewWidth, floatViewHeight)
681
+ pictureInPictureParams.setAspectRatio(aspectRatio).build()
682
+ this.enterPictureInPictureMode(pictureInPictureParams.build())
683
+ }
684
+ }
685
+ ```
686
+
687
+ **Handling the PiP Transition:** When the system successfully enters PiP mode, the `onPictureInPictureModeChanged` callback is triggered. You must update the **CallCoreView** layout to `CallLayoutTemplate.Pip` at this stage.
688
+
689
+ ```kotlin
690
+ val callCoreView = CallCoreView(context)
691
+
692
+ override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean) {
693
+ super.onPictureInPictureModeChanged(isInPictureInPictureMode)
694
+ if (isInPictureInPictureMode) {
695
+ callCoreView.setLayoutTemplate(CallLayoutTemplate.Pip)
696
+ }
697
+ }
698
+ ```
699
+
700
+ **Restoring Full-Screen Layout:** When the user returns to the app from PiP (restoring full screen), the `onResume` callback will be triggered. You should reset the layout based on the current number of participants:
701
+
702
+ - **1 v 1 Calls:** Use `CallLayoutTemplate.Float`.
703
+ - **Group Calls:** Use `CallLayoutTemplate.Grid`.
704
+
705
+ ```kotlin
706
+ val callCoreView = CallCoreView(context)
707
+
708
+ override fun onResume() {
709
+ super.onResume()
710
+ val allParticipants = CallStore.shared.observerState.allParticipants.value
711
+ if (allParticipants.size > 2) {
712
+ callCoreView?.setLayoutTemplate(CallLayoutTemplate.Grid)
713
+ } else {
714
+ callCoreView?.setLayoutTemplate(CallLayoutTemplate.Float)
715
+ }
716
+ }
717
+ ```
718
+
719
+ ### Keep the Screen Awake During Calls
720
+
721
+ Keeping the screen active during a call is a fundamental requirement for communication apps. The simplest and most recommended approach in Android is using the `WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON` flag.
722
+
723
+ ```kotlin
724
+ class CallActivity : AppCompatActivity() {
725
+ override fun onCreate(savedInstanceState: Bundle?) {
726
+ super.onCreate(savedInstanceState)
727
+
728
+ // Apply flags to handle screen behavior during calls
729
+ window.addFlags(
730
+ WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or // Show activity over lock screen
731
+ WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD or // Dismiss non-secure keyguards
732
+ WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON or // Prevent the screen from sleeping
733
+ WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON // Turn the screen on when activity starts
734
+ )
735
+
736
+ setContentView(R.layout.activity_call)
737
+ }
738
+ }
739
+ ```
740
+
741
+ ### Enable Background Audio/Video Capture
742
+
743
+ To maintain audio and video capture when the app is in the background, you must implement a Foreground Service. Starting from Android 9.0 (API 28), foreground service permissions are required, and Android 14 (API 34) mandates declaring specific service types for the camera and microphone.
744
+
745
+ 1. **Configure Permissions and Service (`AndroidManifest.xml`):** Declare the necessary foreground service permissions and specify the service types within the `<application>` tag.
746
+
747
+ ```xml
748
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
749
+
750
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
751
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_CAMERA" />
752
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
753
+
754
+ <application>
755
+ <service
756
+ android:name=".CallForegroundService"
757
+ android:enabled="true"
758
+ android:exported="false"
759
+ android:foregroundServiceType="camera|microphone" />
760
+ </application>
761
+ </manifest>
762
+ ```
763
+
764
+ 2. **Create the Foreground Service Class (`CallForegroundService`):** The service ensures the system does not kill the app's process while it is in the background by displaying a persistent notification.
765
+
766
+ ```kotlin
767
+ import android.app.Notification
768
+ import android.app.NotificationChannel
769
+ import android.app.NotificationManager
770
+ import android.app.Service
771
+ import android.content.Context
772
+ import android.content.Intent
773
+ import android.os.Build
774
+ import android.os.IBinder
775
+ import androidx.core.app.NotificationCompat
776
+
777
+ class CallForegroundService : Service() {
778
+ companion object {
779
+ private const val NOTIFICATION_ID = 1001
780
+ private const val CHANNEL_ID = "call_foreground_channel"
781
+
782
+ /**
783
+ * Start the foreground service to maintain call connectivity
784
+ */
785
+ fun start(context: Context) {
786
+ val intent = Intent(context, CallForegroundService::class.java)
787
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
788
+ context.startForegroundService(intent)
789
+ } else {
790
+ context.startService(intent)
791
+ }
792
+ }
793
+
794
+ /**
795
+ * Stop the service once the call ends
796
+ */
797
+ fun stop(context: Context) {
798
+ val intent = Intent(context, CallForegroundService::class.java)
799
+ context.stopService(intent)
800
+ }
801
+ }
802
+
803
+ override fun onCreate() {
804
+ super.onCreate()
805
+ createNotificationChannel()
806
+ // Start foreground with notification to secure background capture permissions
807
+ startForeground(NOTIFICATION_ID, createNotification())
808
+ }
809
+
810
+ override fun onBind(intent: Intent?): IBinder? = null
811
+
812
+ private fun createNotification(): Notification {
813
+ return NotificationCompat.Builder(this, CHANNEL_ID)
814
+ .setContentTitle("Call in Progress")
815
+ .setContentText("App is running in the background to maintain the call.")
816
+ .setSmallIcon(android.R.drawable.ic_menu_call) // Replace with your app icon
817
+ .setPriority(NotificationCompat.PRIORITY_HIGH)
818
+ .build()
819
+ }
820
+
821
+ private fun createNotificationChannel() {
822
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
823
+ val channel = NotificationChannel(
824
+ CHANNEL_ID,
825
+ "Call Keep-Alive Service",
826
+ NotificationManager.IMPORTANCE_HIGH
827
+ )
828
+ val manager = getSystemService(NotificationManager::class.java)
829
+ manager?.createNotificationChannel(channel)
830
+ }
831
+ }
832
+ }
833
+ ```
834
+
835
+ ## FAQs
836
+
837
+ ### Why does the UI appear distorted or show the wrong screen after navigating to another page while in PiP mode?
838
+
839
+ **Cause:** Android's Picture-in-Picture (PiP) mode operates based on the Task Stack. By default, all Activities in an app share the same task stack. If you launch a new Activity while the app is in PiP mode, that new page is pushed onto the current stack. Consequently, the new screen incorrectly attempts to render within the tiny PiP window, leading to UI anomalies.
840
+
841
+ **Solution:** Declare the Call Activity as a separate task with its own affinity in the `AndroidManifest.xml`. This ensures the call remains isolated in its own window regardless of other app navigation.
842
+
843
+ ```xml
844
+ <activity
845
+ android:name=".view.CallActivity"
846
+ android:launchMode="singleTask"
847
+ android:taskAffinity="${applicationId}.call" />
848
+ ```
849
+
850
+ ### Can an invitee receive a call event if they go offline and then come back online within the call timeout period?
851
+
852
+ For one-on-one calls, if the callee comes online within the timeout, they will receive the incoming call invitation. For group calls, if the callee comes online within the timeout, up to 20 unprocessed group messages will be retrieved, and if there is a call invitation, the incoming call event will be triggered.