接入概览(Android)

最近更新时间:2026-05-06 20:41:44

我的收藏
本文档提供 AtomicXCore 的核心功能的集成指南,开发者使用 Atomicx 快速实现大型网络研讨会、企业全员大会或在线教育大班课的基础能力。开发者可完全自定义 UI 界面,专注于业务逻辑和用户体验的实现。
标准会议与研讨会有何不同?
标准会议(Conference):适用于中小规模的多人协作场景,所有参与者均具有平等的音视频权限,提供屏幕共享、成员管理等完整互动能力。
研讨会(Webinar):专为大型直播演讲设计,支持无上限观众进房观看。观众可举手申请上台成为嘉宾后参与讨论,且系统针对万人级互动场景优化了消息并发性能,满足更专业的演讲互动需求。

功能展示

主持人
嘉宾
观众
支持主持人发起高清音视频推流与屏幕共享,支持对成员进行管理。
支持嘉宾开启麦克风进行实时语音讨论和分享。
支持观众无上限并发进房,以超低延迟观看实况并参与高频消息互动。支持观众通过“举手”功能向主持人申请变更为嘉宾。




核心功能

通过 AtomicXCore,您可以快速获得构建专业研讨会场景所需的完整能力:
双层角色体系: 房间内用户分为嘉宾(可音视频发言)和观众(仅观看),主持人可动态邀请观众上麦或将嘉宾移出发言席。
支持屏幕分享:在研讨会进行期间,主持人可将自己的屏幕内容实时共享给所有成员,以此辅助讲解并促进交流。
成员与权限管理: 包含嘉宾列表、观众列表展示,以及管理员设置、禁言/禁画等控场功能。
超大规模消息互动:针对万人级以上房间优化了消息削峰机制,确保超高并发量下的即时聊天和弹幕互动依然流畅稳定。
AtomicXCore 核心模块包含以下两个:
RoomStore:房间管理功能入口,包含功能:创建房间、加入房间等。
RoomParticipantStore:房间内成员功能入口, 包含功能:管理员设置、嘉宾观众管理、成员移出房间、嘉宾设备控制等。

准备工作

步骤1:开通服务

请参考 开通服务 领取 TUILiveKit 体验版或开通 TUILiveKit 正式版。
注意:
研讨会场景基于底层的直播能力构建。请注意接入研讨会能力需要领取 TUILiveKit 体验版或开通 TUILiveKit 正式版,以确保相关功能的正常运行。

步骤2:环境要求

Android 5.0 (SDK API level 21)及以上。
Gradle 8.0 及以上。
Android 5.0 以上的设备。
需使用 JDK 17、18 或 19 版本。

步骤3:集成 AtomicXCore SDK

请在您的 build.gradle 文件中添加如下依赖,然后执行 Gradle Sync
dependencies {
implementation 'io.trtc.uikit:atomicx-core:4.0.6.181'
api "com.tencent.imsdk:imsdk-plus:8.7.7201"
}

步骤4:实现登录逻辑

在项目中调用 LoginStore.shared.login 完成登录,这是使用 AtomicXCore 所有功能的关键前提
重要:
推荐在自身的用户账户登录成功后,再调用 LoginStore.shared.login,以确保登录业务逻辑的清晰和一致。
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import io.trtc.tuikit.atomicxcore.api.login.LoginStore
import io.trtc.tuikit.atomicxcore.api.CompletionHandler
import android.util.Log

class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

