@tencentcloud/sdk-mcp 1.4.1 → 1.4.2

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 (29) hide show
  1. package/package.json +1 -1
  2. package/resource/doc/callkit/core-sdk/android/answer-call.md +942 -0
  3. package/resource/doc/callkit/core-sdk/android/make-call.md +876 -0
  4. package/resource/doc/callkit/core-sdk/android/offline-push.md +368 -0
  5. package/resource/doc/callkit/core-sdk/flutter/answer-call.md +1132 -0
  6. package/resource/doc/callkit/core-sdk/flutter/make-call.md +1206 -0
  7. package/resource/doc/callkit/core-sdk/flutter/offline-push.md +357 -0
  8. package/resource/doc/callkit/core-sdk/ios/answer-call.md +1009 -0
  9. package/resource/doc/callkit/core-sdk/ios/make-call.md +1154 -0
  10. package/resource/doc/callkit/core-sdk/ios/offline-push.md +470 -0
  11. package/resource/doc/callkit/core-sdk/uni-app/answer-call.md +1146 -0
  12. package/resource/doc/callkit/core-sdk/uni-app/make-call.md +1267 -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/tools/call/get_native_callkit_core_integration.js +67 -0
  19. package/tools/call/get_native_callkit_core_integration.js.map +1 -0
  20. package/tools/common/present_framework_choice.js +1 -1
  21. package/tools/common/present_framework_choice.js.map +1 -1
  22. package/tools/definitions/get_native_call_uikit_integration_def.js +9 -3
  23. package/tools/definitions/get_native_call_uikit_integration_def.js.map +1 -1
  24. package/tools/definitions/get_native_callkit_core_integration_def.js +63 -0
  25. package/tools/definitions/get_native_callkit_core_integration_def.js.map +1 -0
  26. package/tools/index.js +2 -0
  27. package/tools/index.js.map +1 -1
  28. package/utils/report-cls-client.js +1 -1
  29. package/utils/report-es-client.js +1 -1
