@94ai/softphone 4.0.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/.yarnrc.yml +19 -0
- package/README.md +941 -0
- package/bin/index.js +50 -0
- package/html-softphone-demo/CryptoJS.js +3 -0
- package/html-softphone-demo/README.md +10 -0
- package/html-softphone-demo/RootCA.pem +20 -0
- package/html-softphone-demo/antique_phone.mp3 +0 -0
- package/html-softphone-demo/cert.pem +21 -0
- package/html-softphone-demo/fp.umd.min.js +9 -0
- package/html-softphone-demo/gg.xccjh.top.key +27 -0
- package/html-softphone-demo/gg.xccjh.top.pem +61 -0
- package/html-softphone-demo/index-test.html +867 -0
- package/html-softphone-demo/index.html +845 -0
- package/html-softphone-demo/localhost.crt +21 -0
- package/html-softphone-demo/localhost.key +28 -0
- package/html-softphone-demo/pcm-player.js +131 -0
- package/html-softphone-demo/private.key +28 -0
- package/html-softphone-demo/softphone.umd.min.js +2 -0
- package/html-softphone-demo/tapd_35238004_base64_1695010473_859.gif +0 -0
- package/html-softphone-demo/test.html +190 -0
- package/html-softphone-demo.7z +0 -0
- package/lib/index.d.ts +531 -0
- package/lib/softphone.cjs.min.cjs +1 -0
- package/lib/softphone.esm-bundler.min.mjs +1 -0
- package/lib/softphone.umd.min.js +2 -0
- package/package.json +88 -0
- package/src/const/index.ts +120 -0
- package/src/index.ts +123 -0
- package/src/sdk/index.ts +1986 -0
- package/src/types/index.ts +343 -0
- package/src/utils/index.ts +301 -0
package/README.md
ADDED
|
@@ -0,0 +1,941 @@
|
|
|
1
|
+
# 前言
|
|
2
|
+
|
|
3
|
+
94ai软电话前端SDK,框架无关(支持vue,react,angular等)
|
|
4
|
+
|
|
5
|
+
项目参考demo: https://gitee.com/softphone-demo(有权限问题,具体找94智能开发同事拉入团队组织即可)
|
|
6
|
+
|
|
7
|
+
本地调试:
|
|
8
|
+
yarn add sip.js@portal:../SIP修改v2.js
|
|
9
|
+
|
|
10
|
+
# 安装
|
|
11
|
+
94智能sdk目前暂不在公网公布sdk源码,如有需要,可以联系94智能开发同事加入团队,具体步骤:
|
|
12
|
+
|
|
13
|
+
## 获取加入团队邀请链接
|
|
14
|
+
1. 向94智能开发同事申请到【加入到团队的连接】,如:
|
|
15
|
+
https://account-devops.aliyun.com/account/invite?sign=ab965d478dbed5dad3cc145a5a5b406c&next_url=https%3A%2F%2Fpackages.aliyun.com%3ForgId%3D644f755a97d94d909e43534c
|
|
16
|
+
|
|
17
|
+
2. 进入上述链接后,进到阿里云登录页
|
|
18
|
+
|
|
19
|
+
- 如果你是主账号,直接登录即可:
|
|
20
|
+
|
|
21
|
+

|
|
22
|
+
|
|
23
|
+
- 如果你是RAM子账号,选择下面RAM用户登录:
|
|
24
|
+
|
|
25
|
+

|
|
26
|
+
|
|
27
|
+
进入RAM 用户登录页登录即可:
|
|
28
|
+
|
|
29
|
+

|
|
30
|
+
|
|
31
|
+
## 阿里的数字安全码
|
|
32
|
+
|
|
33
|
+
登录过程会提示你填写 验证虚拟MFA设备数字安全码
|
|
34
|
+
|
|
35
|
+
### 1.如果以前使用过阿里云产品并绑定过
|
|
36
|
+
可以直接在【阿里云app】或使用【虚拟MFA验证小程序】获取【数字安全码】填写即可,如下:
|
|
37
|
+
- 下载阿里云app
|
|
38
|
+
|
|
39
|
+

|
|
40
|
+
|
|
41
|
+
- 定位首页的mfa图标
|
|
42
|
+
|
|
43
|
+

|
|
44
|
+
|
|
45
|
+
- 找到对应账号的数字安全码
|
|
46
|
+
|
|
47
|
+

|
|
48
|
+
|
|
49
|
+
### 2.如果以前没有绑定过
|
|
50
|
+
- 如下面选择 虚拟 MFA 设备
|
|
51
|
+
|
|
52
|
+

|
|
53
|
+
|
|
54
|
+
- 然后在手机安装【阿里云app】或使用【小程序虚拟MFA验证】,然后扫码绑定设备
|
|
55
|
+
|
|
56
|
+

|
|
57
|
+
|
|
58
|
+
- 在设备添加账号点确定
|
|
59
|
+
|
|
60
|
+

|
|
61
|
+
|
|
62
|
+
- 之后会每隔一小段时间会刷新获取到最新的【数字安全码】
|
|
63
|
+
|
|
64
|
+

|
|
65
|
+
|
|
66
|
+
### 3.如果使用小程序
|
|
67
|
+
|
|
68
|
+
在微信搜索mfa二次验证如下,具体可以百度下:
|
|
69
|
+
|
|
70
|
+

|
|
71
|
+
|
|
72
|
+
### 4.设置昵称申请加入
|
|
73
|
+
|
|
74
|
+
登录成功后会提示你加入团队,设置号昵称点加入
|
|
75
|
+
|
|
76
|
+

|
|
77
|
+
|
|
78
|
+

|
|
79
|
+
|
|
80
|
+
## 查看个人账号信息
|
|
81
|
+
|
|
82
|
+
等待审核通过后,刷新页面,点击进入企业
|
|
83
|
+
|
|
84
|
+

|
|
85
|
+
|
|
86
|
+
### 1.选择角色
|
|
87
|
+
|
|
88
|
+
选择作为研发者,开始工作
|
|
89
|
+
|
|
90
|
+

|
|
91
|
+
|
|
92
|
+
### 2.进入制品仓库
|
|
93
|
+
在工作台选择【制品仓库】进入
|
|
94
|
+
|
|
95
|
+

|
|
96
|
+
|
|
97
|
+
### 3.查看账号密码
|
|
98
|
+
|
|
99
|
+
选择设置查看个人账号信息
|
|
100
|
+
|
|
101
|
+

|
|
102
|
+
|
|
103
|
+