LoginStore.shared.login(
this, // context
1400000001, // 替换为项目的 sdkAppID
"test_001", // 替换为项目的 userID
"xxxxxxxxxxx", // 替换为项目的 userSig
object : CompletionHandler {
override fun onSuccess() {
// 登录成功处理
Log.d("Login", "login success");
}

override fun onFailure(code: Int, desc: String) {
// 登录失败处理
Log.e("Login", "login failed, code: $code, error: $desc");
}
}
)
}
}
登录接口参数说明:
参数
类型
说明
sdkAppID
Int
控制台 获取,通常是以 140160 开头的 10 位整数。
userID
String
当前用户的唯一 ID,仅包含英文字母、数字、连字符和下划线。为避免多端登录冲突,请勿使用 1123 等简单 ID
userSig
String
用于腾讯云鉴权的票据。更多信息请参见 如何计算及使用 UserSig
注意:
开发环境:可以采用本地 GenerateTestUserSig.genTestSig 函数生成 userSig 或者通过 UserSig 辅助工具 生成临时的 UserSig。
生产环境:为了防止密钥泄露,请务必采用服务端生成 UserSig 的方式。详细信息请参考 服务端生成 UserSig

搭建研讨会房间

步骤1:创建并加入研讨会房间

创建并加入房间具体流程如下,只需执行以下几步操作,即可快速搭建出研讨会房间。



提示:
房主创建并加入房间的业务代码,可以参考 TUIRoomKit 开源项目中 RoomMainActivity.ktRoomMainView.kt 文件来了解完整的实现逻辑。

1. 创建并加入房间

实现方式:
1.1 配置房间初始化参数:初始化 CreateRoomOptions 设置房间名称,房间配置。
1.2 创建并加入房间:调用 RoomStorecreateAndJoinRoom 接口执行核心操作。
示例代码:
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import io.trtc.tuikit.atomicxcore.api.CompletionHandler
import io.trtc.tuikit.atomicxcore.api.room.CreateRoomOptions
import io.trtc.tuikit.atomicxcore.api.room.RoomStore
import io.trtc.tuikit.atomicxcore.api.room.RoomType

// RoomMainActivity 代表房间主视图 Activity
class RoomMainActivity : AppCompatActivity() {
private val roomID = "webinar_123456"

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//自动创建并加入房间逻辑
createAndJoinRoom()
}

// 调用此方法完成创建并加入房间的复合操作
private fun createAndJoinRoom() {
// 1. 配置房间初始化参数
// CreateRoomOptions 用于定义房间的基础属性及初始权限控制
val options = CreateRoomOptions()
options.roomName = "我的研讨会" // 设置房间对外展示的名称
options.password = "" // 研讨会房间暂时不支持密码,这里可以填空

// 2. 房间权限预设:在创建之初即可控制全场权限(通常用于正式会议场景)
options.isAllCameraDisabled = false // 全员开启/禁用摄像头的初始状态
options.isAllMessageDisabled = false // 全员开启/禁言的初始状态
options.isAllMicrophoneDisabled = false // 全员开启/静音的初始状态
options.isAllScreenShareDisabled = false // 全员禁止/允许屏幕分享的初始状态

// 3. 创建并加入房间
// 该方法是一个复合操作:若房间不存在则先创建,随后自动执行加入流程
RoomStore.shared().createAndJoinRoom(roomID, RoomType.WEBINAR, options, object : CompletionHandler {
override fun onSuccess() {
// 创建并加入成功:此时可以开始订阅房间状态、打开本地摄像头或麦克风
Log.d("Room", "创建并加入研讨会房间成功")
}

override fun onFailure(code: Int, desc: String) {
// 创建并加入失败:可能由于权限不足、网络异常或参数非法
Log.e("Room", "创建并加入研讨会房间失败 [错误码: $code]: $desc")
}
})
}
}
createAndJoinRoom 接口参数详细说明
参数名
类型
必填
说明
roomID
String
字符串类型的房间唯一标识符。
限制长度为 0-48 字节。
建议仅包含数字、英文字母(区分大小写)、下划线(_)和连字符(-)。避免使用空格和中文字符。
roomType
RoomType
房间类型。
STANDARD:标准房间。
WEBINAR:大型研讨会房间。
研讨会场景这里填写 WEBINAR 房间类型。
options
CreateRoomOptions
创建房间配置对象。
详细用法请参考:CreateRoomOptions 结构体详细说明
completion
CompletionHandler
操作完成回调,用于返回创建和加入房间的结果。若创建失败则会返回错误码和错误信息。
CreateRoomOptions 结构体详细说明
参数名
类型
必填
说明
roomName
String
房间名称,可以不设置,默认为空字符串。
限制长度为 0-60 字节。
【推荐用法】支持中英文、数字、特殊字符。
password
String
房间密码,空字符串 "" 通常表示该房间不设密码。
限制长度为 0-32 字节。
推荐使用 4-8 位纯数字,方便移动端输入。设置后,其他用户加入房间时需输入密码。建议不要存储明文敏感信息。
研讨会场景暂时不支持密码功能,这里可以填空。
isAllMicrophoneDisabled
Boolean
是否全员禁止打开麦克风。开启后,除房主/管理员外,嘉宾默认禁止打开麦克风。
true:禁止。
false:取消禁止 (默认值)。
isAllCameraDisabled
Boolean
是否全员禁止打开摄像头。开启后,除房主/管理员外,嘉宾默认禁止打开摄像头。
true:禁止。
false:取消禁止(默认值)。
isAllScreenShareDisabled
Boolean
是否全员禁止发起屏幕共享。开启后,仅房主/管理员可进行屏幕共享。
true:禁止。
false:取消禁止(默认值)。
isAllMessageDisabled
Boolean
是否全员禁止发送聊天消息(禁言)。开启后,成员无法在房间内发送文字消息。
true:禁止。
false:取消禁止(默认值)。

