tencent cloud

实时音视频

动态与公告
产品动态
产品近期公告
关于 TRTC Live 正式上线的公告
关于TRTC Conference 正式版上线的公告
Conference 商业化版本即将推出
关于多人音视频 Conference 开启内测公告
关于音视频通话 Call 正式版上线的公告
关于腾讯云音视频终端 SDK 播放升级及新增授权校验的公告
关于 TRTC 应用订阅套餐服务上线的相关说明
产品简介
产品概述
基本概念
产品功能
产品优势
应用场景
性能数据
购买指南
计费概述
免费时长说明
月订阅
现收现付
TRTC 逾期与暂停政策
常见问题解答
退款说明
新手指引
Demo 体验
视频通话 SDK
组件介绍
开通服务
跑通 Demo
快速接入
离线唤醒
会话聊天
云端录制
AI 降噪
界面定制
Chat 集成通话能力
更多特性
无 UI 集成
服务端 API
客户端 API
解决方案
错误码表
发布日志
常见问题
视频会议 SDK
组件介绍(TUIRoomKit)
开通服务(TUIRoomKit)
跑通 Demo(TUIRoomKit)
快速接入(TUIRoomKit)
屏幕共享(TUIRoomKit)
预定会议(TUIRoomKit)
会中呼叫(TUIRoomKit)
界面定制(TUIRoomKit)
虚拟背景(TUIRoomKit)
会议控制(TUIRoomKit)
云端录制(TUIRoomKit)
AI 降噪(TUIRoomKit)
会中聊天(TUIRoomKit)
机器人推流(TUIRoomKit)
更多特性(TUIRoomKit)
客户端 API(TUIRoomKit)
服务端 API(TUIRoomKit)
常见问题(TUIRoomKit)
错误码 (TUIRoomKit)
SDK更新日志(TUIRoomKit)
直播与语聊 SDK
Live 视频直播计费说明
组件介绍
开通服务(TUILiveKit)
跑通 Demo
无 UI 集成
UI 自定义
直播监播
视频直播
语聊房
高级功能
客户端 API
服务端 API
错误码
发布日志
常见问题
RTC Engine
开通服务
SDK 下载
API-Example
接入指引
API-参考手册
高级功能
AI 集成
概述
MCP 配置
Skills 配置
集成指南
常见问题
RTC RESTFUL API
History
Introduction
API Category
Room Management APIs
Stream mixing and relay APIs
On-cloud recording APIs
Data Monitoring APIs
Pull stream Relay Related interface
Web Record APIs
AI Service APIs
Cloud Slicing APIs
Cloud Moderation APIs
Making API Requests
Call Quality Monitoring APIs
Usage Statistics APIs
Data Types
Appendix
Error Codes
控制台指南
应用管理
套餐包管理
用量统计
监控仪表盘
开发辅助
解决方案
实时合唱
常见问题
迁移指南
计费相关
功能相关
UserSig 相关
应对防火墙限制相关
缩减安装包体积相关
Andriod 与 iOS 相关
Web 端相关
Flutter 相关
Electron 相关
TRTCCalling Web 相关
音视频质量相关
其他问题
旧版文档
RTC RoomEngine SDK(旧)
集成 TUIRoom (Web)
集成 TUIRoom (Android)
集成 TUIRoom (iOS)
集成 TUIRoom (Flutter)
集成 TUIRoom (Electron)
TUIRoom API 查询
实现云端录制与回放(旧)
监控仪表盘计费(旧)
协议与策略
安全合规认证
安全白皮书
信息安全说明
服务等级协议
苹果隐私策略:PrivacyInfo.xcprivacy
TRTC 政策
隐私协议
数据处理和安全协议
词汇表

拨打第一通电话

PDF
聚焦模式
字号
最后更新时间: 2026-03-26 14:51:09
本文档将帮助您使用 AtomicXCore SDKDeviceStoreCallStore 以及核心组件 CallCoreView,快速完成拨打电话功能。


核心功能

AtomicXCore 中用于搭建多人音视频通话场景所需要使用到的核心模块包含以下三个:
模块
功能描述
通话视图核心组件。自动监听 CallStore 数据并完成画面渲染,同时支持 1v1 和多人通话布局自动切换。
CallStore
通话生命周期管理:拨打电话、接通电话、拒接电话、挂断电话。实时获取参与通话人员音视频状态,通话计时、通话记录等数据。
音视频设备控制:麦克风(开关 / 音量)、摄像头(开关 / 切换 / 画质)、屏幕共享,设备状态实时监听。

准备工作

步骤1:开通服务

请参见 开通服务,获取体验版或付费版 SDK。

步骤2:集成 SDK

安装组件:在工程的根目录下,通过命令行执行以下命令安装 atomic_x_core 插件。
flutter pub add atomic_x_core

步骤3:初始化与登录流程

Android 配置