@@ -0,0 +1,876 @@
1
+ 本文档将帮助您使用 **AtomicXCore SDK** 的 **DeviceStore**、**CallStore** 以及核心组件 **CallCoreView**,快速完成拨打电话功能。
2
+
3
+ ## 核心功能
4
+
5
+ **AtomicXCore** 中用于搭建多人音视频通话场景所需要使用到的核心模块包含以下三个:
6
+
7
+ | 模块 | 功能描述 |
8
+ |------|----------|
9
+ | [CallCoreView](https://liteav.sdk.cloudcachetci.com/doc/product/tuikit/atomic-x/android/zh/-atomic-x%20-core%20-a-p-i/io.trtc.tuikit.atomicxcore.api.view/-call-core-view/index.html) | 通话视图核心组件。自动监听 CallStore 数据并完成画面渲染,同时提供布局切换、头像与图标配置等 UI 定制化能力。 |
10
+ | [CallStore](https://liteav.sdk.cloudcachetci.com/doc/product/tuikit/atomic-x/android/zh/-atomic-x%20-core%20-a-p-i/io.trtc.tuikit.atomicxcore.api.call/-call-store/index.html) | 通话生命周期管理:拨打电话、接通电话、拒接电话、挂断电话。实时获取参与通话人员音视频状态,通话计时、通话记录等数据。 |
11
+ | [DeviceStore](https://liteav.sdk.cloudcachetci.com/doc/product/tuikit/atomic-x/android/zh/-atomic-x%20-core%20-a-p-i/io.trtc.tuikit.atomicxcore.api.device/-device-store/index.html) | 音视频设备控制:麦克风(开关 / 音量)、摄像头(开关 / 切换 / 画质)、屏幕共享,设备状态实时监听。 |
12
+
13
+ ## 准备工作
14
+
15
+ ### 步骤1:开通服务
16
+
17
+ 请参见 开通服务,获取体验版或付费版 SDK。
18
+
19
+ ### 步骤2:集成 SDK
20
+
21
+ 1. 请在您的 **build.gradle** 文件中添加 `api "io.trtc.uikit:atomicx-core:latest.release"` 和 `api "com.tencent.imsdk:imsdk-plus:8.7.7201"` 依赖,然后执行 **Gradle Sync**。
22
+
23
+ ```kotlin
24
+ dependencies {
25
+ api "io.trtc.uikit:atomicx-core:latest.release"
26
+ api "com.tencent.imsdk:imsdk-plus:8.7.7201"
27
+ // 其他依赖...
28
+ }
29
+ ```
30
+
31
+ 2. 由于 SDK 内部使用了 Java 反射特性,需要将部分类加入不混淆名单。请在 `app` 目录下的 `proguard-rules.pro` 文件末尾添加如下代码。添加完后,点击右上角的 **Sync Now**,同步代码。
32
+
33
+ ```
34
+ -keep class com.tencent.** { *; }
35
+ ```
36
+
37
+ 3. 在 `app` 目录下找到 `AndroidManifest.xml` 文件,在 `application` 节点中添加 `tools:replace="android:allowBackup"`,覆盖组件内的设置,使用自己的设置。
38
+
39
+ ```xml
40
+ <application
41
+ android:name=".BaseApplication"
42
+ android:allowBackup="false"
43
+ android:icon="@drawable/app_ic_launcher"
44
+ android:label="@string/app_name"
45
+ android:largeHeap="true"
46
+ android:theme="@style/AppTheme"
47
+ tools:replace="android:allowBackup">
48
+ ```
49
+
50
+ ### 步骤3:初始化与登录流程
51
+
52
+ 启动通话服务需依次完成以下三步:
53
+
54
+ 1. **实例化 CallStore**:`CallStore.shared` 初始化通话数据层。
55
+ 2. **用户登录**:调用 `LoginStore.shared.login(...)` 完成 IM 登录。
56
+ 3. **初始化通话引擎**:登录成功后调用 `TUICallEngine.createInstance(context).init(sdkAppId, userId, userSig, null)` 初始化 TRTC 通话信令引擎(**必须调用,否则无法正常收发通话**)。
57
+
58
+ 示例代码如下:
59
+
60
+ ```kotlin
61
+ class MainActivity : ComponentActivity() {
62
+ override fun onCreate(savedInstanceState: Bundle?) {
63
+ super.onCreate(savedInstanceState)
64
+
65
+ // CallStore 初始化
66
+ CallStore.shared
67
+
68
+ val sdkAppId = 1400000001 // 替换为您的 SDKAppID
69
+ val userId = "test_001" // 替换为您的 UserID
70
+ val userSig = "xxxxxxxxxxx" // 联调填临时 UserSig;MCP 已配置 sdkAppId、sdkSecretKey 时可由工具自动生成
71
+
72
+ LoginStore.shared.login(
73
+ this,
74
+ sdkAppId,
75
+ userId,
76
+ userSig,
77
+ object : CompletionHandler {
78
+ override fun onSuccess() {
79
+ // 完成 TUICallEngine 的初始化
80
+ TUICallEngine.createInstance(this@MainActivity).init(sdkAppId, userId, userSig, null)
81
+ Log.d("Login", "login success")
82
+ }
83
+ override fun onFailure(code: Int, desc: String) {
84
+ Log.e("Login", "login failed, code: $code, error: $desc")
85
+ }
86
+ }
87
+ )
88
+ }
89
+ }
90
+ ```
91
+
92
+ **登录接口参数说明:**
93
+
94
+ | 参数 | 类型 | 说明 |
95
+ | --- | --- | --- |
96
+ | `userId` | String | 当前用户的唯一 ID,长度不超过32字节,不支持使用特殊字符,建议使用英文字母或数字。为避免多端登录冲突,请勿使用 1、123 等简单 ID。 |
97
+ | `sdkAppId` | int | 从控制台获取,通常是以 140 或 160 开头的 10 位整数 |
98
+ | `userSig` | String | IM 登录鉴权票据。联调可本地生成临时值(如 `GenerateTestUserSig.genTestUserSig`);**Tencent RTC MCP Server** 已配置 `sdkAppId` 与 `sdkSecretKey` 时可由工具自动生成。**生产环境**须由业务服务端使用密钥签发 UserSig,勿在客户端存储或下发 SecretKey。 |
99
+
100
+ ## 实现步骤
101
+
102
+ **发起通话前,请确保完成登录**,这是服务可用的必要前提。以下将分 5 步为您讲解如何"拨打一通电话"。
103
+
104
+ ### 步骤1:创建通话界面
105
+
106
+ 新建一个 Activity 作为通话宿主页面,通话页面绑定 `CallCoreView`:
107
+
108
+ ```kotlin
109
+ class CallActivity : AppCompatActivity() {
110
+ private var callCoreView: CallCoreView? = null
111
+
112
+ override fun onCreate(savedInstanceState: Bundle?) {
113
+ super.onCreate(savedInstanceState)
114
+ callCoreView = CallCoreView(this)
115
+ setContentView(callCoreView)
116
+ }
117
+ }
118
+ ```
119
+
120
+ **CallCoreView 视图组件功能说明:**
121
+
122
+ | 功能 | 说明 |
123
+ |------|------|
124
+ | 设置布局模式 | 支持自由切换布局模式,若未设置将根据通话人数自动适配 |
125
+ | 设置头像 | 支持传入头像资源路径,为特定用户自定义头像 |
126
+ | 设置音量提示图标 | 支持根据不同音量等级配置个性化的音量指示图标 |
127
+ | 设置网络提示图标 | 支持根据实时网络质量配置对应的网络状态提示图标 |
128
+ | 设置等待接听动画 | 多人通话场景下,支持传入 GIF 图像路径为待接听状态用户展示动画 |
129
+
130
+ ### 步骤2:添加通话控制按钮
131
+
132
+ 您可以参考 DeviceStore、CallStore 提供的 API,自定义添加您的按钮。
133
+
134
+ - **DeviceStore 功能说明:** 麦克风(开关/音量)、摄像头(开关/切换/画质)、屏幕共享,设备状态实时监听。建议将对应方法绑定至按钮点击事件,并通过监听设备状态变更来实时刷新按钮的 UI 状态。
135
+ - **CallStore 功能说明:** 接听、挂断、拒接等核心通话控制能力。建议将对应方法绑定至按钮点击事件,并监听通话状态的变化,以确保按钮显示与当前通话阶段保持同步。
136
+
137
+ **图标资源下载:** 按钮图标可以直接从 GitHub 下载。这些图标由设计师专为 TUICallKit 打造,无版权风险,可放心使用。
138
+
139
+ 图标资源目录:https://github.com/Tencent-RTC/TUIKit_Android/tree/main/atomic_x/src/main/res-callview/drawable-xxhdpi
140
+
141
+ | 文件名 | 用途 |
142
+ |--------|------|
143
+ | callview_ic_dialing.png | 拨号图标 |
144
+ | callview_ic_hangup.png | 挂断图标 |
145
+ | callview_ic_camera_enable.png | 摄像头开启 |
146
+ | callview_ic_camera_disable.png | 摄像头关闭 |
147
+ | callview_ic_mic_unmute.png | 麦克风开启 |
148
+ | callview_ic_mic_mute.png | 麦克风静音 |
149
+ | callview_ic_handsfree_enable.png | 免提开启 |
150
+ | callview_ic_handsfree_disable.png | 免提关闭 |
151
+
152
+ #### 2.1 创建底部栏容器
153
+
154
+ 在通话页面底部创建一个容器,用于添加挂断、麦克风、摄像头按钮。
155
+
156
+ ```kotlin
157
+ import io.trtc.tuikit.atomicxcore.api.call.CallStore
158
+ import io.trtc.tuikit.atomicxcore.api.view.CallCoreView
159
+ import io.trtc.tuikit.atomicxcore.api.device.DeviceStore
160
+
161
+ class CallActivity : AppCompatActivity() {
162
+ private var buttonContainer: LinearLayout? = null
163
+
164
+ override fun onCreate(savedInstanceState: Bundle?) {
165
+ super.onCreate(savedInstanceState)
166
+ createButtonContainer()
167
+ setContentView(buttonContainer)
168
+ }
169
+
170
+ private fun createButtonContainer() {
171
+ buttonContainer = LinearLayout(this).apply {
172
+ orientation = LinearLayout.HORIZONTAL
173
+ gravity = Gravity.CENTER
174
+ layoutParams = FrameLayout.LayoutParams(
175
+ FrameLayout.LayoutParams.MATCH_PARENT,
176
+ FrameLayout.LayoutParams.WRAP_CONTENT
177
+ ).apply {
178
+ gravity = Gravity.BOTTOM
179
+ bottomMargin = dpToPx(80)
180
+ }
181
+ }
182
+ }
183
+ }
184
+ ```
185
+
186
+ #### 2.2 添加挂断按钮
187
+
188
+ 在点击事件中调用 `hangup` 接口并销毁页面:
189
+
190
+ ```kotlin
191
+ class CallActivity : AppCompatActivity() {
192
+ private var buttonContainer: LinearLayout? = null
193
+ private var buttonHangup: Button? = null
194
+
195
+ override fun onCreate(savedInstanceState: Bundle?) {
196
+ super.onCreate(savedInstanceState)
197
+ addHangupButton()
198
+ setContentView(buttonContainer)
199
+ }
200
+
201
+ private fun addHangupButton() {
202
+ buttonHangup = Button(this).apply {
203
+ text = "挂断"
204
+ layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f).apply {
205
+ marginEnd = dpToPx(8)
206
+ }
207
+ setOnClickListener {
208
+ CallStore.shared.hangup(null)
209
+ finish()
210
+ }
211
+ }
212
+ buttonContainer?.addView(buttonHangup)
213
+ }
214
+ }
215
+ ```
216
+
217
+ #### 2.3 添加麦克风开关按钮
218
+
219
+ 调用 `openLocalMicrophone` 或 `closeLocalMicrophone` 接口:
220
+
221
+ ```kotlin
222
+ class CallActivity : AppCompatActivity() {
223
+ private var buttonContainer: LinearLayout? = null
224
+ private var buttonMicrophone: Button? = null
225
+
226
+ override fun onCreate(savedInstanceState: Bundle?) {
227
+ super.onCreate(savedInstanceState)
228
+ addMicrophoneButton()
229
+ setContentView(buttonContainer)
230
+ }
231
+
232
+ private fun addMicrophoneButton() {
233
+ buttonMicrophone = Button(this).apply {
234
+ layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f).apply {
235
+ setMargins(16, 0, 16, 0)
236
+ }
237
+ setOnClickListener {
238
+ val isMicrophoneOpen = DeviceStore.shared().deviceState.microphoneStatus.value == DeviceStatus.ON
239
+ if (isMicrophoneOpen) {
240
+ DeviceStore.shared().closeLocalMicrophone()
241
+ } else {
242
+ DeviceStore.shared().openLocalMicrophone(null)
243
+ }
244
+ }
245
+ }
246
+ val isMicrophoneOpen = DeviceStore.shared().deviceState.microphoneStatus.value == DeviceStatus.ON
247
+ buttonMicrophone?.text = if (isMicrophoneOpen) "关麦克风" else "开麦克风"
248
+ buttonContainer?.addView(buttonMicrophone)
249
+ }
250
+ }
251
+ ```
252
+
253
+ #### 2.4 添加摄像头开关按钮
254
+
255
+ 调用 `openLocalCamera` 或 `closeLocalCamera` 接口:
256
+
257
+ ```kotlin
258
+ class CallActivity : AppCompatActivity() {
259
+ private var buttonContainer: LinearLayout? = null
260
+ private var buttonCamera: Button? = null
261
+
262
+ override fun onCreate(savedInstanceState: Bundle?) {
263
+ super.onCreate(savedInstanceState)
264
+ addCameraButton()
265
+ setContentView(buttonContainer)
266
+ }
267
+
268
+ private fun addCameraButton() {
269
+ buttonCamera = Button(this).apply {
270
+ layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f).apply {
271
+ setMargins(16, 0, 16, 0)
272
+ }
273
+ setOnClickListener {
274
+ val isCameraOpen = DeviceStore.shared().deviceState.cameraStatus.value == DeviceStatus.ON
275
+ if (isCameraOpen) {
276
+ DeviceStore.shared().closeLocalCamera()
277
+ } else {
278
+ val isFrontCamera = DeviceStore.shared().deviceState.isFrontCamera.value
279
+ DeviceStore.shared().openLocalCamera(isFrontCamera, null)
280
+ }
281
+ }
282
+ }
283
+ val isCameraOpen = DeviceStore.shared().deviceState.cameraStatus.value == DeviceStatus.ON
284
+ buttonCamera?.text = if (isCameraOpen) "关摄像头" else "开摄像头"
285
+ buttonContainer?.addView(buttonCamera)
286
+ }
287
+ }
288
+ ```
289
+
290
+ #### 2.5 实时更新媒体设备按钮文本
291
+
292
+ 监听麦克风和摄像头的状态,实时更新按钮文本:
293
+
294
+ ```kotlin
295
+ import io.trtc.tuikit.atomicxcore.api.call.*
296
+ import io.trtc.tuikit.atomicxcore.api.device.DeviceStore
297
+ import kotlinx.coroutines.*
298
+
299
+ class CallActivity : AppCompatActivity() {
300
+ private var buttonCamera: Button? = null
301
+ private var buttonMicrophone: Button? = null
302
+ private var deviceStateJob: Job? = null
303
+
304
+ override fun onCreate(savedInstanceState: Bundle?) {
305
+ super.onCreate(savedInstanceState)
306
+ observeDeviceState()
307
+ }
308
+
309
+ private fun observeDeviceState() {
310
+ deviceStateJob = CoroutineScope(Dispatchers.Main).launch {
311
+ supervisorScope {
312
+ launch {
313
+ DeviceStore.shared().deviceState.cameraStatus.collect { status ->
314
+ buttonCamera?.text = if (status == DeviceStatus.ON) "关闭摄像头" else "开启摄像头"
315
+ }
316
+ }
317
+ launch {
318
+ DeviceStore.shared().deviceState.microphoneStatus.collect { status ->
319
+ buttonMicrophone?.text = if (status == DeviceStatus.ON) "关闭麦克风" else "开启麦克风"
320
+ }
321
+ }
322
+ }
323
+ }
324
+ }
325
+ }
326
+ ```
327
+
328
+ ### 步骤3:申请麦克风/摄像头权限
329
+
330
+ **1. 声明权限**(`AndroidManifest.xml`):
331
+
332
+ ```xml
333
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
334
+ <uses-permission android:name="android.permission.RECORD_AUDIO" />
335
+ <uses-permission android:name="android.permission.CAMERA" />
336
+
337
+ <application>
338
+ <activity
339
+ android:name=".CallActivity"
340
+ android:exported="false"
341
+ android:screenOrientation="portrait" />
342
+ </application>
343
+ </manifest>
344
+ ```
345
+
346
+ **2. 动态申请权限:**
347
+
348
+ ```kotlin
349
+ private fun requestAllCallPermissions() {
350
+ val allPermissions = arrayOf(
351
+ Manifest.permission.CAMERA,
352
+ Manifest.permission.RECORD_AUDIO
353
+ )
354
+ if (!checkPermissions(allPermissions)) {
355
+ ActivityCompat.requestPermissions(this, allPermissions, PERMISSION_REQUEST_CODE)
356
+ }
357
+ }
358
+
359
+ override fun onRequestPermissionsResult(
360
+ requestCode: Int,
361
+ permissions: Array<out String>,
362
+ grantResults: IntArray
363
+ ) {
364
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults)
365
+ if (requestCode == PERMISSION_REQUEST_CODE) {
366
+ var allGranted = true
367
+ for (result in grantResults) {
368
+ if (result != PackageManager.PERMISSION_GRANTED) {
369
+ allGranted = false
370
+ break
371
+ }
372
+ }
373
+ if (allGranted) {
374
+ // 权限申请成功
375
+ } else {
376
+ // 部分权限被拒绝
377
+ }
378
+ }
379
+ }
380
+ ```
381
+
382
+ ### 步骤4:发起通话
383
+
384
+ 在 `calls` 调用成功后跳转通话界面,根据媒体类型自动开启麦克风或摄像头:
385
+
386
+ ```kotlin
387
+ import io.trtc.tuikit.atomicxcore.api.CompletionHandler
388
+ import io.trtc.tuikit.atomicxcore.api.call.CallMediaType
389
+ import io.trtc.tuikit.atomicxcore.api.call.CallStore
390
+
391
+ class MainActivity : ComponentActivity() {
392
+
393
+ private fun startCall(userIdList: List<String>, mediaType: CallMediaType) {
394
+ CallStore.shared.calls(userIdList, mediaType, null, object : CompletionHandler {
395
+ override fun onFailure(code: Int, desc: String) {
396
+ }
397
+ override fun onSuccess() {
398
+ openDeviceForMediaType(mediaType)
399
+ val intent = Intent(this@MainActivity, CallActivity::class.java)
400
+ startActivity(intent)
401
+ }
402
+ })
403
+ }
404
+
405
+ private fun openDeviceForMediaType(mediaType: CallMediaType?) {
406
+ if (mediaType == null) {
407
+ return
408
+ }
409
+ DeviceStore.shared().openLocalMicrophone(null)
410
+ if (mediaType == CallMediaType.Video) {
411
+ val isFrontCamera = DeviceStore.shared().deviceState.isFrontCamera.value
412
+ DeviceStore.shared().openLocalCamera(isFrontCamera, null)
413
+ }
414
+ }
415
+ }
416
+ ```
417
+
418
+ **`calls` 接口参数说明:**
419
+
420
+ | 参数 | 类型 | 是否必填 | 说明 |
421
+ |------|------|----------|------|
422
+ | `userIdList` | `List<String>` | 是 | 目标用户的 userId 列表 |
423
+ | `mediaType` | `CallMediaType` | 是 | `CallMediaType.Video`:视频通话;`CallMediaType.Audio`:语音通话 |
424
+ | `params` | `CallParams` | 否 | 扩展参数:`roomId`(房间ID)、`timeout`(超时秒数)、`userData`(自定义数据)、`chatGroupId`(群组ID)、`isEphemeralCall`(是否加密通话) |
425
+
426
+ ### 步骤5: 结束通话
427
+
428
+ 监听 `onCallEnded` 事件,通话结束时销毁页面:
429
+
430
+ ```kotlin
431
+ import io.trtc.tuikit.atomicxcore.api.call.CallEndReason
432
+ import io.trtc.tuikit.atomicxcore.api.call.CallListener
433
+ import io.trtc.tuikit.atomicxcore.api.call.CallMediaType
434
+ import io.trtc.tuikit.atomicxcore.api.call.CallStore
435
+
436
+ class CallActivity : AppCompatActivity() {
437
+ private var callListener: CallListener? = null
438
+
439
+ override fun onCreate(savedInstanceState: Bundle?) {
440
+ super.onCreate(savedInstanceState)
441
+ addListener()
442
+ }
443
+
444
+ private fun addListener() {
445
+ callListener = object : CallListener() {
446
+ override fun onCallEnded(callId: String, mediaType: CallMediaType, reason: CallEndReason, userId: String) {
447
+ finish()
448
+ }
449
+ }
450
+ callListener?.let { CallStore.shared.addListener(it) }
451
+ }
452
+ }
453
+ ```
454
+
455
+ **`onCallEnded` 事件参数说明:**
456
+
457
+ | 参数 | 类型 | 说明 |
458
+ |------|------|------|
459
+ | `callId` | String | 此次通话的唯一标识。 |
460
+ | `mediaType` | [CallMediaType](https://liteav.sdk.cloudcachetci.com/doc/product/tuikit/atomic-x/android/zh/-atomic-x%20-core%20-a-p-i/io.trtc.tuikit.atomicxcore.api.call/-call-media-type/index.html) | 通话媒体类型,用于指定发起音频通话还是视频通话。CallMediaType.Video:视频通话。CallMediaType.Audio:语音通话。 |
461
+ | `reason` | [CallEndReason](https://liteav.sdk.cloudcachetci.com/doc/product/tuikit/atomic-x/android/zh/-atomic-x%20-core%20-a-p-i/io.trtc.tuikit.atomicxcore.api.call/-call-end-reason/index.html) | 通话结束的原因。Unknown:未知原因,无法确定结束原因。Hangup:正常挂断,用户主动挂断通话。Reject:拒绝接听,被叫方拒绝来电。NoResponse:无响应,被叫方未在超时时间内接听。Offline:对方离线,被叫方不在线。LineBusy:对方忙线,被叫方正在通话中。Canceled:通话取消,主叫方在对方接听前取消。OtherDeviceAccepted:其他设备已接听,通话已在另一登录设备上接听。OtherDeviceReject:其他设备已拒绝,通话已在另一登录设备上拒绝。EndByServer:服务器结束,通话被服务器终止。 |
462
+ | `userId` | String | 触发结束的用户 ID 。 |
463
+
464
+ ### 运行效果
465
+
466
+ 当您完成以上 5 步后,"拨打一通电话"运行效果如下:
467
+
468
+ ## 定制页面
469
+
470
+ **CallCoreView** 提供了完善的 UI 定制能力,支持头像及音量提示等图标的自由替换。**为助力快速集成,您可以直接从** [GitHub](https://github.com/Tencent-RTC/TUIKit_Android/tree/main/atomic_x/src/main/res-callview/drawable-xxhdpi) **下载。这些图标由我们的设计师专为 TUICallKit 打造,无版权风险,可放心使用。**
471
+
472
+ ### 自定义音量提示的图标
473
+
474
+ 您可以调用 **CallCoreView** 组件的 [setVolumeLevelIcons](https://liteav.sdk.cloudcachetci.com/doc/product/tuikit/atomic-x/android/zh/-atomic-x%20-core%20-a-p-i/io.trtc.tuikit.atomicxcore.api.view/-call-core-view/set-volume-level-icons.html) 设置音量大小等级不同的提示图标。
475
+
476
+ **setVolumeLevelIcons 示例代码:**
477
+
478
+ ```kotlin
479
+ private fun setIconResourcePath() {
480
+ val volumeLevelIcons = mapOf(VolumeLevel.Mute to "对应图标资源的路径")
481
+ val callCoreView = CallCoreView(context)
482
+ callCoreView.setVolumeLevelIcons(volumeLevelIcons)
483
+ }
484
+ ```
485
+
486
+ **setVolumeLevelIcons 接口参数详细说明:**
487
+
488
+ | 参数 | 类型 | 是否必填 | 说明 |
489
+ |------|------|----------|------|
490
+ | icons | Map<VolumeLevel, String> | 是 | 音量等级与图标资源的映射表。Key(VolumeLevel)表示音量等级,Value(String)表示对应音量等级的图标资源路径 |
491
+
492
+ **VolumeLevel 等级:**
493
+
494
+ | 等级 | 说明 |
495
+ |------|------|
496
+ | `VolumeLevel.Mute` | 麦克风关闭,静音状态 |
497
+ | `VolumeLevel.Low` | 音量范围 (0-25] |
498
+ | `VolumeLevel.Medium` | 音量范围 (25-50] |
499
+ | `VolumeLevel.High` | 音量范围 (50-75] |
500
+ | `VolumeLevel.Peak` | 音量范围 (75-100] |
501
+
502
+ **音量提示图标:**
503
+
504
+ | 文件名 | 说明 | 下载地址 |
505
+ |--------|------|----------|
506
+ | callview_ic_audio_input.png | 【图标含义】音量提示图标。<br>【推荐用法】您可以将该图标等级设置为 `VolumeLevel.Low` 或 `VolumeLevel.Medium`,当用户音量大于对应等级时显示。 | https://github.com/Tencent-RTC/TUIKit_Android/blob/main/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_audio_input.png |
507
+ | callview_ic_self_mute.png | 【图标含义】静音图标。<br>【推荐用法】您可以将该图标等级设置为 `VolumeLevel.Mute`,当该用户静音时显示。 | https://github.com/Tencent-RTC/TUIKit_Android/blob/main/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_self_mute.png |
508
+
509
+ ### 自定义网络提示图标
510
+
511
+ 您可以调用 **CallCoreView** 组件的 [setNetworkQualityIcons](https://liteav.sdk.cloudcachetci.com/doc/product/tuikit/atomic-x/android/zh/-atomic-x%20-core%20-a-p-i/io.trtc.tuikit.atomicxcore.api.view/-call-core-view/set-network-quality-icons.html) 设置不同网络状态的提示图标。
512
+
513
+ **setNetworkQualityIcons 示例代码:**
514
+
515
+ ```kotlin
516
+ private fun setNetworkQualityIcons() {
517
+ val networkIcons = mapOf(NetworkQuality.BAD to "对应图标的路径")
518
+ val callCoreView = CallCoreView(context)
519
+ callCoreView.setNetworkQualityIcons(networkIcons)
520
+ }
521
+ ```
522
+
523
+ **setNetworkQualityIcons 接口参数详细说明:**
524
+
525
+ | 参数 | 类型 | 是否必填 | 说明 |
526
+ |------|------|----------|------|
527
+ | icons | Map<NetworkQuality, String> | 是 | 网络质量与图标资源的映射表。Key(NetworkQuality)表示网络质量等级,Value(String)表示对应网络状态的图标资源路径 |
528
+
529
+ **NetworkQuality 等级:**
530
+
531
+ | 等级 | 说明 |
532
+ |------|------|
533
+ | `NetworkQuality.UNKNOWN` | 未知网络状态 |
534
+ | `NetworkQuality.EXCELLENT` | 网络状态极佳 |
535
+ | `NetworkQuality.GOOD` | 网络状态较好 |
536
+ | `NetworkQuality.POOR` | 网络状态较差 |
537
+ | `NetworkQuality.BAD` | 网络状态差 |
538
+ | `NetworkQuality.VERY_BAD` | 网络状态极差 |
539
+ | `NetworkQuality.DOWN` | 网络断开 |
540
+
541
+ **网络较差的提示图标:**
542
+
543
+ | 文件名 | 说明 | 下载地址 |
544
+ |--------|------|----------|
545
+ | callview_ic_network_bad.png | 【图标含义】网络较差的提示图标。<br>【推荐用法】您可以将该图标等级设置为 `NetworkQuality.BAD`、`NetworkQuality.VERY_BAD` 或 `NetworkQuality.DOWN`,当网络较差时显示该图标。 | https://github.com/Tencent-RTC/TUIKit_Android/blob/main/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_network_bad.png |
546
+
547
+ ### 自定义默认头像
548
+
549
+ 您可以调用 **CallCoreView** 的 [setParticipantAvatars](https://liteav.sdk.cloudcachetci.com/doc/product/tuikit/atomic-x/android/zh/-atomic-x%20-core%20-a-p-i/io.trtc.tuikit.atomicxcore.api.view/-call-core-view/set-participant-avatars.html) 接口设置用户头像。建议您监听响应式数据 allParticipants(所有参与通话的成员):当获取到用户头像时设置并展示;若用户未设置头像或加载失败,则显示默认头像(占位图)。
550
+
551
+ **setParticipantAvatars 示例代码:**
552
+
553
+ ```kotlin
554
+ private fun setParticipantAvatars() {
555
+ val avatars: MutableMap<String, String> = mutableMapOf()
556
+ val userId = "" // 用户 ID
557
+ val avatarPath = "" // 用户默认头像资源的路径
558
+ avatars[userId] = avatarPath
559
+ val callCoreView = CallCoreView(context)
560
+ callCoreView.setParticipantAvatars(avatars)
561
+ }
562
+ ```
563
+
564
+ **setParticipantAvatars 接口参数详细说明:**
565
+
566
+ | 参数 | 类型 | 是否必填 | 说明 |
567
+ |------|------|----------|------|
568
+ | icons | Map<String, String> | 是 | 用户头像映射表。Key:用户的 userID。Value:该用户的头像资源绝对路径 |
569
+
570
+ **默认头像资源:**
571
+
572
+ | 文件名 | 说明 | 下载地址 |
573
+ |--------|------|----------|
574
+ | callview_ic_avatar.png | 【图标含义】默认头像。<br>【推荐用法】当用户头像加载失败或无头像时,您可以给该用户设置此默认头像。 | https://github.com/Tencent-RTC/TUIKit_Android/blob/main/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_avatar.png |
575
+
576
+ ### 自定义 loading 动画
577
+
578
+ 您可以调用 **CallCoreView** 的 [setWaitingAnimation](https://liteav.sdk.cloudcachetci.com/doc/product/tuikit/atomic-x/android/zh/-atomic-x%20-core%20-a-p-i/io.trtc.tuikit.atomicxcore.api.view/-call-core-view/set-waiting-animation.html) 接口,为等待中用户设置等待动画获得更好的体验。
579
+
580
+ **setWaitingAnimation 示例代码:**
581
+
582
+ ```kotlin
583
+ private fun setWaitingAnimation() {
584
+ val waitingAnimationPath = "" // 等待动画 GIF 图像资源的路径
585
+ val callCoreView = CallCoreView(context)
586
+ callCoreView.setWaitingAnimation(waitingAnimationPath)
587
+ }
588
+ ```
589
+
590
+ **setWaitingAnimation 接口参数详细说明:**
591
+
592
+ | 参数 | 类型 | 是否必填 | 说明 |
593
+ |------|------|----------|------|
594
+ | path | String | 是 | GIF 格式图像资源的绝对路径 |
595
+
596
+ **等待接听的动画:**
597
+
598
+ | 文件名 | 说明 | 下载地址 |
599
+ |--------|------|----------|
600
+ | callview_ic_loading.gif | 【图标含义】用户等待接听动画。<br>【推荐用法】群组通话时设置的动画。设置后,当用户的状态为等待接听时,显示该动画。 | https://github.com/Tencent-RTC/TUIKit_Android/blob/main/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_loading.gif |
601
+
602
+ ### 添加通话计时提示
603
+
604
+ 通过响应式数据 `activeCall` 的 `duration` 字段实时获取通话计时:
605
+
606
+ ```kotlin
607
+ import android.content.Context
608
+ import androidx.appcompat.widget.AppCompatTextView
609
+ import com.tencent.qcloud.tuicore.util.DateTimeUtil
610
+ import io.trtc.tuikit.atomicxcore.api.call.CallStore
611
+ import kotlinx.coroutines.CoroutineScope
612
+ import kotlinx.coroutines.Dispatchers
613
+ import kotlinx.coroutines.Job
614
+ import kotlinx.coroutines.launch
615
+
616
+ class TimerView(context: Context) : AppCompatTextView(context) {
617
+ private var subscribeStateJob: Job? = null
618
+
619
+ override fun onAttachedToWindow() {
620
+ super.onAttachedToWindow()
621
+ registerActiveCallObserver()
622
+ }
623
+
624
+ private fun registerActiveCallObserver() {
625
+ subscribeStateJob = CoroutineScope(Dispatchers.Main).launch {
626
+ CallStore.shared.observerState.activeCall.collect { activeCall ->
627
+ updateDurationView(activeCall)
628
+ }
629
+ }
630
+ }
631
+
632
+ private fun updateDurationView(activeCall: CallInfo) {
633
+ val currentDuration = activeCall.duration
634
+ text = DateTimeUtil.formatSecondsTo00(currentDuration.toInt())
635
+ }
636
+
637
+ override fun onDetachedFromWindow() {
638
+ super.onDetachedFromWindow()
639
+ subscribeStateJob?.cancel()
640
+ }
641
+ }
642
+ ```
643
+
644
+ ## 更多功能
645
+
646
+ ### 设置头像和昵称
647
+
648
+ 通话开始前,您可以通过 setSelfInfo 方法,设置自己的昵称和头像。
649
+
650
+ ```kotlin
651
+ val user = UserProfile()
652
+ user.userID = "" // 您的 userId
653
+ user.avatarURL = "" // 头像的 url
654
+ user.nickname = "" // 需要设置的昵称
655
+
656
+ LoginStore.shared.setSelfInfo(user, object : CompletionHandler {
657
+ override fun onSuccess() {
658
+ // 设置成功回调
659
+ }
660
+ override fun onFailure(code: Int, desc: String) {
661
+ // 设置失败回调
662
+ }
663
+ })
664
+ ```
665
+
666
+ ### 切换布局模式
667
+
668
+ | 模式 | 适用场景 | 视觉效果 |
669
+ |------|---------|---------|
670
+ | **Float** | 1v1 通话 | 全屏显示对方画面,己方以可拖拽悬浮小窗展示在右上角,点击可互换大小画面 |
671
+ | **Grid** | 2人以上 | 所有成员画面等分网格平铺,2人时左右各半,4人时2×2,支持点击放大 |
672
+ | **Pip** | 1v1 或多人 | 1v1 固定显示对方画面;多人时自动展示正在说话的用户(Active Speaker 策略) |
673
+
674
+ ```kotlin
675
+ private fun setLayoutTemplate() {
676
+ val callCoreView = CallCoreView(context)
677
+ val template = CallLayoutTemplate.Grid
678
+ callCoreView.setLayoutTemplate(template)
679
+ setContentView(callCoreView)
680
+ }
681
+ ```
682
+
683
+ ### 设置通话的默认超时时间
684
+
685
+ 您可以在发起通话 calls 时,通过配置参数 CallParams 中的 `timeout` 字段来指定等待超时时间。
686
+
687
+ ```kotlin
688
+ val callParams = CallParams()
689
+ callParams.timeout = 30 // 设置通话超时(秒)
690
+ CallStore.shared.calls(userIdList, CallMediaType.Video, callParams, null)
691
+ ```
692
+
693
+ ### 实现画中画功能
694
+
695
+ 需 Android 8.0 (API 26) 及以上支持。
696
+
697
+ **进入画中画:**
698
+
699
+ ```kotlin
700
+ private fun enterPictureInPictureModeWithBuild() {
701
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && hasPipModePermission()) {
702
+ val pictureInPictureParams: PictureInPictureParams.Builder = PictureInPictureParams.Builder()
703
+ val floatViewWidth = resources.getDimensionPixelSize(R.dimen.callkit_video_small_view_width)
704
+ val floatViewHeight = resources.getDimensionPixelSize(R.dimen.callkit_video_small_view_height)
705
+ val aspectRatio = Rational(floatViewWidth, floatViewHeight)
706
+ pictureInPictureParams.setAspectRatio(aspectRatio).build()
707
+ this.enterPictureInPictureMode(pictureInPictureParams.build())
708
+ }
709
+ }
710
+ ```
711
+
712
+ **进入画中画成功后切换布局:**
713
+
714
+ ```kotlin
715
+ val callCoreView = CallCoreView(context)
716
+
717
+ override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean) {
718
+ super.onPictureInPictureModeChanged(isInPictureInPictureMode)
719
+ if (isInPictureInPictureMode) {
720
+ callCoreView.setLayoutTemplate(CallLayoutTemplate.Pip)
721
+ }
722
+ }
723
+ ```
724
+
725
+ **从画中画返回时恢复布局:**
726
+
727
+ ```kotlin
728
+ override fun onResume() {
729
+ super.onResume()
730
+ val allParticipants = CallStore.shared.observerState.allParticipants.value
731
+ if (allParticipants.size > 2) {
732
+ callCoreView?.setLayoutTemplate(CallLayoutTemplate.Grid)
733
+ } else {
734
+ callCoreView?.setLayoutTemplate(CallLayoutTemplate.Float)
735
+ }
736
+ }
737
+ ```
738
+
739
+ > **注意**:需要在 `AndroidManifest.xml` 中为通话 Activity 声明独立任务栈,避免画中画时 UI 异常:
740
+ > ```xml
741
+ > <activity
742
+ > android:name=".view.CallActivity"
743
+ > android:launchMode="singleTask"
744
+ > android:taskAffinity="${applicationId}.call" />
745
+ > ```
746
+
747
+ ### 设置通话过程中屏幕常亮
748
+
749
+ 通话过程中保持屏幕常亮是通话应用的基本需求。Android 提供了 `WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON` 标志来实现此功能,这是最简单且推荐的方式。
750
+
751
+ ```kotlin
752
+ class CallActivity : AppCompatActivity() {
753
+ override fun onCreate(savedInstanceState: Bundle?) {
754
+ super.onCreate(savedInstanceState)
755
+ window.addFlags(
756
+ WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or
757
+ WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD or
758
+ WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON or
759
+ WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
760
+ )
761
+ setContentView(R.layout.activity_call)
762
+ }
763
+ }
764
+ ```
765
+
766
+ ### 播放等待接听的提示音
767
+
768
+ 您可以监听自己的通话状态,在等待接听时播放铃声,在接听通话或通话结束停止播放铃声。
769
+
770
+ ```kotlin
771
+ MainScope().launch {
772
+ CallStore.shared.observerState.selfInfo.collect { selfInfo ->
773
+ if (selfInfo.status == CallParticipantStatus.Accept || selfInfo.status == CallParticipantStatus.None) {
774
+ // 停止播放铃声
775
+ return@collect
776
+ }
777
+ if (selfInfo.status == CallParticipantStatus.Waiting) {
778
+ // 播放铃声
779
+ }
780
+ }
781
+ }
782
+ ```
783
+
784
+ ### 开启后台采集音频/视频
785
+
786
+ **1. 配置权限与服务**(`AndroidManifest.xml`):
787
+
788
+ ```xml
789
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
790
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
791
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_CAMERA"/>
792
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE"/>
793
+
794
+ <application>
795
+ <service
796
+ android:name=".CallForegroundService"
797
+ android:enabled="true"
798
+ android:exported="false"
799
+ android:foregroundServiceType="camera|microphone"/>
800
+ </application>
801
+ </manifest>
802
+ ```
803
+
804
+ **2. 创建前台服务类:**
805
+
806
+ ```kotlin
807
+ import android.app.Notification
808
+ import android.app.NotificationChannel
809
+ import android.app.NotificationManager
810
+ import android.app.Service
811
+ import android.content.Context
812
+ import android.content.Intent
813
+ import android.os.Build
814
+ import android.os.IBinder
815
+ import androidx.core.app.NotificationCompat
816
+
817
+ class CallForegroundService : Service() {
818
+ companion object {
819
+ private const val NOTIFICATION_ID = 1001
820
+ private const val CHANNEL_ID = "call_foreground_channel"
821
+
822
+ fun start(context: Context) {
823
+ val intent = Intent(context, CallForegroundService::class.java)
824
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
825
+ context.startForegroundService(intent)
826
+ } else {
827
+ context.startService(intent)
828
+ }
829
+ }
830
+
831
+ fun stop(context: Context) {
832
+ val intent = Intent(context, CallForegroundService::class.java)
833
+ context.stopService(intent)
834
+ }
835
+ }
836
+
837
+ override fun onCreate() {
838
+ super.onCreate()
839
+ createNotificationChannel()
840
+ startForeground(NOTIFICATION_ID, createNotification())
841
+ }
842
+
843
+ override fun onBind(intent: Intent?): IBinder? = null
844
+
845
+ private fun createNotification(): Notification {
846
+ return NotificationCompat.Builder(this, CHANNEL_ID)
847
+ .setContentTitle("正在通话中")
848
+ .setContentText("应用正在后台运行以保持通话")
849
+ .setSmallIcon(android.R.drawable.ic_menu_call)
850
+ .setPriority(NotificationCompat.PRIORITY_HIGH)
851
+ .build()
852
+ }
853
+
854
+ private fun createNotificationChannel() {
855
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
856
+ val channel = NotificationChannel(
857
+ CHANNEL_ID,
858
+ "通话保活服务",
859
+ NotificationManager.IMPORTANCE_HIGH
860
+ )
861
+ val manager = getSystemService(NotificationManager::class.java)
862
+ manager.createNotificationChannel(channel)
863
+ }
864
+ }
865
+ }
866
+ ```
867
+
868
+ ## 常见问题
869
+
870
+ ### 进入画中画模式后,应用内跳转其他界面后画面异常?
871
+
872
+ Android 的画中画模式基于任务栈运作,默认所有 Activity 共享同一任务栈。需在 `AndroidManifest.xml` 中为通话 Activity 声明独立任务栈(`launchMode="singleTask"` + 独立 `taskAffinity`)。
873
+
874
+ ### 在通话邀请超时时间内,被邀请者如果离线再上线,能否收到来电事件?
875
+
876
+ 单人通话时,如果在超时时间内上线,会触发来电邀请;群组通话,如果在超时时间内上线,会拉起未处理的 20 条群消息,如果存在通话邀请,则触发来电邀请事件。