2. 主持人/嘉宾 采集媒体设备

研讨会场景下,不同角色具备不同的媒体采集权限。媒体设备权限分配如下:
主持人:支持采集并推送本地音视频流、发起屏幕分享。
嘉宾:支持采集并推送本地音频流进行实时讨论。
观众:仅支持观看音视频流,不具备采集权限。
进房成功后调用 DeviceStore 单例对象的 openLocalCameraopenLocalMicrophone 接口打开本地音视频设备采集,
调用 startScreenShare 可以开启屏幕分享(屏幕分享时需要关闭摄像头)。
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import io.trtc.tuikit.atomicxcore.api.device.DeviceStore

// RoomMainActivity 代表房间主视图 Activity
class RoomMainActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
openDevices()
}

private fun openDevices() {
// 1. 打开前置摄像头
DeviceStore.shared().openLocalCamera(true, completion = null)
// 2. 打开麦克风
DeviceStore.shared().openLocalMicrophone(completion = null)
// 3. 开始屏幕分享 (屏幕分享开启的时候需要关闭摄像头)
// DeviceStore.shared().closeLocalCamera() //关闭摄像头
// DeviceStore.shared().startScreenShare() //开始屏幕分享
}
}
注意:
研讨会模式下,移动端仅支持上行一路视频流,即开启摄像头的时候需要关闭屏幕分享,相应的开启屏幕分享的时候需要关闭摄像头推流。
openLocalCamera 接口参数详细说明
参数名
类型
必填
说明
isFront
Boolean
是否开启前置摄像头。
true :开启前置摄像头。
false :开启后置摄像头。
completion
CompletionHandler
操作完成回调,用于返回开启摄像头的结果。若开启失败则会返回错误码和错误信息。

3. 结束房间

当房主期望结束房间时,调用 RoomStoreendRoom 接口即可结束当前房间。结束房间后,房间内的所有成员都会收到房间结束事件。
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import io.trtc.tuikit.atomicxcore.api.CompletionHandler
import io.trtc.tuikit.atomicxcore.api.room.RoomStore

// RoomMainActivity 代表房间主视图 Activity
class RoomMainActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
endRoom()
}