1. 由于 SDK 内部使用了 Java 反射机制(或特性),需要将部分 SDK 类加入不进行混淆处理的名单。
请在工程的android/app/目录下找到build.gradle.kts(或build.gradle )文件中配置并开启混淆规则:
build.gradle.kts
build.gradle
android {
buildTypes {
release {
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android.txt"),
"proguard-rules.pro"
)
}
}
}
android {
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}

在工程的 android/app 目录下创建 proguard-rules.pro 文件,并在其中添加如下代码:
-keep class com.tencent.** { *; }
2. (可选)如果您需要在应用外使用 CallKit 的悬浮窗能力,需要开启系统画中画特性。
请在 App 主工程的 AndroidManifest.xml 里设置 MainActivityandroid:supportsPictureInPicture 为 true:
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<activity
android:name=".MainActivity"
android:supportsPictureInPicture="true"
</activity>
</application>
</manifest>

iOS 配置

由于 tencent_rtc_sdk 通过 Flutter FFI 调用接口,iOS Release 构建时 Xcode 的符号裁剪优化可能误移除 TRTC 的 C 符号,引发 `symbol not found` 错误。解决方案如下:
1. 在项目的 Build Settings 中找到 Deployment Postprocessing,将其设置为 Yes 。

2. 在项目的 Build Settings 中找到 Strip Style,将其中 Release 的值设置为 Non-Global Symbols 。

Flutter 初始化与登录流程

启动通话服务需依次完成 CallStore 初始化与用户登录。CallStore 通过监听登录成功事件自动同步用户信息,从而进入就绪状态。流程图与示例代码如下:

import 'package:atomic_x_core/atomicxcore.dart';
import 'package:rtc_room_engine/api/call/tui_call_engine.dart';

Future<void> _login() async {
int sdkAppId = 1400000001; // 替换为您的 SDKAppID
String userId = 'test_001'; // 替换为您的 UserID
String userSig = 'xxxxxxxxxxx'; // 替换为您的 UserSig

CallStore.shared;
final result = await LoginStore.shared.login(sdkAppId, userId, userSig);
TUICallEngine.instance.init(sdkAppId, userId, userSig);
if (result.isSuccess) {
// 登录成功
debugPrint('login success');
} else {
// 登录失败
debugPrint('login failed, code: ${result.code}, message: ${result.message}');
}
}
参数
类型
说明
userId
String
当前用户的唯一 ID,仅包含英文字母、数字、连字符和下划线。为避免多端登录冲突,请勿使用 1、123 等简单 ID。
sdkAppId
int
控制台 获取,通常是以 140 或 160 开头的 10 位整数。
userSig
String
用于腾讯云鉴权的票据。请注意:
开发环境:您可以采用本地 GenerateTestUserSig.genTestUserSig 函数生成 userSig 或者通过 UserSig 辅助工具 生成临时的 UserSig。
生产环境:为了防止密钥泄露,请务必采用服务端生成 UserSig 的方式。详细信息请参考 服务端生成 UserSig
更多信息请参见 如何计算及使用 UserSig

实现步骤

发起通话前,请确保完成登录,这是服务可用的必要前提。以下将分 5 步为您讲解如何"拨打一通电话"。

步骤1:创建通话界面

您需要创建一个通话页面,当发起通话唤起通话页面时,实现方式如下:
1. 创建通话页面:您可以新建一个 StatefulWidget 作为通话宿主页面,用于响应来电时的跳转逻辑。
2. 通话页面使用 CallCoreView 组件:通话视图核心组件,需要传入 controller 参数,自动监听 CallStore 数据并完成画面渲染,支持 1v1 和多人通话布局自动切换。
import 'package:flutter/material.dart';
import 'package:atomic_x_core/atomicxcore.dart';

// 1. 创建通话页面 Widget
class CallPage extends StatefulWidget {
const CallPage({super.key});

@override
State<CallPage> createState() => _CallPageState();
}

class _CallPageState extends State<CallPage> {
late CallCoreController controller;

@override
void initState() {
super.initState();
controller = CallCoreController.create();
}

@override
Widget build(BuildContext context) {
// 2. 通话页面使用 CallCoreView Widget
return CallCoreView(controller: controller);
}
}

CallCoreView Widget 功能说明:
功能
说明
参考文档
设置布局模式
支持自由切换布局模式。若未设置,将根据通话人数自动适配布局。
设置头像
支持通过传入头像资源路径,为特定用户自定义头像。
设置音量提示图标
支持根据不同音量等级,配置个性化的音量指示图标。
设置网络提示图标
支持根据实时网络质量,配置对应的网络状态提示图标。
设置等待接听用户的动画
在多人通话场景下,支持传入 GIF 图像路径,为待接听状态的用户展示动画。

步骤2:添加通话控制按钮

