@tencentcloud/sdk-mcp 1.4.1 → 1.4.3-beta.1
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/common/record_skill_session.js +17 -6
- package/tools/common/record_skill_session.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-cls-client.js.map +1 -1
- package/utils/report-es-client.js +1 -1
- package/utils/report-es-client.js.map +1 -1
- package/utils/report-runtime-error-cls-client.js +91 -0
- package/utils/report-runtime-error-cls-client.js.map +1 -0
|
@@ -0,0 +1,1146 @@
|
|
|
1
|
+
本文档将帮助您使用 **AtomicXCore SDK** 的 **DeviceState**、**CallState** 以及核心组件 **CallCoreView**,快速构建一个支持通话功能的 **App**。
|
|
2
|
+
|
|
3
|
+
## 准备工作
|
|
4
|
+
|
|
5
|
+
### 步骤1:开通服务
|
|
6
|
+
|
|
7
|
+
请参见 开通服务,获取体验版或付费版 SDK。
|
|
8
|
+
|
|
9
|
+
### 步骤2:在当前项目中导入 AtomicXCore
|
|
10
|
+
|
|
11
|
+
1. **安装组件**:打开 [AtomicXCore SDK](https://ext.dcloud.net.cn/plugin?name=tuikit-atomic-x),单击**下载插件并导入 HBuilderX**,选择需要集成的项目并单击**确定**。也可从 [GitHub 仓库](https://github.com/Tencent-RTC/TUIKit_uni-app) 下载源码,将 `App/uni_modules/tuikit-atomic-x` 目录拷贝至您项目的 `uni_modules/` 下。
|
|
12
|
+
|
|
13
|
+
2. **配置工程权限:** 请在应用的 manifest.json 文件的 app-plus > distribute 节点下,确保添加了以下必要的权限:
|
|
14
|
+
|
|
15
|
+
Android 平台(`Android`节点):在 `permissions` 数组中,请确保包含以下权限:
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
"<uses-permission android:name=\"android.permission.RECORD_AUDIO\" />",
|
|
19
|
+
"<uses-permission android:name=\"android.permission.CAMERA\"/>"
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
iOS 平台(`iOS` 节点):在 `privacyDescription` 对象中,请确保包含以下描述:
|
|
23
|
+
|
|
24
|
+
```json
|
|
25
|
+
"NSCameraUsageDescription" : "应用需要访问您的相机以进行通话",
|
|
26
|
+
"NSMicrophoneUsageDescription" : "应用需要访问您的麦克风以进行通话"
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
在 `UIBackgroundModes` 数组中,添加 `audio` 以支持后台音频播放。
|
|
30
|
+
|
|
31
|
+
```json
|
|
32
|
+
"UIBackgroundModes" : [ "audio" ]
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### 步骤3:实现登录逻辑
|
|
36
|
+
|
|
37
|
+
在您的项目中调用 LoginState 中的 login 方法完成登录,**这是使用 AtomicXCore 所有功能的关键前提**。
|
|
38
|
+
|
|
39
|
+
**重要:**
|
|
40
|
+
|
|
41
|
+
推荐在您 App 自身的用户账户登录成功后,再调用 LoginState 中的 login 方法,以确保登录业务逻辑的清晰和一致。
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
import { useLoginState } from "@/uni_modules/tuikit-atomic-x/state/LoginState";
|
|
45
|
+
const { login } = useLoginState();
|
|
46
|
+
const handleLogin = () => {
|
|
47
|
+
login({
|
|
48
|
+
sdkAppID: 1400000001, // 替换为您的 SDKAppID
|
|
49
|
+
userID: "test_001", // 替换为您的 UserID
|
|
50
|
+
userSig: "xxxxxxxxxx" // 联调填临时 UserSig;MCP 已配置 sdkAppID、sdkSecretKey 时可由工具自动生成
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**登录接口参数说明:**
|
|
56
|
+
|
|
57
|
+
| **参数** | **类型** | **说明** |
|
|
58
|
+
| --- | --- | --- |
|
|
59
|
+
| `sdkAppID` | number | 从控制台获取,通常是以 140 或 160 开头的 10 位整数 |
|
|
60
|
+
| `userID` | string | 当前用户的唯一 ID,仅包含英文字母、数字、连字符和下划线。为避免多端登录冲突,**请勿使用** 1、123 **等简单 ID**。 |
|
|
61
|
+
| `userSig` | string | IM 登录鉴权票据。联调可参考 `genTestUserSig`(见 <https://github.com/Tencent-RTC/TUIKit_uni-app/blob/main/App/debug/GenerateTestUserSig.js>);**Tencent RTC MCP Server** 已配置 `sdkAppID` 与 `sdkSecretKey` 时可由工具自动生成。**生产环境**须由业务服务端使用密钥签发 UserSig,勿在客户端存储或下发 SecretKey。 |
|
|
62
|
+
|
|
63
|
+
## 实现接听通话
|
|
64
|
+
|
|
65
|
+
### 步骤1:创建通话界面
|
|
66
|
+
|
|
67
|
+
您需要创建一个通话页面,当发起通话时唤起通话页面,实现方式如下:
|
|
68
|
+
|
|
69
|
+
1. **创建通话页面**:您可以新建一个页面作为通话宿主页面,用于响应来电时的跳转逻辑。
|
|
70
|
+
|
|
71
|
+
2. **通话页面绑定 CallCoreView**:通话视图核心组件,自动监听 **CallState** 数据并完成画面渲染,同时提供布局切换、头像与图标配置等 UI 定制化能力。
|
|
72
|
+
|
|
73
|
+
```vue
|
|
74
|
+
<call-core-view style="height: 50px; width: 50px;"></call-core-view>
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**说明:**
|
|
78
|
+
|
|
79
|
+
**CallCoreView** 为原生组件,不需要在文件中单独进行 **import**。挂载在页面上即可使用。
|
|
80
|
+
|
|
81
|
+
**CallCoreView 视图组件功能说明:**
|
|
82
|
+
|
|
83
|
+
| **功能** | **说明** | **参考文档** |
|
|
84
|
+
| --- | --- | --- |
|
|
85
|
+
| **设置布局模式** | 支持自由切换布局模式。若未设置,将根据通话人数自动适配布局。 | 切换布局模式 |
|
|
86
|
+
| **设置头像** | 支持通过传入头像资源路径,为特定用户自定义头像。 | 自定义默认头像 |
|
|
87
|
+
| **设置音量提示图标** | 支持根据不同音量等级,配置个性化的音量指示图标。 | 自定义音量提示的图标 |
|
|
88
|
+
| **设置网络提示图标** | 支持根据实时网络质量,配置对应的网络状态提示图标。 | 自定义网络提示的图标 |
|
|
89
|
+
| **设置等待接听用户的动画** | 在多人通话场景下,支持传入 GIF 图像路径,为待接听状态的用户展示动画。 | 自定义 loading 动画 |
|
|
90
|
+
|
|
91
|
+
### 步骤2:添加通话控制按钮
|
|
92
|
+
|
|
93
|
+
您可以参考 **DeviceState** 、**CallState** 提供的 API ,自定义添加您的按钮。
|
|
94
|
+
|
|
95
|
+
**DeviceState 功能说明:**麦克风(开关 / 音量)、摄像头(开关 / 切换 / 画质)、屏幕共享,设备状态实时监听。建议将对应方法绑定至按钮点击事件,并通过监听设备状态变更来实时刷新按钮的 UI 状态。
|
|
96
|
+
|
|
97
|
+
**CallState 功能说明:**接听、挂断、拒接等核心通话控制能力。建议将对应方法绑定至按钮点击事件,并监听通话状态的变化,以确保按钮显示与当前通话阶段保持同步。
|
|
98
|
+
|
|
99
|
+
**图标资源下载:**按钮图标可以直接从 GitHub 下载。这些图标由我们的设计师专为 TUICallKit 打造,无版权风险,可放心使用。
|
|
100
|
+
|
|
101
|
+
| **资源文件名** | **用途** | **下载地址** |
|
|
102
|
+
| --- | --- | --- |
|
|
103
|
+
| accept.png | 接听按钮图标 | [下载地址](https://github.com/Tencent-RTC/TUIKit_uni-app/blob/main/App/uni_modules/tuikit-atomic-x/static/icon/accept.png) |
|
|
104
|
+
| hangup.png | 挂断 / 拒绝按钮图标 | [下载地址](https://github.com/Tencent-RTC/TUIKit_uni-app/blob/main/App/uni_modules/tuikit-atomic-x/static/icon/hangup.png) |
|
|
105
|
+
| camera-off.png | 摄像头关闭 | [下载地址](https://github.com/Tencent-RTC/TUIKit_uni-app/blob/main/App/uni_modules/tuikit-atomic-x/static/icon/camera-off.png) |
|
|
106
|
+
| camera-on.png | 摄像头开启 | [下载地址](https://github.com/Tencent-RTC/TUIKit_uni-app/blob/main/App/uni_modules/tuikit-atomic-x/static/icon/camera-on.png) |
|
|
107
|
+
| mic-off.png | 麦克风关闭 | [下载地址](https://github.com/Tencent-RTC/TUIKit_uni-app/blob/main/App/uni_modules/tuikit-atomic-x/static/icon/mic-off.png) |
|
|
108
|
+
| mic-on.png | 麦克风开启 | [下载地址](https://github.com/Tencent-RTC/TUIKit_uni-app/blob/main/App/uni_modules/tuikit-atomic-x/static/icon/mic-on.png) |
|
|
109
|
+
| handsfree-off.png | 扬声器关闭 | [下载地址](https://github.com/Tencent-RTC/TUIKit_uni-app/blob/main/App/uni_modules/tuikit-atomic-x/static/icon/handsfree-off.png) |
|
|
110
|
+
| handsfree-on.png | 扬声器开启 | [下载地址](https://github.com/Tencent-RTC/TUIKit_uni-app/blob/main/App/uni_modules/tuikit-atomic-x/static/icon/handsfree-on.png) |
|
|
111
|
+
|
|
112
|
+
**以下是添加"接听"和"拒接"按钮的实现方式:**
|
|
113
|
+
|
|
114
|
+
1. **添加接听按钮:**添加"接听"按钮,将其点击事件绑定至 accept 方法。
|
|
115
|
+
|
|
116
|
+
```vue
|
|
117
|
+
<template>
|
|
118
|
+
<view class="btn" @tap="handleAccept">
|
|
119
|
+
<image class="btn-img" :src="ACCEPT_SRC"></image>
|
|
120
|
+
<text class="btn-text">接听</text>
|
|
121
|
+
</view>
|
|
122
|
+
</template>
|
|
123
|
+
|
|
124
|
+
<script setup lang="ts">
|
|
125
|
+
import ACCEPT_SRC from "../../../static/icon/accept.png"; // 替换为您项目的真实图标路径
|
|
126
|
+
|
|
127
|
+
import { useCallState } from '@/uni_modules/tuikit-atomic-x/state/CallState';
|
|
128
|
+
|
|
129
|
+
const { accept } = useCallState();
|
|
130
|
+
|
|
131
|
+
const handleAccept = () => {
|
|
132
|
+
accept();
|
|
133
|
+
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
</script>
|
|
137
|
+
|
|
138
|
+
<style scoped>
|
|
139
|
+
.btn {
|
|
140
|
+
width: 60px;
|
|
141
|
+
|
|
142
|
+
margin: 10px 20px;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.btn-img {
|
|
146
|
+
width: 60px;
|
|
147
|
+
|
|
148
|
+
height: 60px;
|
|
149
|
+
|
|
150
|
+
border-radius: 140px;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.btn-text {
|
|
154
|
+
font-size: 12px;
|
|
155
|
+
|
|
156
|
+
color: #d5e0f2;
|
|
157
|
+
|
|
158
|
+
font-weight: 400;
|
|
159
|
+
|
|
160
|
+
text-align: center;
|
|
161
|
+
|
|
162
|
+
margin-top: 10px;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
</style>
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
2. **添加拒接按钮:**添加"拒接"按钮,将其点击事件分别绑定至 reject 方法。
|
|
169
|
+
|
|
170
|
+
```vue
|
|
171
|
+
<template>
|
|
172
|
+
<view class="btn" @tap="handleReject">
|
|
173
|
+
<image class="btn-img" :src="REJECT_SRC"></image>
|
|
174
|
+
<text class="btn-text">拒绝</text>
|
|
175
|
+
</view>
|
|
176
|
+
</template>
|
|
177
|
+
|
|
178
|
+
<script setup lang="ts">
|
|
179
|
+
import REJECT_SRC from "../../../static/icon/hangup.png"; // 替换为您项目的真实图标路径
|
|
180
|
+
|
|
181
|
+
import { useCallState } from '@/uni_modules/tuikit-atomic-x/state/CallState';
|
|
182
|
+
|
|
183
|
+
const { reject } = useCallState();
|
|
184
|
+
|
|
185
|
+
const handleReject = () => {
|
|
186
|
+
reject();
|
|
187
|
+
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
</script>
|
|
191
|
+
|
|
192
|
+
<style scoped>
|
|
193
|
+
.btn {
|
|
194
|
+
margin: 10px 20px;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.btn-img {
|
|
198
|
+
width: 60px;
|
|
199
|
+
|
|
200
|
+
height: 60px;
|
|
201
|
+
|
|
202
|
+
border-radius: 140px;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
.btn-text {
|
|
206
|
+
font-size: 12px;
|
|
207
|
+
|
|
208
|
+
color: #d5e0f2;
|
|
209
|
+
|
|
210
|
+
font-weight: 400;
|
|
211
|
+
|
|
212
|
+
text-align: center;
|
|
213
|
+
|
|
214
|
+
margin-top: 10px;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
</style>
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
3. **拨打方取消通话或您拒接时销毁界面:**无论拨打方取消呼叫,还是接收方拒接,均会触发 `onCallEnded`(通话结束)事件。建议监听此事件,以便在通话终止时及时关闭(销毁)通话界面。
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
import { useCallState } from '@/uni_modules/tuikit-atomic-x/state/CallState';
|
|
224
|
+
import { useDeviceState } from '@/uni_modules/tuikit-atomic-x/state/DeviceState';
|
|
225
|
+
const { addCallListener } = useCallState();
|
|
226
|
+
const { closeLocalCamera, closeLocalMicrophone } = useDeviceState();
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
|
|
231
|
+
* 初始化通话服务(最小示例:仅处理通话结束事件并回退页面)建议在应用初始化时执行该函数
|
|
232
|
+
|
|
233
|
+
*/
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
export function initCallService() {
|
|
237
|
+
addCallListener('onCallEnded', (event: string) => {
|
|
238
|
+
let res: any;
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
try {
|
|
242
|
+
res = JSON.parse(event);
|
|
243
|
+
|
|
244
|
+
} catch (error) {
|
|
245
|
+
console.error('[CallService] onCallEnded parse error:', error);
|
|
246
|
+
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
console.log('[CallService] onCallEnded, reason:', res.reason);
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
// 关闭本地设备
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
closeLocalCamera();
|
|
257
|
+
|
|
258
|
+
closeLocalMicrophone();
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
// 页面回退
|
|
262
|
+
const pages = getCurrentPages();
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
if (pages.length > 1) {
|
|
266
|
+
uni.navigateBack({ delta: 1 });
|
|
267
|
+
|
|
268
|
+
} else {
|
|
269
|
+
uni.redirectTo({ url: '/pages/index/index' }); // 示例路径:请结合您的项目真实路径进行修改
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
### 步骤3:来电唤起通话界面
|
|
275
|
+
|
|
276
|
+
在您的项目中监听来电事件(`onCallReceived`),当收到来电时唤起通话页面。实现方式如下:
|
|
277
|
+
|
|
278
|
+
1. **监听来电事件:**订阅 `onCallReceived` 事件。
|
|
279
|
+
|
|
280
|
+
2. **唤起通话界面:**收到来电唤起通话页面。
|
|
281
|
+
|
|
282
|
+
```typescript
|
|
283
|
+
import { useCallState } from '@/uni_modules/tuikit-atomic-x/state/CallState';
|
|
284
|
+
const { addCallListener } = useCallState();
|
|
285
|
+
// 通话页面路由
|
|
286
|
+
const CALL_PAGE = '/uni_modules/tuikit-atomic-x/pages/call'; // 替换为您在步骤1 中创建的真实路径
|
|
287
|
+
// 建议在应用初始化的时候执行该方法
|
|
288
|
+
export function initCallService() {
|
|
289
|
+
addCallListener('onCallReceived', (event: string) => {
|
|
290
|
+
uni.navigateTo({
|
|
291
|
+
url: CALL_PAGE
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### 步骤4:来电播放提示
|
|
298
|
+
|
|
299
|
+
在您的项目中监听来电事件(`onCallReceived`),当收到来电时播放铃声。实现方式如下:
|
|
300
|
+
|
|
301
|
+
1. **监听来电事件:**订阅 `onCallReceived` 事件。
|
|
302
|
+
|
|
303
|
+
2. **唤起通话界面:**收到来电播放铃声。
|
|
304
|
+
|
|
305
|
+
```typescript
|
|
306
|
+
import { watch } from 'vue';
|
|
307
|
+
import { useCallState } from '@/uni_modules/tuikit-atomic-x/state/CallState';
|
|
308
|
+
const { addCallListener, selfInfo } = useCallState();
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
|
|
313
|
+
* 创建或获取音频播放实例(单例复用)
|
|
314
|
+
|
|
315
|
+
*/
|
|
316
|
+
|
|
317
|
+
```typescript
|
|
318
|
+
function ensureAudioContext() {
|
|
319
|
+
if (!uni.$innerAudioContext) {
|
|
320
|
+
const ctx = uni.createInnerAudioContext();
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
ctx.onError((err: any) => {
|
|
324
|
+
console.error('[CallService] innerAudioContext onError:', err?.errCode, err?.errMsg || err);
|
|
325
|
+
});
|
|
326
|
+
uni.$innerAudioContext = ctx;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return uni.$innerAudioContext;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
|
|
334
|
+
* 播放铃声
|
|
335
|
+
|
|
336
|
+
* @param src - 铃声文件路径
|
|
337
|
+
|
|
338
|
+
*/
|
|
339
|
+
|
|
340
|
+
```typescript
|
|
341
|
+
function playRingtone(src: string) {
|
|
342
|
+
const ctx = ensureAudioContext();
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
try {
|
|
346
|
+
ctx.stop();
|
|
347
|
+
} catch (_e) {}
|
|
348
|
+
|
|
349
|
+
ctx.src = src;
|
|
350
|
+
|
|
351
|
+
ctx.loop = true;
|
|
352
|
+
|
|
353
|
+
ctx.autoplay = true;
|
|
354
|
+
|
|
355
|
+
setTimeout(() => {
|
|
356
|
+
try {
|
|
357
|
+
ctx.play();
|
|
358
|
+
|
|
359
|
+
} catch (e) {
|
|
360
|
+
console.warn('[CallService] playRingtone play() error:', e);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
}, 50);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
|
|
368
|
+
* 停止铃声
|
|
369
|
+
|
|
370
|
+
*/
|
|
371
|
+
|
|
372
|
+
```typescript
|
|
373
|
+
function stopRingtone() {
|
|
374
|
+
if (uni.$innerAudioContext) {
|
|
375
|
+
try {
|
|
376
|
+
uni.$innerAudioContext.loop = false;
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
uni.$innerAudioContext.stop();
|
|
380
|
+
|
|
381
|
+
} catch (e) {
|
|
382
|
+
console.warn('[CallService] stopRingtone error:', e);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
```typescript
|
|
388
|
+
export { playRingtone, stopRingtone };
|
|
389
|
+
// 在应用启动的时候执行该方法
|
|
390
|
+
export function initCallService() {
|
|
391
|
+
ensureAudioContext();
|
|
392
|
+
// 监听通话状态变化:接通(status=2)或结束(status=0)时停止铃声
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
watch(() => selfInfo.value, (newVal) => {
|
|
396
|
+
if (newVal?.status === 2 || newVal?.status === 0) {
|
|
397
|
+
stopRingtone();
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
}, { immediate: true, deep: true });
|
|
401
|
+
|
|
402
|
+
```typescript
|
|
403
|
+
// 收到来电时播放铃声
|
|
404
|
+
addCallListener('onCallReceived', () => {
|
|
405
|
+
playRingtone('/static/audio/ring.mp3');
|
|
406
|
+
});
|
|
407
|
+
// 通话结束时停止铃声
|
|
408
|
+
addCallListener('onCallEnded', () => {
|
|
409
|
+
stopRingtone();
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
### 步骤5:来电打开媒体设备
|
|
415
|
+
|
|
416
|
+
收到来电时,您可以通过 `onCallReceived` 事件获取本次通话的媒体类型。为了提供更佳的用户体验,建议在唤起通话界面时,根据通话类型预先开启对应的媒体设备。实现步骤如下:
|
|
417
|
+
|
|
418
|
+
1. **监听来电事件**:订阅 `onCallReceived` 事件。
|
|
419
|
+
|
|
420
|
+
2. **根据来电媒体类型打开设备:**若为语音通话仅开启麦克风,若为视频通话开启麦克风和摄像头。
|
|
421
|
+
|
|
422
|
+
```typescript
|
|
423
|
+
import { useCallState } from '@/uni_modules/tuikit-atomic-x/state/CallState';
|
|
424
|
+
import { useDeviceState } from '@/uni_modules/tuikit-atomic-x/state/DeviceState';
|
|
425
|
+
const { addCallListener } = useCallState();
|
|
426
|
+
const { openLocalCamera, openLocalMicrophone } = useDeviceState();
|
|
427
|
+
// 通话页面路由
|
|
428
|
+
const CALL_PAGE = '/uni_modules/tuikit-atomic-x/pages/call'; // 替换为您在步骤1 中创建的真实路径
|
|
429
|
+
// 在应用启动的时候执行该方法
|
|
430
|
+
export function initCallService() {
|
|
431
|
+
addCallListener('onCallReceived', (event: string) => {
|
|
432
|
+
let res: any;
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
try {
|
|
436
|
+
res = JSON.parse(event);
|
|
437
|
+
|
|
438
|
+
} catch (error) {
|
|
439
|
+
console.error('[CallService] onCallReceived parse error:', error);
|
|
440
|
+
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
```typescript
|
|
445
|
+
// 视频通话时打开摄像头
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
if (res.mediaType === 1) {
|
|
449
|
+
openLocalCamera({ isFront: true });
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
```typescript
|
|
453
|
+
// 打开麦克风
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
openLocalMicrophone();
|
|
457
|
+
|
|
458
|
+
```typescript
|
|
459
|
+
// 跳转到通话页面
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
uni.navigateTo({
|
|
463
|
+
url: CALL_PAGE
|
|
464
|
+
});
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
### 运行效果
|
|
469
|
+
|
|
470
|
+
当您完成以上 6 步后,"接听一通电话"运行效果如下:
|
|
471
|
+
|
|
472
|
+
## 定制页面
|
|
473
|
+
|
|
474
|
+
**CallCoreView** 提供了完善的 UI 定制能力,支持头像及音量提示等图标的自由替换。**为助力快速集成,您可以直接从** GitHub **下载。这些图标由我们的设计师专为 TUICallKit 打造,无版权风险,可放心使用。**
|
|
475
|
+
|
|
476
|
+
### 自定义音量提示的图标
|
|
477
|
+
|
|
478
|
+
您可以通过设置 **CallCoreView** 组件的 `volumeLevelIcons` 属性来设置音量大小等级不同的提示图标。
|
|
479
|
+
|
|
480
|
+
`volumeLevelIcons` **属性设置示例代码**:
|
|
481
|
+
|
|
482
|
+
```vue
|
|
483
|
+
<template>
|
|
484
|
+
<view class="call-view-container">
|
|
485
|
+
<view :style="{ height: systemInfo?.windowHeight + 'px', width: systemInfo?.safeArea?.width + 'px' }">
|
|
486
|
+
<call-core-view
|
|
487
|
+
:style="{ height: systemInfo?.windowHeight + 'px', width: systemInfo?.safeArea?.width + 'px' }"
|
|
488
|
+
|
|
489
|
+
:volumeLevelIcons="volumeLevelIcons"
|
|
490
|
+
|
|
491
|
+
></call-core-view>
|
|
492
|
+
|
|
493
|
+
</view>
|
|
494
|
+
</view>
|
|
495
|
+
</template>
|
|
496
|
+
|
|
497
|
+
<script setup>
|
|
498
|
+
import { ref, onMounted } from 'vue';
|
|
499
|
+
|
|
500
|
+
const systemInfo = ref({});
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
|
|
504
|
+
* 将 /static/ 相对路径转为原生层可用的本地绝对路径
|
|
505
|
+
|
|
506
|
+
*/
|
|
507
|
+
|
|
508
|
+
function toAbsolutePath(relativePath) {
|
|
509
|
+
if (plus && plus.io && plus.io.convertLocalFileSystemURL) {
|
|
510
|
+
return plus.io.convertLocalFileSystemURL(relativePath);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
return relativePath;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
|
|
518
|
+
* 批量转换对象中所有 value 的路径为绝对路径
|
|
519
|
+
|
|
520
|
+
*/
|
|
521
|
+
|
|
522
|
+
function convertIconPaths(iconMap) {
|
|
523
|
+
const result = {};
|
|
524
|
+
|
|
525
|
+
Object.keys(iconMap).forEach(key => {
|
|
526
|
+
result[key] = toAbsolutePath(iconMap[key]);
|
|
527
|
+
});
|
|
528
|
+
return result;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// 音量等级图标:key 为等级名称,value 为图标本地绝对路径
|
|
532
|
+
|
|
533
|
+
const volumeLevelIcons = ref(convertIconPaths({
|
|
534
|
+
"Mute": "/static/images/callview-self-mute.png", // 可以替换您项目中的实际图标路径
|
|
535
|
+
"Low": "/static/images/callview-network.png" // 可以替换您项目中的实际图标路径
|
|
536
|
+
}));
|
|
537
|
+
|
|
538
|
+
onMounted(() => {
|
|
539
|
+
uni.getSystemInfo({
|
|
540
|
+
success: (res) => {
|
|
541
|
+
systemInfo.value = res;
|
|
542
|
+
}
|
|
543
|
+
});
|
|
544
|
+
});
|
|
545
|
+
</script>
|
|
546
|
+
|
|
547
|
+
<style>
|
|
548
|
+
.call-view-container {
|
|
549
|
+
background: rgba(15, 16, 20, 0.5);
|
|
550
|
+
|
|
551
|
+
overflow: hidden;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
</style>
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
**说明:**
|
|
558
|
+
|
|
559
|
+
uni-app 传递给原生层的参数只支持本地路径,因此需要将相对路径转化为本地路径
|
|
560
|
+
|
|
561
|
+
**属性参数详细说明:**
|
|
562
|
+
|
|
563
|
+
| **参数** | **类型** | **是否必填** | **说明** |
|
|
564
|
+
| --- | --- | --- | --- |
|
|
565
|
+
| icons | \[String: String\] | 是 | 音量等级与图标资源的映射表。结构说明如下:key ( VolumeLevel ) 表示音量等级:Mute:表示麦克风关闭,静音状态。Low:表示音量范围 (0-25\]。Medium:表示音量范围 (25-50\]。High:表示音量范围 (50-75\]。Peak:表示音量范围 (75-100\]。Value ( String ) 表示对应音量等级的图标资源路径。 |
|
|
566
|
+
|
|
567
|
+
**音量提示图标:**
|
|
568
|
+
|
|
569
|
+
| **图标** | **说明** | **下载地址** |
|
|
570
|
+
| --- | --- | --- |
|
|
571
|
+
| 【图标含义】音量提示图标。【推荐用法】您可以将该图标等级设置为 Low 或 Medium,当用户音量大于对应等级时显示。 | [下载地址](https://github.com/Tencent-RTC/TUIKit_uni-app/blob/main/App/static/images/callview-network.png) |
|
|
572
|
+
| 【图标含义】静音图标。【推荐用法】您可以将该图标等级设置为 Mute,当该用户静音时显示。 | [下载地址](https://github.com/Tencent-RTC/TUIKit_uni-app/blob/main/App/static/images/callview-self-mute.png) |
|
|
573
|
+
|
|
574
|
+
### 自定义网络提示的图标
|
|
575
|
+
|
|
576
|
+
您可以设置 **CallCoreView** 组件的 `networkQualityIcons` 属性设置不同网络状态的提示图标。
|
|
577
|
+
|
|
578
|
+
`networkQualityIcons` **属性设置示例代码:**
|
|
579
|
+
<template>
|
|
580
|
+
<view class="call-view-container">
|
|
581
|
+
<view :style="{ height: systemInfo?.windowHeight + 'px', width: systemInfo?.safeArea?.width + 'px' }">
|
|
582
|
+
<call-core-view
|
|
583
|
+
:style="{ height: systemInfo?.windowHeight + 'px', width: systemInfo?.safeArea?.width + 'px' }"
|
|
584
|
+
|
|
585
|
+
:networkQualityIcons="networkQualityIcons"
|
|
586
|
+
|
|
587
|
+
></call-core-view>
|
|
588
|
+
|
|
589
|
+
</view>
|
|
590
|
+
</view>
|
|
591
|
+
</template>
|
|
592
|
+
|
|
593
|
+
```vue
|
|
594
|
+
<script setup>
|
|
595
|
+
import { ref, onMounted } from 'vue';
|
|
596
|
+
const systemInfo = ref({});
|
|
597
|
+
```
|
|
598
|
+
|
|
599
|
+
/**
|
|
600
|
+
|
|
601
|
+
* 将 /static/ 相对路径转为原生层可用的本地绝对路径
|
|
602
|
+
|
|
603
|
+
*/
|
|
604
|
+
|
|
605
|
+
```typescript
|
|
606
|
+
function toAbsolutePath(relativePath) {
|
|
607
|
+
if (plus && plus.io && plus.io.convertLocalFileSystemURL) {
|
|
608
|
+
return plus.io.convertLocalFileSystemURL(relativePath);
|
|
609
|
+
}
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
return relativePath;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
|
|
617
|
+
* 批量转换对象中所有 value 的路径为绝对路径
|
|
618
|
+
|
|
619
|
+
*/
|
|
620
|
+
|
|
621
|
+
```typescript
|
|
622
|
+
function convertIconPaths(iconMap) {
|
|
623
|
+
const result = {};
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
Object.keys(iconMap).forEach(key => {
|
|
627
|
+
result[key] = toAbsolutePath(iconMap[key]);
|
|
628
|
+
});
|
|
629
|
+
return result;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
```typescript
|
|
633
|
+
const networkQualityIcons = ref(convertIconPaths({
|
|
634
|
+
"BAD": "/static/images/callview-network-bad.png", // 可以替换您项目中的实际图标路径
|
|
635
|
+
"VERY_BAD": "/static/images/callview-network-bad.png" // 可以替换您项目中的实际图标路径
|
|
636
|
+
}));
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
onMounted(() => {
|
|
640
|
+
uni.getSystemInfo({
|
|
641
|
+
success: (res) => {
|
|
642
|
+
systemInfo.value = res;
|
|
643
|
+
}
|
|
644
|
+
});
|
|
645
|
+
});
|
|
646
|
+
</script>
|
|
647
|
+
|
|
648
|
+
```vue
|
|
649
|
+
<style>
|
|
650
|
+
.call-view-container {
|
|
651
|
+
background: rgba(15, 16, 20, 0.5);
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
overflow: hidden;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
</style>
|
|
658
|
+
`networkQualityIcons` **接口参数详细说明:**
|
|
659
|
+
|
|
660
|
+
| **参数** | **类型** | **是否必填** | **说明** |
|
|
661
|
+
| --- | --- | --- | --- |
|
|
662
|
+
| icons | \[String: String\] | 是 | 网络质量与图标资源的映射表。结构说明如下:Key ( String):表示网络质量等级:UNKNOWN:未知网络状态。EXCELLENT:网络状态极佳。GOOD:网络状态较好。POOR:网络状态较差。BAD:网络状态差。VERY\_BAD:网络状态极差。DOWN:网络断开。Value ( String ):对应网络状态的图标资源路径。 |
|
|
663
|
+
|
|
664
|
+
**网络较差的提示图标:**
|
|
665
|
+
|
|
666
|
+
| **图标** | **说明** | **下载地址** |
|
|
667
|
+
| --- | --- | --- |
|
|
668
|
+
| 【图标含义】网络较差的提示图标。【推荐用法】您可以将该图标等级设置为 BAD、VERY\_BAD 或 DOWN ,当网络较差时显示该图标。 | [下载地址](https://github.com/Tencent-RTC/TUIKit_uni-app/blob/main/App/static/images/callview-network-bad.png) |
|
|
669
|
+
|
|
670
|
+
### 自定义默认头像
|
|
671
|
+
|
|
672
|
+
您可以设置 **CallCoreView** 的 `participantAvatars` 属性设置用户头像。建议您监听响应式数据 allParticipants(所有参与通话的成员):当获取到用户头像时设置并展示;若用户未设置头像或加载失败,则显示默认头像(占位图)。
|
|
673
|
+
|
|
674
|
+
`participantAvatars` **属性设置示例代码:**
|
|
675
|
+
<template>
|
|
676
|
+
<view class="call-view-container">
|
|
677
|
+
<view :style="{ height: systemInfo?.windowHeight + 'px', width: systemInfo?.safeArea?.width + 'px' }">
|
|
678
|
+
<call-core-view
|
|
679
|
+
:style="{ height: systemInfo?.windowHeight + 'px', width: systemInfo?.safeArea?.width + 'px' }"
|
|
680
|
+
|
|
681
|
+
:participantAvatars="participantAvatars"
|
|
682
|
+
|
|
683
|
+
></call-core-view>
|
|
684
|
+
|
|
685
|
+
</view>
|
|
686
|
+
</view>
|
|
687
|
+
</template>
|
|
688
|
+
|
|
689
|
+
```vue
|
|
690
|
+
<script setup>
|
|
691
|
+
import { ref, watch, onMounted } from 'vue';
|
|
692
|
+
import { useCallState } from '@/uni_modules/tuikit-atomic-x/state/CallState';
|
|
693
|
+
const { allParticipants } = useCallState();
|
|
694
|
+
const systemInfo = ref({});
|
|
695
|
+
const participantAvatars = ref({});
|
|
696
|
+
const avatarCache = {};
|
|
697
|
+
const DEFAULT_AVATAR_URL = "https://liteav.sdk.qcloud.com/app/res/picture/voiceroom/avatar/user_avatar1.png";
|
|
698
|
+
```
|
|
699
|
+
|
|
700
|
+
let defaultAvatarLocalPath = "";
|
|
701
|
+
|
|
702
|
+
let defaultAvatarReady = false;
|
|
703
|
+
|
|
704
|
+
/**
|
|
705
|
+
|
|
706
|
+
* 将相对路径转为原生层可用的本地绝对路径
|
|
707
|
+
|
|
708
|
+
*/
|
|
709
|
+
|
|
710
|
+
```typescript
|
|
711
|
+
function toAbsolutePath(relativePath) {
|
|
712
|
+
if (plus && plus.io && plus.io.convertLocalFileSystemURL) {
|
|
713
|
+
return plus.io.convertLocalFileSystemURL(relativePath);
|
|
714
|
+
}
|
|
715
|
+
```
|
|
716
|
+
|
|
717
|
+
return relativePath;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
/**
|
|
721
|
+
|
|
722
|
+
* 下载头像并缓存,返回本地绝对路径
|
|
723
|
+
|
|
724
|
+
*/
|
|
725
|
+
|
|
726
|
+
```typescript
|
|
727
|
+
function downloadAvatar(url) {
|
|
728
|
+
if (avatarCache[url]) {
|
|
729
|
+
return Promise.resolve(avatarCache[url]);
|
|
730
|
+
}
|
|
731
|
+
```
|
|
732
|
+
|
|
733
|
+
return new Promise((resolve) => {
|
|
734
|
+
uni.downloadFile({
|
|
735
|
+
url,
|
|
736
|
+
success: (res) => {
|
|
737
|
+
if (res.statusCode === 200) {
|
|
738
|
+
const absolutePath = toAbsolutePath(res.tempFilePath);
|
|
739
|
+
|
|
740
|
+
avatarCache[url] = absolutePath;
|
|
741
|
+
|
|
742
|
+
resolve(absolutePath);
|
|
743
|
+
|
|
744
|
+
} else {
|
|
745
|
+
resolve(defaultAvatarLocalPath || "");
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
},
|
|
749
|
+
fail: () => {
|
|
750
|
+
resolve(defaultAvatarLocalPath || "");
|
|
751
|
+
}
|
|
752
|
+
});
|
|
753
|
+
});
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
```typescript
|
|
757
|
+
// 预下载默认头像
|
|
758
|
+
const defaultAvatarPromise = downloadAvatar(DEFAULT_AVATAR_URL).then(localPath => {
|
|
759
|
+
defaultAvatarLocalPath = localPath;
|
|
760
|
+
```
|
|
761
|
+
|
|
762
|
+
defaultAvatarReady = true;
|
|
763
|
+
|
|
764
|
+
```typescript
|
|
765
|
+
// 默认头像就绪后,补刷一次参与者头像
|
|
766
|
+
```
|
|
767
|
+
|
|
768
|
+
if (allParticipants.value && allParticipants.value.length > 0) {
|
|
769
|
+
updateParticipantAvatars(allParticipants.value);
|
|
770
|
+
}
|
|
771
|
+
});
|
|
772
|
+
|
|
773
|
+
/**
|
|
774
|
+
|
|
775
|
+
* 根据参与者列表,下载头像并构建 { userId: localPath } 映射
|
|
776
|
+
|
|
777
|
+
*/
|
|
778
|
+
|
|
779
|
+
async function updateParticipantAvatars(participants) {
|
|
780
|
+
if (!participants || participants.length === 0) {
|
|
781
|
+
participantAvatars.value = {};
|
|
782
|
+
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
if (!defaultAvatarReady && defaultAvatarPromise) {
|
|
787
|
+
await defaultAvatarPromise;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
```typescript
|
|
791
|
+
const avatarMap = {};
|
|
792
|
+
const tasks = [];
|
|
793
|
+
```
|
|
794
|
+
|
|
795
|
+
participants.forEach(participant => {
|
|
796
|
+
if (participant.id && participant.avatarURL) {
|
|
797
|
+
tasks.push(
|
|
798
|
+
downloadAvatar(participant.avatarURL).then(localPath => {
|
|
799
|
+
avatarMap[participant.id] = localPath;
|
|
800
|
+
|
|
801
|
+
```typescript
|
|
802
|
+
})
|
|
803
|
+
```
|
|
804
|
+
|
|
805
|
+
);
|
|
806
|
+
|
|
807
|
+
} else if (participant.id) {
|
|
808
|
+
avatarMap[participant.id] = defaultAvatarLocalPath || "";
|
|
809
|
+
}
|
|
810
|
+
});
|
|
811
|
+
await Promise.all(tasks);
|
|
812
|
+
|
|
813
|
+
participantAvatars.value = avatarMap;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
```typescript
|
|
817
|
+
// 监听参与者变化,更新头像映射
|
|
818
|
+
```
|
|
819
|
+
|
|
820
|
+
watch(() => allParticipants.value, async (newVal) => {
|
|
821
|
+
if (!newVal) return;
|
|
822
|
+
|
|
823
|
+
await updateParticipantAvatars(newVal);
|
|
824
|
+
|
|
825
|
+
}, { immediate: true, deep: true });
|
|
826
|
+
|
|
827
|
+
onMounted(() => {
|
|
828
|
+
uni.getSystemInfo({
|
|
829
|
+
success: (res) => {
|
|
830
|
+
systemInfo.value = res;
|
|
831
|
+
}
|
|
832
|
+
});
|
|
833
|
+
});
|
|
834
|
+
</script>
|
|
835
|
+
|
|
836
|
+
```vue
|
|
837
|
+
<style>
|
|
838
|
+
.call-view-container {
|
|
839
|
+
background: rgba(15, 16, 20, 0.5);
|
|
840
|
+
```
|
|
841
|
+
|
|
842
|
+
overflow: hidden;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
</style>
|
|
846
|
+
`participantAvatars` **属性参数详细说明:**
|
|
847
|
+
|
|
848
|
+
| **参数** | **类型** | **是否必填** | **说明** |
|
|
849
|
+
| --- | --- | --- | --- |
|
|
850
|
+
| avatars | \[String: String\] | 是 | 用户头像映射表。字典结构说明如下:Key:用户的 userID。Value:该用户的头像资源绝对路径。 |
|
|
851
|
+
|
|
852
|
+
**默认头像资源:**
|
|
853
|
+
|
|
854
|
+
| **图标** | **说明** | **下载地址** |
|
|
855
|
+
| --- | --- | --- |
|
|
856
|
+
| 【图标含义】默认头像。【推荐用法】当用户头像加载失败或无头像时,您可以给该用户设置此默认头像。 | [下载地址](https://github.com/Tencent-RTC/TUIKit_iOS/tree/main/atomic_x/Resources/AtomicX.xcassets/icon/call/default_participant_icon.imageset) |
|
|
857
|
+
|
|
858
|
+
### 自定义 loading 动画
|
|
859
|
+
|
|
860
|
+
您可以设置 **CallCoreView** 的 `waitingAnimation` 属性,为等待中用户设置等待动画获得更好的体验。
|
|
861
|
+
|
|
862
|
+
`waitingAnimation` **属性设置示例代码:**
|
|
863
|
+
<template>
|
|
864
|
+
<view class="call-view-container">
|
|
865
|
+
<view :style="{ height: systemInfo?.windowHeight + 'px', width: systemInfo?.safeArea?.width + 'px' }">
|
|
866
|
+
<call-core-view
|
|
867
|
+
:style="{ height: systemInfo?.windowHeight + 'px', width: systemInfo?.safeArea?.width + 'px' }"
|
|
868
|
+
|
|
869
|
+
:waitingAnimation="waitingAnimation"
|
|
870
|
+
|
|
871
|
+
></call-core-view>
|
|
872
|
+
|
|
873
|
+
</view>
|
|
874
|
+
</view>
|
|
875
|
+
</template>
|
|
876
|
+
|
|
877
|
+
```vue
|
|
878
|
+
<script setup>
|
|
879
|
+
import { ref, onMounted } from 'vue';
|
|
880
|
+
const systemInfo = ref({});
|
|
881
|
+
```
|
|
882
|
+
|
|
883
|
+
/**
|
|
884
|
+
|
|
885
|
+
* 将 /static/ 相对路径转为原生层可用的本地绝对路径
|
|
886
|
+
|
|
887
|
+
*/
|
|
888
|
+
|
|
889
|
+
```typescript
|
|
890
|
+
function toAbsolutePath(relativePath) {
|
|
891
|
+
if (plus && plus.io && plus.io.convertLocalFileSystemURL) {
|
|
892
|
+
return plus.io.convertLocalFileSystemURL(relativePath);
|
|
893
|
+
}
|
|
894
|
+
```
|
|
895
|
+
|
|
896
|
+
return relativePath;
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
```typescript
|
|
900
|
+
// 等待动画 GIF:传入本地绝对路径供原生组件渲染
|
|
901
|
+
const waitingAnimation = ref(toAbsolutePath("/static/images/callview-loading.gif")); // 可以替换您项目中的实际图标路径
|
|
902
|
+
```
|
|
903
|
+
|
|
904
|
+
onMounted(() => {
|
|
905
|
+
uni.getSystemInfo({
|
|
906
|
+
success: (res) => {
|
|
907
|
+
systemInfo.value = res;
|
|
908
|
+
}
|
|
909
|
+
});
|
|
910
|
+
});
|
|
911
|
+
</script>
|
|
912
|
+
|
|
913
|
+
```vue
|
|
914
|
+
<style>
|
|
915
|
+
.call-view-container {
|
|
916
|
+
background: rgba(15, 16, 20, 0.5);
|
|
917
|
+
```
|
|
918
|
+
|
|
919
|
+
overflow: hidden;
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
</style>
|
|
923
|
+
`waitingAnimation` **接口参数详细说明:**
|
|
924
|
+
|
|
925
|
+
| **参数** | **类型** | **是否必填** | **说明** |
|
|
926
|
+
| --- | --- | --- | --- |
|
|
927
|
+
| path | String | 是 | GIF 格式图像资源的绝对路径。 |
|
|
928
|
+
|
|
929
|
+
**等待接听的动画:**
|
|
930
|
+
|
|
931
|
+
| **图标** | **说明** | **下载地址** |
|
|
932
|
+
| --- | --- | --- |
|
|
933
|
+
| 【图标含义】用户等待接听动画。【推荐用法】群组通话时设置的动画。设置后,当用户的状态为等待接听时,显示该动画。 | [下载地址](https://github.com/Tencent-RTC/TUIKit_uni-app/blob/main/App/static/images/callview-loading.gif) |
|
|
934
|
+
|
|
935
|
+
### 添加通话计时提示
|
|
936
|
+
|
|
937
|
+
通话计时可通过响应式数据 activeCall 的 `duration` 字段实时获得,实时显示通话计时的实现方式如下:
|
|
938
|
+
|
|
939
|
+
1. **绑定通话计时数据**:将 `activeCall.duration` 字段绑定至 UI 。该字段为响应式数据,会自动驱动 UI 实时刷新,无需手动维护定时器。
|
|
940
|
+
|
|
941
|
+
```vue
|
|
942
|
+
<template>
|
|
943
|
+
<view v-if="isConnected" class="call-timer">
|
|
944
|
+
<text class="timer-text">{{ formattedTime }}</text>
|
|
945
|
+
</view>
|
|
946
|
+
</template>
|
|
947
|
+
|
|
948
|
+
<script setup lang="ts">
|
|
949
|
+
import { computed } from 'vue';
|
|
950
|
+
|
|
951
|
+
import { useCallState } from '@/uni_modules/tuikit-atomic-x/state/CallState';
|
|
952
|
+
|
|
953
|
+
const { selfInfo, activeCall } = useCallState();
|
|
954
|
+
|
|
955
|
+
const isConnected = computed(() => {
|
|
956
|
+
return selfInfo.value?.status === 2;
|
|
957
|
+
});
|
|
958
|
+
|
|
959
|
+
const formattedTime = computed(() => {
|
|
960
|
+
const duration = activeCall.value?.duration ?? 0;
|
|
961
|
+
|
|
962
|
+
const hours = Math.floor(duration / 3600);
|
|
963
|
+
|
|
964
|
+
const minutes = Math.floor((duration % 3600) / 60);
|
|
965
|
+
|
|
966
|
+
const seconds = duration % 60;
|
|
967
|
+
|
|
968
|
+
const pad = (n: number) => n.toString().padStart(2, '0');
|
|
969
|
+
|
|
970
|
+
if (hours > 0) {
|
|
971
|
+
return `${pad(hours)}:${pad(minutes)}:${pad(seconds)}`;
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
return `${pad(minutes)}:${pad(seconds)}`;
|
|
975
|
+
});
|
|
976
|
+
</script>
|
|
977
|
+
|
|
978
|
+
<style scoped>
|
|
979
|
+
.call-timer {
|
|
980
|
+
display: flex;
|
|
981
|
+
|
|
982
|
+
align-items: center;
|
|
983
|
+
|
|
984
|
+
justify-content: center;
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
.timer-text {
|
|
988
|
+
font-size: 28rpx;
|
|
989
|
+
|
|
990
|
+
color: #FFFFFF;
|
|
991
|
+
|
|
992
|
+
font-weight: 400;
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
</style>
|
|
996
|
+
```
|
|
997
|
+
|
|
998
|
+
## 更多功能
|
|
999
|
+
|
|
1000
|
+
### 自定义用户头像/昵称
|
|
1001
|
+
|
|
1002
|
+
通话开始前,您可以通过 setSelfInfo 方法,设置自己的昵称和头像。
|
|
1003
|
+
|
|
1004
|
+
**setSelfInfo 示例代码**:
|
|
1005
|
+
import { useLoginState } from "@/uni_modules/tuikit-atomic-x/state/LoginState";
|
|
1006
|
+
|
|
1007
|
+
```typescript
|
|
1008
|
+
const { setSelfInfo } = useLoginState();
|
|
1009
|
+
```
|
|
1010
|
+
|
|
1011
|
+
/**
|
|
1012
|
+
|
|
1013
|
+
* 设置当前登录用户的个人信息(昵称、头像等)
|
|
1014
|
+
|
|
1015
|
+
*/
|
|
1016
|
+
|
|
1017
|
+
```typescript
|
|
1018
|
+
export const setSelfUserInfo = (userID: string, nickname: string, avatarURL: string): void => {
|
|
1019
|
+
setSelfInfo({
|
|
1020
|
+
userProfile: {
|
|
1021
|
+
userID,
|
|
1022
|
+
nickname,
|
|
1023
|
+
avatarURL,
|
|
1024
|
+
},
|
|
1025
|
+
success: () => {
|
|
1026
|
+
console.log('用户信息设置成功');
|
|
1027
|
+
```
|
|
1028
|
+
|
|
1029
|
+
},
|
|
1030
|
+
fail: (errCode: number, errMsg: string) => {
|
|
1031
|
+
console.error('用户信息设置失败:', errCode, errMsg);
|
|
1032
|
+
|
|
1033
|
+
},
|
|
1034
|
+
});
|
|
1035
|
+
};
|
|
1036
|
+
|
|
1037
|
+
**setSelfInfo 接口参数详细说明:**
|
|
1038
|
+
|
|
1039
|
+
| **参数** | **类型** | **是否必填** | **说明** |
|
|
1040
|
+
| --- | --- | --- | --- |
|
|
1041
|
+
| userProfile | UserProfile | 是 | 用户信息结构体:userID:用户的 IDavatarURL:用户头像的 URLnickname:用户的昵称更多字段详情可参考 UserProfile。 |
|
|
1042
|
+
|
|
1043
|
+
### 切换布局模式
|
|
1044
|
+
|
|
1045
|
+
**CallCoreView** 内置三种布局模式,您可以设置 `layoutTemplate` 属性来设置布局模式。若未主动配置,**CallCoreView** 将根据通话人数自动适配:1 V 1 场景下默认采用 `Float` 模式,多人通话场景下则自动切换为 `Grid` 模式。不同布局模式的说明如下:
|
|
1046
|
+
|
|
1047
|
+
| **模式** | **布局逻辑** | **交互特性** |
|
|
1048
|
+
| --- | --- | --- |
|
|
1049
|
+
| **Float 模式** | 呼叫等待时全屏显示己方画面;接通后全屏显示对方画面,己方画面以悬浮小窗展示。 | 支持小窗拖拽移动,点击小窗可实现大小画面互换。 |
|
|
1050
|
+
| **Grid 模式** | 所有成员画面呈网格状平铺排列成宫格模式布局,适用 2 人以上通话,支持点击放大画面功能。 | 支持点击特定成员画面放大查看。 |
|
|
1051
|
+
| **PIP 模式** | 1 v 1 场景固定显示对方画面,多人场景:采用当前发言者(Active Speaker) 策略,自动识别并全屏展示正在说话的用户。 | 等待时显示自己的画面,接通后还会显示通话计时。 |
|
|
1052
|
+
|
|
1053
|
+
`layoutTemplate` **属性设置示例代码:**
|
|
1054
|
+
<template>
|
|
1055
|
+
<view class="call-view-container">
|
|
1056
|
+
<view :style="{ height: systemInfo?.windowHeight + 'px', width: systemInfo?.safeArea?.width + 'px' }">
|
|
1057
|
+
<call-core-view
|
|
1058
|
+
:style="{ height: systemInfo?.windowHeight + 'px', width: systemInfo?.safeArea?.width + 'px' }"
|
|
1059
|
+
|
|
1060
|
+
:layoutTemplate="layoutTemplate"
|
|
1061
|
+
|
|
1062
|
+
></call-core-view>
|
|
1063
|
+
|
|
1064
|
+
</view>
|
|
1065
|
+
</view>
|
|
1066
|
+
</template>
|
|
1067
|
+
|
|
1068
|
+
```vue
|
|
1069
|
+
<script setup>
|
|
1070
|
+
import { ref, watch, onMounted } from 'vue';
|
|
1071
|
+
import { useCallState } from '@/uni_modules/tuikit-atomic-x/state/CallState';
|
|
1072
|
+
const { activeCall } = useCallState();
|
|
1073
|
+
const systemInfo = ref({});
|
|
1074
|
+
// 布局模板:'Float' 单人浮窗布局,'Grid' 多人宫格布局
|
|
1075
|
+
const layoutTemplate = ref("Grid");
|
|
1076
|
+
// 根据通话信息自动切换布局:多人或群聊用 Grid,单人用 Float
|
|
1077
|
+
```
|
|
1078
|
+
|
|
1079
|
+
watch(() => activeCall.value, (newValue) => {
|
|
1080
|
+
if (!newValue || !newValue.inviteeIds) {
|
|
1081
|
+
return;
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
```typescript
|
|
1085
|
+
const newTemplate = (newValue.inviteeIds.length > 1 || newValue.chatGroupId !== '') ? 'Grid' : 'Float';
|
|
1086
|
+
```
|
|
1087
|
+
|
|
1088
|
+
if (layoutTemplate.value !== newTemplate) {
|
|
1089
|
+
layoutTemplate.value = newTemplate;
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
}, { immediate: true, deep: true });
|
|
1093
|
+
|
|
1094
|
+
onMounted(() => {
|
|
1095
|
+
uni.getSystemInfo({
|
|
1096
|
+
success: (res) => {
|
|
1097
|
+
systemInfo.value = res;
|
|
1098
|
+
}
|
|
1099
|
+
});
|
|
1100
|
+
});
|
|
1101
|
+
</script>
|
|
1102
|
+
|
|
1103
|
+
```vue
|
|
1104
|
+
<style>
|
|
1105
|
+
.call-view-container {
|
|
1106
|
+
background: rgba(15, 16, 20, 0.5);
|
|
1107
|
+
```
|
|
1108
|
+
|
|
1109
|
+
overflow: hidden;
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
</style>
|
|
1113
|
+
`layoutTemplate` **属性参数详细说明:**
|
|
1114
|
+
|
|
1115
|
+
| **参数** | **类型** | **说明** |
|
|
1116
|
+
| --- | --- | --- |
|
|
1117
|
+
| layoutTemplate | String | CallCoreView 的布局模式Float:布局逻辑:呼叫等待时全屏显示己方画面;接通后全屏显示对方画面,己方画面以悬浮小窗展示。交互特性:支持小窗拖拽移动,点击小窗可实现大小画面互换。Grid:布局逻辑:所有成员画面呈网格状平铺排列成宫格模式布局,适用 2 人以上通话,支持点击放大画面功能。交互特性:支持点击特定成员画面放大查看。PIP:布局逻辑:1v1 场景固定显示对方画面,多人场景:采用当前发言者(Active Speaker) 策略,自动识别并全屏展示正在说话的用户。交互特性:等待时显示自己的画面,接通后还会显示通话计时。 |
|
|
1118
|
+
|
|
1119
|
+
## 常见问题
|
|
1120
|
+
|
|
1121
|
+
### 如何保证通话过程中屏幕常亮?
|
|
1122
|
+
|
|
1123
|
+
uni-app 官方提供了对应的设置接口 uni.setScreenBrightness。
|
|
1124
|
+
|
|
1125
|
+
### 如何禁止系统侧滑返回上一个页面的行为?
|
|
1126
|
+
|
|
1127
|
+
iOS 端可以通过设置页面上的 disableSwipeBack 属性进行禁止。详情参考 uni-app 官方文档。
|
|
1128
|
+
|
|
1129
|
+
Android 端可以通过 onBackPress 拦截系统侧滑和物理返回,示例代码如下:
|
|
1130
|
+
|
|
1131
|
+
**注意:**
|
|
1132
|
+
|
|
1133
|
+
使用该方式进行系统侧滑和物理返回的拦截需要确保该页面是使用的是 自定义导航栏。
|
|
1134
|
+
|
|
1135
|
+
```vue
|
|
1136
|
+
<script setup>
|
|
1137
|
+
import { onBackPress } from '@dcloudio/uni-app'
|
|
1138
|
+
// 拦截系统侧滑和物理返回键,防止通话中误退出
|
|
1139
|
+
```
|
|
1140
|
+
|
|
1141
|
+
onBackPress((option) => {
|
|
1142
|
+
if (option.from === 'backbutton') {
|
|
1143
|
+
return true; // 返回 true 阻止默认回退行为
|
|
1144
|
+
}
|
|
1145
|
+
});
|
|
1146
|
+
</script>
|