// 调用此方法结束房间
private fun endRoom() {
// endRoom 接口用于永久结束当前房间。
// 注意:通常该操作仅限房主(Owner)执行,执行后所有成员将被移出房间且音视频采集会强制停止。
RoomStore.shared().endRoom(object : CompletionHandler {
override fun onSuccess() {
// 结束房间成功
Log.d("Room", "结束房间成功")
}

override fun onFailure(code: Int, desc: String) {
// 结束房间失败
Log.e("Room", "结束房间失败 [错误码: $code]: $desc")
}
})
}
}

步骤2:加入研讨会房间

1. 加入房间

需要加入房间时,调用 RoomStorejoinRoom 接口,即可加入房间。
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import io.trtc.tuikit.atomicxcore.api.CompletionHandler
import io.trtc.tuikit.atomicxcore.api.room.RoomStore
import io.trtc.tuikit.atomicxcore.api.room.RoomType

// RoomMainActivity 代表房间主视图 Activity
class RoomMainActivity : AppCompatActivity() {
private val roomID = "webinar_123456"

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
joinRoom()
}

// 调用此方法加入一个已存在的房间
private fun joinRoom() {
// 1. 准备加入参数
// joinRoom 适用于加入一个已知且正在进行的房间
val targetRoomID = this.roomID // 目标房间 ID

// 2. 调用 RoomStore 加入房间接口
RoomStore.shared().joinRoom(targetRoomID, RoomType.WEBINAR, completion = object : CompletionHandler {
override fun onSuccess() {
// 3. 加入成功处理
Log.d("Room", "加入房间成功")
}

override fun onFailure(code: Int, desc: String) {
// 4. 加入失败处理
Log.e("Room", "加入房间失败 [错误码: $code]: $desc")
}
})
}
}
joinRoom 接口参数详细说明
参数名
类型
必填
说明
roomID
String
字符串类型的房间唯一标识符。
限制长度为 0-48 字节。
建议仅包含数字、英文字母(区分大小写)、下划线(_)和连字符(-)。避免使用空格和中文字符。
roomType
RoomType
房间类型。
STANDARD:标准房间
WEBINAR:大型研讨会房间
研讨会场景这里填写 WEBINAR 房间类型。
password
String
房间密码,空字符串 "" 通常表示该房间不设密码。
限制长度为 0-32 字节。
推荐使用 4-8 位纯数字,方便移动端输入。设置后,其他用户加入房间时需输入密码。建议不要存储明文敏感信息。
研讨会场景暂时不支持密码功能,这里可以填空。
completion
CompletionHandler
操作完成回调,用于返回加入房间的结果。若加入房间失败则会返回错误码和错误信息。

2. 离开房间

当要离开房间时,通过调用 RoomStoreleaveRoom 接口即可离开房间。
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import io.trtc.tuikit.atomicxcore.api.CompletionHandler
import io.trtc.tuikit.atomicxcore.api.room.RoomStore

// RoomMainActivity 代表房间主视图 Activity
class RoomMainActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
leaveRoom()
}

// 调用此方法主动退出当前房间
private fun leaveRoom() {
// 1. 业务逻辑说明
// leaveRoom 接口用于普通成员或房主主动退出房间。
// 与 endRoom 不同,房主调用 leaveRoom 只会让自己离开,房间依然存在。

// 2. 调用 RoomStore 离开房间接口
// 该操作会停止音视频流传输,并通知服务器将当前用户移出房间
RoomStore.shared().leaveRoom(object : CompletionHandler {
override fun onSuccess() {
// 3. 退出成功处理
Log.d("Room", "退出房间成功")
}

override fun onFailure(code: Int, desc: String) {
// 4. 退出失败处理
Log.e("Room", "退出房间失败 [错误码: $code]: $desc")
}
})
}
}

步骤3:绑定视频画面视图

RoomView 组件已封装了研讨会的流媒体处理逻辑。开发者只需在页面中渲染 RoomView 组件,即可自动完成研讨会房间的音视频能力建设。视频渲染需要配合 TUIRoomKit 的组件来完成。
RoomView 内置能力:
主持人端:支持开启视频或屏幕分享。
嘉宾端:自动播放主持人的实时音视频流。
观众端:自动拉取并播放超低延迟直播流,确保海量并发下的观看体验。
提示:
RoomView 是用于渲染房间视频流的视图组件,请参考 TUIRoomKit 开源项目中 RoomView.kt 文件来了解完整的实现逻辑。
<com.trtc.uikit.roomkit.view.main.RoomView
android:id="@+id/room_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