您可以参考 DeviceStoreCallStore 提供的 API ,自定义添加您的按钮。
DeviceStore 功能说明:麦克风(开关 / 音量)、摄像头(开关 / 切换 / 画质)、屏幕共享,设备状态实时监听。建议将对应方法绑定至按钮点击事件,并通过监听设备状态变更来实时刷新按钮的 UI 状态。
CallStore 功能说明:接听、挂断、拒接等核心通话控制能力。建议将对应方法绑定至按钮点击事件,并监听通话状态的变化,以确保按钮显示与当前通话阶段保持同步。
图标资源下载:按钮图标可以直接从 GitHub 下载。这些图标由我们的设计师专为 TUICallKit 打造,无版权风险,可放心使用。
图标:






















下载地址:
以添加挂断、麦克风、摄像头按钮为例,实现方式如下:
1.1 创建底部栏容器:在通话页面底部创建一个容器,用于添加挂断、麦克风、摄像头按钮。
import 'package:flutter/material.dart';
import 'package:atomic_x_core/atomicxcore.dart';

// 创建底部控制按钮容器 Widget
class ControlsContainer extends StatelessWidget {
const ControlsContainer({super.key});

@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
// 在这里添加控制按钮
],
);
}
}

1.2 添加挂断按钮:在底部工具栏容器中添加挂断按钮,在点击事件中调用 hangup 接口并销毁页面。
import 'package:atomic_x_core/atomicxcore.dart';
import 'package:flutter/material.dart';

// 挂断按钮 Widget
Widget buildHangupButton() {
return GestureDetector(
onTap: () {
// 调用 hangup 接口结束通话
CallStore.shared.hangup();
},
child: Container(
width: 60,
height: 60,
decoration: const BoxDecoration(
color: Colors.red,
shape: BoxShape.circle,
),
child: const Icon(
Icons.call_end,
color: Colors.white,
size: 30,
),
),
);
}
1.3 添加麦克风开关按钮:在底部工具栏容器中添加麦克风开关按钮,并在点击事件中调用 openLocalMicrophonecloseLocalMicrophone 接口。
import 'package:atomic_x_core/atomicxcore.dart';
import 'package:flutter/material.dart';

// 麦克风开关按钮 Widget
// 使用 ValueListenableBuilder 监听麦克风状态变化
Widget buildMicrophoneButton() {
return ValueListenableBuilder(
valueListenable: DeviceStore.shared.state.microphoneStatus,
builder: (context, status, child) {
final isOn = status == DeviceStatus.on;
return GestureDetector(
onTap: () {
// 根据当前状态开启或关闭麦克风
if (isOn) {
DeviceStore.shared.closeLocalMicrophone();
} else {
DeviceStore.shared.openLocalMicrophone();
}
},
child: Container(
width: 60,
height: 60,
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
shape: BoxShape.circle,
),
child: Icon(
isOn ? Icons.mic : Icons.mic_off,
color: Colors.white,
size: 30,
),
),
);
},
);
}
1.4 添加摄像头开关按钮:在底部工具栏容器中添加摄像头开关按钮,并在点击事件中调用 openLocalCameracloseLocalCamera 接口。
import 'package:atomic_x_core/atomicxcore.dart';
import 'package:flutter/material.dart';

// 摄像头开关按钮 Widget
// 使用 ValueListenableBuilder 监听摄像头状态变化
Widget buildCameraButton() {
return ValueListenableBuilder(
valueListenable: DeviceStore.shared.state.cameraStatus,
builder: (context, status, child) {
final isOn = status == DeviceStatus.on;
return GestureDetector(
onTap: () {
// 根据当前状态开启或关闭摄像头
if (isOn) {
DeviceStore.shared.closeLocalCamera();
} else {
final isFrontCamera = DeviceStore.shared.state.isFrontCamera.value;
DeviceStore.shared.openLocalCamera(isFrontCamera);
}
},
child: Container(
width: 60,
height: 60,
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
shape: BoxShape.circle,
),
child: Icon(
isOn ? Icons.videocam : Icons.videocam_off,
color: Colors.white,
size: 30,
),
),
);
},
);
}
1.5 实时更新媒体设备按钮状态:在 Flutter 中,使用 ValueListenableBuilder 监听麦克风和摄像头的状态变化,UI 会自动更新。
import 'package:atomic_x_core/atomicxcore.dart';
import 'package:flutter/material.dart';

// Flutter 中使用 ValueListenableBuilder 实现状态响应式更新
// 当 DeviceStore 中的状态变化时,UI 会自动重建
Widget buildDeviceControlButtons() {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
// 麦克风按钮 - 自动响应 microphoneStatus 变化
ValueListenableBuilder(
valueListenable: DeviceStore.shared.state.microphoneStatus,
builder: (context, status, _) {
final isOn = status == DeviceStatus.on;
return Text(isOn ? '关闭麦克风' : '开启麦克风');
},
),
// 摄像头按钮 - 自动响应 cameraStatus 变化
ValueListenableBuilder(
valueListenable: DeviceStore.shared.state.cameraStatus,
builder: (context, status, _) {
final isOn = status == DeviceStatus.on;
return Text(isOn ? '关闭摄像头' : '开启摄像头');
},
),
],
);
}

