index.html 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>Document</title>
  7. <script src="./jssip.min.js"></script>
  8. <script src="./uni.webview.1.5.4.js"></script>
  9. <script src="./axios.min.js"></script>
  10. </head>
  11. <body>
  12. <script>
  13. let { extension, password, user_agent, baseURL, ip: server_ip } = getURLParameters(location.search);
  14. const sockets = [
  15. { ip: '47.122.16.161', id: 1 },
  16. ];
  17. const ws_uri = "wss://" + server_ip;
  18. const isRemote = 'remote'
  19. const isLocal = 'local'
  20. let localStream = new MediaStream() // 本地媒体流
  21. const localAudio = new Audio // 本地流
  22. const remoteAudio = new Audio // 远程流
  23. const emtryRecord = new Audio //来电铃声
  24. // emtryRecord.src = './wav/ring.wav'
  25. emtryRecord.src = './wav/emtry.mp3'
  26. emtryRecord.loop = true;
  27. emtryRecord.play();
  28. const ringtone = new Audio //来电铃声
  29. ringtone.src = './wav/ring.wav'
  30. ringtone.loop = true
  31. const ringbacktone = new Audio // 挂断铃声
  32. ringbacktone.src = './wav/hangup.wav'
  33. const phoneStatus = {
  34. isRing: false, // 外呼振铃
  35. isBusy: false, // 通话(来电外呼通用)
  36. isIncoming: false, // 来电振铃
  37. isEnd: false, // 挂断
  38. isLeisure: true // 空闲
  39. };
  40. const phoneStatusInfo = {
  41. msg: '空闲中',
  42. color: '#16c4af'
  43. };
  44. let currentSession = null; // 当前会话
  45. let incomingSession = null; // 来电会话
  46. let lineList = []; // 线路数据
  47. let calleePhone = ''; // 当前来电的手机号码 (不管外呼来电 都当对方的号码)
  48. let callerPhone = ''; // 当前主叫的手机号码 (不管外呼来电 都当我方的号码)
  49. let jsSipStatus = false; // 签入状态
  50. let redisStatus = false; // redis中的状态
  51. let registerInfo = null; // 分机的信息
  52. // 设置jsSip状态
  53. function setJsSipStatus(status) {
  54. jsSipStatus = userAgent.isRegistered();
  55. uni.postMessage({
  56. data: {
  57. data: jsSipStatus,
  58. action: "singInState",
  59. }
  60. });
  61. }
  62. // 设置线路
  63. function setLineList(lineList) {
  64. lineList = lineList;
  65. }
  66. function setPhoneStats(key) {
  67. Object.keys(phoneStatus).forEach(k => {
  68. if (k === key) {
  69. phoneStatus[k] = true
  70. switch (key) {
  71. case 'isRing':
  72. phoneStatusInfo.msg = '振铃中'
  73. phoneStatusInfo.color = '#1890ff'
  74. break
  75. case 'isBusy':
  76. phoneStatusInfo.msg = '通话中'
  77. phoneStatusInfo.color = '#4b7902'
  78. break
  79. case 'isIncoming':
  80. phoneStatusInfo.msg = '来电中'
  81. phoneStatusInfo.color = '#aa00ab'
  82. break
  83. case 'isEnd':
  84. phoneStatusInfo.msg = '挂断中'
  85. phoneStatusInfo.color = '#d9041e'
  86. break
  87. case 'isLeisure':
  88. // 重置手机号码
  89. phoneStatusInfo.msg = '空闲中'
  90. phoneStatusInfo.color = '#16c4af'
  91. break
  92. default:
  93. phoneStatusInfo.msg = '其他'
  94. phoneStatusInfo.color = '#333333'
  95. break
  96. }
  97. } else {
  98. phoneStatus[k] = false
  99. }
  100. });
  101. uni.postMessage({
  102. data: {
  103. data: phoneStatus,
  104. action: "phoneStatus",
  105. }
  106. });
  107. }
  108. let userAgent = null;
  109. function getURLParameters(url) {
  110. var params = {};
  111. var queryString = url.split('?')[1];
  112. if (queryString) {
  113. var keyValuePairs = queryString.split('&');
  114. for (var i = 0; i < keyValuePairs.length; i++) {
  115. var keyValuePair = keyValuePairs[i].split('=');
  116. var key = decodeURIComponent(keyValuePair[0]);
  117. var value = decodeURIComponent(keyValuePair[1] || '');
  118. params[key] = value;
  119. }
  120. }
  121. return params;
  122. }
  123. // 处理来电
  124. function handleCalled({ action }) {
  125. if (action === "answer") {
  126. currentSession.answer({
  127. 'audio': true,
  128. 'mediaConstraints': {
  129. 'video': false
  130. }
  131. });
  132. setPhoneStats("isBusy");
  133. } else if (action === "hangup") {
  134. ringtone.pause()
  135. ringbacktone.play()
  136. currentSession.terminate()
  137. setPhoneStats('isLeisure');
  138. }
  139. }
  140. function unReg() {
  141. return new Promise((resolve, reject) => {
  142. if (!phoneStatus.isLeisure) {
  143. reject()
  144. } else {
  145. if (userAgent) {
  146. userAgent.on('unregistered', () => {
  147. setJsSipStatus();
  148. getRedisStatus();
  149. setTimeout(() => {
  150. register();
  151. }, 2000)
  152. resolve();
  153. })
  154. userAgent.unregister(true)
  155. userAgent.unregister({ all: true })
  156. userAgent.terminateSessions()
  157. userAgent.stop()
  158. } else {
  159. setTimeout(() => {
  160. register();
  161. }, 2000)
  162. resolve()
  163. // Message.info('当前已经是签出状态')
  164. }
  165. }
  166. })
  167. }
  168. // 挂断
  169. function hangup() {
  170. if (!phoneStatus.isLeisure) {
  171. userAgent.terminateSessions()
  172. ringbacktone.play()
  173. }
  174. }
  175. // 获取redis的状态
  176. async function getRedisStatus() {
  177. const { data: result } = await axios.get(baseURL + "/sip/JcSip/getExtensionStatus", {
  178. params: {
  179. extension
  180. },
  181. });
  182. const { data } = result;
  183. redisStatus = data != null;
  184. registerInfo = data;
  185. uni.postMessage({
  186. data: {
  187. data: registerInfo,
  188. action: "registerInfo",
  189. }
  190. });
  191. }
  192. function handleCall(params) {
  193. if (!jsSipStatus || !redisStatus) {
  194. uni.postMessage({
  195. data: {
  196. data: "签入状态异常",
  197. action: "message",
  198. type: "error"
  199. }
  200. });
  201. return
  202. } else if (!phoneStatus.isLeisure) {
  203. uni.postMessage({
  204. data: {
  205. data: phoneStatusInfo.msg + ",请等待状态变为空闲",
  206. action: "message",
  207. type: "error"
  208. }
  209. });
  210. return
  211. }
  212. const options = {
  213. 'eventHandlers': {
  214. //振铃中
  215. progress: function () {
  216. console.log("振铃中");
  217. // success
  218. setPhoneStats('isBusy');
  219. callerPhone = params.callPhone;
  220. calleePhone = params.phone;
  221. let socket = null;
  222. if (registerInfo.register) {
  223. socket = sockets.find(v => v.ip === registerInfo.register['IPv4']);
  224. }
  225. if (!socket) {
  226. // 如果是空的就取第一个
  227. socket = sockets[0];
  228. }
  229. const callParams = {
  230. id: params.recordingId,// cdr的id
  231. caseId: params.caseId,
  232. phone: params.phone,
  233. callee: params.x_callee, // 加密的被叫
  234. caller: params.x_callPhone, // 加密主叫
  235. callType: '2', // 1- 呼入 2-呼出 3-外访
  236. phoneType: '4', // 1-新生代ip 2-枫软固化 3-同网 4-回龙
  237. ipAddressId: socket.id // 录音保存的服务器id
  238. }
  239. uni.postMessage({
  240. data: {
  241. data: callParams,
  242. action: "outbound",
  243. }
  244. });
  245. },
  246. //呼叫失败
  247. failed: function () {
  248. setPhoneStats('isLeisure');
  249. },
  250. //通话结束
  251. ended: function () {
  252. // success
  253. console.log('通话结束')
  254. setPhoneStats('isEnd');
  255. },
  256. //通话中
  257. confirmed: function () {
  258. // success
  259. console.log('已建立通道')
  260. }
  261. },
  262. 'mediaConstraints': { 'audio': true, 'video': false },
  263. 'extraHeaders': [
  264. 'X-DIALER-STRING: ' + params.replacedContact,
  265. 'X-CALLEE: ' + params.x_callee,
  266. 'X-CALLER: ' + params.x_caller,
  267. 'X-ORGID: ' + params.orgId,
  268. 'X-RECORDINGID: ' + params.recordingId,
  269. 'X-TYPE: ' + '1',
  270. 'X-SYSTEM-CODE: ' + params.systemCode,
  271. 'X-BANK-CODE: ' + params.bankCode,
  272. 'X-BANK-BATCH-CODE: ' + params.bankBatchCode,
  273. 'X-EXTEN-NUM: ' + params.extension,
  274. 'X-USER-ID: ' + params.userId,
  275. 'X-NICK-NAME: ' + params.nickName,
  276. 'X-DEPT-ID: ' + params.deptId,
  277. 'X-CALL-PHONE: ' + params.x_callPhone,
  278. 'X-PERSON-PHONE: ' + params.x_callee
  279. ],
  280. 'sessionTimersExpires': 180
  281. }
  282. console.log("外呼:" + params.phone);
  283. userAgent.call(params.phone, options);
  284. };
  285. function register() {
  286. return new Promise((resolve, reject) => {
  287. const config = {
  288. sockets: [],
  289. // uri: 'sip:%s@' + server_ip + ':11450',
  290. uri: 'sip:%s@' + server_ip,
  291. // authorization_user: '1005',
  292. password: '',
  293. // WebSocket重新连接调度之间的最大间隔(秒)。默认值是30。
  294. connection_recovery_max_interval: 60,
  295. // WebSocket重新连接尝试之间的最小间隔(秒)。默认值是2。
  296. connection_recovery_min_interval: 4,
  297. // 外显
  298. // display_name: '10086',
  299. // 来电后,120秒内未接听,将会拒绝.默认60s
  300. no_answer_timeout: 120,
  301. // 会话计时器,默认true
  302. session_timers: false,
  303. // 用于刷新会话计时器的SIP方法。有效值是UPDATE或INVITE。默认值是UPDATE。
  304. session_timers_refresh_method: 'UPDATE',
  305. // 是否应该在启动时自动注册.默认true
  306. register: true,
  307. // 注册过期时间(秒) 默认值是600。
  308. register_expires: 6000,
  309. // User-Agent header中的值,默认是JsSIP
  310. // registrar_server: 'sip:' + server_ip,
  311. }
  312. // 补充配置文件中 分机号和分机号密码
  313. config.uri = config.uri.replace('%s', extension)
  314. config.password = password
  315. config.user_agent = user_agent // 给注册添加一个用户账号的识别标识
  316. // const wsSocket = new JsSIP.WebSocketInterface("wss://172.16.7.100:7565")
  317. console.log(ws_uri)
  318. const wsSocket = new JsSIP.WebSocketInterface(ws_uri)
  319. config.sockets = [wsSocket]
  320. userAgent = new JsSIP.UA(config)
  321. // 启动用户代理,开始与服务器进行通信
  322. userAgent.start()
  323. //注册成功
  324. userAgent.on('registered', () => {
  325. console.log("注册成功");
  326. setJsSipStatus();
  327. getRedisStatus();
  328. resolve();
  329. // uni.postMessage({
  330. // data: {
  331. // data: "注册成功",
  332. // action: "message",
  333. // type: "success",
  334. // }
  335. // });
  336. });
  337. //连接服务断开
  338. userAgent.on('disconnected', (disconnectedData) => {
  339. console.log("连接服务断开");
  340. reject();
  341. setJsSipStatus();
  342. getRedisStatus();
  343. if (disconnectedData.code && disconnectedData.code === 1006) {
  344. userAgent.stop();
  345. register().then(() => {
  346. console.log("重新连接");
  347. }).catch(() => {
  348. console.log("重连失败,重新创建");
  349. uni.postMessage({
  350. data: {
  351. action: "disconnected"
  352. }
  353. });
  354. })
  355. return;
  356. }
  357. // uni.postMessage({
  358. // data: {
  359. // data: "连接服务断开",
  360. // action: "message",
  361. // type: "error"
  362. // }
  363. // });
  364. })
  365. //注册失败
  366. userAgent.on('registrationFailed', (UnRegisteredEvent) => {
  367. console.log("注册失败");
  368. setJsSipStatus();
  369. getRedisStatus();
  370. if (UnRegisteredEvent.response) {
  371. uni.postMessage({
  372. data: {
  373. data: '分机号' + extension + ':' + UnRegisteredEvent.response.reason_phrase,
  374. action: "message",
  375. type: "error"
  376. }
  377. });
  378. } else {
  379. uni.postMessage({
  380. data: {
  381. data: '分机号' + extension + ':' + "签入失败",
  382. action: "message",
  383. type: "error"
  384. }
  385. });
  386. }
  387. reject();
  388. });
  389. //注销回调
  390. userAgent.on('unregistered', () => {
  391. console.log("unregistered");
  392. reject();
  393. setJsSipStatus();
  394. getRedisStatus();
  395. })
  396. userAgent.on('newRTCSession', function (data) {
  397. const session = data.session
  398. const originator = data.originator
  399. const request = data.request
  400. currentSession = session;
  401. session.on('accepted', function (acceptedData) {
  402. ringtone.pause() // 关闭来电铃声
  403. currentSession = session;
  404. if (acceptedData.originator === isLocal) {
  405. // 来电时触发
  406. let connection = session.connection
  407. let receivers = connection.getReceivers() // 处理远程流
  408. if (receivers) {
  409. receivers.forEach(item => {
  410. localStream.addTrack(item.track)
  411. })
  412. remoteAudio.srcObject = localStream
  413. remoteAudio.play()
  414. remoteAudio.volume = 1
  415. }
  416. }
  417. // else{
  418. // // 外呼
  419. // }
  420. })
  421. session.on('failed', function (failedData) {
  422. // 对方来电主动挂断
  423. if (phoneStatus.isIncoming) {
  424. calleePhone = "";
  425. if (failedData.originator === isRemote) {
  426. // info
  427. console.log('会话已过期,对方主动挂断电话')
  428. ringtone.pause()
  429. ringbacktone.play()
  430. // state.calleeNotify.close()
  431. } else {
  432. // 我方主动挂断
  433. // info
  434. // console.log('拒接成功')
  435. ringtone.pause()
  436. ringbacktone.play()
  437. setPhoneStats("isLeisure");
  438. }
  439. } else {
  440. // 等于null不再执行 已接听电话
  441. if (failedData.message === null) return
  442. if (['Request Terminated'].includes(failedData.message.reason_phrase)) {
  443. uni.postMessage({
  444. data: {
  445. data: "会话终止,对方可能拒接您的来电",
  446. action: "message",
  447. type: "error"
  448. }
  449. });
  450. // error
  451. } else {
  452. if (lineList.length > 0 && callerPhone) {
  453. const line = lineList.find(v => v.callPhone === callerPhone);
  454. uni.postMessage({
  455. data: {
  456. data: '线路:' + callerPhone + (line ? ('(' + line.type + ')') : '') + ',failed:' + failedData.message.reason_phrase,
  457. action: "message",
  458. type: "error"
  459. }
  460. });
  461. } else {
  462. uni.postMessage({
  463. data: {
  464. data: 'failed:' + failedData.message.reason_phrase,
  465. action: "message",
  466. type: "error"
  467. }
  468. });
  469. }
  470. // error
  471. }
  472. setPhoneStats("isLeisure");
  473. }
  474. })
  475. session.on('ended', function () {
  476. setPhoneStats("isLeisure");
  477. if (originator === isRemote) {
  478. // 来电挂断
  479. // success
  480. console.log('来电挂断')
  481. calleePhone = "";
  482. // state.calleeNotify.close()
  483. }
  484. // else 外呼挂断不再这里处理
  485. })
  486. session.on('getusermediafailed', function () {
  487. console.log('获取用户媒体设备失败')
  488. // error
  489. })
  490. // 处理session
  491. if (originator === isRemote) {
  492. incomingSession = session;
  493. // 处理来电
  494. const sipUri = incomingSession.remote_identity.uri.toString()
  495. calleePhone = sipUri.match(/sip:(\d+)@/)[1]
  496. // 铃声
  497. ringtone.play();
  498. setPhoneStats("isIncoming");
  499. const recordingId = request.getHeader('X-RECORDINGID')
  500. const ip = request.getHeader('X-IP')
  501. const callPhone = request.getHeader('X-CALL-PHONE')
  502. const personPhone = request.getHeader('X-PERSON-PHONE');
  503. const socket = sockets.find(v => v.ip === ip)
  504. if (recordingId) {
  505. const params = {
  506. id: recordingId,// cdr的id
  507. phone: calleePhone, // 来电号码(真实未加密)
  508. callee: callPhone, // 加密的被叫
  509. caller: personPhone, // 加密主叫
  510. callType: '1', // 1- 呼入 2-呼出 3-外访
  511. phoneType: '4', // 1-新生代ip 2-枫软固化 3-同网 4-回龙
  512. ipAddressId: socket.id // 录音保存的服务器id
  513. }
  514. uni.postMessage({
  515. data: {
  516. data: params,
  517. action: "incoming"
  518. }
  519. });
  520. } else {
  521. uni.postMessage({
  522. data: {
  523. data: "来电没有返回录音id,请一定要联系管理员",
  524. action: "message",
  525. type: "error"
  526. }
  527. });
  528. }
  529. // success
  530. console.log('有来电')
  531. } else {
  532. // 外呼时触发
  533. session.connection.addEventListener('track', (event) => {
  534. localAudio.srcObject = event.streams[0]
  535. localAudio.play()
  536. localAudio.volume = 1
  537. })
  538. }
  539. })
  540. })
  541. }
  542. document.addEventListener('UniAppJSBridgeReady', function () {
  543. register();
  544. });
  545. </script>
  546. </body>
  547. </html>