步骤4:实现嘉宾/观众角色管理

作为房主或管理员,调用 RoomParticipantStorepromoteAudienceToParticipant 接口可以将房间内的观众提升成为嘉宾。
调用 demoteParticipantToAudience 接口可以将房间内的嘉宾降级成为观众。
import android.util.Log
import io.trtc.tuikit.atomicxcore.api.CompletionHandler
import io.trtc.tuikit.atomicxcore.api.room.RoomParticipantStore

fun promoteAudienceToParticipant(userID: String) {
// 前提:需要先完成进房操作。
// 1. 业务逻辑说明
// 通过进房的 roomID 创建 RoomParticipantStore 实例
val participantStore = RoomParticipantStore.create(roomID = "webinar_123456")

// 2. 调用 RoomParticipantStore 提升观众为嘉宾接口
// 注意:只有房主或管理员才有权限执行此操作。
participantStore.promoteAudienceToParticipant(userID = userID, completion = object : CompletionHandler {
override fun onSuccess() {
Log.d("Test", "提升为嘉宾成功")
}

override fun onFailure(code: Int, desc: String) {
Log.e("Test", "提升为嘉宾失败 [错误码: $code]: $desc")
}
})
}

fun demoteParticipantToAudience(userID: String) {
// 前提:需要先完成进房操作。
// 1. 业务逻辑说明
// 通过进房的 roomID 创建 RoomParticipantStore 实例
val participantStore = RoomParticipantStore.create(roomID = "webinar_123456")


// 2. 调用 RoomParticipantStore 降级嘉宾为观众接口
// 注意:只有房主或管理员才有权限执行此操作。
participantStore.demoteParticipantToAudience(userID = userID, completion = object : CompletionHandler {
override fun onSuccess() {
Log.d("Test", "降级为观众成功")
}


override fun onFailure(code: Int, desc: String) {
Log.e("Test", "降级为观众失败 [错误码: $code]: $desc")
}
})
}

步骤5:监听房间内事件

进入房间完成后,通过调用 RoomStoreaddRoomListener 可以订阅到 RoomListener 中与房间相关的被动事件。
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import io.trtc.tuikit.atomicxcore.api.room.RoomInfo
import io.trtc.tuikit.atomicxcore.api.room.RoomListener
import io.trtc.tuikit.atomicxcore.api.room.RoomStore

// RoomMainActivity 代表房间主视图 Activity
class RoomMainActivity : AppCompatActivity() {

// 1. 访问 RoomStore 的 RoomListener
// 该 RoomListener 会推送房间全生命周期的事件,例如呼叫、解散、预约提醒等
private val roomListener = object : RoomListener() {

// 2. 根据事件类型执行相应的业务逻辑
override fun onRoomEnded(roomInfo: RoomInfo) {
Log.d("Room", "当前所在房间已结束")
}

// ... 处理其他 RoomListener 事件 ...
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 核心流程:在初始化时开启事件监听
subscribeRoomEvents()
}

// 调用此方法订阅房间被动事件
private fun subscribeRoomEvents() {
RoomStore.shared().addRoomListener(roomListener)
}

override fun onDestroy() {
super.onDestroy()
// 移除监听器,防止内存泄漏
RoomStore.shared().removeRoomListener(roomListener)
}
}

API 文档

Store/Component
功能描述
API 文档
RoomStore
房间全生命周期管理:创建并加入 / 加入 / 离开 / 结束房间 / 更新、获取房间信息 / 监听房间内被动事件(例如房间解散,房间信息更新等)。
RoomParticipantStore
房间内成员管理:设置管理员 / 转移房主 / 获取嘉宾列表 / 获取观众列表 / 移出房间 / 嘉宾设备控制。