步骤3:申请麦克风/摄像头权限

建议在发起通话前,先行检测音视频权限。若权限缺失,请引导用户动态申请。实现方法如下:
1. Android 权限声明
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" />
</manifest>
2. iOS 权限声明:
在您的 iOS 工程的 Info.plist 文件中,在顶级 <dict> 元素下添加以下两项。
<key>NSCameraUsageDescription</key>
<string>CallingApp需要访问您的相机权限,开启后录制的视频才会有画面</string>
<key>NSMicrophoneUsageDescription</key>
<string>CallingApp需要访问您的麦克风权限,开启后录制的视频才会有声音</string>
3. 动态申请权限:我们推荐使用 permission_handler 插件来动态申请音视频权限。首先使用以下命令添加 permission_handler 插件,然后申请权限:
flutter pub add permission_handler
import 'package:permission_handler/permission_handler.dart';

// 申请音视频权限
Future<bool> requestCallPermissions() async {
// 请求麦克风和摄像头权限
Map<Permission, PermissionStatus> statuses = await [
Permission.microphone,
Permission.camera,
].request();

// 检查权限状态
bool micGranted = statuses[Permission.microphone]?.isGranted ?? false;
bool cameraGranted = statuses[Permission.camera]?.isGranted ?? false;

if (micGranted && cameraGranted) {
// 权限申请成功
return true;
} else {
// 部分权限被拒绝,可引导用户开启权限
return false;
}
}

步骤4:发起通话

您可以在 calls 调用成功后跳转通话界面,我们建议您根据媒体类型自动开启麦克风或摄像头获得更好的通话体验,实现方式如下:
1. 发起通话:调用 calls 发起通话。
2. 开启媒体设备:发起通话成功后开启麦克风,如果是视频通话同时开启摄像头。
3. 唤起通话页面:发起通话成功,唤起通话页面。
import 'package:atomic_x_core/atomicxcore.dart';
import 'package:flutter/material.dart';

// 发起通话
Future<void> startCall(List<String> userIdList, CallMediaType mediaType) async {
final handler = await CallStore.shared.calls(userIdList, mediaType, null);
if (handler.errorCode == 0) {
// 唤起通话页面
if (mounted) {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const CallPage()),
);
}
} else {
debugPrint('Call failed: ${handler.errorCode}, ${handler.errorMessage}');
}
}
calls 接口参数详细说明:
参数
类型
是否必填
说明
userIdList
List<String>
目标用户的 userId 列表。
mediaType
通话媒体类型,用于指定发起音频通话还是视频通话。
CallMediaType.video:视频通话。
CallMediaType.audio:语音通话。
params
通话扩展参数,如:房间号、通话邀请超时时间等。
roomId (String):房间 ID,可选参数,未指定时由服务端自动分配。
timeout (Int) :呼叫超时时间(秒)。
userData (String):用户自定义数据。
chatGroupId (String):Chat 群组 ID,用于群组通话场景。
isEphemeralCall (Boolean):是否为加密通话(不产生通话记录)。

步骤5:结束通话

无论您调用 hangup 挂断还是对方主动结束通话,均会触发 onCallEnded 事件。 建议监听该事件,当该事件被触发(即通话结束)时,执行关闭当前界面的操作。实现方式如下:
1. 监听通话结束事件:监听 onCallEnded 事件。
2. 销毁通话页面onCallEnded 触发后,销毁通话页面。
import 'package:atomic_x_core/atomicxcore.dart';
import 'package:flutter/cupertino.dart';

void addListener(BuildContext context) {
CallEventListener listener = CallEventListener(
onCallEnded: (callId, mediaType, reason, userId) {
Navigator.of(context).pop();
}
);
CallStore.shared.addListener(listener);
}
onCallEnded 事件参数详细说明
参数
类型
说明
callId
String
此次通话的唯一标识。
mediaType
通话媒体类型,用于指定发起音频通话还是视频通话。
CallMediaType.video:视频通话。
CallMediaType.audio:语音通话。
reason
通话结束的原因。
unknown:未知原因,无法确定结束原因。
hangup:正常挂断,用户主动挂断通话。
reject:拒绝接听,被叫方拒绝来电。
noResponse:无响应,被叫方未在超时时间内接听。
offline:对方离线,被叫方不在线。
lineBusy:对方忙线,被叫方正在通话中。
canceled:通话取消,主叫方在对方接听前取消。
otherDeviceAccepted:其他设备已接听,通话已在另一登录设备上接听。
otherDeviceReject:其他设备已拒绝,通话已在另一登录设备上拒绝。
endByServer:服务器结束,通话被服务器终止。
userId
String
触发结束的用户 ID 。

运行效果

当您完成以上 5 步后,"拨打一通电话"运行效果如下:


定制页面

CallCoreView 提供了完善的 UI 定制能力,支持头像及音量提示等图标的自由替换。为助力快速集成,您可以直接从 GitHub 下载。这些图标由我们的设计师专为 TUICallKit 打造,无版权风险,可放心使用。

