sipUtil.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625
  1. /**
  2. * 对象方式的培训助手JS,以对象的方式初始化trainingHelper
  3. * 示例代码如下:
  4. * @author Asy
  5. * @time 2020-12-25 14:36
  6. */
  7. (function ($) {
  8. var $this; // 当前对象
  9. var ua;
  10. var session;
  11. var oNotifICall;
  12. var ringtone = null;
  13. var ringBackTone = null;
  14. var keyboard = null;
  15. var oSipSessionCall;
  16. // sipPhone处理
  17. mySipPhone = {
  18. init: function (options) {
  19. // 可供外部调用的方法和属性
  20. var o = $.extend({
  21. uri: "", // sip服务器的IP和端口
  22. wss: "", // webSocket 地址
  23. user: "", // sip分机号
  24. pwd: "", // 分机号密码
  25. callNumber: "", // 培训默认外呼号码
  26. options: {
  27. audioBW: '', // 设置音频带宽
  28. videoBW: '', // 设置视频带宽
  29. ice: '', // ICE穿透NAT/防火墙
  30. stunUrl: '', // STUN穿透
  31. turnUrl: '', // TURN穿透
  32. turnUserName: '', // TURN用户名
  33. turnCredential: '' // TURN证书
  34. },
  35. connecting: function (evt) {}, // webSocket连接中
  36. connected: function (evt) {}, // webSocket已连接
  37. disconnected: function (evt) {}, // webSocket连接断开
  38. registered: function (evt) {}, // SIP注册成功
  39. unregistered: function (evt) {}, // SIP未注册
  40. registrationFailed: function (evt) {}, // SIP注册失败
  41. busy: function (evt) {}, // 线路忙
  42. ringing: function (num) {}, // 呼入振铃,参数为呼入号码
  43. progress: function (localStream) {}, // 外呼处理中
  44. dialing: function (evt) {}, // 外呼中
  45. confirmed: function (evt) {}, // 外呼已确认
  46. accepted: function (callType, evt) {}, // 呼叫接通,callType:inbound 呼入,outbound 呼出
  47. stream: function (callType, localStream, remoteStream) {}, // 获取到数据流,callType:inbound 呼入,outbound 呼出
  48. failed: function (callType, evt) {}, // 呼叫失败,callType:inbound 呼入,outbound 呼出
  49. ended: function (callType, evt) {}, // 呼叫结束,callType:inbound 呼入,outbound 呼出
  50. refer: function (evt) {} // 转接
  51. }, options);
  52. $this = this;
  53. $this.data('mySipPhone', o);
  54. },
  55. /** 获取注入的数据和事件 */
  56. getData: function () {
  57. var o = $this.data('mySipPhone');
  58. return o;
  59. },
  60. /** 注册SIP */
  61. register: function () {
  62. try {
  63. // 如果当前已经注册过,则直接跳过
  64. if(ua){
  65. return false;
  66. }
  67. var o = mySipPhone.getData();
  68. // 启用浏览器消息通知
  69. if (window.webkitNotifications && window.webkitNotifications.checkPermission() != 0) {
  70. window.webkitNotifications.requestPermission();
  71. }
  72. var socket = new PhoneJs.WebSocketInterface(o.wss);
  73. socket.onconnect = function () {
  74. console.log('mySipPhone:webSocket连接中');
  75. };
  76. var configuration = {
  77. sockets: [socket],
  78. uri: o.user + '@' + o.uri,
  79. password: o.pwd,
  80. authorization_user: o.user,
  81. display_name: o.user,
  82. register: false, // dont't automatic register
  83. trace_sip: true
  84. //connection_recovery_max_interval: 30,
  85. //connection_recovery_min_interval: 2,
  86. };
  87. ua = new PhoneJs.UA(configuration);
  88. if (o.options.videoBW != undefined && o.options.videoBW != '') {
  89. ua.setVideoBandwidth(o.options.videoBW);
  90. }
  91. if (o.options.audioBW != undefined && o.options.audioBW != '') {
  92. ua.setAudioBandwidth(o.options.audioBW);
  93. }
  94. ua.on('connecting', function (e) {
  95. o.connecting(e);
  96. });
  97. ua.on('connected', function (e) {
  98. o.connected(e);
  99. ua.registrator().setExtraHeaders([
  100. 'X-Foo: x-bar'
  101. ]);
  102. ua.register(); // connect to SIP server
  103. });
  104. ua.on('disconnected', function (e) {
  105. o.disconnected(e);
  106. });
  107. ua.on('registered', function (e) {
  108. o.registered(e);
  109. });
  110. ua.on('unregistered', function (e) {
  111. o.unregistered(e);
  112. });
  113. ua.on('registrationFailed', function (e) {
  114. o.registrationFailed(e);
  115. });
  116. ua.on('newRTCSession', function (e) {
  117. session = e.session;
  118. var count = Object.keys(ua._sessions).length;
  119. if (count > 1) {
  120. var extraHeaders = ['X-Foo: foo', 'X-Bar: bar'];
  121. var status_code = "486";
  122. var reason_phrase = "BUSY HERE";
  123. var opts = {
  124. 'extraHeaders': extraHeaders,
  125. 'status_code': status_code,
  126. 'reason_phrase': reason_phrase
  127. };
  128. session.terminate(opts);
  129. o.busy(e);
  130. } else {
  131. if (session.direction == 'incoming') {
  132. mySipPhone._startRingtone($('[name="ringtone"]').val());
  133. var sRemoteNumber = (session.remote_identity._display_name || session.remote_identity._user ||
  134. 'unknown');
  135. o.ringing(sRemoteNumber);
  136. mySipPhone.showNotification(sRemoteNumber);
  137. session.on('peerconnection', function (e) {
  138. var peerconnection = session.connection;
  139. peerconnection.addEventListener('addstream', function (e) {
  140. var localStream = peerconnection.getLocalStreams()[0];
  141. o.stream("inbound", localStream, e.stream);
  142. });
  143. });
  144. session.on('progress', function (e) {
  145. console.log('Call progress event <i>呼入振铃...</i>');
  146. var peerconnection = session.connection;
  147. peerconnection.addEventListener('addstream', function (e) {
  148. var localStream = peerconnection.getLocalStreams()[0];
  149. o.stream("inbound", localStream, e.stream);
  150. });
  151. });
  152. session.on('failed', function (e) {
  153. session = null;
  154. mySipPhone._stopRingtone();
  155. o.failed("inbound", e);
  156. });
  157. session.on('ended', function (e) {
  158. session = null;
  159. o.ended("inbound", e);
  160. });
  161. session.on('accepted', function (e) {
  162. mySipPhone._stopRingtone();
  163. o.accepted("inbound", e);
  164. });
  165. }
  166. }
  167. });
  168. ua.start();
  169. } catch (e) {
  170. console.error(e);
  171. }
  172. },
  173. /**
  174. * 签出
  175. **/
  176. unregister: function () {
  177. if (ua) {
  178. ua.stop(); // shutdown all sessions
  179. ua = null;
  180. }
  181. },
  182. /**
  183. * 来电应答
  184. **/
  185. answer: function (videoEnabled) {
  186. try {
  187. var o = mySipPhone.getData();
  188. if (session) {
  189. var optAnswer = {
  190. 'mediaConstraints': {
  191. 'audio': true,
  192. 'video': videoEnabled
  193. }
  194. };
  195. var stunUrl = o.options.stunUrl;
  196. var turnUrl = o.options.turnUrl;
  197. var turnUserName = o.options.turnUserName;
  198. var turnCredential = o.options.turnCredential;
  199. if (stunUrl && stunUrl.length > 0) {
  200. optAnswer.pcConfig = {
  201. iceServers: [{
  202. urls: [stunUrl]
  203. }]
  204. };
  205. }
  206. if (turnUrl && turnUrl.length > 0) {
  207. if ((turnUserName && turnUserName.length > 0) && (turnCredential && turnCredential.length > 0)) {
  208. if (optAnswer.pcConfig && optAnswer.pcConfig.iceServers) {
  209. optAnswer.pcConfig.iceServers.push({
  210. 'urls': turnUrl,
  211. 'username': turnUserName,
  212. 'credential': turnCredential
  213. });
  214. } else {
  215. optAnswer.pcConfig = {
  216. iceServers: [{
  217. 'urls': turnUrl,
  218. 'username': turnUserName,
  219. 'credential': turnCredential
  220. }]
  221. };
  222. }
  223. } else {
  224. if (optAnswer.pcConfig && optAnswer.pcConfig.iceServers) {
  225. optAnswer.pcConfig.iceServers.push({
  226. 'urls': turnUrl
  227. });
  228. } else {
  229. optAnswer.pcConfig = {
  230. iceServers: [{
  231. 'urls': turnUrl
  232. }]
  233. };
  234. }
  235. }
  236. }
  237. session.answer(optAnswer);
  238. } else {
  239. alert('错误 : No session');
  240. }
  241. } catch (error) {
  242. console.error(error);
  243. }
  244. },
  245. /**
  246. * 外呼
  247. * @param videoEnabled true 表示视频外呼,false 表示语音外呼
  248. * @param callNum 外呼的号码
  249. * */
  250. call: function (videoEnabled, callNum) {
  251. try {
  252. var o = mySipPhone.getData();
  253. var eventHandlers = {
  254. 'progress': function (data) {
  255. // 制造振铃
  256. mySipPhone._startRingbackTone();
  257. var localStream = null;
  258. if(videoEnabled){
  259. var peerconnection = session.connection;
  260. localStream = peerconnection.getLocalStreams()[0];
  261. }
  262. o.progress(localStream);
  263. },
  264. 'failed': function (data) {
  265. session = null;
  266. mySipPhone._stopRingbackTone();
  267. o.failed("outbound", data);
  268. },
  269. 'confirmed': function (data) {
  270. o.confirmed(data);
  271. },
  272. 'ended': function (data) {
  273. session = null;
  274. o.ended("outbound", data);
  275. },
  276. 'connecting': function (data) {
  277. o.dialing(data);
  278. },
  279. 'accepted': function (data) {
  280. mySipPhone._stopRingbackTone();
  281. o.accepted("outbound", data);
  282. },
  283. 'refer': function (data) {
  284. o.refer(data);
  285. }
  286. };
  287. var options = {
  288. 'eventHandlers': eventHandlers,
  289. 'extraHeaders': ['X-Foo: foo', 'X-Bar: bar', 'video : ' + videoEnabled],
  290. 'mediaConstraints': {
  291. 'audio': true,
  292. 'video': videoEnabled
  293. },
  294. 'rtcOfferConstraints': {
  295. offerToReceiveAudio: 1,
  296. offerToReceiveVideo: 1
  297. }
  298. };
  299. var stunUrl = o.options.stunUrl;
  300. var turnUrl = o.options.turnUrl;
  301. var turnUserName = o.options.turnUserName;
  302. var turnCredential = o.options.turnCredential;
  303. //var pcConfig = { iceServers: [ { urls : [ iceServer ] } ] };
  304. if (stunUrl && stunUrl.length > 0) {
  305. options.pcConfig = {
  306. iceServers: [{
  307. urls: [stunUrl]
  308. }]
  309. };
  310. }
  311. if (turnUrl && turnUrl.length > 0) {
  312. if ((turnUserName && turnUserName.length > 0) && (turnCredential && turnCredential.length > 0)) {
  313. if (options.pcConfig && options.pcConfig.iceServers) {
  314. options.pcConfig.iceServers.push({
  315. 'urls': turnUrl,
  316. 'username': turnUserName,
  317. 'credential': turnCredential
  318. });
  319. } else {
  320. options.pcConfig = {
  321. iceServers: [{
  322. 'urls': turnUrl,
  323. 'username': turnUserName,
  324. 'credential': turnCredential
  325. }]
  326. };
  327. }
  328. } else {
  329. if (options.pcConfig && options.pcConfig.iceServers) {
  330. options.pcConfig.iceServers.push({
  331. 'urls': turnUrl
  332. });
  333. } else {
  334. options.pcConfig = {
  335. iceServers: [{
  336. 'urls': turnUrl
  337. }]
  338. };
  339. }
  340. }
  341. }
  342. session = ua.call(callNum, options);
  343. var peerconnection = session.connection;
  344. peerconnection.addEventListener('addstream', function (e) {
  345. console.log("===========外呼");
  346. var localStream = peerconnection.getLocalStreams()[0];
  347. o.stream("outbound", localStream, e.stream);
  348. });
  349. } catch (error) {
  350. console.error(error);
  351. }
  352. },
  353. // holds or resumes the call
  354. hold: function () {
  355. if (session) {
  356. var bHold = session.isOnHold();
  357. if (bHold.local) {
  358. console.log('unhold');
  359. session.unhold();
  360. } else {
  361. console.log('hold');
  362. session.hold();
  363. }
  364. }
  365. },
  366. // Mute or Unmute the call
  367. mute: function () {
  368. if (session) {
  369. var bMute = session.isMuted();
  370. if (bMute && bMute.audio) {
  371. console.log('unmute');
  372. session.unmute({
  373. 'audio': true, // Local audio is muted
  374. 'video': true // Local audio is not muted
  375. });
  376. } else {
  377. console.log('mute');
  378. session.mute({
  379. 'audio': true, // Local audio is muted
  380. 'video': true // Local audio is not muted
  381. });
  382. }
  383. }
  384. },
  385. // terminates the call (SIP BYE or CANCEL)
  386. hangUp: function () {
  387. if (session) {
  388. var extraHeaders = ['X-Foo: foo', 'X-Bar: bar'];
  389. var options = {
  390. 'extraHeaders': extraHeaders
  391. };
  392. session.terminate(options);
  393. }
  394. },
  395. // 发送按键给服务器
  396. sendDTMF: function (c) {
  397. if (oSipSessionCall && c) {
  398. if (oSipSessionCall.dtmf(c) == 0) {
  399. try {
  400. if(keyboard == null){
  401. keyboard = new Audio();
  402. keyboard.src = "./sounds/dtmf.wav";
  403. }
  404. keyboard.play();
  405. } catch (e) {
  406. console.error(e);
  407. }
  408. }
  409. }
  410. },
  411. /**
  412. * 当有来电时展示消息提醒
  413. * @param s_number 来电号码
  414. * */
  415. showNotification: function (s_number) {
  416. // permission already asked when we registered
  417. if (window.webkitNotifications && window.webkitNotifications.checkPermission() == 0) {
  418. if (oNotifICall) {
  419. oNotifICall.cancel();
  420. }
  421. oNotifICall = window.webkitNotifications.createNotification('images/sipml-34x39.png', 'Incaming call',
  422. 'Incoming call from ' + s_number);
  423. oNotifICall.onclose = function () {
  424. oNotifICall = null;
  425. };
  426. oNotifICall.show();
  427. }
  428. },
  429. // transfers the call
  430. transfer: function (transferNum) {
  431. if (session) {
  432. if (transferNum == undefined || transferNum == null || transferNum == '') {
  433. transferNum = prompt('请输入转接号码', '');
  434. }
  435. if (transferNum != undefined && transferNum != "") {
  436. session.refer(transferNum);
  437. }
  438. }
  439. },
  440. consult: function (number) {
  441. if (session) {
  442. if (number == undefined || number == null || number == '') {
  443. number = prompt('请输入咨询号码', '');
  444. }
  445. if (number != undefined && number != "") {
  446. // TODO 发起三方通话
  447. session.refer(number);
  448. }
  449. }
  450. },
  451. meeting: function (number) {
  452. if (session) {
  453. if (number == undefined || number == null || number == '') {
  454. number = prompt('请输入咨询号码', '');
  455. }
  456. if (number != undefined && number != "") {
  457. // TODO 发起三方通话
  458. session.refer(number);
  459. }
  460. }
  461. },
  462. monitor: function (number) {
  463. if (session) {
  464. if (number == undefined || number == null || number == '') {
  465. number = prompt('请输入咨询号码', '');
  466. }
  467. if (number != undefined && number != "") {
  468. // TODO 发起三方通话
  469. session.refer(number);
  470. }
  471. }
  472. },
  473. _startRingtone: function (num) {
  474. try {
  475. /*
  476. if(ringtone == null){
  477. ringtone = new Audio();
  478. ringtone.loop = true;
  479. if(num == 2 )
  480. ringtone.src = "../../sounds/3.wav";
  481. else if(num == 3 )
  482. ringtone.src = "../../sounds/4.wav";
  483. else
  484. ringtone.src = "sounds/ringtone.mp3";
  485. }
  486. */
  487. //ringtone.play();
  488. } catch (e) {
  489. console.error(e);
  490. }
  491. },
  492. _stopRingtone: function () {
  493. try {
  494. if(ringtone != null) {
  495. ringtone.pause();
  496. }
  497. } catch (e) {
  498. console.error(e);
  499. }
  500. },
  501. _startRingbackTone: function () {
  502. try {
  503. if(ringBackTone == null){
  504. ringBackTone = new Audio();
  505. ringBackTone.loop = true;
  506. ringBackTone.src = "../../sounds/ringbacktone.wav";
  507. }
  508. //ringBackTone.play();
  509. } catch (e) {
  510. console.error(e);
  511. }
  512. },
  513. _stopRingbackTone: function () {
  514. try {
  515. if(ringBackTone != null){
  516. ringBackTone.pause();
  517. }
  518. } catch (e) {
  519. console.error(e);
  520. }
  521. }
  522. }
  523. $.fn.mySipPhone = function (method) {
  524. if (mySipPhone[method]) {
  525. return mySipPhone[method].apply(this, Array.prototype.slice.call(arguments, 1));
  526. } else if (typeof method === 'object' || !method) {
  527. return mySipPhone.init.apply(this, arguments);
  528. } else {
  529. $.error('Method ' + method + ' does not exist on mySipPhone');
  530. }
  531. };
  532. })(jQuery)