@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.
- package/package.json +1 -1
- package/resource/doc/callkit/core-sdk/android/answer-call.md +942 -0
- package/resource/doc/callkit/core-sdk/android/make-call.md +876 -0
- package/resource/doc/callkit/core-sdk/android/offline-push.md +368 -0
- package/resource/doc/callkit/core-sdk/flutter/answer-call.md +1132 -0
- package/resource/doc/callkit/core-sdk/flutter/make-call.md +1206 -0
- package/resource/doc/callkit/core-sdk/flutter/offline-push.md +357 -0
- package/resource/doc/callkit/core-sdk/ios/answer-call.md +1009 -0
- package/resource/doc/callkit/core-sdk/ios/make-call.md +1154 -0
- package/resource/doc/callkit/core-sdk/ios/offline-push.md +470 -0
- package/resource/doc/callkit/core-sdk/uni-app/answer-call.md +1146 -0
- package/resource/doc/callkit/core-sdk/uni-app/make-call.md +1267 -0
- package/resource/doc/callkit/preset/android/integration.md +1 -1
- package/resource/doc/callkit/preset/flutter/integration.md +1 -1
- package/resource/doc/callkit/preset/ios/integration.md +1 -1
- package/resource/doc/callkit/preset/react/api.md +1 -1
- package/resource/doc/callkit/preset/vue/api.md +1 -1
- package/tools/call/get_native_callkit_core_integration.js +67 -0
- package/tools/call/get_native_callkit_core_integration.js.map +1 -0
- package/tools/common/present_framework_choice.js +1 -1
- package/tools/common/present_framework_choice.js.map +1 -1
- package/tools/definitions/get_native_call_uikit_integration_def.js +9 -3
- package/tools/definitions/get_native_call_uikit_integration_def.js.map +1 -1
- package/tools/definitions/get_native_callkit_core_integration_def.js +63 -0
- package/tools/definitions/get_native_callkit_core_integration_def.js.map +1 -0
- package/tools/index.js +2 -0
- package/tools/index.js.map +1 -1
- package/utils/report-cls-client.js +1 -1
- package/utils/report-es-client.js +1 -1
|
@@ -0,0 +1,1132 @@
|
|
|
1
|
+
本文档将帮助您使用 **AtomicXCore SDK** 的 **DeviceStore**、**CallStore** 以及核心组件 **CallCoreView**,快速完成接听电话功能。
|
|
2
|
+
|
|
3
|
+
## 核心功能
|
|
4
|
+
|
|
5
|
+
**AtomicXCore** 中用于搭建多人音视频通话场景所需要使用到的核心模块包含以下三个:
|
|
6
|
+
|
|
7
|
+
| **模块** | **功能描述** |
|
|
8
|
+
| --- | --- |
|
|
9
|
+
| [CallCoreView](https://liteav.sdk.qcloud.com/doc/product/tuikit/atomic-x/flutter/v1.0/zh/api_view_call_call_core_view/CallCoreView-class.html) | 通话视图核心 Widget。自动监听 CallStore 数据并完成画面渲染,同时支持 1v1 和多人通话布局自动切换。 |
|
|
10
|
+
| [CallStore](https://liteav.sdk.qcloud.com/doc/product/tuikit/atomic-x/flutter/v1.0/zh/api_call_call_store/CallStore-class.html) | 通话生命周期管理:拨打电话、接通电话、拒接电话、挂断电话。实时获取参与通话人员音视频状态,通话计时、通话记录等数据。 |
|
|
11
|
+
| [DeviceStore](https://liteav.sdk.qcloud.com/doc/product/tuikit/atomic-x/flutter/v1.0/zh/api_device_device_store/DeviceStore-class.html) | 音视频设备控制:麦克风(开关 / 音量)、摄像头(开关 / 切换 / 画质)、屏幕共享,设备状态实时监听。 |
|
|
12
|
+
|
|
13
|
+
## 准备工作
|
|
14
|
+
|
|
15
|
+
### 步骤1:开通服务
|
|
16
|
+
|
|
17
|
+
请参见 开通服务,获取体验版或付费版 SDK 。
|
|
18
|
+
|
|
19
|
+
### 步骤2:集成 SDK
|
|
20
|
+
|
|
21
|
+
**安装组件**:在工程的根目录下,通过命令行执行以下命令安装 atomic\_x\_core 插件。
|
|
22
|
+
|
|
23
|
+
`flutter pub add atomic_x_core`
|
|
24
|
+
|
|
25
|
+
### 步骤3:初始化与登录流程
|
|
26
|
+
|
|
27
|
+
#### Android 配置
|
|
28
|
+
|
|
29
|
+
1. 由于 SDK 内部使用了 Java 反射机制(或特性),需要将部分 SDK 类加入不混淆处理的名单。
|
|
30
|
+
|
|
31
|
+
请在工程的`android/app/`目录下找到`build.gradle.kts`(或`build.gradle` )文件并配置并开启混淆规则:
|
|
32
|
+
|
|
33
|
+
build.gradle.kts
|
|
34
|
+
build.gradle
|
|
35
|
+
```gradle
|
|
36
|
+
android {
|
|
37
|
+
buildTypes {
|
|
38
|
+
release {
|
|
39
|
+
isMinifyEnabled = true
|
|
40
|
+
proguardFiles(
|
|
41
|
+
getDefaultProguardFile("proguard-android.txt"),
|
|
42
|
+
"proguard-rules.pro"
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
android {
|
|
48
|
+
buildTypes {
|
|
49
|
+
release {
|
|
50
|
+
minifyEnabled true
|
|
51
|
+
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
在工程的 `android/app` 目录下创建 `proguard-rules.pro` 文件,并在其中添加如下代码:
|
|
58
|
+
|
|
59
|
+
```proguard
|
|
60
|
+
-keep class com.tencent.** { *; }
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
2. (可选)如果您需要在应用外使用 CallKit 的悬浮窗能力,需要开启系统画中画特性。
|
|
64
|
+
|
|
65
|
+
请在 App 主工程的 `AndroidManifest.xml` 里设置 `MainActivity` 的 `android:supportsPictureInPicture` 为 true:
|
|
66
|
+
|
|
67
|
+
```xml
|
|
68
|
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
|
69
|
+
<application>
|
|
70
|
+
<activity
|
|
71
|
+
android:name=".MainActivity"
|
|
72
|
+
android:supportsPictureInPicture="true"
|
|
73
|
+
</activity>
|
|
74
|
+
</application>
|
|
75
|
+
</manifest>
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
#### iOS 配置
|
|
79
|
+
|
|
80
|
+
由于 tencent\_rtc\_sdk 通过 Flutter FFI 调用接口,iOS Release 构建时 Xcode 的符号裁剪优化可能误移除 TRTC 的 C 符号,引发 `symbol not found` 错误。解决方案如下:
|
|
81
|
+
|
|
82
|
+
1. 在项目的 Build Settings 中找到 **Deployment Postprocessing**,将其设置为 **Yes 。**
|
|
83
|
+
|
|
84
|
+
2. 在项目的 Build Settings 中找到 **Strip Style**,将其中 Release 的值设置为 **Non-Global Symbols 。**
|
|
85
|
+
|
|
86
|
+
#### Flutter 初始化与登录流程
|
|
87
|
+
|
|
88
|
+
启动通话服务需依次完成 CallStore 初始化与用户登录。CallStore 通过监听登录成功事件自动同步用户信息,从而进入就绪状态。流程图与示例代码如下:
|
|
89
|
+
|
|
90
|
+
```dart
|
|
91
|
+
import 'package:atomic_x_core/atomicxcore.dart';
|
|
92
|
+
import 'package:rtc_room_engine/api/call/tui_call_engine.dart';
|
|
93
|
+
Future<void> _login() async {
|
|
94
|
+
int sdkAppId = 1400000001; // 替换为您的 SDKAppID
|
|
95
|
+
String userId = 'test_001'; // 替换为您的 UserID
|
|
96
|
+
String userSig = 'xxxxxxxxxxx'; // 联调填临时 UserSig;MCP 已配置 sdkAppId、sdkSecretKey 时可由工具自动生成
|
|
97
|
+
CallStore.shared;
|
|
98
|
+
final result = await LoginStore.shared.login(sdkAppId, userId, userSig);
|
|
99
|
+
TUICallEngine.instance.init(sdkAppId, userId, userSig);
|
|
100
|
+
if (result.isSuccess) {
|
|
101
|
+
// 登录成功
|
|
102
|
+
debugPrint('login success');
|
|
103
|
+
} else {
|
|
104
|
+
// 登录失败
|
|
105
|
+
debugPrint('login failed, code: ${result.code}, message: ${result.message}');
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
| **参数** | **类型** | **说明** |
|
|
111
|
+
| --- | --- | --- |
|
|
112
|
+
| `userId` | String | 当前用户的唯一 ID,仅包含英文字母、数字、连字符和下划线。为避免多端登录冲突,请勿使用 1、123 等简单 ID。 |
|
|
113
|
+
| `sdkAppId` | int | 从控制台获取,通常是以 140 或 160 开头的 10 位整数 |
|
|
114
|
+
| `userSig` | String | IM 登录鉴权票据。联调可本地生成临时值(见 `GenerateTestUserSig.genTestUserSig`:<https://github.com/Tencent-RTC/TUIKit_Flutter/blob/main/call/example/lib/debug/generate_test_user_sig.dart>);**Tencent RTC MCP Server** 已配置 `sdkAppId` 与 `sdkSecretKey` 时可由工具自动生成。**生产环境**须由业务服务端使用密钥签发 UserSig,勿在客户端存储或下发 SecretKey。 |
|
|
115
|
+
|
|
116
|
+
## 实现接听通话
|
|
117
|
+
|
|
118
|
+
**接听通话前,请确保已完成登录,这是服务可用的必要前提**。接下来,我们将通过 6 个步骤带您实现‘接听一通电话’的功能。
|
|
119
|
+
|
|
120
|
+
### 步骤1:创建通话页面
|
|
121
|
+
|
|
122
|
+
您需要创建一个通话页面,当收到来电时唤起通话页面,实现方式如下:
|
|
123
|
+
|
|
124
|
+
1. **创建通话页面**:您可以新建一个 StatefulWidget 作为通话宿主页面,用于响应来电时的跳转逻辑。
|
|
125
|
+
|
|
126
|
+
2. **通话页面使用 CallCoreView** Widget:通话视图核心组件,需要传入 controller 参数,自动监听 **CallStore** 数据并完成画面渲染,支持 1v1 和多人通话布局自动切换。
|
|
127
|
+
|
|
128
|
+
```dart
|
|
129
|
+
import 'package:flutter/material.dart';
|
|
130
|
+
import 'package:atomic_x_core/atomicxcore.dart';
|
|
131
|
+
// 1. 创建通话页面 Widget
|
|
132
|
+
class CallPage extends StatefulWidget {
|
|
133
|
+
const CallPage({super.key});
|
|
134
|
+
@override
|
|
135
|
+
State<CallPage> createState() => _CallPageState();
|
|
136
|
+
}
|
|
137
|
+
class _CallPageState extends State<CallPage> {
|
|
138
|
+
late CallCoreController controller;
|
|
139
|
+
@override
|
|
140
|
+
void initState() {
|
|
141
|
+
super.initState();
|
|
142
|
+
controller = CallCoreController.create();
|
|
143
|
+
}
|
|
144
|
+
@override
|
|
145
|
+
Widget build(BuildContext context) {
|
|
146
|
+
// 2. 通话页面使用 CallCoreView Widget
|
|
147
|
+
return CallCoreView(controller: controller);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
**CallCoreView Widget 功能说明:**
|
|
153
|
+
|
|
154
|
+
| **功能** | **说明** | **参考文档** |
|
|
155
|
+
| --- | --- | --- |
|
|
156
|
+
| **设置布局模式** | 支持自由切换布局模式。若未设置,将根据通话人数自动适配布局。 | 切换布局模式 |
|
|
157
|
+
| **设置头像** | 支持通过传入头像资源路径,为特定用户自定义头像。 | 自定义默认头像 |
|
|
158
|
+
| **设置音量提示图标** | 支持根据不同音量等级,配置个性化的音量指示图标。 | 自定义音量提示的图标 |
|
|
159
|
+
| **设置网络提示图标** | 支持根据实时网络质量,配置对应的网络状态提示图标。 | 自定义网络提示的图标 |
|
|
160
|
+
| **设置等待接听用户的动画** | 在多人通话场景下,支持传入 GIF 路径,为待接听状态的用户展示动画。 | 自定义 loading 动画 |
|
|
161
|
+
|
|
162
|
+
### **步骤2:添加接听和拒接按钮**
|
|
163
|
+
|
|
164
|
+
您可以参考 DeviceStore 、CallStore 提供的 API ,自定义添加您的按钮。
|
|
165
|
+
|
|
166
|
+
**DeviceStore 功能说明:**麦克风(开关 / 音量)、摄像头(开关 / 切换 / 画质)、屏幕共享,设备状态实时监听。建议将对应方法绑定至按钮点击事件,并通过监听设备状态变更来实时刷新按钮的 UI 状态。
|
|
167
|
+
|
|
168
|
+
**CallStore 功能说明:**接听、挂断、拒接等核心通话控制能力。建议将对应方法绑定至按钮点击事件,并监听通话状态的变化,以确保按钮显示与当前通话阶段保持同步。
|
|
169
|
+
|
|
170
|
+
**图标资源下载:**按钮图标可以直接从 GitHub 下载。这些图标由我们的设计师专为 TUICallKit 打造,无版权风险,可放心使用。
|
|
171
|
+
|
|
172
|
+
| **资源名** | **说明** | **下载地址** |
|
|
173
|
+
| --- | --- | --- |
|
|
174
|
+
| callview_ic_dialing | 拨号图标 | [下载地址](https://github.com/Tencent-RTC/TUIKit_Android/blob/main/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_dialing.png) |
|
|
175
|
+
| callview_ic_hangup | 挂断图标 | [下载地址](https://github.com/Tencent-RTC/TUIKit_Android/blob/main/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_hangup.png) |
|
|
176
|
+
| callview_ic_camera_disable | 摄像头关闭图标 | [下载地址](https://github.com/Tencent-RTC/TUIKit_Android/blob/main/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_camera_disable.png) |
|
|
177
|
+
| callview_ic_camera_enable | 摄像头开启图标 | [下载地址](https://github.com/Tencent-RTC/TUIKit_Android/blob/main/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_camera_enable.png) |
|
|
178
|
+
| callview_ic_mic_unmute | 麦克风开启图标 | [下载地址](https://github.com/Tencent-RTC/TUIKit_Android/blob/main/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_mic_unmute.png) |
|
|
179
|
+
| callview_ic_mic_mute | 麦克风关闭图标 | [下载地址](https://github.com/Tencent-RTC/TUIKit_Android/blob/main/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_mic_mute.png) |
|
|
180
|
+
| callview_ic_handsfree_disable | 免提关闭图标 | [下载地址](https://github.com/Tencent-RTC/TUIKit_Android/blob/main/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_handsfree_disable.png) |
|
|
181
|
+
| callview_ic_handsfree_enable | 免提开启图标 | [下载地址](https://github.com/Tencent-RTC/TUIKit_Android/blob/main/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_handsfree_enable.png) |
|
|
182
|
+
|
|
183
|
+
**以下是添加"接听"和"拒接"按钮的实现方式:**
|
|
184
|
+
|
|
185
|
+
1.1 **添加接听和拒接按钮:**创建底部按钮栏容器,并在其中添加"接听"与"拒接"按钮,将其点击事件分别绑定至 accept 和 reject 方法。
|
|
186
|
+
|
|
187
|
+
```dart
|
|
188
|
+
import 'package:flutter/material.dart';
|
|
189
|
+
import 'package:atomic_x_core/atomicxcore.dart';
|
|
190
|
+
// 接听和拒接按钮 Widget
|
|
191
|
+
class AcceptRejectButtons extends StatelessWidget {
|
|
192
|
+
const AcceptRejectButtons({super.key});
|
|
193
|
+
@override
|
|
194
|
+
Widget build(BuildContext context) {
|
|
195
|
+
return Row(
|
|
196
|
+
children: [
|
|
197
|
+
// 接听按钮
|
|
198
|
+
_buildAcceptButton(),
|
|
199
|
+
// 拒接按钮
|
|
200
|
+
_buildRejectButton(),
|
|
201
|
+
],
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
// 接听按钮
|
|
205
|
+
Widget _buildAcceptButton() {
|
|
206
|
+
return GestureDetector(
|
|
207
|
+
onTap: () {
|
|
208
|
+
// 调用 accept 接口接听通话
|
|
209
|
+
CallStore.shared.accept();
|
|
210
|
+
},
|
|
211
|
+
child: Container(
|
|
212
|
+
width: 60,
|
|
213
|
+
height: 60,
|
|
214
|
+
decoration: const BoxDecoration(
|
|
215
|
+
color: Colors.green,
|
|
216
|
+
shape: BoxShape.circle,
|
|
217
|
+
),
|
|
218
|
+
child: const Icon(
|
|
219
|
+
Icons.call,
|
|
220
|
+
color: Colors.white,
|
|
221
|
+
size: 30,
|
|
222
|
+
),
|
|
223
|
+
),
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
// 拒接按钮
|
|
227
|
+
Widget _buildRejectButton() {
|
|
228
|
+
return GestureDetector(
|
|
229
|
+
onTap: () {
|
|
230
|
+
// 调用 reject 接口拒接通话
|
|
231
|
+
CallStore.shared.reject();
|
|
232
|
+
},
|
|
233
|
+
child: Container(
|
|
234
|
+
width: 60,
|
|
235
|
+
height: 60,
|
|
236
|
+
decoration: const BoxDecoration(
|
|
237
|
+
color: Colors.red,
|
|
238
|
+
shape: BoxShape.circle,
|
|
239
|
+
),
|
|
240
|
+
child: const Icon(
|
|
241
|
+
Icons.call_end,
|
|
242
|
+
color: Colors.white,
|
|
243
|
+
size: 30,
|
|
244
|
+
),
|
|
245
|
+
),
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
1.2 **拨打方取消通话或您拒接时销毁界面:**无论拨打方取消呼叫,还是接收方拒接,均会触发 onCallEnded(通话结束)事件。建议监听此事件,以便在通话终止时及时关闭(销毁)通话界面。
|
|
252
|
+
|
|
253
|
+
```dart
|
|
254
|
+
import 'package:atomic_x_core/atomicxcore.dart';
|
|
255
|
+
import 'package:flutter/cupertino.dart';
|
|
256
|
+
void addListener(BuildContext context) {
|
|
257
|
+
CallEventListener listener = CallEventListener(
|
|
258
|
+
onCallEnded: (callId, mediaType, reason, userId) {
|
|
259
|
+
Navigator.of(context).pop();
|
|
260
|
+
}
|
|
261
|
+
);
|
|
262
|
+
CallStore.shared.addListener(listener);
|
|
263
|
+
}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
**onCallEnded 事件参数详细说明**:
|
|
267
|
+
|
|
268
|
+
| **参数** | **类型** | **说明** |
|
|
269
|
+
| --- | --- | --- |
|
|
270
|
+
| callId | String | 此次通话的唯一标识。 |
|
|
271
|
+
| mediaType | [CallMediaType](https://liteav.sdk.qcloud.com/doc/product/tuikit/atomic-x/flutter/v1.0/zh/api_call_call_store/CallMediaType.html) | 通话媒体类型,用于指定发起音频通话还是视频通话。<br>- `CallMediaType.video`:视频通话。<br>- `CallMediaType.audio`:语音通话。 |
|
|
272
|
+
| reason | [CallEndReason](https://liteav.sdk.qcloud.com/doc/product/tuikit/atomic-x/flutter/v1.0/zh/api_call_call_store/CallEndReason.html) | 通话结束的原因。<br>- `unknown`:未知原因,无法确定结束原因。<br>- `hangup`:正常挂断,用户主动挂断通话。<br>- `reject`:拒绝接听,被叫方拒绝来电。<br>- `noResponse`:无响应,被叫方未在超时时间内接听。<br>- `offline`:对方离线,被叫方不在线。<br>- `lineBusy`:对方忙线,被叫方正在通话中。<br>- `canceled`:通话取消,主叫方在对方接听前取消。<br>- `otherDeviceAccepted`:其他设备已接听,通话已在另一登录设备上接听。<br>- `otherDeviceReject`:其他设备已拒绝,通话已在另一登录设备上拒绝。<br>- `endByServer`:服务器结束,通话被服务器终止。 |
|
|
273
|
+
| userId | String | 触发结束的用户 ID 。 |
|
|
274
|
+
|
|
275
|
+
### 步骤3:申请麦克风/摄像头权限
|
|
276
|
+
|
|
277
|
+
建议在发起通话前,先行检测音视频权限。若权限缺失,请引导用户动态申请。实现方法如下:
|
|
278
|
+
|
|
279
|
+
1. **Android 权限声明**:
|
|
280
|
+
|
|
281
|
+
在 `AndroidManifest.xml` 文件中声明应用需要的摄像头和麦克风权限。
|
|
282
|
+
|
|
283
|
+
```xml
|
|
284
|
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
|
285
|
+
<!-- 麦克风权限 -->
|
|
286
|
+
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
|
287
|
+
<!-- 摄像头权限 -->
|
|
288
|
+
<uses-permission android:name="android.permission.CAMERA" />
|
|
289
|
+
</manifest>
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
2. **iOS 权限声明:**
|
|
293
|
+
|
|
294
|
+
在您的 iOS 工程的 `Info.plist` 文件中,在顶级 `<dict>` 元素下添加以下两项。
|
|
295
|
+
|
|
296
|
+
```xml
|
|
297
|
+
<key>NSCameraUsageDescription</key>
|
|
298
|
+
<string>CallingApp需要访问您的相机权限,开启后录制的视频才会有画面</string>
|
|
299
|
+
<key>NSMicrophoneUsageDescription</key>
|
|
300
|
+
<string>CallingApp需要访问您的麦克风权限,开启后录制的视频才会有声音</string>
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
3. **动态申请权限:**我们推荐使用 permission\_handler 插件来动态申请音视频权限。首先使用以下命令添加 permission\_handler 插件:
|
|
304
|
+
|
|
305
|
+
flutter pub add permission_handler
|
|
306
|
+
|
|
307
|
+
4. **iOS Podfile 配置(必须):**
|
|
308
|
+
|
|
309
|
+
`permission_handler` 在 iOS 端使用条件编译宏控制权限模块。默认所有权限宏为 0(关闭),您必须在 `ios/Podfile` 的 `post_install` 中显式开启需要的权限宏,否则权限请求将直接返回 `denied` 而不会弹出系统授权弹窗。
|
|
310
|
+
|
|
311
|
+
打开 `ios/Podfile`,修改 `post_install` 部分:
|
|
312
|
+
|
|
313
|
+
```ruby
|
|
314
|
+
post_install do |installer|
|
|
315
|
+
installer.pods_project.targets.each do |target|
|
|
316
|
+
flutter_additional_ios_build_settings(target)
|
|
317
|
+
target.build_configurations.each do |config|
|
|
318
|
+
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
|
|
319
|
+
'$(inherited)',
|
|
320
|
+
'PERMISSION_CAMERA=1',
|
|
321
|
+
'PERMISSION_MICROPHONE=1',
|
|
322
|
+
]
|
|
323
|
+
end
|
|
324
|
+
end
|
|
325
|
+
end
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
> **注意:** 修改 Podfile 后需要重新执行 `cd ios && pod install` 使配置生效。
|
|
329
|
+
|
|
330
|
+
5. **申请权限:**在接听通话前调用以下代码动态申请音视频权限:
|
|
331
|
+
|
|
332
|
+
```dart
|
|
333
|
+
import 'package:permission_handler/permission_handler.dart';
|
|
334
|
+
// 申请音视频权限
|
|
335
|
+
Future<bool> requestCallPermissions() async {
|
|
336
|
+
// 请求麦克风和摄像头权限
|
|
337
|
+
Map<Permission, PermissionStatus> statuses = await [
|
|
338
|
+
Permission.microphone,
|
|
339
|
+
Permission.camera,
|
|
340
|
+
].request();
|
|
341
|
+
// 检查权限状态
|
|
342
|
+
bool micGranted = statuses[Permission.microphone]?.isGranted ?? false;
|
|
343
|
+
bool cameraGranted = statuses[Permission.camera]?.isGranted ?? false;
|
|
344
|
+
if (micGranted && cameraGranted) {
|
|
345
|
+
// 权限申请成功
|
|
346
|
+
return true;
|
|
347
|
+
} else {
|
|
348
|
+
// 部分权限被拒绝,可引导用户开启权限
|
|
349
|
+
return false;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
### 步骤4: 来电播放提示
|
|
355
|
+
|
|
356
|
+
您可以监听当前用户的通话状态,在收到来电时播放铃声或振动,接听、挂断后停止播放来电提示,实现方法如下:
|
|
357
|
+
|
|
358
|
+
1. **数据层订阅:**订阅 `CallStore.shared.state.selfInfo` , 建立当前登录用户信息的响应式监听。
|
|
359
|
+
|
|
360
|
+
2. **播放或停止来电提示:**若当前用户的通话状态 (`selfInfo.status`) 为等待接听状态(`CallParticipantStatus.waiting`)播放铃声或振动,若当前用户的通话状态 (`selfInfo.status`) 为已接听状态(`CallParticipantStatus.accept`)停止铃声或振动。
|
|
361
|
+
|
|
362
|
+
```dart
|
|
363
|
+
CallStore.shared.state.selfInfo.addListener(() {
|
|
364
|
+
CallParticipantInfo info = CallStore.shared.state.selfInfo.value;
|
|
365
|
+
if (info.status == CallParticipantStatus.accept || info.status == CallParticipantStatus.none) {
|
|
366
|
+
// 停止播放铃音
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
if (info.status == CallParticipantStatus.waiting) {
|
|
370
|
+
// 播放铃音
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
### 步骤5:来电打开媒体设备
|
|
376
|
+
|
|
377
|
+
收到来电时,您可以通过 onCallReceived 事件获取本次通话的媒体类型。为了提供更佳的用户体验,建议在唤起通话界面时,根据通话类型预先开启对应的媒体设备。实现步骤如下:
|
|
378
|
+
|
|
379
|
+
1. **监听来电事件**:订阅 `onCallReceived` 事件。
|
|
380
|
+
|
|
381
|
+
2. **根据来电媒体类型打开设备:**若为语音通话仅开启麦克风,若为视频通话开启麦克风和摄像头。
|
|
382
|
+
|
|
383
|
+
```dart
|
|
384
|
+
import 'package:atomic_x_core/atomicxcore.dart';
|
|
385
|
+
CallEventListener? callListener;
|
|
386
|
+
void initCallListener() {
|
|
387
|
+
// 1.监听来电事件
|
|
388
|
+
callListener = CallEventListener(
|
|
389
|
+
onCallReceived: (callId, mediaType, userData) {
|
|
390
|
+
// 2.根据来电媒体类型打开设备
|
|
391
|
+
openDeviceForMediaType(mediaType);
|
|
392
|
+
},
|
|
393
|
+
);
|
|
394
|
+
if (callListener != null) {
|
|
395
|
+
CallStore.shared.addListener(callListener!);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
void openDeviceForMediaType(CallMediaType? mediaType) {
|
|
399
|
+
if (mediaType == null) return;
|
|
400
|
+
DeviceStore.shared.openLocalMicrophone();
|
|
401
|
+
if (mediaType == CallMediaType.video) {
|
|
402
|
+
final isFrontCamera = DeviceStore.shared.state.isFrontCamera.value;
|
|
403
|
+
DeviceStore.shared.openLocalCamera(isFrontCamera);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
**onCallReceived 事件详细说明:**
|
|
409
|
+
|
|
410
|
+
| **参数** | **类型** | **说明** |
|
|
411
|
+
| --- | --- | --- |
|
|
412
|
+
| callId | String | 此次通话的唯一标识。 |
|
|
413
|
+
| mediaType | [CallMediaType](https://liteav.sdk.qcloud.com/doc/product/tuikit/atomic-x/flutter/v1.0/zh/api_call_call_store/CallMediaType.html) | 通话媒体类型,用于指定发起音频通话还是视频通话。<br>- `CallMediaType.video`:视频通话。<br>- `CallMediaType.audio`:语音通话。 |
|
|
414
|
+
|
|
415
|
+
**openLocalCamera** **接口参数详细说明:**
|
|
416
|
+
|
|
417
|
+
| **参数名** | **类型** | **必填** | **说明** |
|
|
418
|
+
| --- | --- | --- | --- |
|
|
419
|
+
| isFront | bool | 是 | 是否开启前置摄像头。<br>- `true`:开启前置摄像头。<br>- `false`:开启后置摄像头。 |
|
|
420
|
+
| completion | [CompletionHandler](https://liteav.sdk.qcloud.com/doc/product/tuikit/atomic-x/flutter/v1.0/zh/api_define/CompletionHandler-class.html) | 否 | 操作完成回调,用于返回开启摄像头的结果。若开启失败则会返回错误码和错误信息。 |
|
|
421
|
+
|
|
422
|
+
**openLocalMicrophone 接口参数详细说明:**
|
|
423
|
+
|
|
424
|
+
| **参数名** | **类型** | **必填** | **说明** |
|
|
425
|
+
| --- | --- | --- | --- |
|
|
426
|
+
| completion | [CompletionHandler](https://liteav.sdk.qcloud.com/doc/product/tuikit/atomic-x/flutter/v1.0/zh/api_define/CompletionHandler-class.html) | 否 | 操作完成回调,用于返回开启麦克风的结果。若开启失败则会返回错误码和错误信息。 |
|
|
427
|
+
|
|
428
|
+
### 步骤6:来电唤起通话界面
|
|
429
|
+
|
|
430
|
+
您在 步骤5 已订阅了 onCallReceived 事件,您可以在该事件中唤起通话页面。实现方式如下:
|
|
431
|
+
|
|
432
|
+
```dart
|
|
433
|
+
import 'package:atomic_x_core/atomicxcore.dart';
|
|
434
|
+
import 'package:flutter/material.dart';
|
|
435
|
+
CallEventListener? callListener;
|
|
436
|
+
void addListener(BuildContext context) {
|
|
437
|
+
callListener = CallEventListener(
|
|
438
|
+
onCallReceived: (callId, mediaType, userData) {
|
|
439
|
+
// 唤起通话页面(使用 Navigator 跳转)
|
|
440
|
+
Navigator.push(
|
|
441
|
+
context,
|
|
442
|
+
MaterialPageRoute(builder: (context) => const CallPage()),
|
|
443
|
+
);
|
|
444
|
+
},
|
|
445
|
+
);
|
|
446
|
+
if (callListener != null) {
|
|
447
|
+
CallStore.shared.addListener(callListener!);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
### 运行效果
|
|
453
|
+
|
|
454
|
+
当您完成以上 6 步后,"接听一通电话"运行效果如下:
|
|
455
|
+
|
|
456
|
+
## 接入离线推送
|
|
457
|
+
|
|
458
|
+
如果您想要应用在离线状态下也可以收到通话邀请,请参考 接入离线推送功能 完成功能接入。
|
|
459
|
+
|
|
460
|
+
**说明:**
|
|
461
|
+
|
|
462
|
+
## 定制页面
|
|
463
|
+
|
|
464
|
+
**CallCoreView** 提供了完善的 UI 定制能力,支持头像及音量提示等图标的自由替换。**为助力快速集成,您可以直接从** **Github** **下载。这些图标由我们的设计师专为 TUICallKit 打造,无版权风险,可放心使用。**
|
|
465
|
+
|
|
466
|
+
### 自定义音量提示的图标
|
|
467
|
+
|
|
468
|
+
您可以调用 **CallCoreView** 组件的 volumeIcons 参数设置音量大小等级不同的提示图标。
|
|
469
|
+
|
|
470
|
+
**volumeIcons 示例代码:**
|
|
471
|
+
|
|
472
|
+
```dart
|
|
473
|
+
Widget _buildCallCoreView() {
|
|
474
|
+
Map<VolumeLevel, Image> volumeIcons = {
|
|
475
|
+
VolumeLevel.mute : Image.asset(''), // 每个音量等级对应的图片
|
|
476
|
+
};
|
|
477
|
+
return CallCoreView(
|
|
478
|
+
controller: CallCoreController.create(),
|
|
479
|
+
volumeIcons: volumeIcons,
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
**volumeIcons 参数详细说明:**
|
|
485
|
+
|
|
486
|
+
| **参数** | **类型** | **是否必填** | **说明** |
|
|
487
|
+
| --- | --- | --- | --- |
|
|
488
|
+
| volumeIcons | Map\<[VolumeLevel](https://liteav.sdk.qcloud.com/doc/product/tuikit/atomic-x/flutter/v1.0/zh/api_view_call_call_core_view/VolumeLevel.html), Image\> | 否 | 音量等级与图标资源的映射表。<br>Key (VolumeLevel) 表示音量等级:<br>- `VolumeLevel.mute`:表示麦克风关闭,静音状态。<br>- `VolumeLevel.low`:表示音量范围 (0-25]。<br>- `VolumeLevel.medium`:表示音量范围 (25-50]。<br>- `VolumeLevel.high`:表示音量范围在 (50-75]。<br>- `VolumeLevel.peak`:表示音量范围在 (75-100]。<br>Value (Image) 表示对应音量等级的图标资源。 |
|
|
489
|
+
|
|
490
|
+
**音量提示图标:**
|
|
491
|
+
|
|
492
|
+
| **资源名** | **说明** | **下载地址** |
|
|
493
|
+
| --- | --- | --- |
|
|
494
|
+
| callview_ic_audio_input | 【图标含义】音量提示图标。【推荐用法】您可以将该图标等级设置为 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) |
|
|
495
|
+
| callview_ic_self_mute | 【图标含义】静音图标。【推荐用法】您可以将该图标等级设置为 VolumeLevel.mute ,当该用户静音时显示。 | [下载地址](https://github.com/Tencent-RTC/TUIKit_Android/blob/main/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_self_mute.png) |
|
|
496
|
+
|
|
497
|
+
### 自定义网络提示的图标
|
|
498
|
+
|
|
499
|
+
您可以调用 **CallCoreView** 组件的 networkQualityIcons 参数设置不同网络状态的提示图标。
|
|
500
|
+
|
|
501
|
+
**networkQualityIcons 示例代码:**
|
|
502
|
+
|
|
503
|
+
```dart
|
|
504
|
+
Widget _buildCallCoreView() {
|
|
505
|
+
Map<NetworkQuality, Image> networkQualityIcons = {
|
|
506
|
+
NetworkQuality.bad : Image.asset(''), // 每个网络质量等级对应的图片
|
|
507
|
+
};
|
|
508
|
+
return CallCoreView(
|
|
509
|
+
controller: CallCoreController.create(),
|
|
510
|
+
networkQualityIcons: networkQualityIcons,
|
|
511
|
+
);
|
|
512
|
+
}
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
**networkQualityIcons** **参数详细说明:**
|
|
516
|
+
|
|
517
|
+
| **参数** | **类型** | **是否必填** | **说明** |
|
|
518
|
+
| --- | --- | --- | --- |
|
|
519
|
+
| networkQualityIcons | Map\<[NetworkQuality](https://liteav.sdk.qcloud.com/doc/product/tuikit/atomic-x/flutter/v1.0/zh/api_device_device_store/NetworkQuality.html), Image\> | 否 | 网络质量与图标资源的映射表。<br>Key (NetworkQuality) 表示网络质量等级:<br>- `NetworkQuality.unknown`:未知网络状态。<br>- `NetworkQuality.excellent`:网络状态极佳。<br>- `NetworkQuality.good`:网络状态较好。<br>- `NetworkQuality.poor`:网络状态较差。<br>- `NetworkQuality.bad`:网络状态差。<br>- `NetworkQuality.veryBad`:网络状态极差。<br>- `NetworkQuality.down`:网络断开。<br>Value (Image) : 对应网络状态的图标资源。 |
|
|
520
|
+
|
|
521
|
+
**网络较差的提示图标:**
|
|
522
|
+
|
|
523
|
+
| **资源名** | **说明** | **下载地址** |
|
|
524
|
+
| --- | --- | --- |
|
|
525
|
+
| callview_ic_network_bad | 【图标含义】网络较差的提示图标。【推荐用法】您可以将该图标等级设置为 NetworkQuality.bad、NetworkQuality.veryBad 或 NetworkQuality.down ,当网络较差时显示该图标。 | [下载地址](https://github.com/Tencent-RTC/TUIKit_Android/blob/main/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_network_bad.png) |
|
|
526
|
+
|
|
527
|
+
### 自定义默认头像
|
|
528
|
+
|
|
529
|
+
您可以调用 **CallCoreView** 的 defaultAvatar 参数设置用户默认头像。建议您监听响应式数据 allParticipants(所有参与通话的成员):当获取到用户头像时设置并展示;若用户未设置头像或加载失败,则显示默认头像(占位图)。
|
|
530
|
+
|
|
531
|
+
**defaultAvatar** **示例代码:**
|
|
532
|
+
|
|
533
|
+
```dart
|
|
534
|
+
Widget _buildCallCoreView() {
|
|
535
|
+
Image defaultAvatarImage = Image.asset(''); // 默认用户头像图片
|
|
536
|
+
return CallCoreView(
|
|
537
|
+
controller: CallCoreController.create(),
|
|
538
|
+
defaultAvatar: defaultAvatarImage,
|
|
539
|
+
);
|
|
540
|
+
}
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
**defaultAvatar** **接口参数详细说明:**
|
|
544
|
+
|
|
545
|
+
| **参数** | **类型** | **是否必填** | **说明** |
|
|
546
|
+
| --- | --- | --- | --- |
|
|
547
|
+
| defaultAvatar | Image | 否 | 用户默认头像。 |
|
|
548
|
+
|
|
549
|
+
**默认头像资源:**
|
|
550
|
+
|
|
551
|
+
| **资源名** | **说明** | **下载地址** |
|
|
552
|
+
| --- | --- | --- |
|
|
553
|
+
| callview_ic_avatar | 【图标含义】默认头像【推荐用法】当用户头像加载失败或无头像时,您可以给该用户设置此默认头像。 | [下载地址](https://github.com/Tencent-RTC/TUIKit_Android/blob/main/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_avatar.png) |
|
|
554
|
+
|
|
555
|
+
### 自定义 loading 动画
|
|
556
|
+
|
|
557
|
+
您可以调用 **CallCoreView** 的 loadingAnimation 参数,为等待中用户设置等待动画获得更好的体验。
|
|
558
|
+
|
|
559
|
+
**loadingAnimation 示例代码:**
|
|
560
|
+
|
|
561
|
+
```dart
|
|
562
|
+
Widget _buildCallCoreView() {
|
|
563
|
+
Image loading = Image.asset(''); // 默认加载动画资源
|
|
564
|
+
return CallCoreView(
|
|
565
|
+
controller: CallCoreController.create(),
|
|
566
|
+
loadingAnimation: loading,
|
|
567
|
+
);
|
|
568
|
+
}
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
**loadingAnimation 接口参数详细说明:**
|
|
572
|
+
|
|
573
|
+
| **参数** | **类型** | **是否必填** | **说明** |
|
|
574
|
+
| --- | --- | --- | --- |
|
|
575
|
+
| loadingAnimation | Image | 否 | GIF 格式图像资源。 |
|
|
576
|
+
|
|
577
|
+
**等待接听的动画:**
|
|
578
|
+
|
|
579
|
+
| **资源名** | **说明** | **下载地址** |
|
|
580
|
+
| --- | --- | --- |
|
|
581
|
+
| callview_ic_loading | 【图标含义】用户等待接听动画。【推荐用法】群组通话时设置的动画。设置后,当用户的状态为等待接听时,显示该动画。 | [下载地址](https://github.com/Tencent-RTC/TUIKit_Android/blob/main/atomic_x/src/main/res-callview/drawable-xxhdpi/callview_ic_loading.gif) |
|
|
582
|
+
|
|
583
|
+
### 添加通话计时提示
|
|
584
|
+
|
|
585
|
+
通话计时可通过响应式数据 activeCall 的 `duration` 字段实时获得,实时显示通话计时的实现方式如下:
|
|
586
|
+
|
|
587
|
+
1. **数据层订阅:**订阅 `CallStore.shared.state.activeCall` , 建立当前活跃通话的响应式监听。
|
|
588
|
+
|
|
589
|
+
2. **绑定通话计时数据**:将 `activeCall.duration` 字段绑定至 UI 控件。该字段为响应式数据,会自动驱动 UI 实时刷新,无需手动维护定时器。
|
|
590
|
+
|
|
591
|
+
```dart
|
|
592
|
+
import 'package:atomic_x_core/atomicxcore.dart';
|
|
593
|
+
import 'package:flutter/material.dart';
|
|
594
|
+
class TimerWidget extends StatelessWidget {
|
|
595
|
+
final double? fontSize;
|
|
596
|
+
final FontWeight? fontWeight;
|
|
597
|
+
const TimerWidget({
|
|
598
|
+
super.key,
|
|
599
|
+
this.fontSize,
|
|
600
|
+
this.fontWeight,
|
|
601
|
+
});
|
|
602
|
+
@override
|
|
603
|
+
Widget build(BuildContext context) {
|
|
604
|
+
return ValueListenableBuilder(
|
|
605
|
+
valueListenable: CallStore.shared.state.selfInfo,
|
|
606
|
+
builder: (context, info, child) {
|
|
607
|
+
if (info.status == CallParticipantStatus.accept) {
|
|
608
|
+
return ValueListenableBuilder(
|
|
609
|
+
valueListenable: CallStore.shared.state.activeCall,
|
|
610
|
+
builder: (context, activeCall, child) {
|
|
611
|
+
return Text(
|
|
612
|
+
formatDuration(activeCall.duration.toInt()),
|
|
613
|
+
style: TextStyle(
|
|
614
|
+
fontSize: fontSize,
|
|
615
|
+
fontWeight: fontWeight,
|
|
616
|
+
),
|
|
617
|
+
);
|
|
618
|
+
},
|
|
619
|
+
);
|
|
620
|
+
} else {
|
|
621
|
+
return Container();
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
);
|
|
625
|
+
}
|
|
626
|
+
String formatDuration(int timeCount) {
|
|
627
|
+
int hour = timeCount ~/ 3600;
|
|
628
|
+
int minute = (timeCount % 3600) ~/ 60;
|
|
629
|
+
String minuteShow = minute <= 9 ? "0$minute" : "$minute";
|
|
630
|
+
int second = timeCount % 60;
|
|
631
|
+
String secondShow = second <= 9 ? "0$second" : "$second";
|
|
632
|
+
if (hour > 0) {
|
|
633
|
+
String hourShow = hour <= 9 ? "0$hour" : "$hour";
|
|
634
|
+
return '$hourShow:$minuteShow:$secondShow';
|
|
635
|
+
} else {
|
|
636
|
+
return '$minuteShow:$secondShow';
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
```
|
|
641
|
+
|
|
642
|
+
**说明:**
|
|
643
|
+
|
|
644
|
+
若您想了解更多通话状态响应式数据,详细可参考:CallState 。
|
|
645
|
+
|
|
646
|
+
## 更多功能
|
|
647
|
+
|
|
648
|
+
### 设置头像和昵称
|
|
649
|
+
|
|
650
|
+
通话开始前,您可以通过 setSelfInfo 方法,设置自己的昵称和头像。
|
|
651
|
+
|
|
652
|
+
**setSelfInfo 示例代码:**
|
|
653
|
+
|
|
654
|
+
```dart
|
|
655
|
+
UserProfile profile = UserProfile(
|
|
656
|
+
userID: "", // 您的 UserId
|
|
657
|
+
avatarURL: "", // 头像的 URL
|
|
658
|
+
nickname: "", // 需要设置的昵称
|
|
659
|
+
);
|
|
660
|
+
CompletionHandler result = await LoginStore.shared.setSelfInfo(userInfo: profile);
|
|
661
|
+
if (result.errorCode == 0) {
|
|
662
|
+
print("setSelfInfo success");
|
|
663
|
+
} else {
|
|
664
|
+
print("setSelfInfo failed");
|
|
665
|
+
}
|
|
666
|
+
```
|
|
667
|
+
|
|
668
|
+
**setSelfInfo 接口参数详细说明:**
|
|
669
|
+
|
|
670
|
+
| **参数** | **类型** | **是否必填** | **说明** |
|
|
671
|
+
| --- | --- | --- | --- |
|
|
672
|
+
| userProfile | [UserProfile](https://liteav.sdk.qcloud.com/doc/product/tuikit/atomic-x/flutter/v1.0/zh/api_login_login_store/UserProfile-class.html) | 是 | 用户信息结构体。<br>- `userID` (String):用户的 ID 。<br>- `avatarURL` (String) : 用户头像的 URL。<br>- `nickname` (String) :用户的昵称。<br>更多字段详情可参考 [UserProfile](https://liteav.sdk.qcloud.com/doc/product/tuikit/atomic-x/flutter/v1.0/zh/api_login_login_store/UserProfile-class.html) 。 |
|
|
673
|
+
| completion | CompletionHandler | 否 | 操作完成回调,用于返回接通电话的结果。 |
|
|
674
|
+
|
|
675
|
+
### 切换布局模式
|
|
676
|
+
|
|
677
|
+
您可以通过 setLayoutTemplate 接口灵活切换布局模式。若未主动配置,**CallCoreView** 将根据通话人数自动适配:1v1 场景下默认采用 `Float` 模式,多人通话场景下则自动切换为 `Grid` 模式。不同布局模式的说明如下:
|
|
678
|
+
|
|
679
|
+
| **Float 模式** | **Grid 模式** | **PIP 模式** |
|
|
680
|
+
| ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
|
|
681
|
+
| | | |
|
|
682
|
+
| **布局逻辑:**呼叫等待时全屏显示己方画面;接通后全屏显示对方画面,己方画面以悬浮小窗展示。**交互特性:**支持小窗拖拽移动,点击小窗可实现大小画面互换。 | **布局逻辑:**所有成员画面呈网格状平铺排列成宫格模式布局,适用 2 人以上通话,支持点击放大画面功能。**交互特性:**支持点击特定成员画面放大查看。 | **布局逻辑:**1v1 场景固定显示对方画面,多人场景:采用当前发言者(Active Speaker) 策略,自动识别并全屏展示正在说话的用户。**交互特性:**等待时显示自己的画面,接通后还会显示通话计时。 |
|
|
683
|
+
|
|
684
|
+
**setLayoutTemplate** **示例代码:**
|
|
685
|
+
|
|
686
|
+
```dart
|
|
687
|
+
CallCoreController controller = CallCoreController.create();
|
|
688
|
+
CallLayoutTemplate template = CallLayoutTemplate.float;
|
|
689
|
+
controller.setLayoutTemplate(template);
|
|
690
|
+
```
|
|
691
|
+
|
|
692
|
+
**setLayoutTemplate 接口参数详细说明:**
|
|
693
|
+
|
|
694
|
+
| **参数** | **类型** | **是否必填** | **说明** |
|
|
695
|
+
| --- | --- | --- | --- |
|
|
696
|
+
| template | [CallLayoutTemplate](https://liteav.sdk.qcloud.com/doc/product/tuikit/atomic-x/flutter/v1.0/zh/api_view_call_call_core_view/CallLayoutTemplate.html) | 是 | CallCoreView 的布局模式。<br>- `CallLayoutTemplate.float`:布局逻辑:呼叫等待时全屏显示己方画面;接通后全屏显示对方画面,己方画面以悬浮小窗展示。交互特性:支持小窗拖拽移动,点击小窗可实现大小画面互换。<br>- `CallLayoutTemplate.grid`:布局逻辑:所有成员画面呈网格状平铺排列成宫格模式布局,适用 2 人以上通话,支持点击放大画面功能。交互特性:支持点击特定成员画面放大查看。<br>- `CallLayoutTemplate.pip`:布局逻辑:1v1 场景固定显示对方画面,多人场景:采用当前发言者(Active Speaker) 策略,自动识别并全屏展示正在说话的用户。交互特性:等待时显示自己的画面,接通后还会显示通话计时。 |
|
|
697
|
+
|
|
698
|
+
### 设置通话的默认超时时间
|
|
699
|
+
|
|
700
|
+
您可以在发起通话 calls 时,通过配置参数 CallParams 中的 `timeout` 字段来指定等待超时时间。示例代码如下:
|
|
701
|
+
|
|
702
|
+
```dart
|
|
703
|
+
void startCall(List<String> userIdList, CallMediaType mediaType) {
|
|
704
|
+
CallParams params = CallParams(
|
|
705
|
+
timeout: 30, // 设置通话等待超时时间
|
|
706
|
+
);
|
|
707
|
+
CallStore.shared.calls(userIdList, mediaType, params);
|
|
708
|
+
}
|
|
709
|
+
```
|
|
710
|
+
|
|
711
|
+
**calls 接口参数详细说明:**
|
|
712
|
+
|
|
713
|
+
| **参数** | **类型** | **是否必填** | **说明** |
|
|
714
|
+
| --- | --- | --- | --- |
|
|
715
|
+
| userIdList | List\<String\> | 是 | 目标用户的 userId 列表。 |
|
|
716
|
+
| mediaType | [CallMediaType](https://liteav.sdk.qcloud.com/doc/product/tuikit/atomic-x/flutter/v1.0/zh/api_call_call_store/CallMediaType.html) | 是 | 通话媒体类型,用于指定发起音频通话还是视频通话。<br>- `CallMediaType.video`:视频通话。<br>- `CallMediaType.audio`:语音通话。 |
|
|
717
|
+
| params | [CallParams](https://liteav.sdk.qcloud.com/doc/product/tuikit/atomic-x/flutter/v1.0/zh/api_call_call_store/CallParams-class.html) | 否 | 通话扩展参数,如:房间号、通话邀请超时时间等。<br>- `roomId` (String):房间 ID,可选参数,未指定时由服务端自动分配。<br>- `timeout` (Int):呼叫超时时间(秒)。<br>- `userData` (String):用户自定义数据。<br>- `chatGroupId` (String):Chat 群组 ID,用于群组通话场景。<br>- `isEphemeralCall` (Boolean):是否为加密通话(不产生通话记录)。 |
|
|
718
|
+
|
|
719
|
+
### 实现应用内悬浮窗
|
|
720
|
+
|
|
721
|
+
在通话界面因页面导航(如用户返回、跳转至其他页面)而被覆盖时,在应用内生成一个可拖拽的悬浮窗。该悬浮窗需持续展示关键通话状态(如通话时长、对方信息),并提供一键返回完整通话界面的入口,从而提升多任务场景下的通话体验。
|
|
722
|
+
|
|
723
|
+
```dart
|
|
724
|
+
_buildPipWindowWidget() {
|
|
725
|
+
final pipWidth = MediaQuery.of(context).size.width;
|
|
726
|
+
final pipHeight = MediaQuery.of(context).size.height;
|
|
727
|
+
final scale = pipWidth / originWidth;
|
|
728
|
+
CallCoreController controller = CallCoreController.create();
|
|
729
|
+
controller.setLayoutTemplate(CallLayoutTemplate.pip);
|
|
730
|
+
return Scaffold(
|
|
731
|
+
body: SizedBox(
|
|
732
|
+
width: pipWidth,
|
|
733
|
+
height: pipHeight,
|
|
734
|
+
child: Container(
|
|
735
|
+
width: pipWidth,
|
|
736
|
+
height: pipHeight,
|
|
737
|
+
decoration: const BoxDecoration(color: Colors.transparent),
|
|
738
|
+
child: MediaQuery(
|
|
739
|
+
data: MediaQuery.of(context).copyWith(
|
|
740
|
+
size: Size(originWidth ?? pipWidth, originHeight ?? pipHeight)
|
|
741
|
+
),
|
|
742
|
+
child: ClipRect(
|
|
743
|
+
child: Transform.scale(
|
|
744
|
+
scale: scale,
|
|
745
|
+
alignment: Alignment.center,
|
|
746
|
+
child: OverflowBox(
|
|
747
|
+
maxWidth: originWidth,
|
|
748
|
+
maxHeight: originHeight,
|
|
749
|
+
alignment: Alignment.center,
|
|
750
|
+
child: CallCoreView(
|
|
751
|
+
controller: controller,
|
|
752
|
+
),
|
|
753
|
+
),
|
|
754
|
+
),
|
|
755
|
+
),
|
|
756
|
+
),
|
|
757
|
+
),
|
|
758
|
+
),
|
|
759
|
+
);
|
|
760
|
+
}
|
|
761
|
+
```
|
|
762
|
+
|
|
763
|
+
### 实现 Android 应用外画中画
|
|
764
|
+
|
|
765
|
+
画中画功能需 Android 8.0 (API 26) 及以上版本支持。
|
|
766
|
+
|
|
767
|
+
1. MainActivity 配置
|
|
768
|
+
|
|
769
|
+
监听 MainActivity 的生命周期,当 enablePictureInPicture 为 true 时,当应用退到后台时自动拉起画中画。
|
|
770
|
+
|
|
771
|
+
```kotlin
|
|
772
|
+
import android.app.PictureInPictureParams
|
|
773
|
+
import android.content.pm.PackageManager
|
|
774
|
+
import android.os.Build
|
|
775
|
+
import android.util.Log
|
|
776
|
+
import android.util.Rational
|
|
777
|
+
import io.flutter.embedding.android.FlutterActivity
|
|
778
|
+
import io.flutter.embedding.engine.FlutterEngine
|
|
779
|
+
import io.flutter.plugin.common.MethodChannel
|
|
780
|
+
class MainActivity : FlutterActivity() {
|
|
781
|
+
companion object {
|
|
782
|
+
private const val TAG = "MainActivity"
|
|
783
|
+
private const val CHANNEL = "atomic_x/pip"
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
private var enablePictureInPicture = false
|
|
787
|
+
|
|
788
|
+
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
|
789
|
+
super.configureFlutterEngine(flutterEngine)
|
|
790
|
+
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
|
|
791
|
+
when (call.method) {
|
|
792
|
+
"enablePictureInPicture" -> {
|
|
793
|
+
val enable = call.argument<Boolean>("enable") ?: false
|
|
794
|
+
val success = enablePIP(enable)
|
|
795
|
+
result.success(success)
|
|
796
|
+
}
|
|
797
|
+
"enterPictureInPicture" -> {
|
|
798
|
+
val success = enterPIP()
|
|
799
|
+
result.success(success)
|
|
800
|
+
}
|
|
801
|
+
else -> result.notImplemented()
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
override fun onUserLeaveHint() {
|
|
806
|
+
super.onUserLeaveHint()
|
|
807
|
+
// 用户按 Home 键时自动进入画中画
|
|
808
|
+
if (enablePictureInPicture) {
|
|
809
|
+
enterPIP()
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
private fun enablePIP(enable: Boolean): Boolean {
|
|
814
|
+
Log.i(TAG, "enablePictureInPicture: $enable")
|
|
815
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O &&
|
|
816
|
+
packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)) {
|
|
817
|
+
enablePictureInPicture = enable
|
|
818
|
+
return true
|
|
819
|
+
}
|
|
820
|
+
return false
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
private fun enterPIP(): Boolean {
|
|
824
|
+
if (!enablePictureInPicture) return false
|
|
825
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
826
|
+
try {
|
|
827
|
+
val aspectRatio = Rational(9, 16)
|
|
828
|
+
val params = PictureInPictureParams.Builder()
|
|
829
|
+
.setAspectRatio(aspectRatio)
|
|
830
|
+
.build()
|
|
831
|
+
return enterPictureInPictureMode(params)
|
|
832
|
+
} catch (e: Exception) {
|
|
833
|
+
Log.e(TAG, "enterPIP failed: ${e.message}")
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
return false
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
2. AndroidManifest 配置
|
|
841
|
+
|
|
842
|
+
在项目的 AndroidManifest.xml 文件中为 MainActivity 添加画中画能力配置: `android:supportsPictureInPicture="true"`
|
|
843
|
+
|
|
844
|
+
```xml
|
|
845
|
+
<activity
|
|
846
|
+
android:name=".MainActivity"
|
|
847
|
+
android:exported="true"
|
|
848
|
+
android:launchMode="singleTop"
|
|
849
|
+
android:taskAffinity=""
|
|
850
|
+
android:theme="@style/LaunchTheme"
|
|
851
|
+
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
|
852
|
+
android:hardwareAccelerated="true"
|
|
853
|
+
android:windowSoftInputMode="adjustResize"
|
|
854
|
+
android:supportsPictureInPicture="true">
|
|
855
|
+
<meta-data
|
|
856
|
+
android:name="io.flutter.embedding.android.NormalTheme"
|
|
857
|
+
android:resource="@style/NormalTheme"
|
|
858
|
+
/>
|
|
859
|
+
<intent-filter>
|
|
860
|
+
<action android:name="android.intent.action.MAIN"/>
|
|
861
|
+
<category android:name="android.intent.category.LAUNCHER"/>
|
|
862
|
+
</intent-filter>
|
|
863
|
+
</activity>
|
|
864
|
+
```
|
|
865
|
+
|
|
866
|
+
3. Dart 层配置
|
|
867
|
+
|
|
868
|
+
```dart
|
|
869
|
+
import 'package:flutter/services.dart';
|
|
870
|
+
class PipManager {
|
|
871
|
+
static const MethodChannel _channel = MethodChannel('atomic_x/pip');
|
|
872
|
+
/// 启用/禁用画中画功能
|
|
873
|
+
static Future<bool> enablePictureInPicture(bool enable) async {
|
|
874
|
+
try {
|
|
875
|
+
final result = await _channel.invokeMethod<bool>('enablePictureInPicture', {'enable': enable});
|
|
876
|
+
return result ?? false;
|
|
877
|
+
} catch (e) {
|
|
878
|
+
return false;
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
/// 立即进入画中画模式
|
|
882
|
+
static Future<bool> enterPictureInPicture() async {
|
|
883
|
+
try {
|
|
884
|
+
final result = await _channel.invokeMethod<bool>('enterPictureInPicture');
|
|
885
|
+
return result ?? false;
|
|
886
|
+
} catch (e) {
|
|
887
|
+
return false;
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
```
|
|
892
|
+
|
|
893
|
+
在您的通话开始前,选择合适的时机启用画中画;在通话结束后,关闭画中画。
|
|
894
|
+
|
|
895
|
+
### 实现 iOS 应用外画中画
|
|
896
|
+
|
|
897
|
+
iOS 端支持通过底层 TRTC 引擎实现应用外画中画功能。当应用进入后台时,通话画面可以以系统画中画形式悬浮在其他应用之上,用户可以边使用其他应用边进行视频通话。实现方式如下:
|
|
898
|
+
|
|
899
|
+
**说明:**
|
|
900
|
+
|
|
901
|
+
需要在 Xcode 的 `Signing & Capabilities` 中添加 `Background Modes` 能力,并勾选 `Audio, AirPlay, and Picture in Picture。`
|
|
902
|
+
|
|
903
|
+
需要 iOS 15.0 及以上版本支持。
|
|
904
|
+
|
|
905
|
+
1. 开启画中画
|
|
906
|
+
|
|
907
|
+
```dart
|
|
908
|
+
import 'package:tencent_rtc_sdk/trtc_cloud.dart';
|
|
909
|
+
|
|
910
|
+
TRTCCloud.sharedInstance().then((trtcCloud) {
|
|
911
|
+
trtcCloud.callExperimentalAPI('''
|
|
912
|
+
{
|
|
913
|
+
"api": "configPictureInPicture",
|
|
914
|
+
"params": {
|
|
915
|
+
"enable": true,
|
|
916
|
+
"cameraBackgroundCapture": true,
|
|
917
|
+
"canvas": {
|
|
918
|
+
"width": 720,
|
|
919
|
+
"height": 1280,
|
|
920
|
+
"backgroundColor": "#111111"
|
|
921
|
+
},
|
|
922
|
+
"regions": [
|
|
923
|
+
{
|
|
924
|
+
"userId": "remoteUserId",
|
|
925
|
+
"userName": "",
|
|
926
|
+
"width": 1.0,
|
|
927
|
+
"height": 1.0,
|
|
928
|
+
"x": 0.0,
|
|
929
|
+
"y": 0.0,
|
|
930
|
+
"fillMode": 0,
|
|
931
|
+
"streamType": "high",
|
|
932
|
+
"backgroundColor": "#111111",
|
|
933
|
+
"backgroundImage": "file:///path/to/avatar.png"
|
|
934
|
+
},
|
|
935
|
+
{
|
|
936
|
+
"userId": "localUserId",
|
|
937
|
+
"userName": "",
|
|
938
|
+
"width": 0.333,
|
|
939
|
+
"height": 0.333,
|
|
940
|
+
"x": 0.65,
|
|
941
|
+
"y": 0.05,
|
|
942
|
+
"fillMode": 0,
|
|
943
|
+
"streamType": "high",
|
|
944
|
+
"backgroundColor": "#111111"
|
|
945
|
+
}
|
|
946
|
+
]
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
''');
|
|
950
|
+
});
|
|
951
|
+
```
|
|
952
|
+
|
|
953
|
+
2. 关闭画中画
|
|
954
|
+
|
|
955
|
+
```dart
|
|
956
|
+
import 'package:tencent_rtc_sdk/trtc_cloud.dart';
|
|
957
|
+
|
|
958
|
+
TRTCCloud.sharedInstance().then((trtcCloud) {
|
|
959
|
+
trtcCloud.callExperimentalAPI('''
|
|
960
|
+
{
|
|
961
|
+
"api": "configPictureInPicture",
|
|
962
|
+
"params": {
|
|
963
|
+
"enable": false
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
''');
|
|
967
|
+
});
|
|
968
|
+
```
|
|
969
|
+
|
|
970
|
+
### 开启后台采集音频/视频
|
|
971
|
+
|
|
972
|
+
为了确保应用在进入后台时仍能正常采集音频和视频(例如用户锁屏或切换到其他应用),您需要分别在 Android 、iOS 进行如下配置:
|
|
973
|
+
|
|
974
|
+
#### Android 端配置
|
|
975
|
+
|
|
976
|
+
1. **配置权限与服务(AndroidManifest.xml):** 从 Android 9.0 (API 28) 开始需要声明前台服务权限;Android 14 (API 34) 强制要求声明具体的服务类型(麦克风和摄像头)。
|
|
977
|
+
|
|
978
|
+
```xml
|
|
979
|
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
|
980
|
+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
|
981
|
+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CAMERA" />
|
|
982
|
+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
|
|
983
|
+
<application>
|
|
984
|
+
<service
|
|
985
|
+
android:name=".CallForegroundService"
|
|
986
|
+
android:enabled="true"
|
|
987
|
+
android:exported="false"
|
|
988
|
+
android:foregroundServiceType="camera|microphone" />
|
|
989
|
+
</application>
|
|
990
|
+
</manifest>
|
|
991
|
+
```
|
|
992
|
+
|
|
993
|
+
2. **创建前台服务类(CallForegroundService):**
|
|
994
|
+
|
|
995
|
+
```kotlin
|
|
996
|
+
import android.app.Notification
|
|
997
|
+
import android.app.NotificationChannel
|
|
998
|
+
import android.app.NotificationManager
|
|
999
|
+
import android.app.Service
|
|
1000
|
+
import android.content.Context
|
|
1001
|
+
import android.content.Intent
|
|
1002
|
+
import android.os.Build
|
|
1003
|
+
import android.os.IBinder
|
|
1004
|
+
import androidx.core.app.NotificationCompat
|
|
1005
|
+
class CallForegroundService : Service() {
|
|
1006
|
+
companion object {
|
|
1007
|
+
private const val NOTIFICATION_ID = 1001
|
|
1008
|
+
private const val CHANNEL_ID = "call_foreground_channel"
|
|
1009
|
+
fun start(context: Context) {
|
|
1010
|
+
val intent = Intent(context, CallForegroundService::class.java)
|
|
1011
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
1012
|
+
context.startForegroundService(intent)
|
|
1013
|
+
} else {
|
|
1014
|
+
context.startService(intent)
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
```
|
|
1018
|
+
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
fun stop(context: Context) {
|
|
1023
|
+
val intent = Intent(context, CallForegroundService::class.java)
|
|
1024
|
+
context.stopService(intent)
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
override fun onCreate() {
|
|
1029
|
+
super.onCreate()
|
|
1030
|
+
createNotificationChannel()
|
|
1031
|
+
// 启动前台通知,确保后台采集权限
|
|
1032
|
+
startForeground(NOTIFICATION_ID, createNotification())
|
|
1033
|
+
}
|
|
1034
|
+
override fun onBind(intent: Intent?): IBinder? = null
|
|
1035
|
+
|
|
1036
|
+
private fun createNotification(): Notification {
|
|
1037
|
+
return NotificationCompat.Builder(this, CHANNEL_ID)
|
|
1038
|
+
.setContentTitle("正在通话中")
|
|
1039
|
+
.setContentText("应用正在后台运行以保持通话")
|
|
1040
|
+
.setSmallIcon(android.R.drawable.ic_menu_call) // 请替换为您的应用图标
|
|
1041
|
+
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
|
1042
|
+
.build()
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
private fun createNotificationChannel() {
|
|
1046
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
1047
|
+
val channel = NotificationChannel(
|
|
1048
|
+
CHANNEL_ID,
|
|
1049
|
+
"通话保活服务",
|
|
1050
|
+
NotificationManager.IMPORTANCE_HIGH
|
|
1051
|
+
)
|
|
1052
|
+
val manager = getSystemService(NotificationManager::class.java)
|
|
1053
|
+
manager.createNotificationChannel(channel)
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
#### iOS 端配置
|
|
1059
|
+
|
|
1060
|
+
**配置步骤:**在 Xcode 中打开您的项目,按照以下步骤操作:
|
|
1061
|
+
|
|
1062
|
+
1. 选择项目的 `Target` → `Signing & Capabilities`。
|
|
1063
|
+
|
|
1064
|
+
2. 点击 `+ Capability` 。
|
|
1065
|
+
|
|
1066
|
+
3. 搜索并添加 `Background Modes`。
|
|
1067
|
+
|
|
1068
|
+
4. 勾选以下三个选项:
|
|
1069
|
+
|
|
1070
|
+
`Audio, AirPlay, and Picture in Picture`(保持音频采集和画中画功能)。
|
|
1071
|
+
|
|
1072
|
+
`Voice over IP`(支持 VoIP 通话)。
|
|
1073
|
+
|
|
1074
|
+
`Remote notifications`(可选,用于接收离线推送)。
|
|
1075
|
+
|
|
1076
|
+
配置完成后,您的 `Info.plist` 文件会自动添加以下内容:
|
|
1077
|
+
|
|
1078
|
+
```xml
|
|
1079
|
+
<key>UIBackgroundModes</key>
|
|
1080
|
+
<array>
|
|
1081
|
+
<string>audio</string>
|
|
1082
|
+
<string>voip</string>
|
|
1083
|
+
<string>remote-notification</string>
|
|
1084
|
+
</array>
|
|
1085
|
+
```
|
|
1086
|
+
|
|
1087
|
+
**配置音频会话(AVAudioSession)**
|
|
1088
|
+
|
|
1089
|
+
为了确保通话音频在后台正常工作,您需要在通话开始前配置音频会话。建议在通话界面的 `viewDidLoad` 或发起通话前设置:
|
|
1090
|
+
|
|
1091
|
+
```swift
|
|
1092
|
+
import AVFoundation
|
|
1093
|
+
/**
|
|
1094
|
+
* 设置音频会话,支持后台音频采集
|
|
1095
|
+
*
|
|
1096
|
+
* 建议在以下场景调用:
|
|
1097
|
+
* 1. 通话界面的 viewDidLoad 中
|
|
1098
|
+
* 2. 发起通话 (calls) 之前
|
|
1099
|
+
* 3. 接听通话 (accept) 之前
|
|
1100
|
+
*/
|
|
1101
|
+
private func start() {
|
|
1102
|
+
let audioSession = AVAudioSession.sharedInstance()
|
|
1103
|
+
do {
|
|
1104
|
+
// 设置音频会话类别为播放和录音
|
|
1105
|
+
// .allowBluetooth: 支持蓝牙耳机
|
|
1106
|
+
// .allowBluetoothA2DP: 支持高质量蓝牙音频(A2DP 协议)
|
|
1107
|
+
try audioSession.setCategory(.playAndRecord, options: [.allowBluetooth, .allowBluetoothA2DP])
|
|
1108
|
+
// 激活音频会话
|
|
1109
|
+
try audioSession.setActive(true)
|
|
1110
|
+
} catch {
|
|
1111
|
+
// 音频会话配置失败
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
```
|
|
1115
|
+
|
|
1116
|
+
**注意:**
|
|
1117
|
+
|
|
1118
|
+
您需要在应用中合适的时机,使用 MethodChannel 调用 start() 方法以开启后台保活能力。
|
|
1119
|
+
|
|
1120
|
+
## 常见问题
|
|
1121
|
+
|
|
1122
|
+
### iOS release 包运行时 \[symbol not found\]?
|
|
1123
|
+
|
|
1124
|
+
由于 tencent\_rtc\_sdk 通过 Flutter FFI 调用接口,iOS Release 构建时 Xcode 的符号裁剪优化可能误移除 TRTC 的 C 符号,引发 `symbol not found` 错误。解决方案如下:
|
|
1125
|
+
|
|
1126
|
+
1. 在项目的 Build Settings中找到 **Deployment Postprocessing**,将其设置为 **Yes 。**
|
|
1127
|
+
|
|
1128
|
+
2. 在项目的 Build Settings中找到 **Strip Style**,将其中 Release 的值设置为 **Non-Global Symbols 。**
|
|
1129
|
+
|
|
1130
|
+
### 在通话邀请超时时间内,被邀请者如果离线再上线,能否收到来电事件?
|
|
1131
|
+
|
|
1132
|
+
单人通话时,如果在超时时间内上线,会触发来电邀请;群组通话,如果在超时时间内上线,会拉起未处理的20条群消息,如果存在通话邀请,则触发来电邀请事件。
|