自定义音量提示的图标

您可以调用 CallCoreView 组件的 volumeIcons 参数设置音量大小等级不同的提示图标。

volumeIcons 示例代码:
Widget _buildCallCoreView() {
Map<VolumeLevel, Image> volumeIcons = {
VolumeLevel.mute : Image.asset(''), // 每个音量等级对应的图片
};
return CallCoreView(
controller: CallCoreController.create(),
volumeIcons: volumeIcons,
);
}
volumeIcons 参数详细说明:
参数
类型
是否必填
说明
volumeIcons
Map<VolumeLevel, Image>
音量等级与图标资源的映射表。
key ( VolumeLevel ) 表示音量等级:
VolumeLevel.mute:表示麦克风关闭,静音状态。
VolumeLevel.low:表示音量范围 (0-25]。
VolumeLevel.medium:表示音量范围 (25-50]。
VolumeLevel.high:表示音量范围在 (50-75]。
VolumeLevel.peak:表示音量范围在 (75-100]。
Value ( Image ) 表示对应音量等级的图标资源。
音量提示图标:
图标
说明
下载地址

【图标含义】音量提示图标。
【推荐用法】您可以将该图标等级设置为 VolumeLevel.lowVolumeLevel.medium ,当用户音量大于对应等级时显示。

【图标含义】静音图标。
【推荐用法】您可以将该图标等级设置为 VolumeLevel.mute ,当该用户静音时显示。

自定义网络提示的图标

您可以调用 CallCoreView 组件的 networkQualityIcons 参数设置不同网络状态的提示图标。

networkQualityIcons 示例代码:
Widget _buildCallCoreView() {
Map<NetworkQuality, Image> networkQualityIcons = {
NetworkQuality.bad : Image.asset(''), // 每个网络质量等级对应的图片
};
return CallCoreView(
controller: CallCoreController.create(),
networkQualityIcons: networkQualityIcons,
);
}

networkQualityIcons 参数详细说明:
参数
类型
是否必填
说明
networkQualityIcons
Map<NetworkQuality, Image>
网络质量与图标资源的映射表。
Key ( NetworkQuality ) : 表示网络质量等级。
NetworkQuality.unknown:未知网络状态。
NetworkQuality.excellent:网络状态极佳。
NetworkQuality.good:网络状态较好。
NetworkQuality.poor:网络状态较差。
NetworkQuality.bad:网络状态差。
NetworkQuality.veryBad :网络状态极差。
NetworkQuality.down :网络断开。
Value ( Image ) : 对应网络状态的图标资源。
网络较差的提示图标:
图标
说明
下载地址

【图标含义】网络较差的提示图标。
【推荐用法】您可以将该图标等级设置为 NetworkQuality.badNetworkQuality.veryBadNetworkQuality.down ,当网络较差时显示该图标。

自定义默认头像

您可以调用 CallCoreViewdefaultAvatar 参数设置用户默认头像。建议您监听响应式数据 allParticipants(所有参与通话的成员):当获取到用户头像时设置并展示;若用户未设置头像或加载失败,则显示默认头像(占位图)。
defaultAvatar 示例代码:
Widget _buildCallCoreView() {
Image defaultAvatarImage = Image.asset(''); // 默认用户头像图片
return CallCoreView(
controller: CallCoreController.create(),
defaultAvatar: defaultAvatarImage,
);
}

defaultAvatar 接口参数详细说明:
参数
类型
是否必填
说明
defaultAvatar
Image
用户默认头像。
默认头像资源:
图标
说明
下载地址

【图标含义】默认头像。
【推荐用法】当用户头像加载失败或无头像时,您可以给该用户设置此默认头像。

自定义 loading 动画

您可以调用 CallCoreViewloadingAnimation 参数,为等待中用户设置等待动画获得更好的体验。

loadingAnimation 示例代码:
Widget _buildCallCoreView() {
Image loading = Image.asset(''); // 默认加载动画资源
return CallCoreView(
controller: CallCoreController.create(),
loadingAnimation: loading,
);
}

loadingAnimation 接口参数详细说明:
参数
类型
是否必填
说明
loadingAnimation
Image
GIF 格式图像资源。
等待接听的动画:
图标
说明
下载地址

【图标含义】用户等待接听动画。
【推荐用法】群组通话时设置的动画。设置后,当用户的状态为等待接听时,显示该动画。

添加通话计时提示

通话计时可通过响应式数据 activeCallduration 字段实时获得,实时显示通话计时的实现方式如下:
1. 数据层订阅:订阅 CallStore.shared.state.activeCall , 建立当前活跃通话的响应式监听。
2. 绑定通话计时数据:将 activeCall.duration 字段绑定至 UI 控件。该字段为响应式数据,会自动驱动 UI 实时刷新,无需手动维护定时器。
import 'package:atomic_x_core/atomicxcore.dart';
import 'package:flutter/material.dart';

