/* Jappix - An open social platform These are the call common management functions ------------------------------------------------- License: AGPL Author: Valérian Saliou */ // Bundle var Call = (function() { /** * Alias of this * @private */ var self = {}; /* Variables */ self._start_stamp = 0; self.call_auto_accept = { 'from' : null, 'sid' : null }; /** * Provides an adapter to the JSJaCJingle console implementation which is different * @private * @return {object} */ self._consoleAdapter = (function() { /** * Alias of this * @private */ var _console = {}; /** * Console logging interface (adapted) * @public * @param {string} message * @param {number} loglevel * @return {undefined} */ _console.log = function(message, loglevel) { try { if(!message) { throw 'No message passed to console adapter!'; } switch(loglevel) { case 0: Console.warn(message); break; case 1: Console.error(message); break; case 2: Console.info(message); break; case 4: Console.debug(message); break; default: Console.log(message); } } catch(e) { Console.error('Call._consoleAdapter.log', e); } }; /** * Return sub-class scope */ return _console; })(); /** * Initializes Jingle router * @public * @return {undefined} */ self.init = function() { try { // Listen for incoming Jingle/Muji packet JSJaCJingle.listen({ connection: con, debug: self._consoleAdapter, // TODO: setting a fallback fucks up some calls... // fallback: './server/jingle.php', single_initiate: function(stanza) { try { // Already in a call? if(self.is_ongoing()) { // Try to restore SID there var stanza_id = stanza.getID(); var sid = null; if(stanza_id) { var stanza_id_split = stanza_id.split('_'); sid = stanza_id_split[1]; } // Build a temporary Jingle session var jingle_close = new JSJaCJingle.session( JSJAC_JINGLE_SESSION_SINGLE, { to: stanza.getFrom(), debug: JSJaCJingleStorage.get_debug() } ); if(sid) { jingle_close._set_sid(sid); } jingle_close.terminate(JSJAC_JINGLE_REASON_BUSY); Console.warn('session_initiate_success', 'Dropped incoming call because already in a call.'); return; } var xid = Common.fullXID(Common.getStanzaFrom(stanza)); Console.info('Incoming call from: ' + xid); // Session values Jingle.receive(xid, stanza); } catch(e) { Console.error('Call.init[single_initiate]', e); } }, single_propose: function(stanza, proposed_medias) { try { var stanza_from = stanza.getFrom() || null; var call_id = JSJaCJingleBroadcast.get_call_id(stanza); // Request for Jingle session to be accepted if(stanza_from && call_id) { var call_media_main = 'audio'; if(JSJAC_JINGLE_MEDIA_VIDEO in proposed_medias) { call_media_main = 'video'; } Call.notify( JSJAC_JINGLE_SESSION_SINGLE, Common.bareXID(stanza_from), ('broadcast_' + call_media_main), call_media_main, null, { full_xid: stanza_from, call_id: call_id, medias: proposed_medias } ); Audio.play('incoming-call', true); // Save initiator (security: don't save SID until it's accepted) self.call_auto_accept.from = stanza_from; self.call_auto_accept.sid = null; } } catch(e) { Console.error('Call.init[single_propose]', e); } }, single_retract: function(stanza) { try { var stanza_from = stanza.getFrom() || null; var call_id = JSJaCJingleBroadcast.get_call_id(stanza); var call_medias = JSJaCJingleBroadcast.get_call_medias(call_id); // Call retracted (from initiator) if(self.call_auto_accept.from == stanza_from) { Audio.stop('incoming-call'); Call.notify( JSJAC_JINGLE_SESSION_SINGLE, Common.bareXID(stanza_from), 'remote_canceled' ); } } catch(e) { Console.error('Call.init[single_retract]', e); } }, single_accept: function(stanza) { try { var stanza_from = stanza.getFrom() || null; var call_id = JSJaCJingleBroadcast.get_call_id(stanza); // Another resource accepted the call if(self.call_auto_accept.sid == call_id && stanza_from && Common.getFullXID() != stanza_from) { self._unnotify(); Audio.stop('incoming-call'); } } catch(e) { Console.error('Call.init[single_accept]', e); } }, single_reject: function(stanza) { try { var stanza_from = stanza.getFrom() || null; var call_id = JSJaCJingleBroadcast.get_call_id(stanza); // Another resource rejected the call if(self.call_auto_accept.sid == call_id && stanza_from && Common.getFullXID() != stanza_from) { self._unnotify(); Audio.stop('incoming-call'); } } catch(e) { Console.error('Call.init[single_reject]', e); } }, single_proceed: function(stanza) { try { // Read broadcast parameters var call_to = stanza.getFrom() || null; var call_id = JSJaCJingleBroadcast.get_call_id(stanza); var call_medias = JSJaCJingleBroadcast.get_call_medias(call_id); // Check medias to include var has_media_video = false; for(var i = 0; i < call_medias.length; i++) { if(call_medias[i] === JSJAC_JINGLE_MEDIA_VIDEO) { has_media_video = true; break; } } var call_media_picked = has_media_video ? JSJAC_JINGLE_MEDIA_VIDEO : JSJAC_JINGLE_MEDIA_AUDIO; // Follow up Jingle call Jingle.follow_up(call_to, call_media_picked, call_id); } catch(e) { Console.error('Call.init[single_proceed]', e); } }, // Receive a multiparty (Muji) call muji_invite: function(stanza, args) { try { if(!self.is_ongoing()) { // Session values Muji.receive(args, stanza); } } catch(e) { Console.error('Call.init[muji_invite]', e); } } }); // Enable Jingle/Muji UI elements if plugin could start if(JSJAC_JINGLE_AVAILABLE) { $('.jingle-hidable, .muji-hidable').show(); } } catch(e) { Console.error('Call.init', e); } }; /** * Opens the call interface * @public * @return {undefined} */ self.open = function() { try { if(Jingle.in_call()) { Jingle.open(); } else if(Muji.in_call()) { Muji.open(); } } catch(e) { Console.error('Call.open', e); } }; /** * Stops current call * @public * @param {boolean} abort * @return {boolean} */ self.stop = function(abort) { try { Jingle.stop(abort); Muji.stop(abort); } catch(e) { Console.error('Call.stop', e); } finally { return false; } }; /** * Mutes current call * @public * @param {object} session * @param {object} controls * @return {undefined} */ self.mute = function(session, controls) { try { if(session) { // Toggle interface buttons controls.filter('.mute').hide(); controls.filter('.unmute').show(); // Actually mute audio stream if(session.get_mute(JSJAC_JINGLE_MEDIA_AUDIO) === false) { session.mute(JSJAC_JINGLE_MEDIA_AUDIO); } } } catch(e) { Console.error('Call.mute', e); } }; /** * Unmutes current call * @public * @param {object} session * @param {object} controls * @return {undefined} */ self.unmute = function(session, controls) { try { if(session) { controls.filter('.unmute').hide(); controls.filter('.mute').show(); if(session.get_mute(JSJAC_JINGLE_MEDIA_AUDIO) === true) { session.unmute(JSJAC_JINGLE_MEDIA_AUDIO); } } } catch(e) { Console.error('Call.mute', e); } }; /** * Checks whether user is in call or not * @public * @return {boolean} */ self.is_ongoing = function() { is_ongoing = false; try { is_ongoing = (Jingle.in_call() === true || Muji.in_call() === true); } catch(e) { Console.error('Call.is_ongoing', e); } finally { return is_ongoing; } }; /** * Checks if the given call SID is the same as the current call's one * @public * @param {object} session * @param {object} compare_session * @return {boolean} */ self.is_same_sid = function(session, compare_session) { is_same = false; try { if(compare_session && session && compare_session.get_sid() === session.get_sid()) { is_same = true; } } catch(e) { Console.error('Call.is_same_sid', e); } finally { return is_same; } }; /** * Returns if current call is audio * @public * @param {object} session * @return {boolean} */ self.is_audio = function(session) { audio = false; try { if(session && session.get_media() === JSJAC_JINGLE_MEDIA_AUDIO) { audio = true; } } catch(e) { Console.error('Call.is_audio', e); } finally { return audio; } }; /** * Returns if current call is video * @public * @param {object} session * @return {boolean} */ self.is_video = function(session) { video = false; try { if(session && session.get_media() === JSJAC_JINGLE_MEDIA_VIDEO) { video = true; } } catch(e) { Console.error('Call.is_video', e); } finally { return video; } }; /** * Set the Muji session as started * @public * @param {string} mode * @return {boolean} */ self.start_session = function(mode) { try { if(!(mode in JSJAC_JINGLE_MEDIAS)) { throw 'Unknown mode: ' + (mode || 'none'); } var call_tool_sel = $('#top-content .tools.call'); call_tool_sel.removeClass('audio video active'); call_tool_sel.addClass('streaming').addClass(mode); Console.info('Call session successfully started, mode: ' + (mode || 'none')); } catch(e) { Console.error('Call.start_session', e); } finally { return false; } }; /** * Set the Jingle session as stopped * @public * @param {string} mode * @return {boolean} */ self.stop_session = function() { try { $('#top-content .tools.call').removeClass('audio video active streaming'); Console.info('Call session successfully stopped'); } catch(e) { Console.error('Call.stop_session', e); } finally { return false; } }; /** * Generates ICE servers configuration * @public * @return {object} */ self.generate_ice_servers = function() { ice_servers = { stun: [], turn: [] }; try { if(HOST_STUN) { ice_servers.stun.push({ 'host': HOST_STUN }); } if(HOST_TURN) { ice_servers.turn.push({ 'host': HOST_TURN, 'username': HOST_TURN_USERNAME, 'credential': HOST_TURN_PASSWORD }); } } catch(e) { Console.error('Call.generate_ice_servers', e); } finally { return is_ongoing; } }; /** * Returns the notification map (based on call type) * @private * @param {string} call_type * @return {object} */ self._get_notify_map = function(call_type) { var map = {}; try { switch(call_type) { case JSJAC_JINGLE_SESSION_SINGLE: map = Jingle._notify_map(); break; case JSJAC_JINGLE_SESSION_MUJI: map = Muji._notify_map(); break; default: return; } } catch(e) { Console.error('Call._get_notify_map', e); } finally { return map; } }; /** * Notify for something related to calls * @public * @param {string} call_type * @param {string} xid * @param {string} type * @param {string} mode * @param {object} [options_arr] * @return {boolean} */ self.notify = function(call_type, xid, type, mode, sender_xid, options_arr) { try { sender_xid = sender_xid || xid; // Notification data map var map = self._get_notify_map(call_type); if(!(type in map)) { throw 'Notification type not recognized!'; } // Selectors var call_tools_all_sel = $('#top-content .tools-all:has(.tools.call)'); var call_tool_sel = call_tools_all_sel.find('.tools.call'); var call_content_sel = call_tools_all_sel.find('.call-content'); var call_subitem_sel = call_content_sel.find('.tools-content-subitem'); // Generate proper full name var fullname; if(call_type === JSJAC_JINGLE_SESSION_MUJI && sender_xid === Common.getXID()) { fullname = Common._e("Conference call"); } else { fullname = Name.getBuddy(sender_xid).htmlEnc(); } // Generate buttons code var buttons_html = ''; var i = 0; if(typeof map[type].buttons === 'object') { $.each(map[type].buttons, function(button, attrs) { buttons_html += '
'; }); } // Append notification to DOM call_subitem_sel.html( '