|
|
/* globals SIP,user,moment, Stopwatch */
var ctxSip;
$(document).ready(function() {
if (typeof(user) === 'undefined') {
user = JSON.parse(localStorage.getItem('SIPCreds')); } ctxSip = {
config : { media : { remote : { video: document.getElementById('audioRemote'), audio: document.getElementById('audioRemote') } }, peerConnectionOptions : { rtcConfiguration : { iceServers : [{urls:"stun:stun.l.google.com:19302"}] } }, password : user.Pass, authorizationUser: user.User, displayName : user.Display, uri : user.User +'@'+user.Realm, transportOptions: { traceSip: true, wsServers: user.WSServer, }, registerExpires : 30, ua: {} }, ringtone : document.getElementById('ringtone'), ringbacktone : document.getElementById('ringbacktone'), dtmfTone : document.getElementById('dtmfTone'),
Sessions : [], callTimers : {}, callActiveID : null, callVolume : 1, Stream : null,
/** * Parses a SIP uri and returns a formatted US phone number. * * @param {string} phone number or uri to format * @return {string} formatted number */ formatPhone : function(phone) {
var num;
if (phone.indexOf('@')) { num = phone.split('@')[0]; } else { num = phone; }
num = num.toString().replace(/[^0-9]/g, '');
if (num.length === 10) { return '(' + num.substr(0, 3) + ') ' + num.substr(3, 3) + '-' + num.substr(6,4); } else if (num.length === 11) { return '0' + num.substr(2); } else { return num; } },
// Sound methods
startRingTone : function() { try { ctxSip.ringtone.play(); } catch (e) { } },
stopRingTone : function() { try { ctxSip.ringtone.pause(); } catch (e) { } },
startRingbackTone : function() { try { ctxSip.ringbacktone.play(); } catch (e) { } },
stopRingbackTone : function() { try { ctxSip.ringbacktone.pause(); } catch (e) { } },
// Genereates a rendom string to ID a call
getUniqueID : function() { return Math.random().toString(36).substr(2, 9); },
newSession : function(newSess) {
newSess.displayName = newSess.remoteIdentity.displayName || newSess.remoteIdentity.uri.user; newSess.ctxid = ctxSip.getUniqueID();
var status;
if (newSess.direction === 'incoming') { status = "Incoming: "+ newSess.displayName; ctxSip.startRingTone(); } else { status = "Trying: "+ newSess.displayName; ctxSip.startRingbackTone(); }
ctxSip.logCall(newSess, 'ringing');
ctxSip.setCallSessionStatus(status);
// EVENT CALLBACKS
var remoteVideo = document.getElementById('audioRemote'); var localVideo = document.getElementById('audioRemote'); newSess.on('trackAdded', function() { // We need to check the peer connection to determine which track was added
var pc = newSess.sessionDescriptionHandler.peerConnection; // Gets remote tracks
var remoteStream = new MediaStream(); pc.getReceivers().forEach(function(receiver) { remoteStream.addTrack(receiver.track); }); remoteVideo.srcObject = remoteStream; remoteVideo.play(); }); newSess.on('progress',function(e) { if (e.direction === 'outgoing') { ctxSip.setCallSessionStatus('Calling...'); } });
newSess.on('connecting',function(e) { if (e.direction === 'outgoing') { ctxSip.setCallSessionStatus('Connecting...'); } });
newSess.on('accepted',function(e) { // If there is another active call, hold it
if (ctxSip.callActiveID && ctxSip.callActiveID !== newSess.ctxid) { ctxSip.phoneHoldButtonPressed(ctxSip.callActiveID); }
ctxSip.stopRingbackTone(); ctxSip.stopRingTone(); ctxSip.setCallSessionStatus('Answered'); ctxSip.logCall(newSess, 'answered'); ctxSip.callActiveID = newSess.ctxid;
});
newSess.on('hold', function(e) { ctxSip.callActiveID = null; ctxSip.logCall(newSess, 'holding'); ctxSip.setCallSessionStatus("Holding"); });
newSess.on('unhold', function(e) { ctxSip.logCall(newSess, 'resumed'); ctxSip.callActiveID = newSess.ctxid; ctxSip.setCallSessionStatus("Answered"); });
newSess.on('muted', function(e) { ctxSip.Sessions[newSess.ctxid].isMuted = true; ctxSip.setCallSessionStatus("Muted"); });
newSess.on('unmuted', function(e) { ctxSip.Sessions[newSess.ctxid].isMuted = false; ctxSip.setCallSessionStatus("Answered"); });
newSess.on('cancel', function(e) { ctxSip.stopRingTone(); ctxSip.stopRingbackTone(); ctxSip.setCallSessionStatus("Canceled"); if (this.direction === 'outgoing') { ctxSip.callActiveID = null; newSess = null; ctxSip.logCall(this, 'ended'); } });
newSess.on('bye', function(e) { ctxSip.stopRingTone(); ctxSip.stopRingbackTone(); ctxSip.setCallSessionStatus(""); ctxSip.logCall(newSess, 'ended'); ctxSip.callActiveID = null; newSess = null; });
newSess.on('failed',function(e) { ctxSip.stopRingTone(); ctxSip.stopRingbackTone(); ctxSip.setCallSessionStatus('Terminated'); });
newSess.on('rejected',function(e) { ctxSip.stopRingTone(); ctxSip.stopRingbackTone(); ctxSip.setCallSessionStatus('Rejected'); ctxSip.callActiveID = null; ctxSip.logCall(this, 'ended'); newSess = null; });
ctxSip.Sessions[newSess.ctxid] = newSess;
},
// getUser media request refused or device was not present
getUserMediaFailure : function(e) { window.console.error('getUserMedia failed:', e); ctxSip.setError(true, 'Media Error.', 'You must allow access to your microphone. Check the address bar.', true); },
getUserMediaSuccess : function(stream) { ctxSip.Stream = stream; },
/** * sets the ui call status field * * @param {string} status */ setCallSessionStatus : function(status) { $('#txtCallStatus').html(status); },
/** * sets the ui connection status field * * @param {string} status */ setStatus : function(status) { $("#txtRegStatus").html('<i class="fa fa-signal"></i> '+status); },
/** * logs a call to localstorage * * @param {object} session * @param {string} status Enum 'ringing', 'answered', 'ended', 'holding', 'resumed' */ logCall : function(session, status) {
var log = { clid : session.displayName, uri : session.remoteIdentity.uri.toString(), id : session.ctxid, time : new Date().getTime() }, calllog = JSON.parse(localStorage.getItem('sipCalls'));
if (!calllog) { calllog = {}; }
if (!calllog.hasOwnProperty(session.ctxid)) { calllog[log.id] = { id : log.id, clid : log.clid, uri : log.uri, start : log.time, flow : session.direction }; }
if (status === 'ended') { calllog[log.id].stop = log.time; }
if (status === 'ended' && calllog[log.id].status === 'ringing') { calllog[log.id].status = 'missed'; } else { calllog[log.id].status = status; }
localStorage.setItem('sipCalls', JSON.stringify(calllog)); ctxSip.logShow(); },
/** * adds a ui item to the call log * * @param {object} item log item */ logItem : function(item) {
var callActive = (item.status !== 'ended' && item.status !== 'missed'), callLength = (item.status !== 'ended')? '<span id="'+item.id+'"></span>': moment.duration(item.stop - item.start).humanize(), callClass = '', callIcon, i;
switch (item.status) { case 'ringing' : callClass = 'list-group-item-success'; callIcon = 'fa-bell'; break;
case 'missed' : callClass = 'list-group-item-danger'; if (item.flow === "incoming") { callIcon = 'fa-chevron-left'; } if (item.flow === "outgoing") { callIcon = 'fa-chevron-right'; } break;
case 'holding' : callClass = 'list-group-item-warning'; callIcon = 'fa-pause'; break;
case 'answered' : case 'resumed' : callClass = 'list-group-item-info'; callIcon = 'fa-phone-square'; break;
case 'ended' : if (item.flow === "incoming") { callIcon = 'fa-chevron-left'; } if (item.flow === "outgoing") { callIcon = 'fa-chevron-right'; } break; }
i = '<div class="list-group-item sip-logitem clearfix '+callClass+'" data-uri="'+item.uri+'" data-sessionid="'+item.id+'" title="Double Click to Call">'; i += '<div class="clearfix"><div class="pull-left">'; 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>'; i += '</div>'; i += '<div class="pull-left text-left"><em>'+item.clid+'</em><br>' + callLength+'</div></div>';
if (callActive) { i += '<div class="btn-group btn-group-xs pull-right">'; if (item.status === 'ringing' && item.flow === 'incoming') { i += '<button class="btn btn-xs btn-success btnCall" title="Call"><i class="fa fa-phone"></i></button>'; } else { i += '<button class="btn btn-xs btn-primary btnHoldResume" title="Hold"><i class="fa fa-pause"></i></button>'; i += '<button class="btn btn-xs btn-info btnTransfer" title="Transfer"><i class="fa fa-random"></i></button>'; i += '<button class="btn btn-xs btn-warning btnMute" title="Mute"><i class="fa fa-fw fa-microphone"></i></button>'; } i += '<button class="btn btn-xs btn-danger btnHangUp" title="Hangup"><i class="fa fa-stop"></i></button>'; i += '</div>'; } i += '</div>';
$('#sip-logitems').append(i);
// Start call timer on answer
if (item.status === 'answered') { var tEle = document.getElementById(item.id); ctxSip.callTimers[item.id] = new Stopwatch(tEle); ctxSip.callTimers[item.id].start(); }
if (callActive && item.status !== 'ringing') { ctxSip.callTimers[item.id].start({startTime : item.start}); }
$('#sip-logitems').scrollTop(0); },
/** * updates the call log ui */ logShow : function() {
var calllog = JSON.parse(localStorage.getItem('sipCalls')), x = [];
if (calllog !== null) {
$('#sip-splash').addClass('hide'); $('#sip-log').removeClass('hide');
// empty existing logs
$('#sip-logitems').empty();
// JS doesn't guarantee property order so
// create an array with the start time as
// the key and sort by that.
// Add start time to array
$.each(calllog, function(k,v) { x.push(v); });
// sort descending
x.sort(function(a, b) { return b.start - a.start; });
$.each(x, function(k, v) { ctxSip.logItem(v); });
} else { $('#sip-splash').removeClass('hide'); $('#sip-log').addClass('hide'); } },
/** * removes log items from localstorage and updates the UI */ logClear : function() {
localStorage.removeItem('sipCalls'); ctxSip.logShow(); },
sipCall : function(target) {
try { var s = ctxSip.phone.invite(target, { media : { remote : { video: document.getElementById('audioRemote'), audio: document.getElementById('audioRemote') } }, sessionDescriptionHandlerOptions : { constraints : { audio : true, video : false }, rtcConfiguration : { RTCConstraints : { "optional": [{ 'DtlsSrtpKeyAgreement': 'true'} ]}, stream : ctxSip.Stream } } }); s.direction = 'outgoing'; ctxSip.newSession(s);
var remoteVideo = document.getElementById('audioRemote'); var localVideo = document.getElementById('audioRemote'); s.on('trackAdded', function() { // We need to check the peer connection to determine which track was added
var pc = s.sessionDescriptionHandler.peerConnection; // Gets remote tracks
var remoteStream = new MediaStream(); pc.getReceivers().forEach(function(receiver) { remoteStream.addTrack(receiver.track); }); remoteVideo.srcObject = remoteStream; remoteVideo.play(); });
} catch(e) { throw(e); } },
sipTransfer : function(sessionid) {
var s = ctxSip.Sessions[sessionid], target = window.prompt('Enter destination number', '');
ctxSip.setCallSessionStatus('<i>Transfering the call...</i>'); s.refer(target); },
sipHangUp : function(sessionid) {
var s = ctxSip.Sessions[sessionid]; // s.terminate();
if (!s) { return; } else if (s.startTime) { s.bye(); } else if (s.reject) { s.reject(); } else if (s.cancel) { s.cancel(); }
},
sipSendDTMF : function(digit) {
try { ctxSip.dtmfTone.play(); } catch(e) { }
var a = ctxSip.callActiveID; if (a) { var s = ctxSip.Sessions[a]; s.dtmf(digit); } },
phoneCallButtonPressed : function(sessionid) {
var s = ctxSip.Sessions[sessionid], target = $("#numDisplay").val();
if (!s) {
$("#numDisplay").val(""); ctxSip.sipCall(target);
} else if (s.accept && !s.startTime) {
s.accept({ media : { remote : { video: document.getElementById('audioRemote'), audio: document.getElementById('audioRemote') } }, sessionDescriptionHandlerOptions : { constraints : { audio : true, video : false }, rtcConfiguration : { RTCConstraints : { "optional": [{ 'DtlsSrtpKeyAgreement': 'true'} ]}, stream : ctxSip.Stream } } }); } },
phoneMuteButtonPressed : function (sessionid) {
var s = ctxSip.Sessions[sessionid];
if (!s.isMuted) { s.mute(); } else { s.unmute(); } },
phoneHoldButtonPressed : function(sessionid) {
var s = ctxSip.Sessions[sessionid]; var direction = s.sessionDescriptionHandler.getDirection();
if (direction === 'sendrecv') { console.log('Call is not on hold'); s.hold(); } else { console.log('Call is on hold'); s.unhold(); } },
setError : function(err, title, msg, closable) {
// Show modal if err = true
if (err === true) { $("#mdlError p").html(msg); $("#mdlError").modal('show');
if (closable) { var b = '<button type="button" class="close" data-dismiss="modal">×</button>'; $("#mdlError .modal-header").find('button').remove(); $("#mdlError .modal-header").prepend(b); $("#mdlError .modal-title").html(title); $("#mdlError").modal({ keyboard : true }); } else { $("#mdlError .modal-header").find('button').remove(); $("#mdlError .modal-title").html(title); $("#mdlError").modal({ keyboard : false }); } // $('#numDisplay').prop('disabled', 'disabled');
} else { $('#numDisplay').removeProp('disabled'); $("#mdlError").modal('hide'); } },
/** * Tests for a capable browser, return bool, and shows an * error modal on fail. */ hasWebRTC : function() {
if (navigator.webkitGetUserMedia) { return true; } else if (navigator.mozGetUserMedia) { return true; } else if (navigator.getUserMedia) { return true; } else { ctxSip.setError(true, 'Unsupported Browser.', 'Your browser does not support the features required for this phone.'); window.console.error("WebRTC support not found"); return false; } } };
// Throw an error if the browser can't hack it.
if (!ctxSip.hasWebRTC()) { return true; }
ctxSip.phone = new SIP.UA(ctxSip.config);
ctxSip.phone.on('connected', function(e) { ctxSip.setStatus("Connected"); ctxSip.setError(false) });
ctxSip.phone.on('disconnected', function(e) { ctxSip.setStatus("Disconnected");
// disable phone
ctxSip.setError(true, 'Websocket Disconnected.', 'An Error occurred connecting to the websocket.');
});
ctxSip.phone.on('registered', function(e) { ctxSip.setError(false) var closeEditorWarning = function() { return 'If you close this window, you will not be able to make or receive calls from your browser.'; };
var closePhone = function() { // stop the phone on unload
localStorage.removeItem('ctxPhone'); ctxSip.phone.stop(); };
window.onbeforeunload = closeEditorWarning; window.onunload = closePhone;
// This key is set to prevent multiple windows.
localStorage.setItem('ctxPhone', 'true');
$("#mldError").modal('hide'); ctxSip.setStatus("Ready");
// Get the userMedia and cache the stream
// if (SIP.WebRTC.isSupported()) {
// SIP.WebRTC.getUserMedia({ audio : true, video : false }, ctxSip.getUserMediaSuccess, ctxSip.getUserMediaFailure);
// }
});
ctxSip.phone.on('registrationFailed', function(e) { console.log(e) ctxSip.setError(true, 'Registration Error.', 'An Error occurred registering your phone. Check your settings.'); ctxSip.setStatus("Error: Registration Failed"); });
ctxSip.phone.on('unregistered', function(e) { ctxSip.setError(true, 'Registration Error.', 'An Error occurred registering your phone. Please wait while we try to connect it again.'); ctxSip.setStatus("Error: Registration Failed"); });
ctxSip.phone.on('invite', function (incomingSession) { var s = incomingSession; s.direction = 'incoming'; ctxSip.newSession(s);
},);
// Auto-focus number input on backspace.
$('#sipClient').keydown(function(event) { if (event.which === 8) { $('#numDisplay').focus(); } });
$('#numDisplay').keypress(function(e) { // Enter pressed? so Dial.
if (e.which === 13) { ctxSip.phoneCallButtonPressed(); } });
$('#phoneIcon').click(function(eveny) { // Enter pressed? so Dial.
event.preventDefault(); ctxSip.phoneCallButtonPressed(); });
$('.digit').click(function(event) { event.preventDefault(); var num = $('#numDisplay').val(), dig = $(this).data('digit');
$('#numDisplay').val(num+dig);
ctxSip.sipSendDTMF(dig); return false; });
$('#phoneUI .dropdown-menu').click(function(e) { e.preventDefault(); });
$('#phoneUI').delegate('.btnCall', 'click', function(event) { ctxSip.phoneCallButtonPressed(); // to close the dropdown
return true; });
$('.sipLogClear').click(function(event) { event.preventDefault(); ctxSip.logClear(); });
$('#sip-logitems').delegate('.sip-logitem .btnCall', 'click', function(event) { var sessionid = $(this).closest('.sip-logitem').data('sessionid'); ctxSip.phoneCallButtonPressed(sessionid); return false; });
$('#sip-logitems').delegate('.sip-logitem .btnHoldResume', 'click', function(event) { var sessionid = $(this).closest('.sip-logitem').data('sessionid'); ctxSip.phoneHoldButtonPressed(sessionid); return false; });
$('#sip-logitems').delegate('.sip-logitem .btnHangUp', 'click', function(event) { var sessionid = $(this).closest('.sip-logitem').data('sessionid'); ctxSip.sipHangUp(sessionid); return false; });
$('#sip-logitems').delegate('.sip-logitem .btnTransfer', 'click', function(event) { var sessionid = $(this).closest('.sip-logitem').data('sessionid'); ctxSip.sipTransfer(sessionid); return false; });
$('#sip-logitems').delegate('.sip-logitem .btnMute', 'click', function(event) { var sessionid = $(this).closest('.sip-logitem').data('sessionid'); ctxSip.phoneMuteButtonPressed(sessionid); return false; });
$('#sip-logitems').delegate('.sip-logitem', 'dblclick', function(event) { event.preventDefault();
var uri = $(this).data('uri'); $('#numDisplay').val(uri); ctxSip.phoneCallButtonPressed(); });
$('#sldVolume').on('change', function() {
var v = $(this).val() / 100, // player = $('audio').get()[0],
btn = $('#btnVol'), icon = $('#btnVol').find('i'), active = ctxSip.callActiveID;
// Set the object and media stream volumes
if (ctxSip.Sessions[active]) { ctxSip.Sessions[active].player.volume = v; ctxSip.callVolume = v; }
// Set the others
$('audio').each(function() { $(this).get()[0].volume = v; });
if (v < 0.1) { btn.removeClass(function (index, css) { return (css.match (/(^|\s)btn\S+/g) || []).join(' '); }) .addClass('btn btn-sm btn-danger'); icon.removeClass().addClass('fa fa-fw fa-volume-off'); } else if (v < 0.8) { btn.removeClass(function (index, css) { return (css.match (/(^|\s)btn\S+/g) || []).join(' '); }).addClass('btn btn-sm btn-info'); icon.removeClass().addClass('fa fa-fw fa-volume-down'); } else { btn.removeClass(function (index, css) { return (css.match (/(^|\s)btn\S+/g) || []).join(' '); }).addClass('btn btn-sm btn-primary'); icon.removeClass().addClass('fa fa-fw fa-volume-up'); } return false; });
// Hide the spalsh after 3 secs.
setTimeout(function() { ctxSip.logShow(); }, 3000);
/** * Stopwatch object used for call timers * * @param {dom element} elem * @param {[object]} options */ var Stopwatch = function(elem, options) {
// private functions
function createTimer() { return document.createElement("span"); }
var timer = createTimer(), offset, clock, interval;
// default options
options = options || {}; options.delay = options.delay || 1000; options.startTime = options.startTime || Date.now();
// append elements
elem.appendChild(timer);
function start() { if (!interval) { offset = options.startTime; interval = setInterval(update, options.delay); } }
function stop() { if (interval) { clearInterval(interval); interval = null; } }
function reset() { clock = 0; render(); }
function update() { clock += delta(); render(); }
function render() { timer.innerHTML = moment(clock).format('mm:ss'); }
function delta() { var now = Date.now(), d = now - offset;
offset = now; return d; }
// initialize
reset();
// public API
this.start = start; //function() { start; }
this.stop = stop; //function() { stop; }
};
});
|