class TimerWidget extends StatelessWidget {
final double? fontSize;
final FontWeight? fontWeight;

const TimerWidget({
super.key,
this.fontSize,
this.fontWeight,
});

@override
Widget build(BuildContext context) {
return ValueListenableBuilder(
valueListenable: CallStore.shared.state.selfInfo,
builder: (context, info, child) {
if (info.status == CallParticipantStatus.accept) {
return ValueListenableBuilder(
valueListenable: CallStore.shared.state.activeCall,
builder: (context, activeCall, child) {
return Text(
formatDuration(activeCall.duration.toInt()),
style: TextStyle(
fontSize: fontSize,
fontWeight: fontWeight,
),
);
},
);
} else {
return Container();
}
}
);
}

String formatDuration(int timeCount) {
int hour = timeCount ~/ 3600;
int minute = (timeCount % 3600) ~/ 60;
String minuteShow = minute <= 9 ? "0$minute" : "$minute";
int second = timeCount % 60;
String secondShow = second <= 9 ? "0$second" : "$second";

if (hour > 0) {
String hourShow = hour <= 9 ? "0$hour" : "$hour";
return '$hourShow:$minuteShow:$secondShow';
} else {
return '$minuteShow:$secondShow';
}
}

}
说明:
若您想了解更多通话状态响应式数据,详细可参考:CallState

更多功能

设置头像和昵称

通话开始前,您可以通过 setSelfInfo 方法,设置自己的昵称和头像。
setSelfInfo 示例代码:
UserProfile profile = UserProfile(
userID: "", // 您的 UserId
avatarURL: "", // 头像的 URL
nickname: "", // 需要设置的昵称
);
CompletionHandler result = await LoginStore.shared.setSelfInfo(userInfo: profile);
if (result.errorCode == 0) {
print("setSelfInfo success");
} else {
print("setSelfInfo failed");
}
setSelfInfo 接口参数详细说明:
参数
类型
是否必填
说明
userProfile
用户信息结构体。
userID (String):用户的 ID 。
avatarURL (String) : 用户头像的 URL。
nickname (String) :用户的昵称。
更多字段详情可参考 UserProfile
completion
CompletionHandler
操作完成回调,用于返回接通电话的结果。

切换布局模式

您可以通过 setLayoutTemplate 接口灵活切换布局模式。若未主动配置,CallCoreView 将根据通话人数自动适配:1v1 场景下默认采用 Float 模式,多人通话场景下则自动切换为 Grid 模式。不同布局模式的说明如下:
Float 模式
Grid 模式
PIP 模式



布局逻辑:呼叫等待时全屏显示己方画面;接通后全屏显示对方画面,己方画面以悬浮小窗展示。
交互特性:支持小窗拖拽移动,点击小窗可实现大小画面互换。
布局逻辑:所有成员画面呈网格状平铺排列成宫格模式布局,适用 2 人以上通话,支持点击放大画面功能。
交互特性:支持点击特定成员画面放大查看。
布局逻辑:1v1 场景固定显示对方画面,多人场景:采用当前发言者(Active Speaker) 策略,自动识别并全屏展示正在说话的用户。
交互特性:等待时显示自己的画面,接通后还会显示通话计时。
setLayoutTemplate 示例代码:
CallCoreController controller = CallCoreController.create();
CallLayoutTemplate template = CallLayoutTemplate.float;
controller.setLayoutTemplate(template);
setLayoutTemplate 接口参数详细说明:
参数
类型
是否必填
说明
template
CallCoreView 的布局模式。
CallLayoutTemplate.float
布局逻辑:呼叫等待时全屏显示己方画面;接通后全屏显示对方画面,己方画面以悬浮小窗展示。
交互特性:支持小窗拖拽移动,点击小窗可实现大小画面互换。
CallLayoutTemplate.grid
布局逻辑:所有成员画面呈网格状平铺排列成宫格模式布局,适用 2 人以上通话,支持点击放大画面功能。
交互特性:支持点击特定成员画面放大查看。
CallLayoutTemplate.pip:
布局逻辑:1v1 场景固定显示对方画面,多人场景:采用当前发言者(Active Speaker) 策略,自动识别并全屏展示正在说话的用户。
交互特性:等待时显示自己的画面,接通后还会显示通话计时。

设置通话的默认超时时间

您可以在发起通话 calls 时,通过配置参数 CallParams 中的 timeout 字段来指定等待超时时间。示例代码如下:
void startCall(List<String> userIdList, CallMediaType mediaType) {
CallParams params = CallParams(
timeout: 30, // 设置通话等待超时时间
);
CallStore.shared.calls(userIdList, mediaType, params);
}
calls 接口参数详细说明:
参数
类型
是否必填
说明
userIdList
List<String>
目标用户的 userId 列表。
mediaType
通话媒体类型,用于指定发起音频通话还是视频通话。
CallMediaType.video:视频通话。
CallMediaType.audio:语音通话。
params
通话扩展参数,如:房间号、通话邀请超时时间等。
roomId (String):房间 ID,可选参数,未指定时由服务端自动分配。
timeout (Int):呼叫超时时间(秒)。
userData (String):用户自定义数据。
chatGroupId (String):Chat 群组 ID,用于群组通话场景。
isEphemeralCall (Boolean):是否为加密通话(不产生通话记录)。

