Fork of FusionPBX but with LDAP kinda working
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

895 lines
27 KiB

  1. /* globals SIP,user,moment, Stopwatch */
  2. var ctxSip;
  3. $(document).ready(function() {
  4. if (typeof(user) === 'undefined') {
  5. user = JSON.parse(localStorage.getItem('SIPCreds'));
  6. }
  7. ctxSip = {
  8. config : {
  9. media : {
  10. remote : {
  11. video: document.getElementById('audioRemote'),
  12. audio: document.getElementById('audioRemote')
  13. }
  14. },
  15. peerConnectionOptions : {
  16. rtcConfiguration : {
  17. iceServers : [{urls:"stun:stun.l.google.com:19302"}]
  18. }
  19. },
  20. password : user.Pass,
  21. authorizationUser: user.User,
  22. displayName : user.Display,
  23. uri : user.User +'@'+user.Realm,
  24. transportOptions: {
  25. traceSip: true,
  26. wsServers: user.WSServer,
  27. },
  28. registerExpires : 30,
  29. ua: {}
  30. },
  31. ringtone : document.getElementById('ringtone'),
  32. ringbacktone : document.getElementById('ringbacktone'),
  33. dtmfTone : document.getElementById('dtmfTone'),
  34. Sessions : [],
  35. callTimers : {},
  36. callActiveID : null,
  37. callVolume : 1,
  38. Stream : null,
  39. /**
  40. * Parses a SIP uri and returns a formatted US phone number.
  41. *
  42. * @param {string} phone number or uri to format
  43. * @return {string} formatted number
  44. */
  45. formatPhone : function(phone) {
  46. var num;
  47. if (phone.indexOf('@')) {
  48. num = phone.split('@')[0];
  49. } else {
  50. num = phone;
  51. }
  52. num = num.toString().replace(/[^0-9]/g, '');
  53. if (num.length === 10) {
  54. return '(' + num.substr(0, 3) + ') ' + num.substr(3, 3) + '-' + num.substr(6,4);
  55. } else if (num.length === 11) {
  56. return '0' + num.substr(2);
  57. } else {
  58. return num;
  59. }
  60. },
  61. // Sound methods
  62. startRingTone : function() {
  63. try { ctxSip.ringtone.play(); } catch (e) { }
  64. },
  65. stopRingTone : function() {
  66. try { ctxSip.ringtone.pause(); } catch (e) { }
  67. },
  68. startRingbackTone : function() {
  69. try { ctxSip.ringbacktone.play(); } catch (e) { }
  70. },
  71. stopRingbackTone : function() {
  72. try { ctxSip.ringbacktone.pause(); } catch (e) { }
  73. },
  74. // Genereates a rendom string to ID a call
  75. getUniqueID : function() {
  76. return Math.random().toString(36).substr(2, 9);
  77. },
  78. newSession : function(newSess) {
  79. newSess.displayName = newSess.remoteIdentity.displayName || newSess.remoteIdentity.uri.user;
  80. newSess.ctxid = ctxSip.getUniqueID();
  81. var status;
  82. if (newSess.direction === 'incoming') {
  83. status = "Incoming: "+ newSess.displayName;
  84. ctxSip.startRingTone();
  85. } else {
  86. status = "Trying: "+ newSess.displayName;
  87. ctxSip.startRingbackTone();
  88. }
  89. ctxSip.logCall(newSess, 'ringing');
  90. ctxSip.setCallSessionStatus(status);
  91. // EVENT CALLBACKS
  92. var remoteVideo = document.getElementById('audioRemote');
  93. var localVideo = document.getElementById('audioRemote');
  94. newSess.on('trackAdded', function() {
  95. // We need to check the peer connection to determine which track was added
  96. var pc = newSess.sessionDescriptionHandler.peerConnection;
  97. // Gets remote tracks
  98. var remoteStream = new MediaStream();
  99. pc.getReceivers().forEach(function(receiver) {
  100. remoteStream.addTrack(receiver.track);
  101. });
  102. remoteVideo.srcObject = remoteStream;
  103. remoteVideo.play();
  104. });
  105. newSess.on('progress',function(e) {
  106. if (e.direction === 'outgoing') {
  107. ctxSip.setCallSessionStatus('Calling...');
  108. }
  109. });
  110. newSess.on('connecting',function(e) {
  111. if (e.direction === 'outgoing') {
  112. ctxSip.setCallSessionStatus('Connecting...');
  113. }
  114. });
  115. newSess.on('accepted',function(e) {
  116. // If there is another active call, hold it
  117. if (ctxSip.callActiveID && ctxSip.callActiveID !== newSess.ctxid) {
  118. ctxSip.phoneHoldButtonPressed(ctxSip.callActiveID);
  119. }
  120. ctxSip.stopRingbackTone();
  121. ctxSip.stopRingTone();
  122. ctxSip.setCallSessionStatus('Answered');
  123. ctxSip.logCall(newSess, 'answered');
  124. ctxSip.callActiveID = newSess.ctxid;
  125. });
  126. newSess.on('hold', function(e) {
  127. ctxSip.callActiveID = null;
  128. ctxSip.logCall(newSess, 'holding');
  129. ctxSip.setCallSessionStatus("Holding");
  130. });
  131. newSess.on('unhold', function(e) {
  132. ctxSip.logCall(newSess, 'resumed');
  133. ctxSip.callActiveID = newSess.ctxid;
  134. ctxSip.setCallSessionStatus("Answered");
  135. });
  136. newSess.on('muted', function(e) {
  137. ctxSip.Sessions[newSess.ctxid].isMuted = true;
  138. ctxSip.setCallSessionStatus("Muted");
  139. });
  140. newSess.on('unmuted', function(e) {
  141. ctxSip.Sessions[newSess.ctxid].isMuted = false;
  142. ctxSip.setCallSessionStatus("Answered");
  143. });
  144. newSess.on('cancel', function(e) {
  145. ctxSip.stopRingTone();
  146. ctxSip.stopRingbackTone();
  147. ctxSip.setCallSessionStatus("Canceled");
  148. if (this.direction === 'outgoing') {
  149. ctxSip.callActiveID = null;
  150. newSess = null;
  151. ctxSip.logCall(this, 'ended');
  152. }
  153. });
  154. newSess.on('bye', function(e) {
  155. ctxSip.stopRingTone();
  156. ctxSip.stopRingbackTone();
  157. ctxSip.setCallSessionStatus("");
  158. ctxSip.logCall(newSess, 'ended');
  159. ctxSip.callActiveID = null;
  160. newSess = null;
  161. });
  162. newSess.on('failed',function(e) {
  163. ctxSip.stopRingTone();
  164. ctxSip.stopRingbackTone();
  165. ctxSip.setCallSessionStatus('Terminated');
  166. });
  167. newSess.on('rejected',function(e) {
  168. ctxSip.stopRingTone();
  169. ctxSip.stopRingbackTone();
  170. ctxSip.setCallSessionStatus('Rejected');
  171. ctxSip.callActiveID = null;
  172. ctxSip.logCall(this, 'ended');
  173. newSess = null;
  174. });
  175. ctxSip.Sessions[newSess.ctxid] = newSess;
  176. },
  177. // getUser media request refused or device was not present
  178. getUserMediaFailure : function(e) {
  179. window.console.error('getUserMedia failed:', e);
  180. ctxSip.setError(true, 'Media Error.', 'You must allow access to your microphone. Check the address bar.', true);
  181. },
  182. getUserMediaSuccess : function(stream) {
  183. ctxSip.Stream = stream;
  184. },
  185. /**
  186. * sets the ui call status field
  187. *
  188. * @param {string} status
  189. */
  190. setCallSessionStatus : function(status) {
  191. $('#txtCallStatus').html(status);
  192. },
  193. /**
  194. * sets the ui connection status field
  195. *
  196. * @param {string} status
  197. */
  198. setStatus : function(status) {
  199. $("#txtRegStatus").html('<i class="fa fa-signal"></i> '+status);
  200. },
  201. /**
  202. * logs a call to localstorage
  203. *
  204. * @param {object} session
  205. * @param {string} status Enum 'ringing', 'answered', 'ended', 'holding', 'resumed'
  206. */
  207. logCall : function(session, status) {
  208. var log = {
  209. clid : session.displayName,
  210. uri : session.remoteIdentity.uri.toString(),
  211. id : session.ctxid,
  212. time : new Date().getTime()
  213. },
  214. calllog = JSON.parse(localStorage.getItem('sipCalls'));
  215. if (!calllog) { calllog = {}; }
  216. if (!calllog.hasOwnProperty(session.ctxid)) {
  217. calllog[log.id] = {
  218. id : log.id,
  219. clid : log.clid,
  220. uri : log.uri,
  221. start : log.time,
  222. flow : session.direction
  223. };
  224. }
  225. if (status === 'ended') {
  226. calllog[log.id].stop = log.time;
  227. }
  228. if (status === 'ended' && calllog[log.id].status === 'ringing') {
  229. calllog[log.id].status = 'missed';
  230. } else {
  231. calllog[log.id].status = status;
  232. }
  233. localStorage.setItem('sipCalls', JSON.stringify(calllog));
  234. ctxSip.logShow();
  235. },
  236. /**
  237. * adds a ui item to the call log
  238. *
  239. * @param {object} item log item
  240. */
  241. logItem : function(item) {
  242. var callActive = (item.status !== 'ended' && item.status !== 'missed'),
  243. callLength = (item.status !== 'ended')? '<span id="'+item.id+'"></span>': moment.duration(item.stop - item.start).humanize(),
  244. callClass = '',
  245. callIcon,
  246. i;
  247. switch (item.status) {
  248. case 'ringing' :
  249. callClass = 'list-group-item-success';
  250. callIcon = 'fa-bell';
  251. break;
  252. case 'missed' :
  253. callClass = 'list-group-item-danger';
  254. if (item.flow === "incoming") { callIcon = 'fa-chevron-left'; }
  255. if (item.flow === "outgoing") { callIcon = 'fa-chevron-right'; }
  256. break;
  257. case 'holding' :
  258. callClass = 'list-group-item-warning';
  259. callIcon = 'fa-pause';
  260. break;
  261. case 'answered' :
  262. case 'resumed' :
  263. callClass = 'list-group-item-info';
  264. callIcon = 'fa-phone-square';
  265. break;
  266. case 'ended' :
  267. if (item.flow === "incoming") { callIcon = 'fa-chevron-left'; }
  268. if (item.flow === "outgoing") { callIcon = 'fa-chevron-right'; }
  269. break;
  270. }
  271. i = '<div class="list-group-item sip-logitem clearfix '+callClass+'" data-uri="'+item.uri+'" data-sessionid="'+item.id+'" title="Double Click to Call">';
  272. i += '<div class="clearfix"><div class="pull-left">';
  273. i += '<i class="fa fa-fw '+callIcon+' fa-fw"></i> <strong>'+ctxSip.formatPhone(item.uri)+'</strong><br><small>'+moment(item.start).format('MM/DD hh:mm:ss a')+'</small>';
  274. i += '</div>';
  275. i += '<div class="pull-left text-left"><em>'+item.clid+'</em><br>' + callLength+'</div></div>';
  276. if (callActive) {
  277. i += '<div class="btn-group btn-group-xs pull-right">';
  278. if (item.status === 'ringing' && item.flow === 'incoming') {
  279. i += '<button class="btn btn-xs btn-success btnCall" title="Call"><i class="fa fa-phone"></i></button>';
  280. } else {
  281. i += '<button class="btn btn-xs btn-primary btnHoldResume" title="Hold"><i class="fa fa-pause"></i></button>';
  282. i += '<button class="btn btn-xs btn-info btnTransfer" title="Transfer"><i class="fa fa-random"></i></button>';
  283. i += '<button class="btn btn-xs btn-warning btnMute" title="Mute"><i class="fa fa-fw fa-microphone"></i></button>';
  284. }
  285. i += '<button class="btn btn-xs btn-danger btnHangUp" title="Hangup"><i class="fa fa-stop"></i></button>';
  286. i += '</div>';
  287. }
  288. i += '</div>';
  289. $('#sip-logitems').append(i);
  290. // Start call timer on answer
  291. if (item.status === 'answered') {
  292. var tEle = document.getElementById(item.id);
  293. ctxSip.callTimers[item.id] = new Stopwatch(tEle);
  294. ctxSip.callTimers[item.id].start();
  295. }
  296. if (callActive && item.status !== 'ringing') {
  297. ctxSip.callTimers[item.id].start({startTime : item.start});
  298. }
  299. $('#sip-logitems').scrollTop(0);
  300. },
  301. /**
  302. * updates the call log ui
  303. */
  304. logShow : function() {
  305. var calllog = JSON.parse(localStorage.getItem('sipCalls')),
  306. x = [];
  307. if (calllog !== null) {
  308. $('#sip-splash').addClass('hide');
  309. $('#sip-log').removeClass('hide');
  310. // empty existing logs
  311. $('#sip-logitems').empty();
  312. // JS doesn't guarantee property order so
  313. // create an array with the start time as
  314. // the key and sort by that.
  315. // Add start time to array
  316. $.each(calllog, function(k,v) {
  317. x.push(v);
  318. });
  319. // sort descending
  320. x.sort(function(a, b) {
  321. return b.start - a.start;
  322. });
  323. $.each(x, function(k, v) {
  324. ctxSip.logItem(v);
  325. });
  326. } else {
  327. $('#sip-splash').removeClass('hide');
  328. $('#sip-log').addClass('hide');
  329. }
  330. },
  331. /**
  332. * removes log items from localstorage and updates the UI
  333. */
  334. logClear : function() {
  335. localStorage.removeItem('sipCalls');
  336. ctxSip.logShow();
  337. },
  338. sipCall : function(target) {
  339. try {
  340. var s = ctxSip.phone.invite(target, {
  341. media : {
  342. remote : {
  343. video: document.getElementById('audioRemote'),
  344. audio: document.getElementById('audioRemote')
  345. }
  346. },
  347. sessionDescriptionHandlerOptions : {
  348. constraints : { audio : true, video : false },
  349. rtcConfiguration : { RTCConstraints : { "optional": [{ 'DtlsSrtpKeyAgreement': 'true'} ]}, stream : ctxSip.Stream }
  350. }
  351. });
  352. s.direction = 'outgoing';
  353. ctxSip.newSession(s);
  354. var remoteVideo = document.getElementById('audioRemote');
  355. var localVideo = document.getElementById('audioRemote');
  356. s.on('trackAdded', function() {
  357. // We need to check the peer connection to determine which track was added
  358. var pc = s.sessionDescriptionHandler.peerConnection;
  359. // Gets remote tracks
  360. var remoteStream = new MediaStream();
  361. pc.getReceivers().forEach(function(receiver) {
  362. remoteStream.addTrack(receiver.track);
  363. });
  364. remoteVideo.srcObject = remoteStream;
  365. remoteVideo.play();
  366. });
  367. } catch(e) {
  368. throw(e);
  369. }
  370. },
  371. sipTransfer : function(sessionid) {
  372. var s = ctxSip.Sessions[sessionid],
  373. target = window.prompt('Enter destination number', '');
  374. ctxSip.setCallSessionStatus('<i>Transfering the call...</i>');
  375. s.refer(target);
  376. },
  377. sipHangUp : function(sessionid) {
  378. var s = ctxSip.Sessions[sessionid];
  379. // s.terminate();
  380. if (!s) {
  381. return;
  382. } else if (s.startTime) {
  383. s.bye();
  384. } else if (s.reject) {
  385. s.reject();
  386. } else if (s.cancel) {
  387. s.cancel();
  388. }
  389. },
  390. sipSendDTMF : function(digit) {
  391. try { ctxSip.dtmfTone.play(); } catch(e) { }
  392. var a = ctxSip.callActiveID;
  393. if (a) {
  394. var s = ctxSip.Sessions[a];
  395. s.dtmf(digit);
  396. }
  397. },
  398. phoneCallButtonPressed : function(sessionid) {
  399. var s = ctxSip.Sessions[sessionid],
  400. target = $("#numDisplay").val();
  401. if (!s) {
  402. $("#numDisplay").val("");
  403. ctxSip.sipCall(target);
  404. } else if (s.accept && !s.startTime) {
  405. s.accept({
  406. media : {
  407. remote : {
  408. video: document.getElementById('audioRemote'),
  409. audio: document.getElementById('audioRemote')
  410. }
  411. },
  412. sessionDescriptionHandlerOptions : {
  413. constraints : { audio : true, video : false },
  414. rtcConfiguration : { RTCConstraints : { "optional": [{ 'DtlsSrtpKeyAgreement': 'true'} ]}, stream : ctxSip.Stream }
  415. }
  416. });
  417. }
  418. },
  419. phoneMuteButtonPressed : function (sessionid) {
  420. var s = ctxSip.Sessions[sessionid];
  421. if (!s.isMuted) {
  422. s.mute();
  423. } else {
  424. s.unmute();
  425. }
  426. },
  427. phoneHoldButtonPressed : function(sessionid) {
  428. var s = ctxSip.Sessions[sessionid];
  429. var direction = s.sessionDescriptionHandler.getDirection();
  430. if (direction === 'sendrecv') {
  431. console.log('Call is not on hold');
  432. s.hold();
  433. } else {
  434. console.log('Call is on hold');
  435. s.unhold();
  436. }
  437. },
  438. setError : function(err, title, msg, closable) {
  439. // Show modal if err = true
  440. if (err === true) {
  441. $("#mdlError p").html(msg);
  442. $("#mdlError").modal('show');
  443. if (closable) {
  444. var b = '<button type="button" class="close" data-dismiss="modal">&times;</button>';
  445. $("#mdlError .modal-header").find('button').remove();
  446. $("#mdlError .modal-header").prepend(b);
  447. $("#mdlError .modal-title").html(title);
  448. $("#mdlError").modal({ keyboard : true });
  449. } else {
  450. $("#mdlError .modal-header").find('button').remove();
  451. $("#mdlError .modal-title").html(title);
  452. $("#mdlError").modal({ keyboard : false });
  453. }
  454. // $('#numDisplay').prop('disabled', 'disabled');
  455. } else {
  456. $('#numDisplay').removeProp('disabled');
  457. $("#mdlError").modal('hide');
  458. }
  459. },
  460. /**
  461. * Tests for a capable browser, return bool, and shows an
  462. * error modal on fail.
  463. */
  464. hasWebRTC : function() {
  465. if (navigator.webkitGetUserMedia) {
  466. return true;
  467. } else if (navigator.mozGetUserMedia) {
  468. return true;
  469. } else if (navigator.getUserMedia) {
  470. return true;
  471. } else {
  472. ctxSip.setError(true, 'Unsupported Browser.', 'Your browser does not support the features required for this phone.');
  473. window.console.error("WebRTC support not found");
  474. return false;
  475. }
  476. }
  477. };
  478. // Throw an error if the browser can't hack it.
  479. if (!ctxSip.hasWebRTC()) {
  480. return true;
  481. }
  482. ctxSip.phone = new SIP.UA(ctxSip.config);
  483. ctxSip.phone.on('connected', function(e) {
  484. ctxSip.setStatus("Connected");
  485. ctxSip.setError(false)
  486. });
  487. ctxSip.phone.on('disconnected', function(e) {
  488. ctxSip.setStatus("Disconnected");
  489. // disable phone
  490. ctxSip.setError(true, 'Websocket Disconnected.', 'An Error occurred connecting to the websocket.');
  491. });
  492. ctxSip.phone.on('registered', function(e) {
  493. ctxSip.setError(false)
  494. var closeEditorWarning = function() {
  495. return 'If you close this window, you will not be able to make or receive calls from your browser.';
  496. };
  497. var closePhone = function() {
  498. // stop the phone on unload
  499. localStorage.removeItem('ctxPhone');
  500. ctxSip.phone.stop();
  501. };
  502. window.onbeforeunload = closeEditorWarning;
  503. window.onunload = closePhone;
  504. // This key is set to prevent multiple windows.
  505. localStorage.setItem('ctxPhone', 'true');
  506. $("#mldError").modal('hide');
  507. ctxSip.setStatus("Ready");
  508. // Get the userMedia and cache the stream
  509. // if (SIP.WebRTC.isSupported()) {
  510. // SIP.WebRTC.getUserMedia({ audio : true, video : false }, ctxSip.getUserMediaSuccess, ctxSip.getUserMediaFailure);
  511. // }
  512. });
  513. ctxSip.phone.on('registrationFailed', function(e) {
  514. console.log(e)
  515. ctxSip.setError(true, 'Registration Error.', 'An Error occurred registering your phone. Check your settings.');
  516. ctxSip.setStatus("Error: Registration Failed");
  517. });
  518. ctxSip.phone.on('unregistered', function(e) {
  519. ctxSip.setError(true, 'Registration Error.', 'An Error occurred registering your phone. Please wait while we try to connect it again.');
  520. ctxSip.setStatus("Error: Registration Failed");
  521. });
  522. ctxSip.phone.on('invite',
  523. function (incomingSession) {
  524. var s = incomingSession;
  525. s.direction = 'incoming';
  526. ctxSip.newSession(s);
  527. },);
  528. // Auto-focus number input on backspace.
  529. $('#sipClient').keydown(function(event) {
  530. if (event.which === 8) {
  531. $('#numDisplay').focus();
  532. }
  533. });
  534. $('#numDisplay').keypress(function(e) {
  535. // Enter pressed? so Dial.
  536. if (e.which === 13) {
  537. ctxSip.phoneCallButtonPressed();
  538. }
  539. });
  540. $('#phoneIcon').click(function(eveny) {
  541. // Enter pressed? so Dial.
  542. event.preventDefault();
  543. ctxSip.phoneCallButtonPressed();
  544. });
  545. $('.digit').click(function(event) {
  546. event.preventDefault();
  547. var num = $('#numDisplay').val(),
  548. dig = $(this).data('digit');
  549. $('#numDisplay').val(num+dig);
  550. ctxSip.sipSendDTMF(dig);
  551. return false;
  552. });
  553. $('#phoneUI .dropdown-menu').click(function(e) {
  554. e.preventDefault();
  555. });
  556. $('#phoneUI').delegate('.btnCall', 'click', function(event) {
  557. ctxSip.phoneCallButtonPressed();
  558. // to close the dropdown
  559. return true;
  560. });
  561. $('.sipLogClear').click(function(event) {
  562. event.preventDefault();
  563. ctxSip.logClear();
  564. });
  565. $('#sip-logitems').delegate('.sip-logitem .btnCall', 'click', function(event) {
  566. var sessionid = $(this).closest('.sip-logitem').data('sessionid');
  567. ctxSip.phoneCallButtonPressed(sessionid);
  568. return false;
  569. });
  570. $('#sip-logitems').delegate('.sip-logitem .btnHoldResume', 'click', function(event) {
  571. var sessionid = $(this).closest('.sip-logitem').data('sessionid');
  572. ctxSip.phoneHoldButtonPressed(sessionid);
  573. return false;
  574. });
  575. $('#sip-logitems').delegate('.sip-logitem .btnHangUp', 'click', function(event) {
  576. var sessionid = $(this).closest('.sip-logitem').data('sessionid');
  577. ctxSip.sipHangUp(sessionid);
  578. return false;
  579. });
  580. $('#sip-logitems').delegate('.sip-logitem .btnTransfer', 'click', function(event) {
  581. var sessionid = $(this).closest('.sip-logitem').data('sessionid');
  582. ctxSip.sipTransfer(sessionid);
  583. return false;
  584. });
  585. $('#sip-logitems').delegate('.sip-logitem .btnMute', 'click', function(event) {
  586. var sessionid = $(this).closest('.sip-logitem').data('sessionid');
  587. ctxSip.phoneMuteButtonPressed(sessionid);
  588. return false;
  589. });
  590. $('#sip-logitems').delegate('.sip-logitem', 'dblclick', function(event) {
  591. event.preventDefault();
  592. var uri = $(this).data('uri');
  593. $('#numDisplay').val(uri);
  594. ctxSip.phoneCallButtonPressed();
  595. });
  596. $('#sldVolume').on('change', function() {
  597. var v = $(this).val() / 100,
  598. // player = $('audio').get()[0],
  599. btn = $('#btnVol'),
  600. icon = $('#btnVol').find('i'),
  601. active = ctxSip.callActiveID;
  602. // Set the object and media stream volumes
  603. if (ctxSip.Sessions[active]) {
  604. ctxSip.Sessions[active].player.volume = v;
  605. ctxSip.callVolume = v;
  606. }
  607. // Set the others
  608. $('audio').each(function() {
  609. $(this).get()[0].volume = v;
  610. });
  611. if (v < 0.1) {
  612. btn.removeClass(function (index, css) {
  613. return (css.match (/(^|\s)btn\S+/g) || []).join(' ');
  614. })
  615. .addClass('btn btn-sm btn-danger');
  616. icon.removeClass().addClass('fa fa-fw fa-volume-off');
  617. } else if (v < 0.8) {
  618. btn.removeClass(function (index, css) {
  619. return (css.match (/(^|\s)btn\S+/g) || []).join(' ');
  620. }).addClass('btn btn-sm btn-info');
  621. icon.removeClass().addClass('fa fa-fw fa-volume-down');
  622. } else {
  623. btn.removeClass(function (index, css) {
  624. return (css.match (/(^|\s)btn\S+/g) || []).join(' ');
  625. }).addClass('btn btn-sm btn-primary');
  626. icon.removeClass().addClass('fa fa-fw fa-volume-up');
  627. }
  628. return false;
  629. });
  630. // Hide the spalsh after 3 secs.
  631. setTimeout(function() {
  632. ctxSip.logShow();
  633. }, 3000);
  634. /**
  635. * Stopwatch object used for call timers
  636. *
  637. * @param {dom element} elem
  638. * @param {[object]} options
  639. */
  640. var Stopwatch = function(elem, options) {
  641. // private functions
  642. function createTimer() {
  643. return document.createElement("span");
  644. }
  645. var timer = createTimer(),
  646. offset,
  647. clock,
  648. interval;
  649. // default options
  650. options = options || {};
  651. options.delay = options.delay || 1000;
  652. options.startTime = options.startTime || Date.now();
  653. // append elements
  654. elem.appendChild(timer);
  655. function start() {
  656. if (!interval) {
  657. offset = options.startTime;
  658. interval = setInterval(update, options.delay);
  659. }
  660. }
  661. function stop() {
  662. if (interval) {
  663. clearInterval(interval);
  664. interval = null;
  665. }
  666. }
  667. function reset() {
  668. clock = 0;
  669. render();
  670. }
  671. function update() {
  672. clock += delta();
  673. render();
  674. }
  675. function render() {
  676. timer.innerHTML = moment(clock).format('mm:ss');
  677. }
  678. function delta() {
  679. var now = Date.now(),
  680. d = now - offset;
  681. offset = now;
  682. return d;
  683. }
  684. // initialize
  685. reset();
  686. // public API
  687. this.start = start; //function() { start; }
  688. this.stop = stop; //function() { stop; }
  689. };
  690. });