@94ai/softphone 5.0.7 → 5.0.8
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/html-softphone-demo/index-local.html +7 -0
- package/html-softphone-demo/index-other.html +586 -0
- package/html-softphone-demo/index.html +16 -6
- package/html-softphone-demo/softphone.umd.min.js +1 -2
- package/lib/softphone.cjs.min.cjs +1 -1
- package/lib/softphone.esm-bundler.min.mjs +1 -1
- package/lib/softphone.umd.min.js +1 -1
- package/package.json +1 -1
|
@@ -208,6 +208,13 @@ document.addEventListener('DOMContentLoaded', async (event) => {
|
|
|
208
208
|
const sipServerHost = `sip:${extensionNumber}@${wsRegisterAddress}` // fs地址
|
|
209
209
|
const wsAddress = `${wsProtocol}://${wsRegisterAddress}` // socket地址
|
|
210
210
|
|
|
211
|
+
// const extensionNumber = '2839'
|
|
212
|
+
// const extPassword = 'zjh13542240708'
|
|
213
|
+
// const wsProtocol = 'ws'
|
|
214
|
+
// const wsRegisterAddress = '192.168.31.239:5066'
|
|
215
|
+
// const sipServerHost = `sip:${extensionNumber}@${wsRegisterAddress}`
|
|
216
|
+
// const wsAddress = `${wsProtocol}://${wsRegisterAddress}`
|
|
217
|
+
|
|
211
218
|
const userAgentManager = UserAgentFactory.getUserAgentManager({
|
|
212
219
|
authorizationPassword: extPassword, // 分机密码
|
|
213
220
|
authorizationUsername: extensionNumber, // 分机账号
|
|
@@ -0,0 +1,586 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport"
|
|
6
|
+
content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" />
|
|
7
|
+
<title>softphone</title>
|
|
8
|
+
<style>
|
|
9
|
+
#softphone-app {
|
|
10
|
+
padding-top: 50px;
|
|
11
|
+
overflow: hidden;
|
|
12
|
+
}
|
|
13
|
+
</style>
|
|
14
|
+
</head>
|
|
15
|
+
<body>
|
|
16
|
+
<div id="softphone-app">
|
|
17
|
+
<div style="float: left;margin-left: 20px">
|
|
18
|
+
<div style="margin-bottom: 15px">
|
|
19
|
+
一、当前坐席信息:
|
|
20
|
+
</div>
|
|
21
|
+
<pre id="info"></pre>
|
|
22
|
+
<div>
|
|
23
|
+
2. 当前坐席交流传递到远端的音量大小:<span id="volumn">0.00</span>
|
|
24
|
+
<div>- (强度比例0.00~1.00,用以实现如下音量反馈效果)</div>
|
|
25
|
+
<img src="./tapd_35238004_base64_1695010473_859.gif" style="width: 460px" />
|
|
26
|
+
</div>
|
|
27
|
+
<div style="margin-bottom: 15px">
|
|
28
|
+
3. 当前浏览器状态(通过权限网络检测获取):
|
|
29
|
+
</div>
|
|
30
|
+
<pre id="browserSoftphoneEnv"></pre>
|
|
31
|
+
</div>
|
|
32
|
+
<div style="float: left;margin-left: 10px">
|
|
33
|
+
<div style="margin-bottom: 15px">
|
|
34
|
+
二、调试步骤:
|
|
35
|
+
</div>
|
|
36
|
+
<div>1. 获取坐席是否在线
|
|
37
|
+
<div>
|
|
38
|
+
- agentStatus === 1 -> 在线
|
|
39
|
+
</div>
|
|
40
|
+
<div>
|
|
41
|
+
- agentStatus === 3 -> 小休
|
|
42
|
+
</div>
|
|
43
|
+
<div>
|
|
44
|
+
- tip: 能外呼的条件是:
|
|
45
|
+
<div style="margin-left: 20px;">
|
|
46
|
+
<div>
|
|
47
|
+
1. 软电话签入
|
|
48
|
+
</div>
|
|
49
|
+
<div>
|
|
50
|
+
2. 坐席在线
|
|
51
|
+
</div>
|
|
52
|
+
<div>
|
|
53
|
+
3. 同时确保坐席所在企业配置 【坐席外呼依赖】配置项 是【标识】 (默认是【登录】)
|
|
54
|
+
</div>
|
|
55
|
+
<div style="margin-left: 20px;">
|
|
56
|
+
- 配置项 【登录】:只有登录了94决策系统才可外呼。
|
|
57
|
+
</div>
|
|
58
|
+
<div style="margin-left: 30px;">
|
|
59
|
+
(tip: 如果不确定联系94运维在管理后台查看以及配置)
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
|
|
64
|
+
</div>
|
|
65
|
+
<div>2. 设置坐席在线</div>
|
|
66
|
+
<div>3. 连接外呼服务器(软电话签入)</div>
|
|
67
|
+
<div>4. 手动呼叫或任务外呼</div>
|
|
68
|
+
<div>- 手动呼叫,外呼服务器会先呼叫坐席,坐席接听后才会正式呼叫用户</div>
|
|
69
|
+
<div>- 任务外呼,外呼服务器会先呼叫用户,用户接听后才会正式呼叫坐席,坐席接听前有ai和用户对话</div>
|
|
70
|
+
<div>5. 接听 </div>
|
|
71
|
+
<div>6. 转人工(根据任务的配置:是否是监听任务,手动外呼不是任务外呼,不用转人工) </div>
|
|
72
|
+
<div>- tip:监听任务:接听后只可以听到ai和用户的对话,转人工后才可以和用户直接对话 </div>
|
|
73
|
+
<div>7. 挂断</div>
|
|
74
|
+
</div>
|
|
75
|
+
<div style="float: left;margin-left: 20px">
|
|
76
|
+
<div style="margin-bottom: 15px">
|
|
77
|
+
三、具体步骤:
|
|
78
|
+
</div>
|
|
79
|
+
<button id="nap">设置当前坐席进入小休状态</button>
|
|
80
|
+
<br>
|
|
81
|
+
<br>
|
|
82
|
+
<button id="online">1. 设置当前坐席在线</button>
|
|
83
|
+
<br>
|
|
84
|
+
<br>
|
|
85
|
+
<button id="connectToServer">2. 连接外呼服务器</button>
|
|
86
|
+
<br>
|
|
87
|
+
<br>
|
|
88
|
+
<button id="disconnect" disabled>断开连接服务器</button>
|
|
89
|
+
<br>
|
|
90
|
+
<br>
|
|
91
|
+
<button id="callButton">3. 手动呼叫</button>
|
|
92
|
+
: <input id="call" />
|
|
93
|
+
<br>
|
|
94
|
+
<br>
|
|
95
|
+
<button id="ignore" disabled>忽略</button>
|
|
96
|
+
<br>
|
|
97
|
+
<br>
|
|
98
|
+
<button id="answer" disabled>4. 接听</button>
|
|
99
|
+
<br>
|
|
100
|
+
<br>
|
|
101
|
+
<button id="hangUp" disabled>5. 挂断</button>
|
|
102
|
+
<br>
|
|
103
|
+
<br>
|
|
104
|
+
<button id="sendStarDTMF" disabled>按*转人工</button>
|
|
105
|
+
<br>
|
|
106
|
+
<br>
|
|
107
|
+
<button id="localVoiceAccess" disabled>本地声音接入(默认接入)</button>
|
|
108
|
+
<br>
|
|
109
|
+
<br>
|
|
110
|
+
<button id="remoteSoundAccess" disabled>远程声音接入(默认接入)</button>
|
|
111
|
+
<br>
|
|
112
|
+
<br>
|
|
113
|
+
<button id="localSoundDisconnection" disabled>本地声音断开</button>
|
|
114
|
+
<br>
|
|
115
|
+
<br>
|
|
116
|
+
<button id="remoteSoundDisconnection" disabled>远程声音断开</button>
|
|
117
|
+
<br>
|
|
118
|
+
<br>
|
|
119
|
+
<button id="check">权限网络检测</button>
|
|
120
|
+
<br>
|
|
121
|
+
<br>
|
|
122
|
+
<audio
|
|
123
|
+
id="remoteAudio"
|
|
124
|
+
controls
|
|
125
|
+
style="display:none"
|
|
126
|
+
>
|
|
127
|
+
<p>Your browser doesn't support HTML5 audio - remoteAudio</p>
|
|
128
|
+
</audio>
|
|
129
|
+
<audio
|
|
130
|
+
id="localAudio"
|
|
131
|
+
style="display:none"
|
|
132
|
+
src="./antique_phone.mp3"
|
|
133
|
+
controls
|
|
134
|
+
loop
|
|
135
|
+
>
|
|
136
|
+
<p>Your browser doesn't support HTML5 audio - localAudio</p>
|
|
137
|
+
</audio>
|
|
138
|
+
</div>
|
|
139
|
+
</div>
|
|
140
|
+
<div>
|
|
141
|
+
四、当前聊天记录信息:
|
|
142
|
+
<pre id="chatInfo">
|
|
143
|
+
</pre>
|
|
144
|
+
</div>
|
|
145
|
+
|
|
146
|
+
<script src="./fp.umd.min.js"></script>
|
|
147
|
+
<script src="./CryptoJS.js"></script>
|
|
148
|
+
<script src="./softphone.umd.min.js"></script>
|
|
149
|
+
|
|
150
|
+
<script>
|
|
151
|
+
document.addEventListener('DOMContentLoaded', async (event) => {
|
|
152
|
+
const phoneRing = './antique_phone.mp3'
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* 开启电话响铃
|
|
156
|
+
*/
|
|
157
|
+
const phoneRings = () => {
|
|
158
|
+
remoteAudio.src = phoneRing
|
|
159
|
+
playMedia('localAudio')
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* 关闭电话响铃
|
|
164
|
+
*/
|
|
165
|
+
const stopPhoneRings = () => {
|
|
166
|
+
remoteAudio.src = ''
|
|
167
|
+
pauseMedia('localAudio')
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* 获取dom
|
|
172
|
+
*/
|
|
173
|
+
const info = document.getElementById('info')
|
|
174
|
+
const ignore = document.getElementById('ignore')
|
|
175
|
+
const answer = document.getElementById('answer')
|
|
176
|
+
const hangUp = document.getElementById('hangUp')
|
|
177
|
+
const connectToServer = document.getElementById('connectToServer')
|
|
178
|
+
const call = document.getElementById('call')
|
|
179
|
+
const callButton = document.getElementById('callButton')
|
|
180
|
+
const nap = document.getElementById('nap')
|
|
181
|
+
const chatInfo = document.getElementById('chatInfo')
|
|
182
|
+
const online = document.getElementById('online')
|
|
183
|
+
const disconnect = document.getElementById('disconnect')
|
|
184
|
+
const sendStarDTMF = document.getElementById('sendStarDTMF')
|
|
185
|
+
const localVoiceAccess = document.getElementById('localVoiceAccess')
|
|
186
|
+
const remoteSoundAccess = document.getElementById('remoteSoundAccess')
|
|
187
|
+
const localSoundDisconnection = document.getElementById('localSoundDisconnection')
|
|
188
|
+
const remoteSoundDisconnection = document.getElementById('remoteSoundDisconnection')
|
|
189
|
+
const check = document.getElementById('check')
|
|
190
|
+
const volumn = document.getElementById('volumn')
|
|
191
|
+
const browserSoftphoneEnv = document.getElementById('browserSoftphoneEnv')
|
|
192
|
+
const localAudio = document.getElementById('localAudio')
|
|
193
|
+
const remoteAudio = document.getElementById('remoteAudio')
|
|
194
|
+
|
|
195
|
+
const {
|
|
196
|
+
UserAgentFactory,
|
|
197
|
+
UserAgent,
|
|
198
|
+
playMedia,
|
|
199
|
+
pauseMedia,
|
|
200
|
+
SessionState,
|
|
201
|
+
getMedia
|
|
202
|
+
} = softphone // 👈 sdk
|
|
203
|
+
|
|
204
|
+
const extensionNumber = '3376' // 本地fs 分机账号
|
|
205
|
+
const extPassword = 'zjh13542240708' // 本地fs 分机密码
|
|
206
|
+
const wsProtocol = 'ws' // 本地fs 地址协议
|
|
207
|
+
const wsRegisterAddress = '192.168.31.239:5066' // 本地fs 外呼服务器地址
|
|
208
|
+
const sipServerHost = `sip:${extensionNumber}@${wsRegisterAddress}` // fs地址
|
|
209
|
+
const wsAddress = `${wsProtocol}://${wsRegisterAddress}` // socket地址
|
|
210
|
+
|
|
211
|
+
const userAgentManager = UserAgentFactory.getUserAgentManager({
|
|
212
|
+
authorizationPassword: extPassword, // 分机密码
|
|
213
|
+
authorizationUsername: extensionNumber, // 分机账号
|
|
214
|
+
uri: UserAgent.makeURI(sipServerHost), // fs地址
|
|
215
|
+
transportOptions: {
|
|
216
|
+
server: wsAddress // socket地址
|
|
217
|
+
},
|
|
218
|
+
logLevel: 'error', // 日志等级,默认error
|
|
219
|
+
// viaHost, // fp.umd.min.js生成的唯一值,用来配查呼叫问题,可以不传,内部会自动生成
|
|
220
|
+
contactName: extensionNumber, // sip协议要求,同分机账号即可,注意类型为string, 不传默认同分机账号
|
|
221
|
+
clusterMode: true,
|
|
222
|
+
reClusterRegisterTimeout: 1000,
|
|
223
|
+
reClusterConnectTimeout: 1000,
|
|
224
|
+
sessionDescriptionHandlerFactoryOptions: { // 如果fs开启b2b没模式不需要传,开启p2p模式要传
|
|
225
|
+
iceGatheringTimeout: 2000,
|
|
226
|
+
peerConnectionConfiguration: {
|
|
227
|
+
iceServers: [{ urls: "stun:stun.l.google.com:19302" }], // stun:stun.freeswitch.org
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
refreshSpeekVolumn(value) { // 当来电接通时,实时查看坐席说话的音量实际传送到给对方的音量大小
|
|
231
|
+
volumn.innerText = value
|
|
232
|
+
},
|
|
233
|
+
refreshRequirementCheck(value) { // 权限网络检测
|
|
234
|
+
browserSoftphoneEnv.innerText = JSON.stringify(value, null, 2)
|
|
235
|
+
},
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* 签入
|
|
240
|
+
*/
|
|
241
|
+
const connect = async () => {
|
|
242
|
+
try {
|
|
243
|
+
await userAgentManager.prepareUserAgent(
|
|
244
|
+
{ // config
|
|
245
|
+
refresh(path, value) { // 当软电话状态变化时会实时刷新这个方法
|
|
246
|
+
console.log(path, value)
|
|
247
|
+
},
|
|
248
|
+
registererOptions: { // 设置会话多长时间过期,然后自动重签, 单位秒
|
|
249
|
+
expires: 10 * 60
|
|
250
|
+
}
|
|
251
|
+
},
|
|
252
|
+
{ // event
|
|
253
|
+
onInvite(invitation) { // 当有外呼过来
|
|
254
|
+
// const info = userAgentManager.getCallNumberDetail({
|
|
255
|
+
// id: Number(userAgentManager.businessAttribute.numberId), // 根据你需要哪通电话来,这里在来电时获取当前来电详情
|
|
256
|
+
// taskId: userAgentManager.businessAttribute.taskId, // 如果不是任务外呼不用传
|
|
257
|
+
// })
|
|
258
|
+
// console.log(info, '========')
|
|
259
|
+
setTimeout(() => {
|
|
260
|
+
acceptInvite()
|
|
261
|
+
}, 0)
|
|
262
|
+
console.log(userAgentManager.businessAttribute)
|
|
263
|
+
phoneRings() // 播放模拟来电响铃
|
|
264
|
+
invitation.stateChange.addListener((state) => { // 一旦接听(执行accept),监听会话的生命周期
|
|
265
|
+
switch (state) {
|
|
266
|
+
case SessionState.Initial:
|
|
267
|
+
break
|
|
268
|
+
case SessionState.Establishing:
|
|
269
|
+
break
|
|
270
|
+
case SessionState.Established: // session建立后就可拿到webrtc的各种基础api,如userAgentManager.getPeerConnection(),如userAgentManager.getSenders()等等
|
|
271
|
+
const mediaElement1 = getMedia('remoteAudio')
|
|
272
|
+
mediaElement1.srcObject = userAgentManager.getStream() // 获取流
|
|
273
|
+
mediaElement1.play() // 把softphone流导入到audio接入用户语音
|
|
274
|
+
hangUp.disabled = false
|
|
275
|
+
break
|
|
276
|
+
case SessionState.Terminating:
|
|
277
|
+
case SessionState.Terminated: // 在挂断电话时候会执行
|
|
278
|
+
const mediaElement2 = getMedia('remoteAudio') // 获取audio dom
|
|
279
|
+
mediaElement2.srcObject = null
|
|
280
|
+
mediaElement2.pause() // 释放audio
|
|
281
|
+
stopPhoneRings()
|
|
282
|
+
|
|
283
|
+
ignore.disabled = true
|
|
284
|
+
answer.disabled = true
|
|
285
|
+
hangUp.disabled = true
|
|
286
|
+
localSoundDisconnection.disabled = true
|
|
287
|
+
remoteSoundDisconnection.disabled = true
|
|
288
|
+
localVoiceAccess.disabled = true
|
|
289
|
+
remoteSoundAccess.disabled = true
|
|
290
|
+
break
|
|
291
|
+
default:
|
|
292
|
+
throw new Error('Unknown session state.')
|
|
293
|
+
}
|
|
294
|
+
})
|
|
295
|
+
ignore.disabled = false
|
|
296
|
+
answer.disabled = false
|
|
297
|
+
localSoundDisconnection.disabled = false
|
|
298
|
+
remoteSoundDisconnection.disabled = false
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
) // 👈 一键签入
|
|
302
|
+
} catch (errorInfo) { // 签入容错提示处理
|
|
303
|
+
if (errorInfo.message.indexOf('Invalid WebSocket Server URL') > -1
|
|
304
|
+
|| errorInfo.message.indexOf('Invalid scheme in WebSocket Server URL') > -1) {
|
|
305
|
+
alert('签入配置地址不正确,无法连通完成签入')
|
|
306
|
+
} else if (errorInfo.message === 'sip register fail with code 503' ||
|
|
307
|
+
(errorInfo.message.indexOf('WebSocket closed') > -1 && errorInfo.message.indexOf('code: 1006') > -1)) {
|
|
308
|
+
alert('软电话服务器不可用,签入失败')
|
|
309
|
+
} else {
|
|
310
|
+
alert(errorInfo.message)
|
|
311
|
+
}
|
|
312
|
+
throw errorInfo
|
|
313
|
+
}
|
|
314
|
+
disconnect.disabled = false
|
|
315
|
+
connectToServer.disabled = true
|
|
316
|
+
ignore.disabled = true
|
|
317
|
+
answer.disabled = true
|
|
318
|
+
hangUp.disabled = true
|
|
319
|
+
localSoundDisconnection.disabled = true
|
|
320
|
+
remoteSoundDisconnection.disabled = true
|
|
321
|
+
localVoiceAccess.disabled = true
|
|
322
|
+
remoteSoundAccess.disabled = true
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* 签出
|
|
327
|
+
*/
|
|
328
|
+
const disConnect = async () => {
|
|
329
|
+
await userAgentManager.dispose() // 👈 一个方法安全销毁,一键签出
|
|
330
|
+
connectToServer.disabled = false
|
|
331
|
+
disconnect.disabled = true
|
|
332
|
+
ignore.disabled = true
|
|
333
|
+
answer.disabled = true
|
|
334
|
+
hangUp.disabled = true
|
|
335
|
+
localSoundDisconnection.disabled = true
|
|
336
|
+
remoteSoundDisconnection.disabled = true
|
|
337
|
+
localVoiceAccess.disabled = true
|
|
338
|
+
remoteSoundAccess.disabled = true
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* 按*号转人工,返回promise,可以做loading效果做点击锁
|
|
343
|
+
*/
|
|
344
|
+
|
|
345
|
+
const sendStarDtmf = async () => {
|
|
346
|
+
// 由于 【坐席外呼类型】 没有配置要不要转人工的入口
|
|
347
|
+
// 而:【坐席快速外呼】 不需要 转人工;【坐席ai外呼】 一定要 转人工;其他外呼类型根据userAgentManager.notNeedSendStarDtmf(true不需要,false需要,即本质是根据配置),如任务类型外呼根据任务的配置
|
|
348
|
+
// userAgentManager.getFastOutboundCall判断是否坐席快速外呼(true是false否)
|
|
349
|
+
// userAgentManager.getSitOutboundCall判断是否坐席ai外呼(true是false否)
|
|
350
|
+
if ((!userAgentManager.notNeedSendStarDtmf() && !userAgentManager.getFastOutboundCall()) || userAgentManager.getSitOutboundCall()) {
|
|
351
|
+
await new Promise((resolve, reject) => {
|
|
352
|
+
const fn = async () => {
|
|
353
|
+
await userAgentManager.sendStarDtmf({
|
|
354
|
+
requestDelegate: {
|
|
355
|
+
onAccept () { // 👈 转人工响应成功
|
|
356
|
+
resolve(true)
|
|
357
|
+
},
|
|
358
|
+
async onReject (info) { //👈 转人工响应超时或失败
|
|
359
|
+
alert('转人工响应失败:' + info.message?.statusCode + ':' + info.message?.reasonPhrase)
|
|
360
|
+
reject(new Error(info.message.statusCode + ':' + info.message.reasonPhrase))
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}) // 👈 一键转人工,await的下行代码被执行说明成功发出转人工信令
|
|
364
|
+
sendStarDTMF.disabled = true
|
|
365
|
+
}
|
|
366
|
+
fn()
|
|
367
|
+
})
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* 挂断
|
|
373
|
+
*/
|
|
374
|
+
const hangUpInvite = async () => {
|
|
375
|
+
await new Promise((resolve, reject) => {
|
|
376
|
+
const fn = async () => {
|
|
377
|
+
await userAgentManager.hangUpInvite({
|
|
378
|
+
scoutResponse: true, // 标识是手动挂断,手动挂断无需等待响应,信号成功发出立即重置会话状态。
|
|
379
|
+
byeOptions: {
|
|
380
|
+
requestDelegate: {
|
|
381
|
+
onAccept (response) { // 👈 挂断响应成功
|
|
382
|
+
resolve(response)
|
|
383
|
+
},
|
|
384
|
+
onReject: (response) => { // 👈 挂断响应超时或失败
|
|
385
|
+
alert('挂断响应失败: ' + response?.message?.statusCode + ': ' + response?.message?.reasonPhrase) // 会话终止
|
|
386
|
+
reject(new Error(response?.message?.statusCode + ': ' + response?.message?.reasonPhrase))
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
},
|
|
390
|
+
}) // 👈 一键挂断, 下行代码执行,说明挂端信令成功发出
|
|
391
|
+
stopPhoneRings() // 暂停来电铃声,正常挂断不需要处理铃声(因为已经接听,会话已建立,才能挂断),这里做个兜底
|
|
392
|
+
ignore.disabled = true
|
|
393
|
+
answer.disabled = true
|
|
394
|
+
hangUp.disabled = true
|
|
395
|
+
localSoundDisconnection.disabled = true
|
|
396
|
+
remoteSoundDisconnection.disabled = true
|
|
397
|
+
localVoiceAccess.disabled = true
|
|
398
|
+
remoteSoundAccess.disabled = true
|
|
399
|
+
}
|
|
400
|
+
fn()
|
|
401
|
+
})
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* 忽略
|
|
405
|
+
*/
|
|
406
|
+
const ignoreInvite = async () => {
|
|
407
|
+
stopPhoneRings() // 暂停来电铃声
|
|
408
|
+
await userAgentManager.ignoreInvite() // 👈 一键忽略,下行代码被执行说明忽略信令成功发出
|
|
409
|
+
ignore.disabled = true
|
|
410
|
+
answer.disabled = true
|
|
411
|
+
hangUp.disabled = true
|
|
412
|
+
localSoundDisconnection.disabled = true
|
|
413
|
+
remoteSoundDisconnection.disabled = true
|
|
414
|
+
localVoiceAccess.disabled = true
|
|
415
|
+
remoteSoundAccess.disabled = true
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* 接听
|
|
419
|
+
*/
|
|
420
|
+
const acceptInvite = async () => {
|
|
421
|
+
stopPhoneRings() // 暂停来电铃声
|
|
422
|
+
try {
|
|
423
|
+
await new Promise((resolve, reject) => {
|
|
424
|
+
const fn = async () => {
|
|
425
|
+
try {
|
|
426
|
+
await userAgentManager.acceptInvite({
|
|
427
|
+
onAck (info) { // 👈 接听响应成功
|
|
428
|
+
resolve(info)
|
|
429
|
+
},
|
|
430
|
+
onAckTimeout () { // 👈 接听响应失败
|
|
431
|
+
reject(new Error('接听响应失败'))
|
|
432
|
+
}
|
|
433
|
+
}) // 👈 一键接听,下行代码被执行说明接听信令被成功发出
|
|
434
|
+
} catch (e) {
|
|
435
|
+
console.log('accept error:', e.message)
|
|
436
|
+
reject(e)
|
|
437
|
+
throw e
|
|
438
|
+
}
|
|
439
|
+
console.log('接听信令被成功发出')
|
|
440
|
+
}
|
|
441
|
+
fn()
|
|
442
|
+
})
|
|
443
|
+
// 接听信令不能被发出,可能原因如下:
|
|
444
|
+
} catch (error) { // 接听容错提示处理
|
|
445
|
+
if (error.message === 'Requested device not found'
|
|
446
|
+
|| error.message === 'The object can not be found here.') { // 设备问题
|
|
447
|
+
alert('未检测到耳麦设备,已自动断开来电')
|
|
448
|
+
throw error
|
|
449
|
+
} else if (error.message === 'The request is not allowed by the user agent or the platform in the current context.'
|
|
450
|
+
|| error.message === 'Media devices not available in insecure contexts.') { // 设备权限和浏览器api权限问题
|
|
451
|
+
if (navigator.userAgent.indexOf('Firefox') > -1) {
|
|
452
|
+
alert('软电话功能需要获取麦克风权限,如果没有麦克风权限或者http域名未配置insecure contexts都会引起连接会话立即终止,如果当前浏览器访问的地址是https协议,大部分连接会话终止的原因是没有麦克风权限')
|
|
453
|
+
} else {
|
|
454
|
+
alert('浏览器webrtc功能默认需要网站域名升级到https,如果当前域名为http,请配置浏览器insecure以指定域名白名单使用webrtc api,此时才可获取设备等权限以使用软电话功能')
|
|
455
|
+
}
|
|
456
|
+
throw error
|
|
457
|
+
} else if (error.message === 'Permission denied') { // 麦克风权限问题
|
|
458
|
+
alert('没有麦克风权限,请开启')
|
|
459
|
+
throw error
|
|
460
|
+
} else if (error.message === 'RTCPeerConnection is not defined') { // 浏览器版本问题
|
|
461
|
+
alert('当前浏览器版本过低不支持webrtc,推荐使用新版的谷歌或360或edge浏览器')
|
|
462
|
+
throw error
|
|
463
|
+
}
|
|
464
|
+
throw error
|
|
465
|
+
}
|
|
466
|
+
// 由于 【坐席外呼类型】 没有配置要不要转人工的入口
|
|
467
|
+
// 而:【坐席快速外呼】 不需要 转人工;【坐席ai外呼】 一定要 转人工;其他外呼类型根据userAgentManager.notNeedSendStarDtmf(true不需要,false需要,根据配置),如任务类型外呼根据任务配置
|
|
468
|
+
// userAgentManager.getFastOutboundCall判断是否坐席快速外呼(true是false否)
|
|
469
|
+
// userAgentManager.getSitOutboundCall判断是否坐席ai外呼(true是false否)
|
|
470
|
+
if ((!userAgentManager.notNeedSendStarDtmf() && !userAgentManager.getFastOutboundCall()) || userAgentManager.getSitOutboundCall()) {
|
|
471
|
+
sendStarDTMF.disabled = false // 打开页面转人工功能
|
|
472
|
+
}
|
|
473
|
+
hangUp.disabled = false
|
|
474
|
+
answer.disabled = true
|
|
475
|
+
ignore.disabled = true
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* 本地声音推流通道 -> 默认开
|
|
480
|
+
* 手动打开 本地声音推流通道(打开本地声音发送)
|
|
481
|
+
*/
|
|
482
|
+
const unMuteLocalAudio = () => {
|
|
483
|
+
localVoiceAccess.disabled = true
|
|
484
|
+
localSoundDisconnection.disabled = false
|
|
485
|
+
userAgentManager.unMuteLocalAudio() // 👈 一键
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* 本地声音推流通道 -> 默认开
|
|
489
|
+
* 手动关闭 本地声音推流通道(关闭本地声音发送)
|
|
490
|
+
*/
|
|
491
|
+
const muteLocalAudio = () => {
|
|
492
|
+
remoteSoundAccess.disabled = true
|
|
493
|
+
remoteSoundDisconnection.disabled = false
|
|
494
|
+
userAgentManager.muteLocalAudio() // 👈 一键
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* 远端声音拉流通道 -> 默认开
|
|
498
|
+
* 手动打开 远端声音拉流通道(用以打开远端声音接收)
|
|
499
|
+
*/
|
|
500
|
+
const unMuteRemoteAudio = () => {
|
|
501
|
+
localSoundDisconnection.disabled = true
|
|
502
|
+
localVoiceAccess.disabled = false
|
|
503
|
+
userAgentManager.unMuteRemoteAudio() // 👈 一键
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* 远端声音拉流通道 -> 默认开
|
|
507
|
+
* 手动关闭 远端声音拉流通道(用以关闭远端声音接收)
|
|
508
|
+
*/
|
|
509
|
+
const muteRemoteAudio = () => {
|
|
510
|
+
remoteSoundDisconnection.disabled = true
|
|
511
|
+
remoteSoundAccess.disabled = false
|
|
512
|
+
userAgentManager.muteRemoteAudio() // 👈 一键
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* 呼叫
|
|
517
|
+
*/
|
|
518
|
+
const callPhone = async () => {
|
|
519
|
+
// const callInfo = await userAgentManager.callNumber() // 👈 一键
|
|
520
|
+
// if (callInfo.code === 200) { // 正确响应
|
|
521
|
+
// alert('呼叫成功')
|
|
522
|
+
// } else {
|
|
523
|
+
// alert(callInfo.message)
|
|
524
|
+
// }
|
|
525
|
+
await userAgentManager.invite({
|
|
526
|
+
extension: call.value
|
|
527
|
+
})
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* 切换坐席进入小休
|
|
532
|
+
*/
|
|
533
|
+
const toggleNap = async () => {
|
|
534
|
+
const napInfo = await userAgentManager.toggleNap(true) // 👈 一键
|
|
535
|
+
if (napInfo.code === 200) { // 正确响应
|
|
536
|
+
nap.disabled = true
|
|
537
|
+
online.disabled = false
|
|
538
|
+
alert('进入小休成功')
|
|
539
|
+
} else {
|
|
540
|
+
alert(napInfo.message)
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* 切换坐席进入在线
|
|
546
|
+
*/
|
|
547
|
+
const toggleOnline = async () => {
|
|
548
|
+
const napInfo = await userAgentManager.toggleNap(false) // 👈 一键
|
|
549
|
+
if (napInfo.code === 200) { // 正确响应
|
|
550
|
+
nap.disabled = false
|
|
551
|
+
online.disabled = true
|
|
552
|
+
alert('设置坐席在线成功')
|
|
553
|
+
} else {
|
|
554
|
+
alert(napInfo.message)
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* 环境检测
|
|
560
|
+
*/
|
|
561
|
+
const envCheck = () => {
|
|
562
|
+
userAgentManager.softphoneEnvCheck() // 👈 一键检测,检测出结果后会回调在生成软电话实例时注册的钩子 refreshRequirementCheck
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/** 各种操作按钮 注册事件开始 */
|
|
566
|
+
ignore.addEventListener('click', ignoreInvite) // 忽略
|
|
567
|
+
answer.addEventListener('click', acceptInvite) // 接听
|
|
568
|
+
hangUp.addEventListener('click', hangUpInvite) // 挂断
|
|
569
|
+
connectToServer.addEventListener('click', connect) // 签入
|
|
570
|
+
disconnect.addEventListener('click', disConnect) // 签出
|
|
571
|
+
sendStarDTMF.addEventListener('click', sendStarDtmf) // 转人工
|
|
572
|
+
localVoiceAccess.addEventListener('click', unMuteLocalAudio) // 打开本地声音发送
|
|
573
|
+
remoteSoundAccess.addEventListener('click', muteLocalAudio) // 关闭本地声音发送
|
|
574
|
+
localSoundDisconnection.addEventListener('click', unMuteRemoteAudio) // 打开远端声音接收
|
|
575
|
+
remoteSoundDisconnection.addEventListener('click', muteRemoteAudio) // 关闭远端声音接收
|
|
576
|
+
callButton.addEventListener('click', callPhone) // 呼叫
|
|
577
|
+
nap.addEventListener('click', toggleNap) // 切换坐席进入小休
|
|
578
|
+
online.addEventListener('click', toggleOnline) // 切换坐席进入在线
|
|
579
|
+
check.addEventListener('click', envCheck) // 执行软电话必备的权限&网络环境检测
|
|
580
|
+
/** 注册事件结束 */
|
|
581
|
+
})
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
</script>
|
|
585
|
+
</body>
|
|
586
|
+
</html>
|
|
@@ -332,12 +332,19 @@ document.addEventListener('DOMContentLoaded', async (event) => {
|
|
|
332
332
|
* },
|
|
333
333
|
*/
|
|
334
334
|
const userAgentManager = UserAgentFactory.getUserAgentManager({
|
|
335
|
-
//
|
|
336
|
-
//
|
|
337
|
-
//
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
335
|
+
// sgOpen: '1',
|
|
336
|
+
// sg: '1',
|
|
337
|
+
// openBaseUrl: '1',
|
|
338
|
+
// logLevel: 'debug', // 日志等级,默认error
|
|
339
|
+
agentTag: '15018707394', // 生产线 坐席维度定位
|
|
340
|
+
appKey: '032d44009bff1752', // 生产线 企业维度定位
|
|
341
|
+
appSecret: '4c7304c94c5e8725613516d4d6db679b', // 生产线 企业维度定位 这里直接使用appSecret做演示
|
|
342
|
+
// agentId: '9870', // 生产线 坐席维度定位
|
|
343
|
+
// appKey: 'd85085f084e71477', // 生产线 企业维度定位
|
|
344
|
+
// appSecret: '1df8fdeaa960c7fefd8da39135a693ae', // 生产线 企业维度定位 这里直接使用appSecret做演示
|
|
345
|
+
// agentId: '2357', // 生产线 坐席维度定位
|
|
346
|
+
// appKey: '9297afc05dfe1704', // 生产线 企业维度定位
|
|
347
|
+
// appSecret: '616e860760cbd34deb1970390bf82659', // 生产线 企业维度定位 这里直接使用appSecret做演示
|
|
341
348
|
refreshSpeekVolumn(value) { // 当来电接通时,实时查看坐席说话的音量实际传送到给对方的音量大小
|
|
342
349
|
volumn.innerText = value
|
|
343
350
|
},
|
|
@@ -816,6 +823,9 @@ document.addEventListener('DOMContentLoaded', async (event) => {
|
|
|
816
823
|
} else {
|
|
817
824
|
alert(callInfo.message)
|
|
818
825
|
}
|
|
826
|
+
// await userAgentManager.invite({
|
|
827
|
+
// extension: call.value
|
|
828
|
+
// })
|
|
819
829
|
}
|
|
820
830
|
|
|
821
831
|
/**
|