实现应用内悬浮窗

在通话界面因页面导航(如用户返回、跳转至其他页面)而被覆盖时,在应用内生成一个可拖拽的悬浮窗。该悬浮窗需持续展示关键通话状态(如通话时长、对方信息),并提供一键返回完整通话界面的入口,从而提升多任务场景下的通话体验。
_buildPipWindowWidget() {
final pipWidth = MediaQuery.of(context).size.width;
final pipHeight = MediaQuery.of(context).size.height;
final scale = pipWidth / originWidth;
CallCoreController controller = CallCoreController.create();
controller.setLayoutTemplate(CallLayoutTemplate.pip);
return Scaffold(
body: SizedBox(
width: pipWidth,
height: pipHeight,
child: Container(
width: pipWidth,
height: pipHeight,
decoration: const BoxDecoration(color: Colors.transparent),
child: MediaQuery(
data: MediaQuery.of(context).copyWith(
size: Size(originWidth ?? pipWidth, originHeight ?? pipHeight)
),
child: ClipRect(
child: Transform.scale(
scale: scale,
alignment: Alignment.center,
child: OverflowBox(
maxWidth: originWidth,
maxHeight: originHeight,
alignment: Alignment.center,
child: CallCoreView(
controller: controller,
),
),
),
),
),
),
),
);
}

实现 Android 应用外画中画

画中画功能需 Android 8.0 (API 26) 及以上版本支持。
1. MainActivity 配置
监听 MainActivity 的生命周期,当 enablePictureInPicture 为 true 时,应用退到后台时自动拉起画中画。
import android.app.PictureInPictureParams
import android.content.pm.PackageManager
import android.os.Build
import android.util.Log
import android.util.Rational
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel

class MainActivity : FlutterActivity() {
companion object {
private const val TAG = "MainActivity"
private const val CHANNEL = "atomic_x/pip"
}

private var enablePictureInPicture = false

override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
when (call.method) {
"enablePictureInPicture" -> {
val enable = call.argument<Boolean>("enable") ?: false
val success = enablePIP(enable)
result.success(success)
}
"enterPictureInPicture" -> {
val success = enterPIP()
result.success(success)
}
else -> result.notImplemented()
}
}
}

override fun onUserLeaveHint() {
super.onUserLeaveHint()
// 用户按 Home 键时自动进入画中画
if (enablePictureInPicture) {
enterPIP()
}
}

private fun enablePIP(enable: Boolean): Boolean {
Log.i(TAG, "enablePictureInPicture: $enable")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O &&
packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)) {
enablePictureInPicture = enable
return true
}
return false
}

private fun enterPIP(): Boolean {
if (!enablePictureInPicture) return false
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
try {
val aspectRatio = Rational(9, 16)
val params = PictureInPictureParams.Builder()
.setAspectRatio(aspectRatio)
.build()
return enterPictureInPictureMode(params)
} catch (e: Exception) {
Log.e(TAG, "enterPIP failed: ${e.message}")
}
}
return false
}
}
2. AndroidManifest 配置
在项目的 AndroidManifest.xml 文件中为 MainActivity 添加画中画能力配置: android:supportsPictureInPicture="true"
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize"
android:supportsPictureInPicture="true">
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
3. Dart 层配置
import 'package:flutter/services.dart';

class PipManager {
static const MethodChannel _channel = MethodChannel('atomic_x/pip');

/// 启用/禁用画中画功能
static Future<bool> enablePictureInPicture(bool enable) async {
try {
final result = await _channel.invokeMethod<bool>('enablePictureInPicture', {'enable': enable});
return result ?? false;
} catch (e) {
return false;
}
}

/// 立即进入画中画模式
static Future<bool> enterPictureInPicture() async {
try {
final result = await _channel.invokeMethod<bool>('enterPictureInPicture');
return result ?? false;
} catch (e) {
return false;
}
}
}
在您的通话开始前,选择合适的时机启用画中画;在通话结束后,关闭画中画。

实现 iOS 应用外画中画

iOS 端支持通过底层 TRTC 引擎实现应用外画中画功能。当应用进入后台时,通话画面可以以系统画中画形式悬浮在其他应用之上,用户可以边使用其他应用边进行视频通话。实现方式如下:
说明:
需要在 Xcode 的 Signing & Capabilities 中添加 Background Modes 能力,并勾选 Audio, AirPlay, and Picture in Picture。
需要 iOS 15.0 及以上版本支持。
1. 开启画中画
import 'package:tencent_rtc_sdk/trtc_cloud.dart';