常见问题

集成代码后产生如下图所示编译报错 allowBackup 异常,如何处理?




问题原因:多个模块的 AndroidManifest.xml 中都配置了 allowBackup 属性,造成冲突。
解决方法:您可以在您工程的 AndroidManifest.xml 文件中删除 allowBackup 属性或将该属性改为 false,表示关闭备份和恢复功能;并在 AndroidManifest.xml 文件的 application 节点中添加 tools:replace="android:allowBackup" 表示覆盖其他模块的设置,使用您自己的设置。修复示例如图所示:




调用 DeviceStore 接口打开设备,对方依然看不到画面?

检查是否未申请系统相机和麦克风权限
Android 6.0 及以上版本动态申请系统权限可按照下面步骤:
1. AndroidManifest.xml 声明。
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
2. 动态申请权限。
import android.Manifest
import android.content.pm.PackageManager
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import io.trtc.tuikit.atomicxcore.api.device.DeviceStore

class RoomMainActivity : AppCompatActivity() {

private val PERMISSION_REQUEST_CODE = 1001

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
checkAndRequestPermissions()
}

private fun checkAndRequestPermissions() {
val permissions = arrayOf(
Manifest.permission.CAMERA,
Manifest.permission.RECORD_AUDIO
)

val permissionsToRequest = permissions.filter {
ContextCompat.checkSelfPermission(this, it) != PackageManager.PERMISSION_GRANTED
}

if (permissionsToRequest.isNotEmpty()) {
ActivityCompat.requestPermissions(
this,
permissionsToRequest.toTypedArray(),
PERMISSION_REQUEST_CODE
)
} else {
// 权限已授予,可以打开设备
openDevices()
}
}

override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == PERMISSION_REQUEST_CODE) {
if (grantResults.all { it == PackageManager.PERMISSION_GRANTED }) {
// 所有权限已授予,调用打开设备例如openDevices()
openDevices()
} else {
// 部分权限被拒绝
Log.e("Permission", "部分权限被拒绝")
}
}
}

private fun openDevices() {
// 1. 打开前置摄像头
DeviceStore.shared().openLocalCamera(isFront = true, completion = null)
// 2. 打开麦克风
DeviceStore.shared().openLocalMicrophone(completion = null)
}

Android 14 以上机型切到后台后, 采集音视频无画面/无声音?

Android 14 及以上版本,应用在后台采集摄像头或麦克风数据时,必须启动前台服务并声明对应的服务类型,否则无法正常采集,解决方案可按照下面步骤:
1. AndroidManifest.xml 声明 FOREGROUND_SERVICEFOREGROUND_SERVICE_CAMERAFOREGROUND_SERVICE_MICROPHONE 权限和 service 声明。
<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" />

<service
android:name=".MediaCaptureService"
android:foregroundServiceType="camera|microphone" />
2. 在界面启动后开启后台采集服务。
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.Service
import android.content.Intent
import android.content.pm.ServiceInfo
import android.os.Bundle
import android.os.IBinder
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.NotificationCompat

class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
startForegroundService(Intent(this, MediaCaptureService::class.java))
}
}

class MediaCaptureService : Service() {
override fun onCreate() {
super.onCreate()
// 1. 创建通知渠道
val channel = NotificationChannel("media", "媒体采集", NotificationManager.IMPORTANCE_LOW)
getSystemService(NotificationManager::class.java).createNotificationChannel(channel)

// 2. 构建通知
val notification = NotificationCompat.Builder(this, "media")
.setContentTitle("音视频通话中")
.setSmallIcon(android.R.drawable.ic_menu_call)
.build()

// 3. 启动前台服务,指定 camera 和 microphone 类型(Android 14+ 必须)
startForeground(1, notification,
ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA or ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE)
}

override fun onBind(intent: Intent?): IBinder? = null
}