注意:推荐使用更高效的 AI 集成助手
注意:我们为你提供了全新的 AI 集成方式,只需要描述你的需求,即可自动生成集成代码,大幅提升开发效率。
注意:点击这里,立即体验 AI 集成
一、核心功能
AtomicXCore 中用于搭建多人音视频通话场景所需要使用到的核心模块包含以下三个:
| 模块 | 功能描述 |
| CallCoreView | 通话视图核心组件。自动监听 CallStore 数据并完成画面渲染,同时提供布局切换、头像与图标配置等 UI 定制化能力。 |
| CallStore | 通话生命周期管理:拨打电话、接通电话、拒接电话、挂断电话。实时获取参与通话人员音视频状态,通话计时、通话记录等数据。 |
| DeviceStore | 音视频设备控制:麦克风(开关 / 音量)、摄像头(开关 / 切换 / 画质)、屏幕共享,设备状态实时监听。 |
三、步骤1:开通服务
请参见 开通服务,获取体验版或付费版 SDK。
四、步骤2:集成 SDK
1. 请在你的 build.gradle 文件中添加 api "io.trtc.uikit:atomicx-core:latest.release" 和 api "com.tencent.imsdk:imsdk-plus:8.7.7201" 依赖,然后执行 Gradle Sync。
dependencies { api "io.trtc.uikit:atomicx-core:latest.release" api "com.tencent.imsdk:imsdk-plus:8.7.7201" // 其他依赖…}
2. 由于 SDK 内部使用了 Java 反射特性,需要将部分类加入不混淆名单。请在 app 目录下的 proguard-rules.pro 文件末尾添加如下代码。添加完后,点击右上角的 Sync Now,同步代码。
-keep class com.tencent.** { *; }
3. 在 app 目录下找到 AndroidManifest.xml 文件,在 application 节点中添加 tools:replace="android:allowBackup",覆盖组件内的设置,使用自己的设置。
<application android:name=".BaseApplication" android:allowBackup="false" android:icon="@drawable/app_ic_launcher" android:label="@string/app_name" android:largeHeap="true" android:theme="@style/AppTheme" tools:replace="android:allowBackup">
五、步骤3:初始化与登录流程
启动通话服务需依次完成以下三步:
1. 实例化 CallStore:CallStore.shared 初始化通话数据层。
2. 用户登录:调用 LoginStore.shared.login() 完成用户登录。
3. 初始化通话引擎:登录成功后调用 TUICallEngine.createInstance(context).init() 初始化通话信令引擎。
流程图与示例代码如下:
class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // CallStore 初始化 CallStore.shared val sdkAppId = 1400000001 // 替换为你的 SDKAppID val userId = "test_001" // 替换为你的 UserID val userSig = "xxxxxxxxxxx" // 替换为你的 UserSig LoginStore.shared.login( this, sdkAppId, userId, userSig, object : CompletionHandler { override fun onSuccess() { // 完成 TUICallEngine 的初始化 TUICallEngine.createInstance(this@MainActivity).init(sdkAppId, userId, userSig, null) // 登录成功处理 Log.d("Login", "login success"); } override fun onFailure(code: Int, desc: String) { // 登录失败处理 Log.e("Login", "login failed, code: $code, error: $desc"); } } ) }}
| 参数 | 类型 | 说明 |
| userId | String | 当前用户的唯一 ID,仅包含英文字母、数字、连字符和下划线。为避免多端登录冲突,请勿使用 1、123 等简单 ID。 |
| sdkAppId | int | 从 控制台 获取,通常是以 140 或 160 开头的 10 位整数。 |
| userSig | String | 用于腾讯云鉴权的票据。请注意:开发环境:你可以采用本地 GenerateTestUserSig.genTestUserSig 函数生成 userSig 或者 通过 UserSig 辅助工具 生成临时的 UserSig。生产环境:为了防止密钥泄露,请务必采用服务端生成 UserSig 的方式。详细信息请参考 服务端生成 UserSig。更多信息请参见 如何计算及使用 UserSig。 |
六、实现步骤
发起通话前,请确保完成登录,这是服务可用的必要前提。以下将分 5 步为你讲解如何"拨打一通电话"。
七、步骤1:创建通话界面
你需要创建一个通话页面,当发起通话唤起通话页面,实现方式如下:
1. 创建通话页面:你可以新建一个 Activity 作为通话宿主页面,用于响应来电时的跳转逻辑。
2. 通话页面绑定 CallCoreView : 通话视图核心组件,自动监听 CallStore 数据并完成画面渲染,同时提供布局切换、头像与图标配置等 UI 定制化能力。
class CallActivity : AppCompatActivity() { private var callCoreView: CallCoreView? = null // 1.创建通话页面容器 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // 2.通话页面绑定 CallCoreView callCoreView = CallCoreView(this) setContentView(callCoreView) }}
CallCoreView 视图组件功能说明:
| 功能 | 说明 | 参考文档 |
| 设置布局模式 | 支持自由切换布局模式。若未设置,将根据通话人数自动适配布局。 | 切换布局模式 |
| 设置头像 | 支持通过传入头像资源路径,为特定用户自定义头像。 | 自定义默认头像 |
| 设置音量提示图标 | 支持根据不同音量等级,配置个性化的音量指示图标。 | 自定义音量提示的图标 |
| 设置网络提示图标 | 支持根据实时网络质量,配置对应的网络状态提示图标。 | 自定义网络提示的图标 |
| 设置等待接听用户的动画 | 在多人通话场景下,支持传入 GIF 图像路径,为待接听状态的用户展示动画。 | 自定义 loading 动画 |
八、步骤2:添加通话控制按钮
你可以参考 DeviceStore 、CallStore 提供的 API ,自定义添加你的按钮。
·DeviceStore 功能说明:麦克风(开关 / 音量)、摄像头(开关 / 切换 / 画质)、屏幕共享,设备状态实时监听。建议将对应方法绑定至按钮点击事件,并通过监听设备状态变更来实时刷新按钮的 UI 状态。
·CallStore 功能说明:接听、挂断、拒接等核心通话控制能力。建议将对应方法绑定至按钮点击事件,并监听通话状态的变化,以确保按钮显示与当前通话阶段保持同步。
·图标资源下载:按钮图标可以直接从 Github 下载。这些图标由我们的设计师专为 TUICallKit 打造,无版权风险,可放心使用。
| 图标: | | | | | | | | |
·以添加挂断、麦克风、摄像头按钮为例,实现方式如下:
1.1 创建底部栏容器:在通话页面底部创建一个容器,用于添加挂断、麦克风、摄像头按钮。
import io.trtc.tuikit.atomicxcore.api.call.CallStoreimport io.trtc.tuikit.atomicxcore.api.view.CallCoreViewimport io.trtc.tuikit.atomicxcore.api.device.DeviceStoreclass CallActivity : AppCompatActivity() { private var buttonContainer: Button? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // 其他初始化代码 // 创建底部栏容器 createButtonContainer() setContentView(buttonContainer) } private fun createButtonContainer() { buttonContainer = LinearLayout(this).apply { orientation = LinearLayout.HORIZONTAL gravity = Gravity.CENTER layoutParams = FrameLayout.LayoutParams( FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT ).apply { gravity = Gravity.BOTTOM bottomMargin = dpToPx(80) } } }}
1.2 添加挂断按钮:在底部工具栏容器中添加挂断按钮,在点击事件中调用 hangup 接口并销毁页面。
import io.trtc.tuikit.atomicxcore.api.call.CallStoreimport io.trtc.tuikit.atomicxcore.api.view.CallCoreViewimport io.trtc.tuikit.atomicxcore.api.device.DeviceStoreclass CallActivity : AppCompatActivity() { private var buttonContainer: Button? = null private var buttonHangup: Button? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // 其他初始化代码 // 1.在底部工具栏容器中添加挂断按钮 addHangupButton() setContentView(buttonContainer) } private fun addHangupButton() { buttonHangup = Button(this).apply { text = "挂断" layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f).apply { marginEnd = dpToPx(8) } setOnClickListener { // 2.在点击事件中调用 hangup 接口并销毁页面 CallStore.shared.hangup(null) finish() } } buttonContainer?.addView(buttonHangup) }}
1.3 添加麦克风开关按钮:在底部工具栏容器中添加麦克风开关按钮,并在点击事件中调用 openLocalMicrophone 或 closeLocalMicrophone 接口。
import io.trtc.tuikit.atomicxcore.api.call.CallStoreimport io.trtc.tuikit.atomicxcore.api.view.CallCoreViewimport io.trtc.tuikit.atomicxcore.api.device.DeviceStoreclass CallActivity : AppCompatActivity() { private var buttonContainer: Button? = null private var buttonMicrophone: Button? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // 其他初始化代码 // 1.在底部工具栏容器中添加麦克风开关 addMicrophoneButton() setContentView(buttonContainer) } private fun addMicrophoneButton() { buttonMicrophone = Button(this).apply { layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f).apply { setMargins(16, 0, 16, 0) } setOnClickListener { // 2.点击事件调用开启或关闭麦克风 val isMicrophoneOpen = DeviceStore.shared().deviceState.microphoneStatus.value == DeviceStatus.ON if (isMicrophoneOpen) { DeviceStore.shared().closeLocalMicrophone() } else { DeviceStore.shared().openLocalMicrophone(null) } } } val isMicrophoneOpen = DeviceStore.shared().deviceState.microphoneStatus.value == DeviceStatus.ON buttonMicrophone?.text = if (isMicrophoneOpen) "关麦克风" else "开麦克风" buttonContainer?.addView(buttonMicrophone) }}
1.4 添加摄像头开关按钮:在底部工具栏容器中添加摄像头开关按钮,并在点击事件中调用 openLocalCamera 或 closeLocalCamera 接口。
Androidimport io.trtc.tuikit.atomicxcore.api.call.CallStoreimport io.trtc.tuikit.atomicxcore.api.view.CallCoreViewimport io.trtc.tuikit.atomicxcore.api.device.DeviceStoreclass CallActivity : AppCompatActivity() { private var buttonContainer: Button? = null private var buttonCamera: Button? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // 其他初始化代码 // 1.在底部工具栏容器中添加摄像头开关按钮 addCameraButton() setContentView(buttonContainer) } private fun addCameraButton() { buttonCamera = Button(this).apply { layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f).apply { setMargins(16, 0, 16, 0) } setOnClickListener { // 2.点击事件调用开启或关闭摄像头 val isCameraOpen = DeviceStore.shared().deviceState.cameraStatus.value == DeviceStatus.ON if (isCameraOpen) { DeviceStore.shared().closeLocalCamera() } else { val isFrontCamera = DeviceStore.shared().deviceState.isFrontCamera.value DeviceStore.shared().openLocalCamera(isFrontCamera, null) } } } val isCameraOpen = DeviceStore.shared().deviceState.cameraStatus.value == DeviceStatus.ON buttonCamera?.text = if (isCameraOpen) "关摄像头" else "开摄像头" buttonContainer?.addView(buttonCamera) }}
1.5 实时更新媒体设备按钮文本:监听麦克风和摄像头的状态,实时更新按钮文本。
import io.trtc.tuikit.atomicxcore.api.call.*import io.trtc.tuikit.atomicxcore.api.device.DeviceStoreimport kotlinx.coroutines.*class CallActivity : AppCompatActivity() { private var buttonCamera: Button? = null private var buttonMicrophone: Button? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // 其他初始化代码 // 1.监听麦克风和摄像头的状态 observeDeviceState() } private fun observeDeviceState() { deviceStateJob = CoroutineScope(Dispatchers.Main).launch { supervisorScope { launch { DeviceStore.shared().deviceState.cameraStatus.collect { status -> // 2.更新摄像头按钮文本 buttonCamera?.text = if (status == DeviceStatus.ON) "关闭摄像头" else "开启摄像头" } } launch { DeviceStore.shared().deviceState.microphoneStatus.collect { status -> // 2.更新麦克风按钮文本 buttonMicrophone?.text = if (status == DeviceStatus.ON) "关闭麦克风" else "开启麦克风" } } } } }}
九、步骤3:申请麦克风/摄像头权限
建议在发起通话前,先行检测音视频权限。若权限缺失,请引导用户动态申请。实现方法如下:
1. 声明权限:首先,在 AndroidManifest.xml 文件中声明应用需要的摄像头和麦克风权限。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <!– 麦克风权限 –> <uses-permission android:name="android.permission.RECORD_AUDIO" /> <!– 摄像头权限 –> <uses-permission android:name="android.permission.CAMERA" /> <application> <!– … –> <activity android:name=".CallActivity" android:exported="false" android:screenOrientation="portrait" /> </application></manifest>
2. 动态申请权限:我们推荐你在发起通话时,根据通话媒体类型动态申请音视频权限。以下是权限处理的示例代码:
// 动态申请权限private fun requestAllCallPermissions() { val allPermissions = arrayOf( Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO ) if (!checkPermissions(allPermissions)) { ActivityCompat.requestPermissions(this, allPermissions, PERMISSION_REQUEST_CODE) }}// 处理权限申请结果override fun onRequestPermissionsResult( requestCode: Int, permissions: Array<out String>, grantResults: IntArray) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) if (requestCode == PERMISSION_REQUEST_CODE) { // 检查是否所有权限都已授予 var allGranted = true for (result in grantResults) { if (result != PackageManager.PERMISSION_GRANTED) { allGranted = false break } } if (allGranted) { // 权限申请成功 } else { // 部分权限被拒绝 } }}
十、步骤4:发起通话
你可以在 calls 调用成功后跳转通话界面,我们建议你根据媒体类型自动开启麦克风或摄像头获得更好的通话体验,实现方式如下:
1. 发起通话:调用 calls 发起通话。
2. 开启媒体设备:发起通话成功后开启麦克风,如果是视频通话同时开启摄像头。
3. 唤起通话页面:发起通话成功,唤起通话页面。
import io.trtc.tuikit.atomicxcore.api.CompletionHandlerimport io.trtc.tuikit.atomicxcore.api.call.CallMediaTypeimport io.trtc.tuikit.atomicxcore.api.call.CallStoreclass MainActivity : ComponentActivity() { // 1.发起通话 private fun startCall(userIdList: List<String>, mediaType: CallMediaType) { CallStore.shared.calls(userIdList, mediaType, null, object : CompletionHandler { override fun onFailure(code: Int, desc: String) { } override fun onSuccess() { // 2.开启媒体设备 openDeviceForMediaType(mediaType) // 3.唤起通话页面 val intent = Intent(this@MainActivity, CallActivity::class.java) startActivity(intent) } }) } private fun openDeviceForMediaType(mediaType: CallMediaType?) { if (mediaType == null) { return } DeviceStore.shared().openLocalMicrophone(null) if (mediaType == CallMediaType.Video) { val isFrontCamera = DeviceStore.shared().deviceState.isFrontCamera.value DeviceStore.shared().openLocalCamera(isFrontCamera, null) } }}
calls 接口参数详细说明:
| 参数 | 类型 | 是否必填 | 说明 |
| userIdList | List<String> | 是 | 目标用户的 userId 列表。 |
| mediaType | CallMediaType | 是 | 通话媒体类型,用于指定发起音频通话还是视频通话。CallMediaType.Video : 视频通话。CallMediaType.Audio : 语音通话。 |
| params | CallParams | 否 | 通话扩展参数,如:房间号、通话邀请超时时间等。roomId (String) : 房间 ID,可选参数,未指定时由服务端自动分配。timeout (Int) : 呼叫超时时间(秒)。userData (String) : 用户自定义数据。chatGroupId (String) : Chat 群组 ID,用于群组通话场景。isEphemeralCall (Boolean) : 是否为加密通话(不产生通话记录)。 |
十一、步骤5: 结束通话
无论你调用 hangup 挂断还是对方主动结束通话,均会触发 onCallEnded 事件。 建议监听该事件,当该事件被触发(即通话结束)时,执行关闭当前界面的操作。实现方式如下:
1. 监听通话结束事件:监听 onCallEnded 事件。
2. 销毁通话页面:onCallEnded 触发后,销毁通话页面。
import io.trtc.tuikit.atomicxcore.api.call.CallEndReasonimport io.trtc.tuikit.atomicxcore.api.call.CallListenerimport io.trtc.tuikit.atomicxcore.api.call.CallMediaTypeimport io.trtc.tuikit.atomicxcore.api.call.CallStoreclass CallActivity : AppCompatActivity() { private var callListener: CallListener? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // … 其他初始化代码 // 1.监听通话结束事件 addListener() } private fun addListener() { callListener = object : CallListener() { override fun onCallEnded(callId: String, mediaType: CallMediaType, reason: CallEndReason, userId: String) { // 2.销毁通话页面 finish() } } callListener?.let { CallStore.shared.addListener(it) } }}
onCallEnded 事件参数详细说明:
| 参数 | 类型 | 说明 |
| callId | String | 此次通话的唯一标识。 |
| mediaType | CallMediaType | 通话媒体类型,用于指定发起音频通话还是视频通话。CallMediaType.Video : 视频通话。CallMediaType.Audio : 语音通话。 |
| reason | CallEndReason | 通话结束的原因。Unknown : 未知原因,无法确定结束原因。Hangup : 正常挂断,用户主动挂断通话。Reject : 拒绝接听,被叫方拒绝来电。NoResponse : 无响应,被叫方未在超时时间内接听。Offline : 对方离线,被叫方不在线。LineBusy : 对方忙线,被叫方正在通话中。Canceled : 通话取消,主叫方在对方接听前取消。OtherDeviceAccepted : 其他设备已接听,通话已在另一登录设备上接听。OtherDeviceReject : 其他设备已拒绝,通话已在另一登录设备上拒绝。EndByServer : 服务器结束,通话被服务器终止。 |
| userId | String | 触发结束的用户 ID 。 |
十二、运行效果
当你完成以上 5 步后,"拨打一通电话"运行效果如下:
十三、定制页面
CallCoreView 提供了完善的 UI 定制能力,支持头像及音量提示等图标的自由替换。为助力快速集成,你可以直接从 Github 下载。这些图标由我们的设计师专为 TUICallKit 打造,无版权风险,可放心使用。
十四、自定义音量提示的图标
你可以调用 CallCoreView 组件的 setVolumeLevelIcons 设置音量大小等级不同的提示图标。
·setVolumeLevelIcons 示例代码:
private fun setIconResourcePath() { val volumeLevelIcons = mapOf(VolumeLevel.Mute to "对应图标资源的路径") val callCoreView = CallCoreView(context) callCoreView.setVolumeLevelIcons(volumeLevelIcons)}
·setVolumeLevelIcons 接口参数详细说明:
| 参数 | 类型 | 是否必填 | 说明 |
| icons | Map<VolumeLevel, String> | 是 | 音量等级与图标资源的映射表。key ( VolumeLevel ) 表示音量等级:VolumeLevel.Mute :表示麦克风关闭,静音状态。VolumeLevel.Low :表示音量范围 (0-25]VolumeLevel.Medium : 表示音量范围 (25-50]VolumeLevel.High : 表示音量范围在 (50-75]VolumeLevel.Peak : 表示音量范围在 (75-100]。Value ( String ) 表示对应音量等级的图标资源路径。 |
·音量提示图标:
| 图标 | 说明 |
| | 【图标含义】音量提示图标。【推荐用法】你可以将该图标等级设置为 VolumeLevel.Low 或 VolumeLevel.Medium ,当用户音量大于对应等级时显示。 |
| | 【图标含义】静音图标。【推荐用法】你可以将该图标等级设置为 VolumeLevel.Mute ,当该用户静音时显示。 |
十五、自定义网络提示的图标
你可以调用 CallCoreView 组件的 setNetworkQualityIcons 设置不同网络状态的提示图标。
·setNetworkQualityIcons 示例代码:
private fun setNetworkQualityIcons() { val volumeLevelIcons = mapOf(NetworkQuality.BAD to "对应图标的路径") val callCoreView = CallCoreView(context) callCoreView.setNetworkQualityIcons(volumeLevelIcons)}
·setNetworkQualityIcons 接口参数详细说明:
| 参数 | 类型 | 是否必填 | 说明 |
| icons | Map<NetworkQuality, String> | 是 | 网络质量与图标资源的映射表。Key ( NetworkQuality ) : 表示网络质量等级。NetworkQuality.UNKNOWN :未知网络状态。NetworkQuality.EXCELLENT:网络状态极佳。NetworkQuality.GOOD : 网络状态较好。NetworkQuality.POOR : 网络状态较差。NetworkQuality.BAD : 网络状态差。NetworkQuality.VERY_BAD :网络状态极差。NetworkQuality.DOWN :网络断开。Value ( String ) : 对应网络状态的图标资源路径。 |
·网络较差的提示图标:
| 图标 | 说明 |
| | 【图标含义】网络较差的提示图标。【推荐用法】你可以将该图标等级设置为 NetworkQuality.BAD、NetworkQuality.VERY_BAD 或 NetworkQuality.DOWN ,当网络较差时显示该图标。 |
十六、自定义默认头像
你可以调用 CallCoreView 的 setParticipantAvatars 接口设置用户头像。建议你监听响应式数据 allParticipants(所有参与通话的成员):当获取到用户头像时设置并展示;若用户未设置头像或加载失败,则显示默认头像(占位图)。
·setParticipantAvatars 示例代码:
private fun setParticipantAvatars() { val avatars: MutableMap<String, String> = mutableMapOf() val userId = "" // 用户 ID val avatarPath = "" // 用户默认头像资源的路径 avatars[userId] = avatarPath val callCoreView = CallCoreView(context) callCoreView.setParticipantAvatars(avatars)}
·setParticipantAvatars 接口参数详细说明:
| 参数 | 类型 | 是否必填 | 说明 |
| icons | Map<String, String> | 是 | 用户头像映射表。Key : 用户的 userID 。Value : 该用户的头像资源绝对路径。 |
·默认头像资源:
| 图标 | 说明 |
| | 【图标含义】默认头像【推荐用法】当用户头像加载失败或无头像时,你可以给该用户设置此默认头像。 |
十七、自定义 loading 动画
你可以调用 CallCoreView 的 setWaitingAnimation 接口,为等待中用户设置等待动画获得更好的体验。
·setWaitingAnimation 示例代码:
private fun setWaitingAnimation() { val waitingAnimationPath = "" // 等待动画 GIF 图像资源的路径 val callCoreView = CallCoreView(context) callCoreView.setWaitingAnimation(waitingAnimationPath)}
·setWaitingAnimation 接口参数详细说明:
| 参数 | 类型 | 是否必填 | 说明 |
| path | String | 是 | GIF 格式图像资源的绝对路径。 |
·等待接听的动画:
| 图标 | 说明 |
| | 【图标含义】用户等待接听动画。【推荐用法】群组通话时设置的动画。设置后,当用户的状态为等待接听时,显示该动画。 |
十八、添加通话计时提示
通话计时可通过响应式数据 activeCall 的 duration 字段实时获得,实时显示通话计时的实现方式如下:
1. 数据层订阅:订阅 CallStore.observerState.activeCall , 建立当前活跃通话的响应式监听。
2. 绑定通话计时数据:将 activeCall.duration 字段绑定至 UI 控件。该字段为响应式数据,会自动驱动 UI 实时刷新,无需手动维护定时器。
import android.content.Contextimport androidx.appcompat.widget.AppCompatTextViewimport androidx.core.content.ContextCompatimport com.tencent.qcloud.tuicore.util.DateTimeUtilimport io.trtc.tuikit.atomicxcore.api.call.CallStoreimport io.trtc.tuikit.atomicxcore.api.call.CallParticipantStatusimport kotlinx.coroutines.CoroutineScopeimport kotlinx.coroutines.Dispatchersimport kotlinx.coroutines.Jobimport kotlinx.coroutines.launchclass TimerView(context: Context) : AppCompatTextView(context) { private var subscribeStateJob: Job? = null override fun onAttachedToWindow() { super.onAttachedToWindow() // 1.数据层订阅 activeCall registerActiveCallObserver() } private fun registerActiveCallObserver() { subscribeStateJob = CoroutineScope(Dispatchers.Main).launch { CallStore.shared.observerState.activeCall.collect { activeCall -> // 2.绑定通话计时数据,更新通话计时 updateDurationView(activeCall) } } } private fun updateDurationView(activeCall: CallInfo) { val currentDuration = activeCall.duration text = DateTimeUtil.formatSecondsTo00(currentDuration.toInt()) } override fun onDetachedFromWindow() { super.onDetachedFromWindow() subscribeStateJob?.cancel() }}
注意:说明:
注意:若你想了解更多通话状态响应式数据,详细可参考:CallState 。
20、设置头像和昵称
通话开始前,你可以通过 setSelfInfo 方法,设置自己的昵称和头像。
·setSelfInfo 示例代码:
val user = UserProfile()user.userID = "" // 你的 userIduser.avatarURL = "" // 头像的 urluser.nickname = "" // 需要设置的昵称LoginStore.shared.setSelfInfo(user, object : CompletionHandler { override fun onSuccess() { // 设置成功回调 } override fun onFailure(code: Int, desc: String) { // 设置失败回调 }})
·setSelfInfo 接口参数详细说明:
| 参数 | 类型 | 是否必填 | 说明 |
| userProfile | UserProfile | 是 | 用户信息结构体。userID (String):用户的 ID 。avatarURL (String) : 用户头像的 URL。nickname (String) :用户的昵称。更多字段详情可参考 UserProfile 。 |
| completion | CompletionHandler | 否 | 操作完成回调,用于返回接通电话的结果。 |
21、切换布局模式
你可以通过 setLayoutTemplate 接口灵活切换布局模式。若未主动配置,CallCoreView 将根据通话人数自动适配:1 V 1 场景下默认采用 Float 模式,多人通话场景下则自动切换为 Grid 模式。不同布局模式的说明如下:
| Float 模式 | Grid 模式 | PIP 模式 |
| | | |
| 布局逻辑:呼叫等待时全屏显示己方画面;接通后全屏显示对方画面,己方画面以悬浮小窗展示。交互特性:支持小窗拖拽移动,点击小窗可实现大小画面互换。 | 布局逻辑:所有成员画面呈网格状平铺排列成宫格模式布局,适用 2 人以上通话,支持点击放大画面功能。交互特性:支持点击特定成员画面放大查看。 | 布局逻辑:1v1 场景固定显示对方画面,多人场景:采用当前发言者(Active Speaker) 策略,自动识别并全屏展示正在说话的用户。交互特性:等待时显示自己的画面,接通后还会显示通话计时。 |
·setLayoutTemplate 示例代码:
private fun setLayoutTemplate() { val callCoreView = CallCoreView(context) val template = CallLayoutTemplate.Grid // 设置布局模式 callCoreView.setLayoutTemplate(template) setContentView(callCoreView)}
·setLayoutTemplate 接口参数详细说明:
| 参数 | 类型 | 是否必填 | 说明 |
| template | CallLayoutTemplate | 是 | CallCoreView 的布局模式。CallLayoutTemplate.Float :布局逻辑:呼叫等待时全屏显示己方画面;接通后全屏显示对方画面,己方画面以悬浮小窗展示。交互特性:支持小窗拖拽移动,点击小窗可实现大小画面互换。CallLayoutTemplate.Grid :布局逻辑:所有成员画面呈网格状平铺排列成宫格模式布局,适用 2 人以上通话,支持点击放大画面功能。交互特性:支持点击特定成员画面放大查看。CallLayoutTemplate.Pip : 布局逻辑:1v1 场景固定显示对方画面,多人场景:采用当前发言者(Active Speaker) 策略,自动识别并全屏展示正在说话的用户。交互特性:等待时显示自己的画面,接通后还会显示通话计时。 |
22、设置通话的默认超时时间
你可以在发起通话 calls 时,通过配置参数 CallParams 中的 timeout 字段来指定等待超时时间。示例代码如下:
val callParams = CallParams()callParams.timeout = 30 // 设置通话超时CallStore.shared.calls(userIdList, CallMediaType.Video, callParams, null)
calls 接口参数详细说明:
| 参数 | 类型 | 是否必填 | 说明 |
| userIdList | List<String> | 是 | 目标用户的 userId 列表。 |
| mediaType | CallMediaType | 是 | 通话媒体类型,用于指定发起音频通话还是视频通话。CallMediaType.Video : 视频通话。CallMediaType.Audio : 语音通话。 |
| params | CallParams | 否 | 通话扩展参数,如:房间号、通话邀请超时时间等。roomId (String) : 房间 ID,可选参数,未指定时由服务端自动分配。timeout (Int) : 呼叫超时时间(秒)。userData (String) : 用户自定义数据。chatGroupId (String) : Chat 群组 ID,用于群组通话场景。isEphemeralCall (Boolean) : 是否为加密通话(不产生通话记录)。 |
23、实现画中画功能
画中画功能需 Android 8.0 (API 26) 及以上版本支持。建议你在进入画中画模式时,将 CallCoreView 切换为 Pip 布局获得更好的体验。
·开始进入画中画:调用系统方法 enterPictureInPictureMode 启动画中画模式。
private fun enterPictureInPictureModeWithBuild() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && hasPipModePermission()) { val pictureInPictureParams: PictureInPictureParams.Builder = PictureInPictureParams.Builder() val floatViewWidth = resources.getDimensionPixelSize(R.dimen.callkit_video_small_view_width) val floatViewHeight = resources.getDimensionPixelSize(R.dimen.callkit_video_small_view_height) val aspectRatio = Rational(floatViewWidth, floatViewHeight) pictureInPictureParams.setAspectRatio(aspectRatio).build() this.enterPictureInPictureMode(pictureInPictureParams.build()) } }
·进入画中画成功:在 onPictureInPictureModeChanged 回调中,若检测到进入画中画模式,请将 CallCoreView 的布局设置为 Pip 模式:
val callCoreView = CallCoreView(context)override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean) { super.onPictureInPictureModeChanged(isInPictureInPictureMode) if (isInPictureInPictureMode) { callCoreView.setLayoutTemplate(CallLayoutTemplate.Pip) }}
·从画中画返回到通话界面:当从画中画返回应用(恢复全屏)时,会触发 onResume 回调。你可以在该回调中,根据当前通话的人数重新设置布局。若当前为 1 v 1 通话:设置为 Float,多人通话:设置为 Grid。
val callCoreView = CallCoreView(context)override fun onResume() { super.onResume() val allParticipants = CallStore.shared.observerState.allParticipants.value if (allParticipants.size > 2) { callCoreView?.setLayoutTemplate(CallLayoutTemplate.Grid) } else { callCoreView?.setLayoutTemplate(CallLayoutTemplate.Float) }}
24、设置通话过程中屏幕常亮
通话过程中保持屏幕常亮是通话应用的基本需求。Android 提供了 WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON 标志来实现此功能,这是最简单且推荐的方式。
class CallActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) window.addFlags( WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or // 锁屏时显示 WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD or // 解锁屏幕 WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON or // 保持屏幕常亮 WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON // 点亮屏幕 ) setContentView(R.layout.activity_call) }}
25、播放等待接听的提示音
你可以监听自己的通话状态,在等待接听时播放铃声,在接听通话或通话结束停止播放铃声。
MainScope().launch { CallStore.shared.observerState.selfInfo.collect { selfInfo -> if (selfInfo.status == CallParticipantStatus.Accept || selfInfo.status == CallParticipantStatus.None) { // 停止播放铃声 return@collect } if (selfInfo.status == CallParticipantStatus.Waiting) { // 播放铃声 }}
26、开启后台采集音频/视频
1. 配置权限与服务(AndroidManifest.xml): 从 Android 9.0 (API 28) 开始需要声明前台服务权限;Android 14 (API 34) 强制要求声明具体的服务类型(麦克风和摄像头)。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_CAMERA" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" /> <application> <service android:name=".CallForegroundService" android:enabled="true" android:exported="false" android:foregroundServiceType="camera|microphone" /> </application></manifest>
2. 创建前台服务类(CallForegroundService):
import android.app.Notificationimport android.app.NotificationChannelimport android.app.NotificationManagerimport android.app.Serviceimport android.content.Contextimport android.content.Intentimport android.os.Buildimport android.os.IBinderimport androidx.core.app.NotificationCompatclass CallForegroundService : Service() { companion object { private const val NOTIFICATION_ID = 1001 private const val CHANNEL_ID = "call_foreground_channel" fun start(context: Context) { val intent = Intent(context, CallForegroundService::class.java) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { context.startForegroundService(intent) } else { context.startService(intent) } } fun stop(context: Context) { val intent = Intent(context, CallForegroundService::class.java) context.stopService(intent) } } override fun onCreate() { super.onCreate() createNotificationChannel() // 启动前台通知,确保后台采集权限 startForeground(NOTIFICATION_ID, createNotification()) } override fun onBind(intent: Intent?): IBinder? = null private fun createNotification(): Notification { return NotificationCompat.Builder(this, CHANNEL_ID) .setContentTitle("正在通话中") .setContentText("应用正在后台运行以保持通话") .setSmallIcon(android.R.drawable.ic_menu_call) // 请替换为你的应用图标 .setPriority(NotificationCompat.PRIORITY_HIGH) .build() } private fun createNotificationChannel() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val channel = NotificationChannel( CHANNEL_ID, "通话保活服务", NotificationManager.IMPORTANCE_HIGH ) val manager = getSystemService(NotificationManager::class.java) manager.createNotificationChannel(channel) } }}
27、下一步
恭喜你,已经完成了"拨打一通电话",接下来,你可以实现接听第一通电话功能,可参考下表:
| 功能 | 描述 | 集成指引 |
| 接听第一通电话 | 引导你快速完成接听流程集成。涵盖通话界面唤起、接听及拒接等核心控制功能。 | 接听第一通电话 |
29、进入画中画模式后,应用内跳转其他界面后画面异常?
·原因:Android 的画中画模式是基于任务栈(Task Stack)运作的。默认情况下,应用内所有 Activity 共享同一个任务栈。因此,若在画中画期间启动新的 Activity,该页面会被压入当前栈中,导致其错误地显示在画中画的小窗口内,引发 UI 异常。
·解决方法:在 AndroidManifest.xml 声明通话界面的 Activity 为一个独立的任务栈。
<activity android:name=".view.CallActivity" <!– 你的通话界面 –> android:launchMode="singleTask" android:taskAffinity="${applicationId}.call" />
30、在通话邀请超时时间内,被邀请者如果离线再上线,能否收到来电事件?
单人通话时,如果在超时时间内上线,会触发来电邀请;群组通话,如果在超时时间内上线,会拉起未处理的20条群消息,如果存在通话邀请,则触发来电邀请事件。
31、联系我们
如果你在使用过程中,有什么建议或者意见,可以 联系我们,感谢你的反馈。