TRTCCloud.sharedInstance().then((trtcCloud) {
trtcCloud.callExperimentalAPI('''
{
"api": "configPictureInPicture",
"params": {
"enable": true,
"cameraBackgroundCapture": true,
"canvas": {
"width": 720,
"height": 1280,
"backgroundColor": "#111111"
},
"regions": [
{
"userId": "remoteUserId",
"userName": "",
"width": 1.0,
"height": 1.0,
"x": 0.0,
"y": 0.0,
"fillMode": 0,
"streamType": "high",
"backgroundColor": "#111111",
"backgroundImage": "file:///path/to/avatar.png"
},
{
"userId": "localUserId",
"userName": "",
"width": 0.333,
"height": 0.333,
"x": 0.65,
"y": 0.05,
"fillMode": 0,
"streamType": "high",
"backgroundColor": "#111111"
}
]
}
}
''');
});
2. 关闭画中画
import 'package:tencent_rtc_sdk/trtc_cloud.dart';

TRTCCloud.sharedInstance().then((trtcCloud) {
trtcCloud.callExperimentalAPI('''
{
"api": "configPictureInPicture",
"params": {
"enable": false
}
}
''');
});

播放等待接听的提示音

您可以监听自己的通话状态,在等待接听时播放铃声,在接听通话或通话结束停止播放铃声。
CallStore.shared.state.selfInfo.addListener(() {
CallParticipantInfo info = CallStore.shared.state.selfInfo.value;
if (info.status == CallParticipantStatus.accept || info.status == CallParticipantStatus.none) {
// 停止播放铃音
return;
}
if (info.status == CallParticipantStatus.waiting) {
// 播放铃音
}
});

开启后台采集音频/视频

为了确保应用在进入后台时仍能正常采集音频和视频(例如用户锁屏或切换到其他应用),您需要分别在 Android 、iOS 进行如下配置:

Android 端配置

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.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.Service
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.IBinder
import androidx.core.app.NotificationCompat

class 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)
}
}
}

iOS 端配置

配置步骤:在 Xcode 中打开您的项目,按照以下步骤操作:
1. 选择项目的 Target > Signing & Capabilities
2. 点击 + Capability
3. 搜索并添加 Background Modes
4. 勾选以下三个选项:
Audio, AirPlay, and Picture in Picture(保持音频采集和画中画功能)。
Voice over IP(支持 VoIP 通话)。
Remote notifications(可选,用于接收离线推送)。
配置完成后,您的 Info.plist 文件会自动添加以下内容:
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
<string>voip</string>
<string>remote-notification</string>
</array>
配置音频会话(AVAudioSession)
为了确保通话音频在后台正常工作,您需要在通话开始前配置音频会话。建议在通话界面的 viewDidLoad 或发起通话前设置:
import AVFoundation

/**
* 设置音频会话,支持后台音频采集
*
* 建议在以下场景调用:
* 1. 通话界面的 viewDidLoad 中
* 2. 发起通话 (calls) 之前
* 3. 接听通话 (accept) 之前
*/
private func start() {
let audioSession = AVAudioSession.sharedInstance()
do {
// 设置音频会话类别为播放和录音
// .allowBluetooth: 支持蓝牙耳机
// .allowBluetoothA2DP: 支持高质量蓝牙音频(A2DP 协议)
try audioSession.setCategory(.playAndRecord, options: [.allowBluetooth, .allowBluetoothA2DP])
// 激活音频会话
try audioSession.setActive(true)
} catch {
// 音频会话配置失败
}
}
注意:
您需要在应用中合适的时机,使用 MethodChannel 调用 start() 方法以开启后台保活能力。

下一步

恭喜您,已经完成了"拨打一通电话",接下来,您可以实现接听第一通电话功能,可参考下表:
功能
描述
集成指引
接听第一通电话
引导您快速完成接听流程集成。涵盖通话界面唤起、接听及拒接等核心控制功能。

常见问题

iOS release 包运行时 [symbol not found]?

由于 tencent_rtc_sdk 通过 Flutter FFI 调用接口,iOS Release 构建时 Xcode 的符号裁剪优化可能误移除 TRTC 的 C 符号,引发 `symbol not found` 错误。解决方案如下:
1. 在项目的 Build Settings中找到 Deployment Postprocessing,将其设置为 Yes 。

2. 在项目的 Build Settings中找到 Strip Style,将其中 Release 的值设置为 Non-Global Symbols 。


在通话邀请超时时间内,被邀请者如果离线再上线,能否收到来电事件?

单人通话时,如果在超时时间内上线,会触发来电邀请;群组通话,如果在超时时间内上线,会拉起未处理的20条群消息,如果存在通话邀请,则触发来电邀请事件。

联系我们

如果您在接入或使用过程中有任何疑问或者建议,欢迎加入 Telegram 技术交流群组,或 联系我们 获取支持。


帮助和支持

本页内容是否解决了您的问题?

填写满意度调查问卷,共创更好文档体验。

文档反馈