|
|
104
|
+
|
|
105
|
+
## 设置私库源
|
|
106
|
+
|
|
107
|
+
### 1.新建rc文件
|
|
108
|
+
|
|
109
|
+
在需要使用94智能sdk的项目根目录,新建一个rc文件:
|
|
110
|
+
|
|
111
|
+
- 如果你使用的npm或pnpm,新建.npmrc,内容如下:
|
|
112
|
+
```hell
|
|
113
|
+
@94ai:registry=https://packages.aliyun.com/644f755a97d94d909e43534c/npm/npm-registry/
|
|
114
|
+
//packages.aliyun.com/644f755a97d94d909e43534c/npm/npm-registry/:username=username
|
|
115
|
+
//packages.aliyun.com/644f755a97d94d909e43534c/npm/npm-registry/:_password=password
|
|
116
|
+
//packages.aliyun.com/644f755a97d94d909e43534c/npm/npm-registry/:always-auth=true
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
- 如果你使用的yarn1,新建.yarnrc,内容如下:
|
|
120
|
+
```shell
|
|
121
|
+
"@94ai:registry" "https://packages.aliyun.com/644f755a97d94d909e43534c/npm/npm-registry/"
|
|
122
|
+
"//packages.aliyun.com/644f755a97d94d909e43534c/npm/npm-registry/:username" "username"
|
|
123
|
+
"//packages.aliyun.com/644f755a97d94d909e43534c/npm/npm-registry/:_password" "password"
|
|
124
|
+
"//packages.aliyun.com/644f755a97d94d909e43534c/npm/npm-registry/:always-auth" true
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
- 如果你使用的yarn2~3,新建.yarnrc.yml,内容如下:
|
|
128
|
+
```shell
|
|
129
|
+
enableImmutableInstalls: false
|
|
130
|
+
nodeLinker: node-modules
|
|
131
|
+
yarnPath: .yarn/releases/yarn-3.1.1.cjs
|
|
132
|
+
#npmRegistryServer: "https://nexusdev.k8s.94ai.pro/repository/npm-group/"
|
|
133
|
+
npmScopes:
|
|
134
|
+
94ai:
|
|
135
|
+
npmRegistryServer: "https://packages.aliyun.com/644f755a97d94d909e43534c/npm/npm-registry/"
|
|
136
|
+
npmAlwaysAuth: true
|
|
137
|
+
npmAuthIdent: "username:password"
|
|
138
|
+
#unsafeHttpWhitelist:
|
|
139
|
+
# - 192.168.20.9
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### 2.配置个人账号信息
|
|
143
|
+
|
|
144
|
+
1. 把对应rc文件`username`替换成你的账号名称
|
|
145
|
+
|
|
146
|
+
2. 如果你使用的是yarn1,npm或pnpm,则把你的账号密码做base64加密之后替换掉对应rc文件`password`,如在这里[在线base64加密](https://stackoverflow.org.cn/base64/)获取base64加密的密码
|
|
147
|
+
|
|
148
|
+

|
|
149
|
+
|
|
150
|
+
3. 如果你使用的是yarn2~3,则把你的账号密码直接替换掉对应rc文件`password`
|
|
151
|
+
|
|
152
|
+
### 3.安装依赖
|
|
153
|
+
|
|
154
|
+
之后该项目只要是@94ai域的私包都会经过94智能私服抓取
|
|
155
|
+
|
|
156
|
+
其他包【包括@94ai域私包的子包(非@94ai域)】都会经由个人电脑配置的npm包registry源抓取
|
|
157
|
+
|
|
158
|
+
执行install命令即可成功安装@94ai域的所有私包,包括sdk,如
|
|
159
|
+
|
|
160
|
+
```shell
|
|
161
|
+
$ yarn add @94ai/softphone
|
|
162
|
+
# or
|
|
163
|
+
$ npm i @94ai/softphone
|
|
164
|
+
# or
|
|
165
|
+
$ pnpm add @94ai/softphone
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
# 使用
|
|
169
|
+
## 初始化
|
|
170
|
+
### 1.作为NPM包使用
|
|
171
|
+
```ts
|
|
172
|
+
// Example:
|
|
173
|
+
// 1.softphone.ts
|
|
174
|
+
import { UserAgent, UserAgentManager, URI } from '@94ai/softphone'
|
|
175
|
+
import FingerprintJS from '@fingerprintjs/fingerprintjs'
|
|
176
|
+
import useToast from '@/utils/useToast'
|
|
177
|
+
|
|
178
|
+
export function createRandomToken(size, base = 32) {
|
|
179
|
+
let token = "";
|
|
180
|
+
for (let i = 0; i < size; i++) {
|
|
181
|
+
const r = Math.floor(Math.random() * base);
|
|
182
|
+
token += r.toString(base);
|
|
183
|
+
}
|
|
184
|
+
return token;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// transport层配置
|
|
188
|
+
export interface TransportOptions {
|
|
189
|
+
/** websocket协商地址, server和wsServers 必须二选一*/
|
|
190
|
+
server?: string
|
|
191
|
+
/** 多个地址开启负载均衡模式 */
|
|
192
|
+
wsServers?: string | string[] | {
|
|
193
|
+
/** websocket协商地址 */
|
|
194
|
+
ws_uri: string,
|
|
195
|
+
/** 权重 */
|
|
196
|
+
weight: number
|
|
197
|
+
}[]
|
|
198
|
+
/**
|
|
199
|
+
* websocke初始化连接等待超时时间
|
|
200
|
+
* @default 5
|
|
201
|
+
*/
|
|
202
|
+
connectionTimeout?: number
|
|
203
|
+
/**
|
|
204
|
+
* transport层客户端保活最大重连尝试次数
|
|
205
|
+
* @default 3
|
|
206
|
+
*/
|
|
207
|
+
maxReconnectionAttempts?: number
|
|
208
|
+
/**
|
|
209
|
+
* transport层客户端保活重连动作执行间隔时间,单位秒,同UserAgentOptions.reconnectionDelay
|
|
210
|
+
* @default 4
|
|
211
|
+
*/
|
|
212
|
+
reconnectionTimeout?: number
|
|
213
|
+
/**
|
|
214
|
+
* transport层The time (Number) in seconds to wait in between CLRF keepAlive sequences are sent.
|
|
215
|
+
* @default 0
|
|
216
|
+
*/
|
|
217
|
+
keepAliveInterval?: number
|
|
218
|
+
/**
|
|
219
|
+
* transport层The time (Number) in seconds to debounce sending CLRF keepAlive sequences by
|
|
220
|
+
* @default 10
|
|
221
|
+
*/
|
|
222
|
+
keepAliveDebounce?: number
|
|
223
|
+
/**
|
|
224
|
+
* transport层If true, messages sent and received by the transport are logged.
|
|
225
|
+
* @default false
|
|
226
|
+
*/
|
|
227
|
+
traceSip?: boolean
|
|
228
|
+
}
|
|
229
|
+
// sip层配置
|
|
230
|
+
export interface UserAgentOptions {
|
|
231
|
+
/**
|
|
232
|
+
* 通过openApi获取坐席账号分机密码
|
|
233
|
+
* @default ''
|
|
234
|
+
*/
|
|
235
|
+
authorizationPassword: string,
|
|
236
|
+
/**
|
|
237
|
+
* 通过openApi获取坐席账号分机用户名
|
|
238
|
+
* @default ''
|
|
239
|
+
*/
|
|
240
|
+
authorizationUsername: string,
|
|
241
|
+
/**
|
|
242
|
+
* 指纹,唯一标志,用来排查线路故障,默认随机指纹,可选
|
|
243
|
+
* @default createRandomToken(12) + ".invalid"
|
|
244
|
+
*/
|
|
245
|
+
viaHost?: string,
|
|
246
|
+
/**
|
|
247
|
+
* sip服务地址,必填,需要服务可达的地址
|
|
248
|
+
* @default new URI("sip", "anonymous." + createRandomToken(6), "anonymous.invalid") })
|
|
249
|
+
*/
|
|
250
|
+
uri: URI,
|
|
251
|
+
/**
|
|
252
|
+
* sip日志查看等级,一般情况下生产开error,开发用debugger
|
|
253
|
+
* @default 'log'
|
|
254
|
+
*/
|
|
255
|
+
logLevel?: 'debug' | 'log' | 'warn' | 'error',
|
|
256
|
+
/**
|
|
257
|
+
* 一般设置同authorizationUsername,相当于MicroSIP的显示名称
|
|
258
|
+
* @default createRandomToken(8)
|
|
259
|
+
*/
|
|
260
|
+
contactName?: string
|
|
261
|
+
/**
|
|
262
|
+
* 签入来电后多长时间不执行接听会话自动结束会话,单位秒
|
|
263
|
+
* @default 60
|
|
264
|
+
*/
|
|
265
|
+
noAnswerTimeout?: number,
|
|
266
|
+
/**
|
|
267
|
+
* transport层协议配置
|
|
268
|
+
*/
|
|
269
|
+
transportOptions: TransportOptions,
|
|
270
|
+
/**
|
|
271
|
+
* sip层 - 重连尝试间隔,为了兼容sipjs,同reconnectionInterval
|
|
272
|
+
* @default 3
|
|
273
|
+
*/
|
|
274
|
+
reconnectionDelay?: number,
|
|
275
|
+
/**
|
|
276
|
+
* sip层 - 重连尝试间隔,单位秒
|
|
277
|
+
* @default 3
|
|
278
|
+
*/
|
|
279
|
+
reconnectionInterval?: number,
|
|
280
|
+
/**
|
|
281
|
+
* sip层 - 重连失败最大尝试次数
|
|
282
|
+
* @default 100
|
|
283
|
+
*/
|
|
284
|
+
reconnectionAttempts?: number,
|
|
285
|
+
/**
|
|
286
|
+
* sip层 - 重连成功后尝试重新注册检间隔,单位秒
|
|
287
|
+
* @default 3
|
|
288
|
+
*/
|
|
289
|
+
registerInterval?: number,
|
|
290
|
+
/**
|
|
291
|
+
* sip层 - 重连成功最大尝试注册次数
|
|
292
|
+
* @default 3
|
|
293
|
+
*/
|
|
294
|
+
registerAttempts?: number
|
|
295
|
+
/**
|
|
296
|
+
* sip层 - ping动作间隔,单位秒
|
|
297
|
+
* @default 8
|
|
298
|
+
*/
|
|
299
|
+
optionsPingInterval?: number
|
|
300
|
+
/**
|
|
301
|
+
* sip层 - ping最大失败尝试次数后开始重连,防止网络抖动引起非必要重连
|
|
302
|
+
* @default 3
|
|
303
|
+
*/
|
|
304
|
+
optionsPingAttempts?: number
|
|
305
|
+
/**
|
|
306
|
+
* sip层 - 自定义通讯header
|
|
307
|
+
*/
|
|
308
|
+
sipHeaders?: Array<string>;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
export class SoftphoneManager {
|
|
312
|
+
static #getDeviceId: () => Promise<string>
|
|
313
|
+
#userAgentManager: InstanceType<typeof UserAgentManager>
|
|
314
|
+
|
|
315
|
+
static {
|
|
316
|
+
/**
|
|
317
|
+
* 获取指纹
|
|
318
|
+
*/
|
|
319
|
+
SoftphoneManager.#getDeviceId = (() => {
|
|
320
|
+
let visitorId = ''
|
|
321
|
+
return async (): Promise<string> => {
|
|
322
|
+
if (visitorId) return visitorId
|
|
323
|
+
const fp = await FingerprintJS.load()
|
|
324
|
+
const result = await fp.get()
|
|
325
|
+
visitorId = result.visitorId
|
|
326
|
+
return visitorId
|
|
327
|
+
}
|
|
328
|
+
})()
|
|
329
|
+
}
|
|
330
|
+
public async getUserAgentManager = () => {
|
|
331
|
+
if (!this.#userAgentManager) {
|
|
332
|
+
this.#userAgentManager = UserAgentFactory.getUserAgentManager(await this.initSeatsInfo()) // 👈 得到softphone代理对象
|
|
333
|
+
}
|
|
334
|
+
return this.#userAgentManager
|
|
335
|
+
}
|
|
336
|
+
public getUserAgentManagerSync = (config?: UserAgentOptions) => {
|
|
337
|
+
if (!this.#userAgentManager) {
|
|
338
|
+
this.#userAgentManager = UserAgentFactory.getUserAgentManager(config) // 👈 得到softphone代理对象
|
|
339
|
+
}
|
|
340
|
+
return this.#userAgentManager
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* 通过openApi获取分机信息
|
|
344
|
+
*/
|
|
345
|
+
public async initSeatsInfo = (): Promise<UserAgentOptions> => {
|
|
346
|
+
const result = await getSeatsInfo()
|
|
347
|
+
if (result.code === 200) {
|
|
348
|
+
const {
|
|
349
|
+
extPassword, // 分机密码
|
|
350
|
+
extensionNumber, // 分机号
|
|
351
|
+
wsRegisterAddress, // socket地址
|
|
352
|
+
wsProtocol // socket协议
|
|
353
|
+
} = result.data
|
|
354
|
+
return {
|
|
355
|
+
authorizationPassword: extPassword,
|
|
356
|
+
authorizationUsername: extensionNumber,
|
|
357
|
+
viaHost: `${await SoftphoneManager.#getDeviceId()}.sip`,
|
|
358
|
+
uri: UserAgent.makeURI(`sip:${extensionNumber}@${wsRegisterAddress}`),
|
|
359
|
+
logLevel: 'error',
|
|
360
|
+
transportOptions: {
|
|
361
|
+
server: `${wsProtocol}://${wsRegisterAddress}`
|
|
362
|
+
},
|
|
363
|
+
contactName: extensionNumber
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
useToast.showToast('获取分机信息失败')
|
|
367
|
+
throw new Error('获取分机信息失败')
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* 签入
|
|
371
|
+
*/
|
|
372
|
+
public connect = async () => {
|
|
373
|
+
try {
|
|
374
|
+
await this.getUserAgentManagerSync().prepareUserAgent(
|
|
375
|
+
/** config */
|
|
376
|
+
{
|
|
377
|
+
/** 当软电话状态变化时会实时刷新这个方法 */
|
|
378
|
+
refresh: (path: keyof typeof userAgentStatusDefault, value: boolean) => {
|
|
379
|
+
|
|
380
|
+
}
|
|
381
|
+
},
|
|
382
|
+
/** event */
|
|
383
|
+
{
|
|
384
|
+
/** 当有外呼过来 */
|
|
385
|
+
onInvite: async (invitation: Invitation) => {
|
|
386
|
+
|
|
387
|
+
}
|
|
388
|
+
})
|
|
389
|
+
} catch (e) {
|
|
390
|
+
const errorInfo = e as Error
|
|
391
|
+
if (errorInfo.message === 'sip register fail with code 503' ||
|
|
392
|
+
errorInfo.message.indexOf('WebSocket closed') > -1 && errorInfo.message.indexOf('code: 1006') > -1) {
|
|
393
|
+
useToast.showToast('软电话服务器异常,签入失败')
|
|
394
|
+
} else {
|
|
395
|
+
useToast.showToast(errorInfo.message)
|
|
396
|
+
}
|
|
397
|
+
throw e
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
// ...
|
|
401
|
+
}
|
|
402
|
+
export default new SoftphoneManager()
|
|
403
|
+
|
|
404
|
+
// 2.App.vue
|
|
405
|
+
import commonSoftphone from './softphone.ts'
|
|
406
|
+
onBeforeMount(async () => {
|
|
407
|
+
await commonSoftphone.getUserAgentManager()
|
|
408
|
+
})
|
|
409
|
+
|
|
410
|
+
// 3. softphone.vue
|
|
411
|
+
// <button @click="connect">签入</button>
|
|
412
|
+
import commonSoftphone from './softphone.ts'
|
|
413
|
+
// 签入
|
|
414
|
+
const connect = async () => {
|
|
415
|
+
await commonSoftphone.connect()
|
|
416
|
+
}
|
|
417
|
+
```
|
|
418
|
+
### 2.直接在JavaScript中使用
|
|
419
|
+
1. 获取umd文件
|
|
420
|
+
- 确保安装了node,随手新建一个目录,打开cmd或bash。
|
|
421
|
+
- 设置@4ai域账号授权,依次执行执行如下,其中`:username=xxxx`替换你的账号名字,`:_password=xxxx`替换你的账号密码在做base64加密后的结果
|
|
422
|
+
```shell
|
|
423
|
+
npm config set @94ai:registry=https://packages.aliyun.com/644f755a97d94d909e43534c/npm/npm-registry/
|
|
424
|
+
npm config set //packages.aliyun.com/644f755a97d94d909e43534c/npm/npm-registry/:username=xxx
|
|
425
|
+
npm config set //packages.aliyun.com/644f755a97d94d909e43534c/npm/npm-registry/:_password=xxx
|
|
426
|
+
npm config set //packages.aliyun.com/644f755a97d94d909e43534c/npm/npm-registry/:always-auth=true
|
|
427
|
+
```
|
|
428
|
+
- 最后执行`npx -y @94ai/softphone -- sdk-umd`,之后在当前执行命令路径下即可获取到`softphone.umd.min.js`
|
|
429
|
+
|
|
430
|
+
2. 在index.html引入`softphone.umd.min.js`文件
|
|
431
|
+
```html
|
|
432
|
+
<!DOCTYPE html>
|
|
433
|
+
<html lang="en">
|
|
434
|
+
<head>
|
|
435
|
+
<meta charset="UTF-8" />
|
|
436
|
+
<meta name="viewport"
|
|
437
|
+
content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" />
|
|
438
|
+
<title>softphone</title>
|
|
439
|
+
<style>
|
|
440
|
+
#softphone-app {
|
|
441
|
+
text-align: center;
|
|
442
|
+
padding-top: 50px;
|
|
443
|
+
}
|
|
444
|
+
</style>
|
|
445
|
+
</head>
|
|
446
|
+
<body>
|
|
447
|
+
<div id="softphone-app">
|
|
448
|
+
<button id="connectToServer">连接服务器</button>
|
|
449
|
+
<br>
|
|
450
|
+
<br>
|
|
451
|
+
<button id="disconnect" disabled>断开连接服务器</button>
|
|
452
|
+
<br>
|
|
453
|
+
<br>
|
|
454
|
+
<button id="ignore" disabled>忽略</button>
|
|
455
|
+
<br>
|
|
456
|
+
<br>
|
|
457
|
+
<button id="answer" disabled>接听</button>
|
|
458
|
+
<br>
|
|
459
|
+
<br>
|
|
460
|
+
<button id="hangUp" disabled>挂断</button>
|
|
461
|
+
<br>
|
|
462
|
+
<br>
|
|
463
|
+
<button id="press" disabled>按*转人工</button>
|
|
464
|
+
<br>
|
|
465
|
+
<br>
|
|
466
|
+
<button id="localVoiceAccess" disabled>本地声音接入</button>
|
|
467
|
+
<br>
|
|
468
|
+
<br>
|
|
469
|
+
<button id="remoteSoundAccess" disabled>远程声音接入</button>
|
|
470
|
+
<br>
|
|
471
|
+
<br>
|
|
472
|
+
<button id="localSoundDisconnection" disabled>本地声音断开</button>
|
|
473
|
+
<br>
|
|
474
|
+
<br>
|
|
475
|
+
<button id="remoteSoundDisconnection" disabled>远程声音断开</button>
|
|
476
|
+
<br>
|
|
477
|
+
<br>
|
|
478
|
+
<audio
|
|
479
|
+
id="remoteAudio"
|
|
480
|
+
controls
|
|
481
|
+
style="display:none"
|
|
482
|
+
>
|
|
483
|
+
<p>Your browser doesn't support HTML5 audio - remoteAudio</p>
|
|
484
|
+
</audio>
|
|
485
|
+
<audio
|
|
486
|
+
id="localAudio"
|
|
487
|
+
style="display:none"
|
|
488
|
+
src="./antique_phone.mp3"
|
|
489
|
+
controls
|
|
490
|
+
loop
|
|
491
|
+
>
|
|
492
|
+
<p>Your browser doesn't support HTML5 audio - localAudio</p>
|
|
493
|
+
</audio>
|
|
494
|
+
</div>
|
|
495
|
+
<script src="../lib/softphone.umd.min.js"></script>
|
|
496
|
+
<script src="./fp.umd.min.js"></script>
|
|
497
|
+
<script>
|
|
498
|
+
/**
|
|
499
|
+
* 1.确保你的企业 【坐席外呼依赖】配置项 是【标识】 (默认是【登录】,如果不确定联系94运维在管理后台查看以及配置)
|
|
500
|
+
* 2. 使用 【https://94ai.yuque.com/staff-kqoz0c/xed39g/obnqst?singleDoc# 《SG-九四智能API开放文档(全)》 密码:tvgm】 3.1 获取坐席信息,以及3.2 修改坐席状态接口,保证坐席状态在线
|
|
501
|
+
* 3. 点击连接服务器
|
|
502
|
+
* 4. 在决策外呼一个任务,确保你的坐席账号在任务配置的坐席组里
|
|
503
|
+
* 5. 电话过来后会触发签入注册好的onInvite,这个时候就可以接听,挂断,忽略,转人工,静音等等
|
|
504
|
+
* 6. 《SG-九四智能API开放文档(全)》该文档获取授权调用接口的问题找 航航同学
|
|
505
|
+
*/
|
|
506
|
+
|
|
507
|
+
const getDeviceId = (() => {
|
|
508
|
+
let visitorId = ''
|
|
509
|
+
return async () => {
|
|
510
|
+
if (visitorId) return visitorId
|
|
511
|
+
const fp = await FingerprintJS.load()
|
|
512
|
+
const result = await fp.get()
|
|
513
|
+
visitorId = result.visitorId
|
|
514
|
+
return visitorId
|
|
515
|
+
}
|
|
516
|
+
})()
|
|
517
|
+
document.addEventListener('DOMContentLoaded', async (event) => {
|
|
518
|
+
const phoneRing = './antique_phone.mp3' // 👈 铃声自己当以一个音频即可
|
|
519
|
+
const ignore = document.getElementById('ignore')
|
|
520
|
+
const answer = document.getElementById('answer')
|
|
521
|
+
const hangUp = document.getElementById('hangUp')
|
|
522
|
+
const connectToServer = document.getElementById('connectToServer')
|
|
523
|
+
const disconnect = document.getElementById('disconnect')
|
|
524
|
+
const press = document.getElementById('press')
|
|
525
|
+
const localVoiceAccess = document.getElementById('localVoiceAccess')
|
|
526
|
+
const remoteSoundAccess = document.getElementById('remoteSoundAccess')
|
|
527
|
+
const localSoundDisconnection = document.getElementById('localSoundDisconnection')
|
|
528
|
+
const remoteSoundDisconnection = document.getElementById('remoteSoundDisconnection')
|
|
529
|
+
const localAudio = document.getElementById('localAudio')
|
|
530
|
+
const remoteAudio = document.getElementById('remoteAudio')
|
|
531
|
+
const viaHost = `${await getDeviceId()}.sip`
|
|
532
|
+
/** 接口获取开始 */
|
|
533
|
+
/** 通过接口获取 https://94ai.yuque.com/staff-kqoz0c/xed39g/obnqst?singleDoc# 《SG-九四智能API开放文档(全)》 密码:tvgm 👈 不知道怎么调接口找 航航同学 */
|
|
534
|
+
/** 3.1坐席相关接口查询坐席信息 */
|
|
535
|
+
const extensionNumber = '9288'
|
|
536
|
+
const extPassword = 'zjh13542240708'
|
|
537
|
+
const wsProtocol = 'wss'
|
|
538
|
+
const wsRegisterAddress = 's28.94ai.com:7443'
|
|
539
|
+
const sipServerHost = `sip:${extensionNumber}@${wsRegisterAddress}`
|
|
540
|
+
const wsAddress = `${wsProtocol}://${wsRegisterAddress}`
|
|
541
|
+
/** 3.2修改坐席账号状态 */
|
|
542
|
+
const request = {
|
|
543
|
+
post(url) {
|
|
544
|
+
console.log('写你的调用的openapi改坐席状态,要写好它')
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
const editSeats = async (params) => {
|
|
548
|
+
return request.post('/seats/editLineStatus')
|
|
549
|
+
}
|
|
550
|
+
await editSeats({ lineStatus: 1 })
|
|
551
|
+
/** 接口获取结束 */
|
|
552
|
+
|
|
553
|
+
const {
|
|
554
|
+
UserAgentFactory,
|
|
555
|
+
UserAgent,
|
|
556
|
+
playMedia,
|
|
557
|
+
pauseMedia,
|
|
558
|
+
SessionState,
|
|
559
|
+
getMedia
|
|
560
|
+
} = softphone // 👈 sdk
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* 开启电话响铃
|
|
564
|
+
*/
|
|
565
|
+
const phoneRings = () => {
|
|
566
|
+
remoteAudio.src = phoneRing
|
|
567
|
+
playMedia('localAudio')
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* 关闭电话响铃
|
|
572
|
+
*/
|
|
573
|
+
const stopPhoneRings = () => {
|
|
574
|
+
remoteAudio.src = ''
|
|
575
|
+
pauseMedia('localAudio')
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/**
|
|
579
|
+
* 获取软电话代理实例
|
|
580
|
+
*/
|
|
581
|
+
const userAgentManager = UserAgentFactory.getUserAgentManager({
|
|
582
|
+
authorizationPassword: extPassword,
|
|
583
|
+
authorizationUsername: extensionNumber,
|
|
584
|
+
viaHost,
|
|
585
|
+
uri: UserAgent.makeURI(sipServerHost),
|
|
586
|
+
logLevel: 'error',
|
|
587
|
+
transportOptions: {
|
|
588
|
+
server: wsAddress
|
|
589
|
+
},
|
|
590
|
+
contactName: extensionNumber
|
|
591
|
+
})
|
|
592
|
+
|
|
593
|
+
/**
|
|
594
|
+
* 签入
|
|
595
|
+
*/
|
|
596
|
+
const connect = () => {
|
|
597
|
+
userAgentManager.prepareUserAgent(
|
|
598
|
+
{ // config
|
|
599
|
+
refresh (path, value) { // 当软电话状态变化时会实时刷新这个方法
|
|
600
|
+
console.log(path, value)
|
|
601
|
+
}
|
|
602
|
+
},
|
|
603
|
+
{ // event
|
|
604
|
+
onInvite (invitation) { // 当有外呼过来
|
|
605
|
+
syncSipMessage(invitation.incomingInviteRequest.message.headers)
|
|
606
|
+
phoneRings() // 播放模拟来电响铃
|
|
607
|
+
ignore.disabled = false
|
|
608
|
+
answer.disabled = false
|
|
609
|
+
localSoundDisconnection.disabled = false
|
|
610
|
+
remoteSoundDisconnection.disabled = false
|
|
611
|
+
invitation.stateChange.addListener((state) => { // 一旦接听(执行accept),监听会话的生命周期
|
|
612
|
+
switch (state) {
|
|
613
|
+
case SessionState.Initial:
|
|
614
|
+
break
|
|
615
|
+
case SessionState.Establishing:
|
|
616
|
+
break
|
|
617
|
+
case SessionState.Established: // session建立后就可拿到webrtc的各种基础api,如userAgentManager.getPeerConnection(),如userAgentManager.getSenders()等等
|
|
618
|
+
const mediaElement1 = getMedia('remoteAudio')
|
|
619
|
+
mediaElement1.srcObject = userAgentManager.getStream() // 获取流
|
|
620
|
+
mediaElement1.play() // 把softphone流导入到audio接入用户语音
|
|
621
|
+
hangUp.disabled = false
|
|
622
|
+
break
|
|
623
|
+
case SessionState.Terminating:
|
|
624
|
+
case SessionState.Terminated: // 在挂断电话时候会执行
|
|
625
|
+
ignore.disabled = true
|
|
626
|
+
answer.disabled = true
|
|
627
|
+
hangUp.disabled = true
|
|
628
|
+
localSoundDisconnection.disabled = true
|
|
629
|
+
remoteSoundDisconnection.disabled = true
|
|
630
|
+
localVoiceAccess.disabled = true
|
|
631
|
+
remoteSoundAccess.disabled = true
|
|
632
|
+
const mediaElement2 = getMedia('remoteAudio') // 获取audio dom
|
|
633
|
+
mediaElement2.srcObject = null
|
|
634
|
+
mediaElement2.pause() // 释放audio
|
|
635
|
+
stopPhoneRings()
|
|
636
|
+
break
|
|
637
|
+
default:
|
|
638
|
+
throw new Error('Unknown session state.')
|
|
639
|
+
}
|
|
640
|
+
})
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
)
|
|
644
|
+
disconnect.disabled = false
|
|
645
|
+
connectToServer.disabled = true
|
|
646
|
+
ignore.disabled = true
|
|
647
|
+
answer.disabled = true
|
|
648
|
+
hangUp.disabled = true
|
|
649
|
+
localSoundDisconnection.disabled = true
|
|
650
|
+
remoteSoundDisconnection.disabled = true
|
|
651
|
+
localVoiceAccess.disabled = true
|
|
652
|
+
remoteSoundAccess.disabled = true
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
/**
|
|
656
|
+
* 签出
|
|
657
|
+
*/
|
|
658
|
+
const disConnect = () => {
|
|
659
|
+
userAgentManager.dispose() // 👈 一个方法安全销毁
|
|
660
|
+
connectToServer.disabled = false
|
|
661
|
+
disconnect.disabled = true
|
|
662
|
+
ignore.disabled = true
|
|
663
|
+
answer.disabled = true
|
|
664
|
+
hangUp.disabled = true
|
|
665
|
+
localSoundDisconnection.disabled = true
|
|
666
|
+
remoteSoundDisconnection.disabled = true
|
|
667
|
+
localVoiceAccess.disabled = true
|
|
668
|
+
remoteSoundAccess.disabled = true
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
/**
|
|
672
|
+
* 先监听后接听
|
|
673
|
+
*/
|
|
674
|
+
let monitorFirstAnswerAfter = true // 先监听任务需要 按*号转人工 才可以让用户听到坐席声音
|
|
675
|
+
/**
|
|
676
|
+
* sip协议通讯
|
|
677
|
+
*/
|
|
678
|
+
const syncSipMessage = (headers) => {
|
|
679
|
+
monitorFirstAnswerAfter = !(String(headers['X-Aftertransferlabour'] && headers['X-Aftertransferlabour'][0].raw) === '1')
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
/**
|
|
683
|
+
* 按*号转人工
|
|
684
|
+
*/
|
|
685
|
+
|
|
686
|
+
const sendStarDtmf = () => {
|
|
687
|
+
if (monitorFirstAnswerAfter) {
|
|
688
|
+
userAgentManager.sendStarDtmf()
|
|
689
|
+
press.disabled = true
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
/**
|
|
694
|
+
* 挂断
|
|
695
|
+
*/
|
|
696
|
+
const hangUpInvite = () => {
|
|
697
|
+
stopPhoneRings() // 暂停来电铃声
|
|
698
|
+
userAgentManager.hangUpInvite() // 👈
|
|
699
|
+
ignore.disabled = true
|
|
700
|
+
answer.disabled = true
|
|
701
|
+
hangUp.disabled = true
|
|
702
|
+
localSoundDisconnection.disabled = true
|
|
703
|
+
remoteSoundDisconnection.disabled = true
|
|
704
|
+
localVoiceAccess.disabled = true
|
|
705
|
+
remoteSoundAccess.disabled = true
|
|
706
|
+
}
|
|
707
|
+
/**
|
|
708
|
+
* 忽略
|
|
709
|
+
*/
|
|
710
|
+
const ignoreInvite = () => {
|
|
711
|
+
stopPhoneRings() // 暂停来电铃声
|
|
712
|
+
userAgentManager.ignoreInvite()
|
|
713
|
+
ignore.disabled = true
|
|
714
|
+
answer.disabled = true
|
|
715
|
+
hangUp.disabled = true
|
|
716
|
+
localSoundDisconnection.disabled = true
|
|
717
|
+
remoteSoundDisconnection.disabled = true
|
|
718
|
+
localVoiceAccess.disabled = true
|
|
719
|
+
remoteSoundAccess.disabled = true
|
|
720
|
+
}
|
|
721
|
+
/**
|
|
722
|
+
* 接听
|
|
723
|
+
*/
|
|
724
|
+
const acceptInvite = () => {
|
|
725
|
+
stopPhoneRings() // 暂停来电铃声
|
|
726
|
+
userAgentManager.acceptInvite()
|
|
727
|
+
if (monitorFirstAnswerAfter) {
|
|
728
|
+
press.disabled = false
|
|
729
|
+
}
|
|
730
|
+
hangUp.disabled = false
|
|
731
|
+
answer.disabled = true
|
|
732
|
+
ignore.disabled = true
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
/**
|
|
736
|
+
* 传输本地声音到远端
|
|
737
|
+
*/
|
|
738
|
+
const unMuteLocalAudio = () => {
|
|
739
|
+
localVoiceAccess.disabled = true
|
|
740
|
+
localSoundDisconnection.disabled = false
|
|
741
|
+
userAgentManager.unMuteLocalAudio()
|
|
742
|
+
}
|
|
743
|
+
/**
|
|
744
|
+
* 不传输本地声音到远端
|
|
745
|
+
*/
|
|
746
|
+
const muteLocalAudio = () => {
|
|
747
|
+
remoteSoundAccess.disabled = true
|
|
748
|
+
remoteSoundDisconnection.disabled = false
|
|
749
|
+
userAgentManager.muteLocalAudio()
|
|
750
|
+
}
|
|
751
|
+
/**
|
|
752
|
+
* 接听远端声音
|
|
753
|
+
*/
|
|
754
|
+
const unMuteRemoteAudio = () => {
|
|
755
|
+
localSoundDisconnection.disabled = true
|
|
756
|
+
localVoiceAccess.disabled = false
|
|
757
|
+
userAgentManager.unMuteRemoteAudio()
|
|
758
|
+
}
|
|
759
|
+
/**
|
|
760
|
+
* 不接听远端声音
|
|
761
|
+
*/
|
|
762
|
+
const muteRemoteAudio = () => {
|
|
763
|
+
remoteSoundDisconnection.disabled = true
|
|
764
|
+
remoteSoundAccess.disabled = false
|
|
765
|
+
userAgentManager.muteRemoteAudio()
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
/** 注册事件开始 */
|
|
769
|
+
ignore.addEventListener('click', ignoreInvite)
|
|
770
|
+
answer.addEventListener('click', acceptInvite)
|
|
771
|
+
hangUp.addEventListener('click', hangUpInvite)
|
|
772
|
+
connectToServer.addEventListener('click', connect)
|
|
773
|
+
disconnect.addEventListener('click', disConnect)
|
|
774
|
+
press.addEventListener('click', sendStarDtmf)
|
|
775
|
+
localVoiceAccess.addEventListener('click', unMuteLocalAudio)
|
|
776
|
+
remoteSoundAccess.addEventListener('click', muteLocalAudio)
|
|
777
|
+
localSoundDisconnection.addEventListener('click', unMuteRemoteAudio)
|
|
778
|
+
remoteSoundDisconnection.addEventListener('click', muteRemoteAudio)
|
|
779
|
+
/** 注册事件结束 */
|
|
780
|
+
})
|
|
781
|
+
|
|
782
|
+
|
|
783
|
+
</script>
|
|
784
|
+
</body>
|
|
785
|
+
</html>
|
|
786
|
+
```
|
|
787
|
+
[](https://cdn.nlark.com/yuque/0/2023/gif/548727/1684390483858-5531b7e1-8f1d-4319-b18f-dcc123dceae6.gif)
|
|
788
|
+
|
|
789
|
+
> 1. tip 获取fingerprintjs: https://unpkg.com/@fingerprintjs/fingerprintjs@3.4.1/dist/fp.umd.min.js
|
|
790
|
+
> 2. const phoneRing = './antique_phone.mp3' // 👈 铃声自己当以一个音频即可
|
|
791
|
+
> 3. 直接双击打开的html的demo打电话这个模拟不了线上域名部署环境可能出现的问题,可以实现看各种接听挂断签入转人工等效果,file协议和localhost默认在浏览器安全域名内。注意线上环境浏览器是有限制要https协议的,http是使用不了浏览器webrtc的api的,要配置下浏览器安全上下文域名白名单
|
|
792
|
+
|
|
793
|
+
## 签入
|
|
794
|
+
```js
|
|
795
|
+
import { playMedia, getMedia, SessionState } from '@94ai/softphone'
|
|
796
|
+
import { ref } from 'vue'
|
|
797
|
+
import popNotice from './usePopNotice.ts' // 通知工具,见下面demo说明
|
|
798
|
+
|
|
799
|
+
const userAgentStatus = ref({ // 视图层响应式
|
|
800
|
+
connectStatus: false, // 软电话是否已签入
|
|
801
|
+
registerStatus: false, // 软电话是否已注册
|
|
802
|
+
invitatingStatus: false, // 软电话是否正拨出
|
|
803
|
+
incomingStatus: false, // 软电话是否正来电
|
|
804
|
+
answerStatus: false, // 软电话是否正接听
|
|
805
|
+
reconnectStatus: false, // 软电话是否正重连
|
|
806
|
+
})
|
|
807
|
+
|
|
808
|
+
userAgentManager.prepareUserAgent(
|
|
809
|
+
{ // config
|
|
810
|
+
refresh(path, value) { // 当软电话状态变化时会实时刷新这个方法
|
|
811
|
+
userAgentStatus[path] = value // 外部想要响应状态可以实时更新外部的userAgentStatus
|
|
812
|
+
}
|
|
813
|
+
},
|
|
814
|
+
{ // event
|
|
815
|
+
onInvite(invitation) { // 当有外呼过来
|
|
816
|
+
playMedia('localAudio') // 播放模拟来电响铃
|
|
817
|
+
popNotice.popNotification('来电了') // 提示通知 来电了
|
|
818
|
+
invitation.stateChange.addListener((state) => { // 一旦接听(执行accept),监听会话的生命周期
|
|
819
|
+
switch (state) {
|
|
820
|
+
case SessionState.Initial:
|
|
821
|
+
break
|
|
822
|
+
case SessionState.Establishing:
|
|
823
|
+
break
|
|
824
|
+
case SessionState.Established: // session建立后就可拿到webrtc的各种基础api,如userAgentManager.getPeerConnection(),如userAgentManager.getSenders()等等
|
|
825
|
+
const mediaElement1 = getMedia('remoteAudio')
|
|
826
|
+
mediaElement1.srcObject = userAgentManager.getStream() // 获取流
|
|
827
|
+
mediaElement1.play() // 把softphone流导入到audio接入用户语音
|
|
828
|
+
break
|
|
829
|
+
case SessionState.Terminating:
|
|
830
|
+
case SessionState.Terminated: // 在挂断电话时候会执行
|
|
831
|
+
const mediaElement2 = getMedia('remoteAudio') // 获取audio dom
|
|
832
|
+
mediaElement2.srcObject = null
|
|
833
|
+
mediaElement2.pause() // 释放audio
|
|
834
|
+
break
|
|
835
|
+
default:
|
|
836
|
+
throw new Error('Unknown session state.')
|
|
837
|
+
}
|
|
838
|
+
})
|
|
839
|
+
}
|
|
840
|
+
})
|
|
841
|
+
```
|
|
842
|
+
## 接听
|
|
843
|
+
```js
|
|
844
|
+
import { refreshShowTime, pauseMedia } from '@94ai/softphone'
|
|
845
|
+
|
|
846
|
+
const acceptInvite = () => {
|
|
847
|
+
refreshShowTime() // 重新计算通话时长(非必须)
|
|
848
|
+
pauseMedia('localAudio') // 暂停来电铃声(非必须)
|
|
849
|
+
userAgentManager.acceptInvite() // 👈 接入电话流
|
|
850
|
+
}
|
|
851
|
+
```
|
|
852
|
+
## 忽略
|
|
853
|
+
```js
|
|
854
|
+
import { pauseMedia } from '@94ai/softphone'
|
|
855
|
+
|
|
856
|
+
const ignoreInvite = () => {
|
|
857
|
+
pauseMedia('localAudio') // 暂停来电铃声(非必须)
|
|
858
|
+
userAgentManager.ignoreInvite() // 👈 忽略
|
|
859
|
+
}
|
|
860
|
+
```
|
|
861
|
+
## 挂断
|
|
862
|
+
```js
|
|
863
|
+
import { refreshShowTime } from '@94ai/softphone'
|
|
864
|
+
|
|
865
|
+
const hangUpInvite = () => {
|
|
866
|
+
refreshShowTime() // 重新计算通话时长(非必须)
|
|
867
|
+
userAgentManager.hangUpInvite() // 👈 挂断
|
|
868
|
+
}
|
|
869
|
+
```
|
|
870
|
+
## 按*号转人工
|
|
871
|
+
```js
|
|
872
|
+
import { refreshShowTime } from '@94ai/softphone'
|
|
873
|
+
|
|
874
|
+
const sendStarDtmf = () => {
|
|
875
|
+
refreshShowTime() // 重新计算通话时长(非必须)
|
|
876
|
+
userAgentManager.sendStarDtmf() // 👈 按*号转人工
|
|
877
|
+
}
|
|
878
|
+
```
|
|
879
|
+
|
|
880
|
+
## 销毁
|
|
881
|
+
```js
|
|
882
|
+
import { refreshShowTime } from '@94ai/softphone'
|
|
883
|
+
|
|
884
|
+
const disconnect = () => {
|
|
885
|
+
refreshShowTime() // 重新计算通话时长(非必须)
|
|
886
|
+
userAgentManager.dispose() // 👈 一个方法安全销毁
|
|
887
|
+
}
|
|
888
|
+
```
|
|
889
|
+
## 传输本地声音到远端
|
|
890
|
+
```js
|
|
891
|
+
const unMuteLocalAudio = () => {
|
|
892
|
+
userAgentManager.unMuteLocalAudio() // 👈 通过webrtc的方式控制 本地音频流 推送到 远端
|
|
893
|
+
}
|
|
894
|
+
```
|
|
895
|
+
## 不传输本地声音到远端
|
|
896
|
+
```js
|
|
897
|
+
const muteLocalAudio = () => {
|
|
898
|
+
userAgentManager.muteLocalAudio() // 👈 通过webrtc的方式控制 本地音频流 禁用推送
|
|
899
|
+
}
|
|
900
|
+
```
|
|
901
|
+
## 接听远端声音
|
|
902
|
+
```js
|
|
903
|
+
const unMuteRemoteAudio = () => {
|
|
904
|
+
userAgentManager.unMuteRemoteAudio() // 👈 通过webrtc的方式控制 是否接受 远端音频流
|
|
905
|
+
}
|
|
906
|
+
```
|
|
907
|
+
## 不接听远端声音
|
|
908
|
+
```js
|
|
909
|
+
const muteRemoteAudio = () => {
|
|
910
|
+
userAgentManager.muteRemoteAudio() // 👈 通过webrtc的方式控制 是否接受 远端音频流
|
|
911
|
+
}
|
|
912
|
+
```
|
|
913
|
+
## 获取webrtc基础api
|
|
914
|
+
```js
|
|
915
|
+
const senders = userAgentManager.getSenders()
|
|
916
|
+
const receivers = userAgentManager.getReceivers()
|
|
917
|
+
const peerConnection = userAgentManager.getPeerConnection()
|
|
918
|
+
```
|
|
919
|
+
## 获取代理用户相关sip协议通讯实例
|
|
920
|
+
```js
|
|
921
|
+
const userAgent = userAgentManager.getUserAgent()
|
|
922
|
+
const sessionDescriptionHandler = userAgentManager.getSessionDescriptionHandler()
|
|
923
|
+
const currentInvitation = userAgentManager.getCurrentInvitation()
|
|
924
|
+
const currentInviter = userAgentManager.getCurrentInviter()
|
|
925
|
+
const localMixedMediaStream = userAgentManager.getStream()
|
|
926
|
+
```
|
|
927
|
+
## 查看代理用户目前状态
|
|
928
|
+
```ts
|
|
929
|
+
interface userAgentStatus {
|
|
930
|
+
connectStatus: boolean, // 软电话是否已签入
|
|
931
|
+
registerStatus: boolean, // 软电话是否已注册
|
|
932
|
+
invitatingStatus: boolean, // 软电话是否正拨出
|
|
933
|
+
incomingStatus: boolean, // 软电话是否正来电
|
|
934
|
+
answerStatus: boolean, // 软电话是否正接听
|
|
935
|
+
reconnectStatus: boolean, // 软电话是否正在重连,网络断掉,服务器重启,宕机等引起服务不可达时软电话代理会尝试重新连接并签入。这个时候用户拨出等动作可以通过此状态做拦截,通知用户软电话服务器可能正重启或断网等导致服务不可用
|
|
936
|
+
}
|
|
937
|
+
const userAgentStatus: userAgentStatus = userAgentManager.getUserAgentStatue()
|
|
938
|
+
```
|
|
939
|
+
|
|
940
|
+
> 软电话状态可以根据企业特定业务流程定制扩展额外的状态,如94智能决策系统 额外扩展了 【监听中】,【整理中】,【小休中】等状态
|
|
941
|
+
|