1
0
Fork 0
mirror of https://github.com/YunoHost-Apps/jappix_ynh.git synced 2024-09-03 19:26:19 +02:00
jappix_ynh/source/app/javascripts/jsjac.jingle.js

7248 lines
212 KiB
JavaScript
Raw Normal View History

2014-03-12 14:52:47 +01:00
/**
* @fileoverview JSJaC Jingle library, implementation of XEP-0166.
* Written originally for Uno.im service requirements
*
* @version v0.7 (dev)
* @url https://github.com/valeriansaliou/jsjac-jingle
* @depends https://github.com/sstrigler/JSJaC
* @author Valérian Saliou http://valeriansaliou.name/
* @license Mozilla Public License v2.0 (MPL v2.0)
*/
/**
* Implements:
*
* See the PROTOCOL.md file for a list of supported protocol extensions
*
*
* Workflow:
*
* This negotiation example associates JSJaCJingle.js methods to a real workflow
* We assume in this workflow example remote user accepts the call he gets
*
* 1.cmt Local user wants to start a WebRTC session with remote user
* 1.snd Local user sends a session-initiate type='set'
* 1.hdl Remote user sends back a type='result' to '1.snd' stanza (ack)
*
* 2.cmt Local user waits silently for remote user to send a session-accept
* 2.hdl Remote user sends a session-accept type='set'
* 2.snd Local user sends back a type='result' to '2.hdl' stanza (ack)
*
* 3.cmt WebRTC session starts
* 3.cmt Users chat, and chat, and chat. Happy Jabbering to them!
*
* 4.cmt Local user wants to stop WebRTC session with remote user
* 4.snd Local user sends a session-terminate type='set'
* 4.hdl Remote user sends back a type='result' to '4.snd' stanza (ack)
*/
/**
* JINGLE WEBRTC
*/
var WEBRTC_GET_MEDIA = ( navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ||
navigator.msGetUserMedia ||
navigator.getUserMedia );
var WEBRTC_PEER_CONNECTION = ( window.webkitRTCPeerConnection ||
window.mozRTCPeerConnection ||
window.RTCPeerConnection );
var WEBRTC_SESSION_DESCRIPTION = ( window.mozRTCSessionDescription ||
window.RTCSessionDescription );
var WEBRTC_ICE_CANDIDATE = ( window.mozRTCIceCandidate ||
window.RTCIceCandidate );
var WEBRTC_CONFIGURATION = {
peer_connection : {
config : {
iceServers : [{
url: 'stun:stun.jappix.com'
}]
},
constraints : {
optional : [{
'DtlsSrtpKeyAgreement': true
}]
}
},
create_offer : {
mandatory: {
'OfferToReceiveAudio' : true,
'OfferToReceiveVideo' : true
}
},
create_answer : {
mandatory: {
'OfferToReceiveAudio' : true,
'OfferToReceiveVideo' : true
}
}
};
var WEBRTC_SDP_LINE_BREAK = '\r\n';
var WEBRTC_SDP_TYPE_OFFER = 'offer';
var WEBRTC_SDP_TYPE_ANSWER = 'answer';
var R_WEBRTC_SDP_ICE_CANDIDATE = /^a=candidate:(\w{1,32}) (\d{1,5}) (udp|tcp) (\d{1,10}) ([a-zA-Z0-9:\.]{1,45}) (\d{1,5}) (typ) (host|srflx|prflx|relay)( (raddr) ([a-zA-Z0-9:\.]{1,45}) (rport) (\d{1,5}))?( (generation) (\d))?/i;
var R_WEBRTC_SDP_ICE_PAYLOAD = {
rtpmap : /^a=rtpmap:(\d+) (([^\s\/]+)\/(\d+)(\/([^\s\/]+))?)?/i,
fmtp : /^a=fmtp:(\d+) (.+)/i,
group : /^a=group:(\S+) (.+)/,
rtcp_fb : /^a=rtcp-fb:(\S+) (\S+)( (\S+))?/i,
rtcp_fb_trr_int : /^a=rtcp-fb:(\d+) trr-int (\d+)/i,
pwd : /^a=ice-pwd:(\S+)/i,
ufrag : /^a=ice-ufrag:(\S+)/i,
ptime : /^a=ptime:(\d+)/i,
maxptime : /^a=maxptime:(\d+)/i,
ssrc : /^a=ssrc:(\d+) (\w+)(:(\S+))?( (\S+))?/i,
rtcp_mux : /^a=rtcp-mux/i,
crypto : /^a=crypto:(\d{1,9}) (\S+) (\S+)( (\S+))?/i,
zrtp_hash : /^a=zrtp-hash:(\S+) (\S+)/i,
fingerprint : /^a=fingerprint:(\S+) (\S+)/i,
setup : /^a=setup:(\S+)/i,
extmap : /^a=extmap:([^\s\/]+)(\/([^\s\/]+))? (\S+)/i,
bandwidth : /^b=(\w+):(\d+)/i,
media : /^m=(audio|video|application|data) /i
};
/**
* JINGLE NAMESPACES
*/
var NS_JINGLE = 'urn:xmpp:jingle:1';
var NS_JINGLE_ERRORS = 'urn:xmpp:jingle:errors:1';
var NS_JINGLE_APPS_RTP = 'urn:xmpp:jingle:apps:rtp:1';
var NS_JINGLE_APPS_RTP_INFO = 'urn:xmpp:jingle:apps:rtp:info:1';
var NS_JINGLE_APPS_RTP_AUDIO = 'urn:xmpp:jingle:apps:rtp:audio';
var NS_JINGLE_APPS_RTP_VIDEO = 'urn:xmpp:jingle:apps:rtp:video';
var NS_JINGLE_APPS_RTP_RTP_HDREXT = 'urn:xmpp:jingle:apps:rtp:rtp-hdrext:0';
var NS_JINGLE_APPS_RTP_RTCP_FB = 'urn:xmpp:jingle:apps:rtp:rtcp-fb:0';
var NS_JINGLE_APPS_RTP_ZRTP = 'urn:xmpp:jingle:apps:rtp:zrtp:1';
var NS_JINGLE_APPS_RTP_SSMA = 'urn:xmpp:jingle:apps:rtp:ssma:0';
var NS_JINGLE_APPS_STUB = 'urn:xmpp:jingle:apps:stub:0';
var NS_JINGLE_APPS_DTLS = 'urn:xmpp:tmp:jingle:apps:dtls:0';
var NS_JINGLE_APPS_GROUPING = 'urn:xmpp:jingle:apps:grouping:0';
var NS_JINGLE_TRANSPORTS_ICEUDP = 'urn:xmpp:jingle:transports:ice-udp:1';
var NS_JINGLE_TRANSPORTS_STUB = 'urn:xmpp:jingle:transports:stub:0';
var NS_JINGLE_SECURITY_STUB = 'urn:xmpp:jingle:security:stub:0';
var NS_EXTDISCO = 'urn:xmpp:extdisco:1';
var NS_IETF_RFC_3264 = 'urn:ietf:rfc:3264';
var NS_IETF_RFC_5576 = 'urn:ietf:rfc:5576';
var NS_IETF_RFC_5888 = 'urn:ietf:rfc:5888';
var R_NS_JINGLE_APP = /^urn:xmpp:jingle:app:(\w+)(:(\w+))?(:(\d+))?$/;
var R_NS_JINGLE_TRANSPORT = /^urn:xmpp:jingle:transport:(\w+)$/;
var MAP_DISCO_JINGLE = [
/* http://xmpp.org/extensions/xep-0166.html#support */
/* http://xmpp.org/extensions/xep-0167.html#support */
NS_JINGLE,
NS_JINGLE_APPS_RTP,
NS_JINGLE_APPS_RTP_AUDIO,
NS_JINGLE_APPS_RTP_VIDEO,
/* http://xmpp.org/extensions/xep-0176.html#support */
NS_JINGLE_TRANSPORTS_ICEUDP,
NS_IETF_RFC_3264,
/* http://xmpp.org/extensions/xep-0339.html#disco */
NS_IETF_RFC_5576,
/* http://xmpp.org/extensions/xep-0338.html#disco */
NS_IETF_RFC_5888,
/* http://xmpp.org/extensions/xep-0293.html#determining-support */
NS_JINGLE_APPS_RTP_RTCP_FB,
/* http://xmpp.org/extensions/xep-0294.html#determining-support */
NS_JINGLE_APPS_RTP_RTP_HDREXT,
/* http://xmpp.org/extensions/xep-0320.html#disco */
NS_JINGLE_APPS_DTLS,
/* http://xmpp.org/extensions/xep-0262.html */
NS_JINGLE_APPS_RTP_ZRTP,
/* http://xmpp.org/extensions/xep-0215.html */
NS_EXTDISCO
];
/**
* JSJAC JINGLE CONSTANTS
*/
var JSJAC_JINGLE_AVAILABLE = WEBRTC_GET_MEDIA ? true : false;
var JSJAC_JINGLE_PEER_TIMEOUT_DEFAULT = 15;
var JSJAC_JINGLE_PEER_TIMEOUT_DISCONNECT = 5;
var JSJAC_JINGLE_STANZA_TIMEOUT = 10;
var JSJAC_JINGLE_STANZA_ID_PRE = 'jj';
var JSJAC_JINGLE_NETWORK = '0';
var JSJAC_JINGLE_GENERATION = '0';
var JSJAC_JINGLE_BROWSER_FIREFOX = 'Firefox';
var JSJAC_JINGLE_BROWSER_CHROME = 'Chrome';
var JSJAC_JINGLE_BROWSER_SAFARI = 'Safari';
var JSJAC_JINGLE_BROWSER_OPERA = 'Opera';
var JSJAC_JINGLE_BROWSER_IE = 'IE';
var JSJAC_JINGLE_SENDERS_BOTH = { jingle: 'both', sdp: 'sendrecv' };
var JSJAC_JINGLE_SENDERS_INITIATOR = { jingle: 'initiator', sdp: 'sendonly' };
var JSJAC_JINGLE_SENDERS_NONE = { jingle: 'none', sdp: 'inactive' };
var JSJAC_JINGLE_SENDERS_RESPONDER = { jingle: 'responder', sdp: 'recvonly' };
var JSJAC_JINGLE_CREATOR_INITIATOR = 'initiator';
var JSJAC_JINGLE_CREATOR_RESPONDER = 'responder';
var JSJAC_JINGLE_STATUS_INACTIVE = 'inactive';
var JSJAC_JINGLE_STATUS_INITIATING = 'initiating';
var JSJAC_JINGLE_STATUS_INITIATED = 'initiated';
var JSJAC_JINGLE_STATUS_ACCEPTING = 'accepting';
var JSJAC_JINGLE_STATUS_ACCEPTED = 'accepted';
var JSJAC_JINGLE_STATUS_TERMINATING = 'terminating';
var JSJAC_JINGLE_STATUS_TERMINATED = 'terminated';
var JSJAC_JINGLE_ACTION_CONTENT_ACCEPT = 'content-accept';
var JSJAC_JINGLE_ACTION_CONTENT_ADD = 'content-add';
var JSJAC_JINGLE_ACTION_CONTENT_MODIFY = 'content-modify';
var JSJAC_JINGLE_ACTION_CONTENT_REJECT = 'content-reject';
var JSJAC_JINGLE_ACTION_CONTENT_REMOVE = 'content-remove';
var JSJAC_JINGLE_ACTION_DESCRIPTION_INFO = 'description-info';
var JSJAC_JINGLE_ACTION_SECURITY_INFO = 'security-info';
var JSJAC_JINGLE_ACTION_SESSION_ACCEPT = 'session-accept';
var JSJAC_JINGLE_ACTION_SESSION_INFO = 'session-info';
var JSJAC_JINGLE_ACTION_SESSION_INITIATE = 'session-initiate';
var JSJAC_JINGLE_ACTION_SESSION_TERMINATE = 'session-terminate';
var JSJAC_JINGLE_ACTION_TRANSPORT_ACCEPT = 'transport-accept';
var JSJAC_JINGLE_ACTION_TRANSPORT_INFO = 'transport-info';
var JSJAC_JINGLE_ACTION_TRANSPORT_REJECT = 'transport-reject';
var JSJAC_JINGLE_ACTION_TRANSPORT_REPLACE = 'transport-replace';
var JSJAC_JINGLE_ERROR_OUT_OF_ORDER = { jingle: 'out-of-order', xmpp: 'unexpected-request', type: 'wait' };
var JSJAC_JINGLE_ERROR_TIE_BREAK = { jingle: 'tie-break', xmpp: 'conflict', type: 'cancel' };
var JSJAC_JINGLE_ERROR_UNKNOWN_SESSION = { jingle: 'unknown-session', xmpp: 'item-not-found', type: 'cancel' };
var JSJAC_JINGLE_ERROR_UNSUPPORTED_INFO = { jingle: 'unsupported-info', xmpp: 'feature-not-implemented', type: 'modify' };
var JSJAC_JINGLE_ERROR_SECURITY_REQUIRED = { jingle: 'security-required', xmpp: 'not-acceptable', type: 'cancel' };
var XMPP_ERROR_UNEXPECTED_REQUEST = { xmpp: 'unexpected-request', type: 'wait' };
var XMPP_ERROR_CONFLICT = { xmpp: 'conflict', type: 'cancel' };
var XMPP_ERROR_ITEM_NOT_FOUND = { xmpp: 'item-not-found', type: 'cancel' };
var XMPP_ERROR_NOT_ACCEPTABLE = { xmpp: 'not-acceptable', type: 'modify' };
var XMPP_ERROR_FEATURE_NOT_IMPLEMENTED = { xmpp: 'feature-not-implemented', type: 'cancel' };
var XMPP_ERROR_SERVICE_UNAVAILABLE = { xmpp: 'service-unavailable', type: 'cancel' };
var XMPP_ERROR_REDIRECT = { xmpp: 'redirect', type: 'modify' };
var XMPP_ERROR_RESOURCE_CONSTRAINT = { xmpp: 'resource-constraint', type: 'wait' };
var XMPP_ERROR_BAD_REQUEST = { xmpp: 'bad-request', type: 'cancel' };
var JSJAC_JINGLE_REASON_ALTERNATIVE_SESSION = 'alternative-session';
var JSJAC_JINGLE_REASON_BUSY = 'busy';
var JSJAC_JINGLE_REASON_CANCEL = 'cancel';
var JSJAC_JINGLE_REASON_CONNECTIVITY_ERROR = 'connectivity-error';
var JSJAC_JINGLE_REASON_DECLINE = 'decline';
var JSJAC_JINGLE_REASON_EXPIRED = 'expired';
var JSJAC_JINGLE_REASON_FAILED_APPLICATION = 'failed-application';
var JSJAC_JINGLE_REASON_FAILED_TRANSPORT = 'failed-transport';
var JSJAC_JINGLE_REASON_GENERAL_ERROR = 'general-error';
var JSJAC_JINGLE_REASON_GONE = 'gone';
var JSJAC_JINGLE_REASON_INCOMPATIBLE_PARAMETERS = 'incompatible-parameters';
var JSJAC_JINGLE_REASON_MEDIA_ERROR = 'media-error';
var JSJAC_JINGLE_REASON_SECURITY_ERROR = 'security-error';
var JSJAC_JINGLE_REASON_SUCCESS = 'success';
var JSJAC_JINGLE_REASON_TIMEOUT = 'timeout';
var JSJAC_JINGLE_REASON_UNSUPPORTED_APPLICATIONS = 'unsupported-applications';
var JSJAC_JINGLE_REASON_UNSUPPORTED_TRANSPORTS = 'unsupported-transports';
var JSJAC_JINGLE_SESSION_INFO_ACTIVE = 'active';
var JSJAC_JINGLE_SESSION_INFO_HOLD = 'hold';
var JSJAC_JINGLE_SESSION_INFO_MUTE = 'mute';
var JSJAC_JINGLE_SESSION_INFO_RINGING = 'ringing';
var JSJAC_JINGLE_SESSION_INFO_UNHOLD = 'unhold';
var JSJAC_JINGLE_SESSION_INFO_UNMUTE = 'unmute';
var JSJAC_JINGLE_MEDIA_AUDIO = 'audio';
var JSJAC_JINGLE_MEDIA_VIDEO = 'video';
var JSJAC_JINGLE_VIDEO_SOURCE_CAMERA = 'camera';
var JSJAC_JINGLE_VIDEO_SOURCE_SCREEN = 'screen';
var JSJAC_JINGLE_STANZA_TYPE_ALL = 'all';
var JSJAC_JINGLE_STANZA_TYPE_RESULT = 'result';
var JSJAC_JINGLE_STANZA_TYPE_SET = 'set';
var JSJAC_JINGLE_STANZA_TYPE_GET = 'get';
/**
* JSJSAC JINGLE CONSTANTS MAPPING
*/
var JSJAC_JINGLE_BROWSERS = {};
JSJAC_JINGLE_BROWSERS[JSJAC_JINGLE_BROWSER_FIREFOX] = 1;
JSJAC_JINGLE_BROWSERS[JSJAC_JINGLE_BROWSER_CHROME] = 1;
JSJAC_JINGLE_BROWSERS[JSJAC_JINGLE_BROWSER_SAFARI] = 1;
JSJAC_JINGLE_BROWSERS[JSJAC_JINGLE_BROWSER_OPERA] = 1;
JSJAC_JINGLE_BROWSERS[JSJAC_JINGLE_BROWSER_IE] = 1;
var JSJAC_JINGLE_SENDERS = {};
JSJAC_JINGLE_SENDERS[JSJAC_JINGLE_SENDERS_BOTH.jingle] = JSJAC_JINGLE_SENDERS_BOTH.sdp;
JSJAC_JINGLE_SENDERS[JSJAC_JINGLE_SENDERS_INITIATOR.jingle] = JSJAC_JINGLE_SENDERS_INITIATOR.sdp;
JSJAC_JINGLE_SENDERS[JSJAC_JINGLE_SENDERS_NONE.jingle] = JSJAC_JINGLE_SENDERS_NONE.sdp;
JSJAC_JINGLE_SENDERS[JSJAC_JINGLE_SENDERS_RESPONDER.jingle] = JSJAC_JINGLE_SENDERS_RESPONDER.sdp;
var JSJAC_JINGLE_CREATORS = {};
JSJAC_JINGLE_CREATORS[JSJAC_JINGLE_CREATOR_INITIATOR] = 1;
JSJAC_JINGLE_CREATORS[JSJAC_JINGLE_CREATOR_RESPONDER] = 1;
var JSJAC_JINGLE_STATUSES = {};
JSJAC_JINGLE_STATUSES[JSJAC_JINGLE_STATUS_INACTIVE] = 1;
JSJAC_JINGLE_STATUSES[JSJAC_JINGLE_STATUS_INITIATING] = 1;
JSJAC_JINGLE_STATUSES[JSJAC_JINGLE_STATUS_INITIATED] = 1;
JSJAC_JINGLE_STATUSES[JSJAC_JINGLE_STATUS_ACCEPTING] = 1;
JSJAC_JINGLE_STATUSES[JSJAC_JINGLE_STATUS_ACCEPTED] = 1;
JSJAC_JINGLE_STATUSES[JSJAC_JINGLE_STATUS_TERMINATING] = 1;
JSJAC_JINGLE_STATUSES[JSJAC_JINGLE_STATUS_TERMINATED] = 1;
var JSJAC_JINGLE_ACTIONS = {};
JSJAC_JINGLE_ACTIONS[JSJAC_JINGLE_ACTION_CONTENT_ACCEPT] = 1;
JSJAC_JINGLE_ACTIONS[JSJAC_JINGLE_ACTION_CONTENT_ADD] = 1;
JSJAC_JINGLE_ACTIONS[JSJAC_JINGLE_ACTION_CONTENT_MODIFY] = 1;
JSJAC_JINGLE_ACTIONS[JSJAC_JINGLE_ACTION_CONTENT_REJECT] = 1;
JSJAC_JINGLE_ACTIONS[JSJAC_JINGLE_ACTION_CONTENT_REMOVE] = 1;
JSJAC_JINGLE_ACTIONS[JSJAC_JINGLE_ACTION_DESCRIPTION_INFO] = 1;
JSJAC_JINGLE_ACTIONS[JSJAC_JINGLE_ACTION_SECURITY_INFO] = 1;
JSJAC_JINGLE_ACTIONS[JSJAC_JINGLE_ACTION_SESSION_ACCEPT] = 1;
JSJAC_JINGLE_ACTIONS[JSJAC_JINGLE_ACTION_SESSION_INFO] = 1;
JSJAC_JINGLE_ACTIONS[JSJAC_JINGLE_ACTION_SESSION_INITIATE] = 1;
JSJAC_JINGLE_ACTIONS[JSJAC_JINGLE_ACTION_SESSION_TERMINATE] = 1;
JSJAC_JINGLE_ACTIONS[JSJAC_JINGLE_ACTION_TRANSPORT_ACCEPT] = 1;
JSJAC_JINGLE_ACTIONS[JSJAC_JINGLE_ACTION_TRANSPORT_INFO] = 1;
JSJAC_JINGLE_ACTIONS[JSJAC_JINGLE_ACTION_TRANSPORT_REJECT] = 1;
JSJAC_JINGLE_ACTIONS[JSJAC_JINGLE_ACTION_TRANSPORT_REPLACE] = 1;
var JSJAC_JINGLE_ERRORS = {};
JSJAC_JINGLE_ERRORS[JSJAC_JINGLE_ERROR_OUT_OF_ORDER.jingle] = 1;
JSJAC_JINGLE_ERRORS[JSJAC_JINGLE_ERROR_TIE_BREAK.jingle] = 1;
JSJAC_JINGLE_ERRORS[JSJAC_JINGLE_ERROR_UNKNOWN_SESSION.jingle] = 1;
JSJAC_JINGLE_ERRORS[JSJAC_JINGLE_ERROR_UNSUPPORTED_INFO.jingle] = 1;
JSJAC_JINGLE_ERRORS[JSJAC_JINGLE_ERROR_SECURITY_REQUIRED.jingle] = 1;
var XMPP_ERRORS = {};
XMPP_ERRORS[XMPP_ERROR_UNEXPECTED_REQUEST.xmpp] = 1;
XMPP_ERRORS[XMPP_ERROR_CONFLICT.xmpp] = 1;
XMPP_ERRORS[XMPP_ERROR_ITEM_NOT_FOUND.xmpp] = 1;
XMPP_ERRORS[XMPP_ERROR_NOT_ACCEPTABLE.xmpp] = 1;
XMPP_ERRORS[XMPP_ERROR_FEATURE_NOT_IMPLEMENTED.xmpp] = 1;
XMPP_ERRORS[XMPP_ERROR_SERVICE_UNAVAILABLE.xmpp] = 1;
XMPP_ERRORS[XMPP_ERROR_REDIRECT.xmpp] = 1;
XMPP_ERRORS[XMPP_ERROR_RESOURCE_CONSTRAINT.xmpp] = 1;
XMPP_ERRORS[XMPP_ERROR_BAD_REQUEST.xmpp] = 1;
var JSJAC_JINGLE_REASONS = {};
JSJAC_JINGLE_REASONS[JSJAC_JINGLE_REASON_ALTERNATIVE_SESSION] = 1;
JSJAC_JINGLE_REASONS[JSJAC_JINGLE_REASON_BUSY] = 1;
JSJAC_JINGLE_REASONS[JSJAC_JINGLE_REASON_CANCEL] = 1;
JSJAC_JINGLE_REASONS[JSJAC_JINGLE_REASON_CONNECTIVITY_ERROR] = 1;
JSJAC_JINGLE_REASONS[JSJAC_JINGLE_REASON_DECLINE] = 1;
JSJAC_JINGLE_REASONS[JSJAC_JINGLE_REASON_EXPIRED] = 1;
JSJAC_JINGLE_REASONS[JSJAC_JINGLE_REASON_FAILED_APPLICATION] = 1;
JSJAC_JINGLE_REASONS[JSJAC_JINGLE_REASON_FAILED_TRANSPORT] = 1;
JSJAC_JINGLE_REASONS[JSJAC_JINGLE_REASON_GENERAL_ERROR] = 1;
JSJAC_JINGLE_REASONS[JSJAC_JINGLE_REASON_GONE] = 1;
JSJAC_JINGLE_REASONS[JSJAC_JINGLE_REASON_INCOMPATIBLE_PARAMETERS] = 1;
JSJAC_JINGLE_REASONS[JSJAC_JINGLE_REASON_MEDIA_ERROR] = 1;
JSJAC_JINGLE_REASONS[JSJAC_JINGLE_REASON_SECURITY_ERROR] = 1;
JSJAC_JINGLE_REASONS[JSJAC_JINGLE_REASON_SUCCESS] = 1;
JSJAC_JINGLE_REASONS[JSJAC_JINGLE_REASON_TIMEOUT] = 1;
JSJAC_JINGLE_REASONS[JSJAC_JINGLE_REASON_UNSUPPORTED_APPLICATIONS] = 1;
JSJAC_JINGLE_REASONS[JSJAC_JINGLE_REASON_UNSUPPORTED_TRANSPORTS] = 1;
var JSJAC_JINGLE_SESSION_INFOS = {};
JSJAC_JINGLE_SESSION_INFOS[JSJAC_JINGLE_SESSION_INFO_ACTIVE] = 1;
JSJAC_JINGLE_SESSION_INFOS[JSJAC_JINGLE_SESSION_INFO_HOLD] = 1;
JSJAC_JINGLE_SESSION_INFOS[JSJAC_JINGLE_SESSION_INFO_MUTE] = 1;
JSJAC_JINGLE_SESSION_INFOS[JSJAC_JINGLE_SESSION_INFO_RINGING] = 1;
JSJAC_JINGLE_SESSION_INFOS[JSJAC_JINGLE_SESSION_INFO_UNHOLD] = 1;
JSJAC_JINGLE_SESSION_INFOS[JSJAC_JINGLE_SESSION_INFO_UNMUTE] = 1;
var JSJAC_JINGLE_MEDIAS = {};
JSJAC_JINGLE_MEDIAS[JSJAC_JINGLE_MEDIA_AUDIO] = { label: '0' };
JSJAC_JINGLE_MEDIAS[JSJAC_JINGLE_MEDIA_VIDEO] = { label: '1' };
var JSJAC_JINGLE_VIDEO_SOURCES = {};
JSJAC_JINGLE_VIDEO_SOURCES[JSJAC_JINGLE_VIDEO_SOURCE_CAMERA] = 1;
JSJAC_JINGLE_VIDEO_SOURCES[JSJAC_JINGLE_VIDEO_SOURCE_SCREEN] = 1;
var JSJAC_JINGLE_STANZAS = {};
JSJAC_JINGLE_STANZAS[JSJAC_JINGLE_STANZA_TYPE_ALL] = 1;
JSJAC_JINGLE_STANZAS[JSJAC_JINGLE_STANZA_TYPE_RESULT] = 1;
JSJAC_JINGLE_STANZAS[JSJAC_JINGLE_STANZA_TYPE_SET] = 1;
JSJAC_JINGLE_STANZAS[JSJAC_JINGLE_STANZA_TYPE_GET] = 1;
/**
* JSJAC JINGLE STORAGE
*/
var JSJAC_JINGLE_STORE_CONNECTION = null;
var JSJAC_JINGLE_STORE_SESSIONS = {};
var JSJAC_JINGLE_STORE_INITIATE = function(stanza) {};
var JSJAC_JINGLE_STORE_DEBUG = {
log : function() {}
};
var JSJAC_JINGLE_STORE_EXTDISCO = {
stun : {},
turn : {}
};
var JSJAC_JINGLE_STORE_FALLBACK = {
stun : {},
turn : {}
};
var JSJAC_JINGLE_STORE_DEFER = {
deferred : false,
count : 0,
fn : []
};
var R_JSJAC_JINGLE_SERVICE_URI = /^(\w+):([^:\?]+)(?::(\d+))?(?:\?transport=(\w+))?/i;
/**
* JSJSAC JINGLE METHODS
*/
/**
* Creates a new XMPP Jingle session.
* @class Somewhat abstract base class for XMPP Jingle sessions. Contains all
* of the code in common for all Jingle sessions
* @constructor
* @param {Object} args Jingle session arguments.
* @param {function} args.session_initiate_pending The initiate pending custom handler.
* @param {function} args.session_initiate_success The initiate success custom handler.
* @param {function} args.session_initiate_error The initiate error custom handler.
* @param {function} args.session_initiate_request The initiate request custom handler.
* @param {function} args.session_accept_pending The accept pending custom handler.
* @param {function} args.session_accept_success The accept success custom handler.
* @param {function} args.session_accept_error The accept error custom handler.
* @param {function} args.session_accept_request The accept request custom handler.
* @param {function} args.session_info_success The info success custom handler.
* @param {function} args.session_info_error The info error custom handler.
* @param {function} args.session_info_request The info request custom handler.
* @param {function} args.session_terminate_pending The terminate pending custom handler.
* @param {function} args.session_terminate_success The terminate success custom handler.
* @param {function} args.session_terminate_error The terminate error custom handler.
* @param {function} args.session_terminate_request The terminate request custom handler.
* @param {DOM} args.local_view The path to the local stream view element.
* @param {DOM} args.remote_view The path to the remote stream view element.
* @param {string} args.to The full JID to start the Jingle session with.
* @param {string} args.media The media type to be used in the Jingle session.
* @param {string} args.resolution The resolution to be used for video in the Jingle session.
* @param {string} args.bandwidth The bandwidth to be limited for video in the Jingle session.
* @param {string} args.fps The framerate to be used for video in the Jingle session.
* @param {object} args.stun A list of STUN servers to use (override the default one)
* @param {object} args.turn A list of TURN servers to use
* @param {object} args.sdp_trace Log SDP trace in console (requires a debug interface)
* @param {JSJaCDebugger} args.debug A reference to a debugger implementing the JSJaCDebugger interface.
*/
function JSJaCJingle(args) {
var self = this;
if(args && args.session_initiate_pending)
/**
* @private
*/
self._session_initiate_pending = args.session_initiate_pending;
if(args && args.session_initiate_success)
/**
* @private
*/
self._session_initiate_success = args.session_initiate_success;
if(args && args.session_initiate_error)
/**
* @private
*/
self._session_initiate_error = args.session_initiate_error;
if(args && args.session_initiate_request)
/**
* @private
*/
self._session_initiate_request = args.session_initiate_request;
if(args && args.session_accept_pending)
/**
* @private
*/
self._session_accept_pending = args.session_accept_pending;
if(args && args.session_accept_success)
/**
* @private
*/
self._session_accept_success = args.session_accept_success;
if(args && args.session_accept_error)
/**
* @private
*/
self._session_accept_error = args.session_accept_error;
if(args && args.session_accept_request)
/**
* @private
*/
self._session_accept_request = args.session_accept_request;
if(args && args.session_info_success)
/**
* @private
*/
self._session_info_success = args.session_info_success;
if(args && args.session_info_error)
/**
* @private
*/
self._session_info_error = args.session_info_error;
if(args && args.session_info_request)
/**
* @private
*/
self._session_info_request = args.session_info_request;
if(args && args.session_terminate_pending)
/**
* @private
*/
self._session_terminate_pending = args.session_terminate_pending;
if(args && args.session_terminate_success)
/**
* @private
*/
self._session_terminate_success = args.session_terminate_success;
if(args && args.session_terminate_error)
/**
* @private
*/
self._session_terminate_error = args.session_terminate_error;
if(args && args.session_terminate_request)
/**
* @private
*/
self._session_terminate_request = args.session_terminate_request;
if(args && args.to)
/**
* @private
*/
self._to = args.to;
if(args && args.media)
/**
* @private
*/
self._media = args.media;
if(args && args.video_source)
/**
* @private
*/
self._video_source = args.video_source;
if(args && args.resolution)
/**
* @private
*/
self._resolution = args.resolution;
if(args && args.bandwidth)
/**
* @private
*/
self._bandwidth = args.bandwidth;
if(args && args.fps)
/**
* @private
*/
self._fps = args.fps;
if(args && args.local_view)
/**
* @private
*/
self._local_view = [args.local_view];
if(args && args.remote_view)
/**
* @private
*/
self._remote_view = [args.remote_view];
if(args && args.stun) {
/**
* @private
*/
self._stun = args.stun;
} else {
self._stun = {};
}
if(args && args.turn) {
/**
* @private
*/
self._turn = args.turn;
} else {
self._turn = {};
}
if(args && args.sdp_trace)
/**
* @private
*/
self._sdp_trace = args.sdp_trace;
if(args && args.debug && args.debug.log) {
/**
* Reference to debugger interface
* (needs to implement method <code>log</code>)
* @type JSJaCDebugger
*/
self._debug = args.debug;
} else {
self._debug = JSJAC_JINGLE_STORE_DEBUG;
}
/**
* @private
*/
self._local_stream = null;
/**
* @private
*/
self._remote_stream = null;
/**
* @private
*/
self._content_local = {};
/**
* @private
*/
self._content_remote = {};
/**
* @private
*/
self._payloads_local = [];
/**
* @private
*/
self._group_local = {};
/**
* @private
*/
self._candidates_local = {};
/**
* @private
*/
self._candidates_queue_local = {};
/**
* @private
*/
self._payloads_remote = {};
/**
* @private
*/
self._group_remote = {};
/**
* @private
*/
self._candidates_remote = {};
/**
* @private
*/
self._candidates_queue_remote = {};
/**
* @private
*/
self._initiator = '';
/**
* @private
*/
self._responder = '';
/**
* @private
*/
self._mute = {};
/**
* @private
*/
self._lock = false;
/**
* @private
*/
self._media_busy = false;
/**
* @private
*/
self._sid = '';
/**
* @private
*/
self._name = {};
/**
* @private
*/
self._senders = {};
/**
* @private
*/
self._creator = {};
/**
* @private
*/
self._status = JSJAC_JINGLE_STATUS_INACTIVE;
/**
* @private
*/
self._reason = JSJAC_JINGLE_REASON_CANCEL;
/**
* @private
*/
self._handlers = {};
/**
* @private
*/
self._peer_connection = null;
/**
* @private
*/
self._id = 0;
/**
* @private
*/
self._sent_id = {};
/**
* @private
*/
self._received_id = {};
/**
* Initiates a new Jingle session.
*/
self.initiate = function() {
self.get_debug().log('[JSJaCJingle] initiate', 4);
try {
// Locked?
if(self.get_lock()) {
self.get_debug().log('[JSJaCJingle] initiate > Cannot initiate, resource locked. Please open another session or check WebRTC support.', 0);
return;
}
// Defer?
if(JSJaCJingle_defer(function() { self.initiate(); })) {
self.get_debug().log('[JSJaCJingle] initiate > Deferred (waiting for the library components to be initiated).', 0);
return;
}
// Slot unavailable?
if(self.get_status() != JSJAC_JINGLE_STATUS_INACTIVE) {
self.get_debug().log('[JSJaCJingle] initiate > Cannot initiate, resource not inactive (status: ' + self.get_status() + ').', 0);
return;
}
self.get_debug().log('[JSJaCJingle] initiate > New Jingle session with media: ' + self.get_media(), 2);
// Common vars
var i, cur_name;
// Trigger init pending custom callback
(self._get_session_initiate_pending())(self);
// Change session status
self._set_status(JSJAC_JINGLE_STATUS_INITIATING);
// Set session values
self._set_sid(self.util_generate_sid());
self._set_initiator(self.util_connection_jid());
self._set_responder(self.get_to());
for(i in self.get_media_all()) {
cur_name = self._util_name_generate(
self.get_media_all()[i]
);
self._set_name(cur_name);
self._set_senders(
cur_name,
JSJAC_JINGLE_SENDERS_BOTH.jingle
);
self._set_creator(
cur_name,
JSJAC_JINGLE_CREATOR_INITIATOR
);
}
// Register session to common router
JSJaCJingle_add(self.get_sid(), self);
// Initialize WebRTC
self._peer_get_user_media(function() {
self._peer_connection_create(function() {
self.get_debug().log('[JSJaCJingle] initiate > Ready to begin Jingle negotiation.', 2);
self.send(JSJAC_JINGLE_STANZA_TYPE_SET, { action: JSJAC_JINGLE_ACTION_SESSION_INITIATE });
});
});
} catch(e) {
self.get_debug().log('[JSJaCJingle] initiate > ' + e, 1);
}
};
/**
* Accepts the Jingle session.
*/
self.accept = function() {
self.get_debug().log('[JSJaCJingle] accept', 4);
try {
// Locked?
if(self.get_lock()) {
self.get_debug().log('[JSJaCJingle] accept > Cannot accept, resource locked. Please open another session or check WebRTC support.', 0);
return;
}
// Defer?
if(JSJaCJingle_defer(function() { self.accept(); })) {
self.get_debug().log('[JSJaCJingle] accept > Deferred (waiting for the library components to be initiated).', 0);
return;
}
// Slot unavailable?
if(self.get_status() != JSJAC_JINGLE_STATUS_INITIATED) {
self.get_debug().log('[JSJaCJingle] accept > Cannot accept, resource not initiated (status: ' + self.get_status() + ').', 0);
return;
}
self.get_debug().log('[JSJaCJingle] accept > New Jingle session with media: ' + self.get_media(), 2);
// Trigger accept pending custom callback
(self._get_session_accept_pending())(self);
// Change session status
self._set_status(JSJAC_JINGLE_STATUS_ACCEPTING);
// Initialize WebRTC
self._peer_get_user_media(function() {
self._peer_connection_create(function() {
self.get_debug().log('[JSJaCJingle] accept > Ready to complete Jingle negotiation.', 2);
// Process accept actions
self.send(JSJAC_JINGLE_STANZA_TYPE_SET, { action: JSJAC_JINGLE_ACTION_SESSION_ACCEPT });
});
});
} catch(e) {
self.get_debug().log('[JSJaCJingle] accept > ' + e, 1);
}
};
/**
* Sends a Jingle session info.
*/
self.info = function(name, args) {
self.get_debug().log('[JSJaCJingle] info', 4);
try {
// Locked?
if(self.get_lock()) {
self.get_debug().log('[JSJaCJingle] info > Cannot accept, resource locked. Please open another session or check WebRTC support.', 0);
return;
}
// Defer?
if(JSJaCJingle_defer(function() { self.info(name, args); })) {
self.get_debug().log('[JSJaCJingle] info > Deferred (waiting for the library components to be initiated).', 0);
return;
}
// Slot unavailable?
if(!(self.get_status() == JSJAC_JINGLE_STATUS_INITIATED || self.get_status() == JSJAC_JINGLE_STATUS_ACCEPTING || self.get_status() == JSJAC_JINGLE_STATUS_ACCEPTED)) {
self.get_debug().log('[JSJaCJingle] info > Cannot send info, resource not active (status: ' + self.get_status() + ').', 0);
return;
}
// Assert
if(typeof args !== 'object') args = {};
// Build final args parameter
args.action = JSJAC_JINGLE_ACTION_SESSION_INFO;
if(name) args.info = name;
self.send(JSJAC_JINGLE_STANZA_TYPE_SET, args);
} catch(e) {
self.get_debug().log('[JSJaCJingle] info > ' + e, 1);
}
};
/**
* Terminates the Jingle session.
*/
self.terminate = function(reason) {
self.get_debug().log('[JSJaCJingle] terminate', 4);
try {
// Locked?
if(self.get_lock()) {
self.get_debug().log('[JSJaCJingle] terminate > Cannot terminate, resource locked. Please open another session or check WebRTC support.', 0);
return;
}
// Defer?
if(JSJaCJingle_defer(function() { self.terminate(reason); })) {
self.get_debug().log('[JSJaCJingle] terminate > Deferred (waiting for the library components to be initiated).', 0);
return;
}
// Slot unavailable?
if(self.get_status() == JSJAC_JINGLE_STATUS_TERMINATED) {
self.get_debug().log('[JSJaCJingle] terminate > Cannot terminate, resource already terminated (status: ' + self.get_status() + ').', 0);
return;
}
// Change session status
self._set_status(JSJAC_JINGLE_STATUS_TERMINATING);
// Trigger terminate pending custom callback
(self._get_session_terminate_pending())(self);
// Process terminate actions
self.send(JSJAC_JINGLE_STANZA_TYPE_SET, { action: JSJAC_JINGLE_ACTION_SESSION_TERMINATE, reason: reason });
} catch(e) {
self.get_debug().log('[JSJaCJingle] terminate > ' + e, 1);
}
};
/**
* Sends a given Jingle stanza packet
*/
self.send = function(type, args) {
self.get_debug().log('[JSJaCJingle] send', 4);
try {
// Locked?
if(self.get_lock()) {
self.get_debug().log('[JSJaCJingle] send > Cannot send, resource locked. Please open another session or check WebRTC support.', 0);
return;
}
// Defer?
if(JSJaCJingle_defer(function() { self.send(type, args); })) {
self.get_debug().log('[JSJaCJingle] send > Deferred (waiting for the library components to be initiated).', 0);
return;
}
// Assert
if(typeof args !== 'object') args = {};
// Build stanza
var stanza = new JSJaCIQ();
stanza.setTo(self.get_to());
if(type) stanza.setType(type);
if(!args.id) args.id = self._get_id_new();
stanza.setID(args.id);
if(type == JSJAC_JINGLE_STANZA_TYPE_SET) {
if(!(args.action && args.action in JSJAC_JINGLE_ACTIONS)) {
self.get_debug().log('[JSJaCJingle] send > Stanza action unknown: ' + (args.action || 'undefined'), 1);
return;
}
self._set_sent_id(args.id);
// Submit to registered handler
switch(args.action) {
case JSJAC_JINGLE_ACTION_CONTENT_ACCEPT:
self.send_content_accept(stanza); break;
case JSJAC_JINGLE_ACTION_CONTENT_ADD:
self.send_content_add(stanza); break;
case JSJAC_JINGLE_ACTION_CONTENT_MODIFY:
self.send_content_modify(stanza); break;
case JSJAC_JINGLE_ACTION_CONTENT_REJECT:
self.send_content_reject(stanza); break;
case JSJAC_JINGLE_ACTION_CONTENT_REMOVE:
self.send_content_remove(stanza); break;
case JSJAC_JINGLE_ACTION_DESCRIPTION_INFO:
self.send_description_info(stanza); break;
case JSJAC_JINGLE_ACTION_SECURITY_INFO:
self.send_security_info(stanza); break;
case JSJAC_JINGLE_ACTION_SESSION_ACCEPT:
self.send_session_accept(stanza, args); break;
case JSJAC_JINGLE_ACTION_SESSION_INFO:
self.send_session_info(stanza, args); break;
case JSJAC_JINGLE_ACTION_SESSION_INITIATE:
self.send_session_initiate(stanza, args); break;
case JSJAC_JINGLE_ACTION_SESSION_TERMINATE:
self.send_session_terminate(stanza, args); break;
case JSJAC_JINGLE_ACTION_TRANSPORT_ACCEPT:
self.send_transport_accept(stanza); break;
case JSJAC_JINGLE_ACTION_TRANSPORT_INFO:
self.send_transport_info(stanza, args); break;
case JSJAC_JINGLE_ACTION_TRANSPORT_REJECT:
self.send_transport_reject(stanza); break;
case JSJAC_JINGLE_ACTION_TRANSPORT_REPLACE:
self.send_transport_replace(stanza); break;
default:
self.get_debug().log('[JSJaCJingle] send > Unexpected error.', 1);
return false;
}
} else if(type != JSJAC_JINGLE_STANZA_TYPE_RESULT) {
self.get_debug().log('[JSJaCJingle] send > Stanza type must either be set or result.', 1);
return false;
}
JSJAC_JINGLE_STORE_CONNECTION.send(stanza);
return true;
} catch(e) {
self.get_debug().log('[JSJaCJingle] send > ' + e, 1);
}
return false;
};
/**
* Handles a given Jingle stanza response
*/
self.handle = function(stanza) {
self.get_debug().log('[JSJaCJingle] handle', 4);
try {
// Locked?
if(self.get_lock()) {
self.get_debug().log('[JSJaCJingle] handle > Cannot handle, resource locked. Please open another session or check WebRTC support.', 0);
return;
}
// Defer?
if(JSJaCJingle_defer(function() { self.handle(stanza); })) {
self.get_debug().log('[JSJaCJingle] handle > Deferred (waiting for the library components to be initiated).', 0);
return;
}
var id = stanza.getID();
var type = stanza.getType();
if(id && type == JSJAC_JINGLE_STANZA_TYPE_RESULT) self._set_received_id(id);
// Submit to custom handler
if(typeof self._get_handlers(type, id) == 'function') {
self.get_debug().log('[JSJaCJingle] handle > Submitted to custom handler.', 2);
(self._get_handlers(type, id))(stanza);
self.unregister_handler(type, id);
return;
}
var jingle = self.util_stanza_jingle(stanza);
// Don't handle non-Jingle stanzas there...
if(!jingle) return;
var action = self.util_stanza_get_attribute(jingle, 'action');
// Don't handle action-less Jingle stanzas there...
if(!action) return;
// Submit to registered handler
switch(action) {
case JSJAC_JINGLE_ACTION_CONTENT_ACCEPT:
self.handle_content_accept(stanza); break;
case JSJAC_JINGLE_ACTION_CONTENT_ADD:
self.handle_content_add(stanza); break;
case JSJAC_JINGLE_ACTION_CONTENT_MODIFY:
self.handle_content_modify(stanza); break;
case JSJAC_JINGLE_ACTION_CONTENT_REJECT:
self.handle_content_reject(stanza); break;
case JSJAC_JINGLE_ACTION_CONTENT_REMOVE:
self.handle_content_remove(stanza); break;
case JSJAC_JINGLE_ACTION_DESCRIPTION_INFO:
self.handle_description_info(stanza); break;
case JSJAC_JINGLE_ACTION_SECURITY_INFO:
self.handle_security_info(stanza); break;
case JSJAC_JINGLE_ACTION_SESSION_ACCEPT:
self.handle_session_accept(stanza); break;
case JSJAC_JINGLE_ACTION_SESSION_INFO:
self.handle_session_info(stanza); break;
case JSJAC_JINGLE_ACTION_SESSION_INITIATE:
self.handle_session_initiate(stanza); break;
case JSJAC_JINGLE_ACTION_SESSION_TERMINATE:
self.handle_session_terminate(stanza); break;
case JSJAC_JINGLE_ACTION_TRANSPORT_ACCEPT:
self.handle_transport_accept(stanza); break;
case JSJAC_JINGLE_ACTION_TRANSPORT_INFO:
self.handle_transport_info(stanza); break;
case JSJAC_JINGLE_ACTION_TRANSPORT_REJECT:
self.handle_transport_reject(stanza); break;
case JSJAC_JINGLE_ACTION_TRANSPORT_REPLACE:
self.handle_transport_replace(stanza); break;
}
} catch(e) {
self.get_debug().log('[JSJaCJingle] handle > ' + e, 1);
}
};
/**
* Mutes a Jingle session (local)
*/
self.mute = function(name) {
self.get_debug().log('[JSJaCJingle] mute', 4);
try {
// Locked?
if(self.get_lock()) {
self.get_debug().log('[JSJaCJingle] mute > Cannot mute, resource locked. Please open another session or check WebRTC support.', 0);
return;
}
// Defer?
if(JSJaCJingle_defer(function() { self.mute(name); })) {
self.get_debug().log('[JSJaCJingle] mute > Deferred (waiting for the library components to be initiated).', 0);
return;
}
// Already muted?
if(self.get_mute(name)) {
self.get_debug().log('[JSJaCJingle] mute > Resource already muted.', 0);
return;
}
self._peer_sound(false);
self._set_mute(name, true);
self.send(JSJAC_JINGLE_STANZA_TYPE_SET, { action: JSJAC_JINGLE_ACTION_SESSION_INFO, info: JSJAC_JINGLE_SESSION_INFO_MUTE, name: name });
} catch(e) {
self.get_debug().log('[JSJaCJingle] mute > ' + e, 1);
}
};
/**
* Unmutes a Jingle session (local)
*/
self.unmute = function(name) {
self.get_debug().log('[JSJaCJingle] unmute', 4);
try {
// Locked?
if(self.get_lock()) {
self.get_debug().log('[JSJaCJingle] unmute > Cannot unmute, resource locked. Please open another session or check WebRTC support.', 0);
return;
}
// Defer?
if(JSJaCJingle_defer(function() { self.unmute(name); })) {
self.get_debug().log('[JSJaCJingle] unmute > Deferred (waiting for the library components to be initiated).', 0);
return;
}
// Already unmute?
if(!self.get_mute(name)) {
self.get_debug().log('[JSJaCJingle] unmute > Resource already unmuted.', 0);
return;
}
self._peer_sound(true);
self._set_mute(name, false);
self.send(JSJAC_JINGLE_STANZA_TYPE_SET, { action: JSJAC_JINGLE_ACTION_SESSION_INFO, info: JSJAC_JINGLE_SESSION_INFO_UNMUTE, name: name });
} catch(e) {
self.get_debug().log('[JSJaCJingle] unmute > ' + e, 1);
}
};
/**
* Toggles media type in a Jingle session
*/
self.media = function(media) {
/* DEV: don't expect this to work as of now! */
self.get_debug().log('[JSJaCJingle] media', 4);
try {
// Locked?
if(self.get_lock()) {
self.get_debug().log('[JSJaCJingle] media > Cannot change media, resource locked. Please open another session or check WebRTC support.', 0);
return;
}
// Defer?
if(JSJaCJingle_defer(function() { self.media(media); })) {
self.get_debug().log('[JSJaCJingle] media > Deferred (waiting for the library components to be initiated).', 0);
return;
}
// Toggle media?
if(!media)
media = (self.get_media() == JSJAC_JINGLE_MEDIA_VIDEO) ? JSJAC_JINGLE_MEDIA_AUDIO : JSJAC_JINGLE_MEDIA_VIDEO;
// Media unknown?
if(!(media in JSJAC_JINGLE_MEDIAS)) {
self.get_debug().log('[JSJaCJingle] media > No media provided or media unsupported (media: ' + media + ').', 0);
return;
}
// Already using provided media?
if(self.get_media() == media) {
self.get_debug().log('[JSJaCJingle] media > Resource already using this media (media: ' + media + ').', 0);
return;
}
// Switch locked for now? (another one is being processed)
if(self.get_media_busy()) {
self.get_debug().log('[JSJaCJingle] media > Resource already busy switching media (busy: ' + self.get_media() + ', media: ' + media + ').', 0);
return;
}
self.get_debug().log('[JSJaCJingle] media > Changing media to: ' + media + '...', 2);
// Store new media
self._set_media(media);
self._set_media_busy(true);
// Toggle video mode (add/remove)
if(media == JSJAC_JINGLE_MEDIA_VIDEO) {
// TODO: the flow is something like that...
/*self._peer_get_user_media(function() {
self._peer_connection_create(function() {
self.get_debug().log('[JSJaCJingle] media > Ready to change media (to: ' + media + ').', 2);
// 'content-add' >> video
// TODO: restart video stream configuration
// WARNING: only change get user media, DO NOT TOUCH THE STREAM THING (don't stop active stream as it's flowing!!)
self.send(JSJAC_JINGLE_STANZA_TYPE_SET, { action: JSJAC_JINGLE_ACTION_CONTENT_ADD, name: JSJAC_JINGLE_MEDIA_VIDEO });
})
});*/
} else {
// TODO: the flow is something like that...
/*self._peer_get_user_media(function() {
self._peer_connection_create(function() {
self.get_debug().log('[JSJaCJingle] media > Ready to change media (to: ' + media + ').', 2);
// 'content-remove' >> video
// TODO: remove video stream configuration
// WARNING: only change get user media, DO NOT TOUCH THE STREAM THING (don't stop active stream as it's flowing!!)
// here, only stop the video stream, do not touch the audio stream
self.send(JSJAC_JINGLE_STANZA_TYPE_SET, { action: JSJAC_JINGLE_ACTION_CONTENT_REMOVE, name: JSJAC_JINGLE_MEDIA_VIDEO });
})
});*/
}
} catch(e) {
self.get_debug().log('[JSJaCJingle] media > ' + e, 1);
}
};
/**
* Registers a given handler on a given Jingle stanza
*/
self.register_handler = function(type, id, fn) {
self.get_debug().log('[JSJaCJingle] register_handler', 4);
try {
type = type || JSJAC_JINGLE_STANZA_TYPE_ALL;
if(typeof fn !== 'function') {
self.get_debug().log('[JSJaCJingle] register_handler > fn parameter not passed or not a function!', 1);
return false;
}
if(id) {
self._set_handlers(type, id, fn);
self.get_debug().log('[JSJaCJingle] register_handler > Registered handler for id: ' + id + ' and type: ' + type, 3);
return true;
} else {
self.get_debug().log('[JSJaCJingle] register_handler > Could not register handler (no ID).', 1);
return false;
}
} catch(e) {
self.get_debug().log('[JSJaCJingle] register_handler > ' + e, 1);
}
return false;
};
/**
* Unregisters the given handler on a given Jingle stanza
*/
self.unregister_handler = function(type, id) {
self.get_debug().log('[JSJaCJingle] unregister_handler', 4);
try {
type = type || JSJAC_JINGLE_STANZA_TYPE_ALL;
if(type in self._handlers && id in self._handlers[type]) {
delete self._handlers[type][id];
self.get_debug().log('[JSJaCJingle] unregister_handler > Unregistered handler for id: ' + id + ' and type: ' + type, 3);
return true;
} else {
self.get_debug().log('[JSJaCJingle] unregister_handler > Could not unregister handler with id: ' + id + ' and type: ' + type + ' (not found).', 2);
return false;
}
} catch(e) {
self.get_debug().log('[JSJaCJingle] unregister_handler > ' + e, 1);
}
return false;
};
/**
* Registers a view element
*/
self.register_view = function(type, view) {
self.get_debug().log('[JSJaCJingle] register_view', 4);
try {
// Get view functions
var fn = self._util_map_register_view(type);
if(fn.type == type) {
var i;
// Check view is not already registered
for(i in (fn.view.get)()) {
if((fn.view.get)()[i] == view) {
self.get_debug().log('[JSJaCJingle] register_view > Could not register view of type: ' + type + ' (already registered).', 2);
return true;
}
}
// Proceeds registration
(fn.view.set)(view);
self._util_peer_stream_attach(
[view],
(fn.stream.get)(),
fn.mute
);
self.get_debug().log('[JSJaCJingle] register_view > Registered view of type: ' + type, 3);
return true;
} else {
self.get_debug().log('[JSJaCJingle] register_view > Could not register view of type: ' + type + ' (type unknown).', 1);
return false;
}
} catch(e) {
self.get_debug().log('[JSJaCJingle] register_view > ' + e, 1);
}
return false;
};
/**
* Unregisters a view element
*/
self.unregister_view = function(type, view) {
self.get_debug().log('[JSJaCJingle] unregister_view', 4);
try {
// Get view functions
var fn = self._util_map_unregister_view(type);
if(fn.type == type) {
var i;
// Check view is registered
for(i in (fn.view.get)()) {
if((fn.view.get)()[i] == view) {
// Proceeds un-registration
self._util_peer_stream_detach(
[view]
);
self.util_array_remove_value(
(fn.view.get)(),
view
);
self.get_debug().log('[JSJaCJingle] unregister_view > Unregistered view of type: ' + type, 3);
return true;
}
}
self.get_debug().log('[JSJaCJingle] unregister_view > Could not unregister view of type: ' + type + ' (not found).', 2);
return true;
} else {
self.get_debug().log('[JSJaCJingle] unregister_view > Could not unregister view of type: ' + type + ' (type unknown).', 1);
return false;
}
} catch(e) {
self.get_debug().log('[JSJaCJingle] unregister_view > ' + e, 1);
}
return false;
};
/**
* JSJSAC JINGLE SENDERS
*/
/**
* Sends the Jingle content accept
*/
self.send_content_accept = function(stanza) {
self.get_debug().log('[JSJaCJingle] send_content_accept', 4);
try {
// TODO: remove from remote 'content-add' queue
// TODO: reprocess content_local/content_remote
// Not implemented for now
self.get_debug().log('[JSJaCJingle] send_content_accept > Feature not implemented!', 0);
} catch(e) {
self.get_debug().log('[JSJaCJingle] send_content_accept > ' + e, 1);
}
};
/**
* Sends the Jingle content add
*/
self.send_content_add = function(stanza) {
self.get_debug().log('[JSJaCJingle] send_content_add', 4);
try {
// TODO: push to local 'content-add' queue
// Not implemented for now
self.get_debug().log('[JSJaCJingle] send_content_add > Feature not implemented!', 0);
} catch(e) {
self.get_debug().log('[JSJaCJingle] send_content_add > ' + e, 1);
}
};
/**
* Sends the Jingle content modify
*/
self.send_content_modify = function(stanza) {
self.get_debug().log('[JSJaCJingle] send_content_modify', 4);
try {
// TODO: push to local 'content-modify' queue
// Not implemented for now
self.get_debug().log('[JSJaCJingle] send_content_modify > Feature not implemented!', 0);
} catch(e) {
self.get_debug().log('[JSJaCJingle] send_content_modify > ' + e, 1);
}
};
/**
* Sends the Jingle content reject
*/
self.send_content_reject = function(stanza) {
self.get_debug().log('[JSJaCJingle] send_content_reject', 4);
try {
// TODO: remove from remote 'content-add' queue
// Not implemented for now
self.get_debug().log('[JSJaCJingle] send_content_reject > Feature not implemented!', 0);
} catch(e) {
self.get_debug().log('[JSJaCJingle] send_content_reject > ' + e, 1);
}
};
/**
* Sends the Jingle content remove
*/
self.send_content_remove = function(stanza) {
self.get_debug().log('[JSJaCJingle] send_content_remove', 4);
try {
// TODO: add to local 'content-remove' queue
// Not implemented for now
self.get_debug().log('[JSJaCJingle] send_content_remove > Feature not implemented!', 0);
} catch(e) {
self.get_debug().log('[JSJaCJingle] send_content_remove > ' + e, 1);
}
};
/**
* Sends the Jingle description info
*/
self.send_description_info = function(stanza) {
self.get_debug().log('[JSJaCJingle] send_description_info', 4);
try {
// Not implemented for now
self.get_debug().log('[JSJaCJingle] send_description_info > Feature not implemented!', 0);
} catch(e) {
self.get_debug().log('[JSJaCJingle] send_description_info > ' + e, 1);
}
};
/**
* Sends the Jingle security info
*/
self.send_security_info = function(stanza) {
self.get_debug().log('[JSJaCJingle] send_security_info', 4);
try {
// Not implemented for now
self.get_debug().log('[JSJaCJingle] send_security_info > Feature not implemented!', 0);
} catch(e) {
self.get_debug().log('[JSJaCJingle] send_security_info > ' + e, 1);
}
};
/**
* Sends the Jingle session accept
*/
self.send_session_accept = function(stanza, args) {
self.get_debug().log('[JSJaCJingle] send_session_accept', 4);
try {
if(self.get_status() != JSJAC_JINGLE_STATUS_ACCEPTING) {
self.get_debug().log('[JSJaCJingle] send_session_accept > Cannot send accept stanza, resource not accepting (status: ' + self.get_status() + ').', 0);
self.send_error(stanza, JSJAC_JINGLE_ERROR_OUT_OF_ORDER);
return;
}
if(!args) {
self.get_debug().log('[JSJaCJingle] send_session_accept > Argument not provided.', 1);
return;
}
// Build Jingle stanza
var jingle = self._util_stanza_generate_jingle(stanza, {
'action' : JSJAC_JINGLE_ACTION_SESSION_ACCEPT,
'responder' : self.get_responder()
});
self._util_stanza_generate_content_local(stanza, jingle);
self._util_stanza_generate_group_local(stanza, jingle);
// Schedule success
self.register_handler(JSJAC_JINGLE_STANZA_TYPE_RESULT, args.id, function(stanza) {
(self._get_session_accept_success())(self, stanza);
self.handle_session_accept_success(stanza);
});
// Schedule error timeout
self.util_stanza_timeout(JSJAC_JINGLE_STANZA_TYPE_RESULT, args.id, {
external: self._get_session_accept_error(),
internal: self.handle_session_accept_error
});
self.get_debug().log('[JSJaCJingle] send_session_accept > Sent.', 4);
} catch(e) {
self.get_debug().log('[JSJaCJingle] send_session_accept > ' + e, 1);
}
};
/**
* Sends the Jingle session info
*/
self.send_session_info = function(stanza, args) {
self.get_debug().log('[JSJaCJingle] send_session_info', 4);
try {
if(!args) {
self.get_debug().log('[JSJaCJingle] send_session_info > Argument not provided.', 1);
return;
}
// Filter info
args.info = args.info || JSJAC_JINGLE_SESSION_INFO_ACTIVE;
// Build Jingle stanza
var jingle = self._util_stanza_generate_jingle(stanza, {
'action' : JSJAC_JINGLE_ACTION_SESSION_INFO,
'initiator' : self.get_initiator()
});
self._util_stanza_generate_session_info(stanza, jingle, args);
// Schedule success
self.register_handler(JSJAC_JINGLE_STANZA_TYPE_RESULT, args.id, function(stanza) {
(self._get_session_info_success())(self, stanza);
self.handle_session_info_success(stanza);
});
// Schedule error timeout
self.util_stanza_timeout(JSJAC_JINGLE_STANZA_TYPE_RESULT, args.id, {
external: self._get_session_info_error(),
internal: self.handle_session_info_error
});
self.get_debug().log('[JSJaCJingle] send_session_info > Sent (name: ' + args.info + ').', 2);
} catch(e) {
self.get_debug().log('[JSJaCJingle] send_session_info > ' + e, 1);
}
};
/**
* Sends the Jingle session initiate
*/
self.send_session_initiate = function(stanza, args) {
self.get_debug().log('[JSJaCJingle] send_session_initiate', 4);
try {
if(self.get_status() != JSJAC_JINGLE_STATUS_INITIATING) {
self.get_debug().log('[JSJaCJingle] send_session_initiate > Cannot send initiate stanza, resource not initiating (status: ' + self.get_status() + ').', 0);
return;
}
if(!args) {
self.get_debug().log('[JSJaCJingle] send_session_initiate > Argument not provided.', 1);
return;
}
// Build Jingle stanza
var jingle = self._util_stanza_generate_jingle(stanza, {
'action' : JSJAC_JINGLE_ACTION_SESSION_INITIATE,
'initiator' : self.get_initiator()
});
self._util_stanza_generate_content_local(stanza, jingle);
self._util_stanza_generate_group_local(stanza, jingle);
// Schedule success
self.register_handler(JSJAC_JINGLE_STANZA_TYPE_RESULT, args.id, function(stanza) {
(self._get_session_initiate_success())(self, stanza);
self.handle_session_initiate_success(stanza);
});
// Schedule error timeout
self.util_stanza_timeout(JSJAC_JINGLE_STANZA_TYPE_RESULT, args.id, {
external: self._get_session_initiate_error(),
internal: self.handle_session_initiate_error
});
self.get_debug().log('[JSJaCJingle] send_session_initiate > Sent.', 2);
} catch(e) {
self.get_debug().log('[JSJaCJingle] send_session_initiate > ' + e, 1);
}
};
/**
* Sends the Jingle session terminate
*/
self.send_session_terminate = function(stanza, args) {
self.get_debug().log('[JSJaCJingle] send_session_terminate', 4);
try {
if(self.get_status() != JSJAC_JINGLE_STATUS_TERMINATING) {
self.get_debug().log('[JSJaCJingle] send_session_terminate > Cannot send terminate stanza, resource not terminating (status: ' + self.get_status() + ').', 0);
return;
}
if(!args) {
self.get_debug().log('[JSJaCJingle] send_session_terminate > Argument not provided.', 1);
return;
}
// Filter reason
args.reason = args.reason || JSJAC_JINGLE_REASON_SUCCESS;
// Store terminate reason
self._set_reason(args.reason);
// Build terminate stanza
var jingle = self._util_stanza_generate_jingle(stanza, {
'action': JSJAC_JINGLE_ACTION_SESSION_TERMINATE
});
var jingle_reason = jingle.appendChild(stanza.buildNode('reason', {'xmlns': NS_JINGLE}));
jingle_reason.appendChild(stanza.buildNode(args.reason, {'xmlns': NS_JINGLE}));
// Schedule success
self.register_handler(JSJAC_JINGLE_STANZA_TYPE_RESULT, args.id, function(stanza) {
(self._get_session_terminate_success())(self, stanza);
self.handle_session_terminate_success(stanza);
});
// Schedule error timeout
self.util_stanza_timeout(JSJAC_JINGLE_STANZA_TYPE_RESULT, args.id, {
external: self._get_session_terminate_error(),
internal: self.handle_session_terminate_error
});
self.get_debug().log('[JSJaCJingle] send_session_terminate > Sent (reason: ' + args.reason + ').', 2);
} catch(e) {
self.get_debug().log('[JSJaCJingle] send_session_terminate > ' + e, 1);
}
};
/**
* Sends the Jingle transport accept
*/
self.send_transport_accept = function(stanza) {
self.get_debug().log('[JSJaCJingle] send_transport_accept', 4);
try {
// Not implemented for now
self.get_debug().log('[JSJaCJingle] send_transport_accept > Feature not implemented!', 0);
} catch(e) {
self.get_debug().log('[JSJaCJingle] send_transport_accept > ' + e, 1);
}
};
/**
* Sends the Jingle transport info
*/
self.send_transport_info = function(stanza, args) {
self.get_debug().log('[JSJaCJingle] send_transport_info', 4);
try {
if(self.get_status() != JSJAC_JINGLE_STATUS_INITIATED && self.get_status() != JSJAC_JINGLE_STATUS_ACCEPTING && self.get_status() != JSJAC_JINGLE_STATUS_ACCEPTED) {
self.get_debug().log('[JSJaCJingle] send_transport_info > Cannot send transport info, resource not initiated, nor accepting, nor accepted (status: ' + self.get_status() + ').', 0);
return;
}
if(!args) {
self.get_debug().log('[JSJaCJingle] send_transport_info > Argument not provided.', 1);
return;
}
if(self.util_object_length(self._get_candidates_queue_local()) === 0) {
self.get_debug().log('[JSJaCJingle] send_transport_info > No local candidate in queue.', 1);
return;
}
// Build Jingle stanza
var jingle = self._util_stanza_generate_jingle(stanza, {
'action' : JSJAC_JINGLE_ACTION_TRANSPORT_INFO,
'initiator' : self.get_initiator()
});
// Build queue content
var cur_name;
var content_queue_local = {};
for(cur_name in self.get_name()) {
content_queue_local[cur_name] = self._util_generate_content(
self.get_creator(cur_name),
cur_name,
self.get_senders(cur_name),
self._get_payloads_local(cur_name),
self._get_candidates_queue_local(cur_name)
);
}
self._util_stanza_generate_content_local(stanza, jingle, content_queue_local);
self._util_stanza_generate_group_local(stanza, jingle);
// Schedule success
self.register_handler(JSJAC_JINGLE_STANZA_TYPE_RESULT, args.id, function(stanza) {
self.handle_transport_info_success(stanza);
});
// Schedule error timeout
self.util_stanza_timeout(JSJAC_JINGLE_STANZA_TYPE_RESULT, args.id, {
internal: self.handle_transport_info_error
});
self.get_debug().log('[JSJaCJingle] send_transport_info > Sent.', 2);
} catch(e) {
self.get_debug().log('[JSJaCJingle] send_transport_info > ' + e, 1);
}
};
/**
* Sends the Jingle transport reject
*/
self.send_transport_reject = function(stanza) {
self.get_debug().log('[JSJaCJingle] send_transport_reject', 4);
try {
// Not implemented for now
self.get_debug().log('[JSJaCJingle] send_transport_reject > Feature not implemented!', 0);
} catch(e) {
self.get_debug().log('[JSJaCJingle] send_transport_reject > ' + e, 1);
}
};
/**
* Sends the Jingle transport replace
*/
self.send_transport_replace = function(stanza) {
self.get_debug().log('[JSJaCJingle] send_transport_replace', 4);
try {
// Not implemented for now
self.get_debug().log('[JSJaCJingle] send_transport_replace > Feature not implemented!', 0);
} catch(e) {
self.get_debug().log('[JSJaCJingle] send_transport_replace > ' + e, 1);
}
};
/**
* Sends the Jingle transport replace
*/
self.send_error = function(stanza, error) {
self.get_debug().log('[JSJaCJingle] send_error', 4);
try {
// Assert
if(!('type' in error)) {
self.get_debug().log('[JSJaCJingle] send_error > Type unknown.', 1);
return;
}
if('jingle' in error && !(error.jingle in JSJAC_JINGLE_ERRORS)) {
self.get_debug().log('[JSJaCJingle] send_error > Jingle condition unknown (' + error.jingle + ').', 1);
return;
}
if('xmpp' in error && !(error.xmpp in XMPP_ERRORS)) {
self.get_debug().log('[JSJaCJingle] send_error > XMPP condition unknown (' + error.xmpp + ').', 1);
return;
}
var stanza_error = new JSJaCIQ();
stanza_error.setType('error');
stanza_error.setID(stanza.getID());
stanza_error.setTo(self.get_to());
var error_node = stanza_error.getNode().appendChild(stanza_error.buildNode('error', {'xmlns': NS_CLIENT, 'type': error.type}));
if('xmpp' in error) error_node.appendChild(stanza_error.buildNode(error.xmpp, { 'xmlns': NS_STANZAS }));
if('jingle' in error) error_node.appendChild(stanza_error.buildNode(error.jingle, { 'xmlns': NS_JINGLE_ERRORS }));
JSJAC_JINGLE_STORE_CONNECTION.send(stanza_error);
self.get_debug().log('[JSJaCJingle] send_error > Sent: ' + (error.jingle || error.xmpp), 2);
} catch(e) {
self.get_debug().log('[JSJaCJingle] send_error > ' + e, 1);
}
};
/**
* JSJSAC JINGLE HANDLERS
*/
/**
* Handles the Jingle content accept
* @param {JSJaCPacket} stanza Jingle handled stanza
*/
self.handle_content_accept = function(stanza) {
self.get_debug().log('[JSJaCJingle] handle_content_accept', 4);
try {
// TODO: start to flow accepted stream
// TODO: remove accepted content from local 'content-add' queue
// TODO: reprocess content_local/content_remote
// Not implemented for now
self.send_error(stanza, XMPP_ERROR_FEATURE_NOT_IMPLEMENTED);
} catch(e) {
self.get_debug().log('[JSJaCJingle] handle_content_accept > ' + e, 1);
}
};
/**
* Handles the Jingle content add
* @param {JSJaCPacket} stanza Jingle handled stanza
*/
self.handle_content_add = function(stanza) {
self.get_debug().log('[JSJaCJingle] handle_content_add', 4);
try {
// TODO: request the user to start this content (need a custom handler)
// on accept: send content-accept
// TODO: push to remote 'content-add' queue
// TODO: reprocess content_local/content_remote
// Not implemented for now
self.send_error(stanza, XMPP_ERROR_FEATURE_NOT_IMPLEMENTED);
} catch(e) {
self.get_debug().log('[JSJaCJingle] handle_content_add > ' + e, 1);
}
};
/**
* Handles the Jingle content modify
* @param {JSJaCPacket} stanza Jingle handled stanza
*/
self.handle_content_modify = function(stanza) {
self.get_debug().log('[JSJaCJingle] handle_content_modify', 4);
try {
// TODO: change 'senders' value (direction of the stream)
// if(send:from_me): notify the user that media is requested
// if(unacceptable): terminate the session
// if(accepted): change local/remote SDP
// TODO: reprocess content_local/content_remote
// Not implemented for now
self.send_error(stanza, XMPP_ERROR_FEATURE_NOT_IMPLEMENTED);
} catch(e) {
self.get_debug().log('[JSJaCJingle] handle_content_modify > ' + e, 1);
}
};
/**
* Handles the Jingle content reject
* @param {JSJaCPacket} stanza Jingle handled stanza
*/
self.handle_content_reject = function(stanza) {
self.get_debug().log('[JSJaCJingle] handle_content_reject', 4);
try {
// TODO: remove rejected content from local 'content-add' queue
// Not implemented for now
self.send_error(stanza, XMPP_ERROR_FEATURE_NOT_IMPLEMENTED);
} catch(e) {
self.get_debug().log('[JSJaCJingle] handle_content_reject > ' + e, 1);
}
};
/**
* Handles the Jingle content remove
* @param {JSJaCPacket} stanza Jingle handled stanza
*/
self.handle_content_remove = function(stanza) {
self.get_debug().log('[JSJaCJingle] handle_content_remove', 4);
try {
// TODO: stop flowing removed stream
// TODO: reprocess content_local/content_remote
// Not implemented for now
self.send_error(stanza, XMPP_ERROR_FEATURE_NOT_IMPLEMENTED);
} catch(e) {
self.get_debug().log('[JSJaCJingle] handle_content_remove > ' + e, 1);
}
};
/**
* Handles the Jingle description info
* @param {JSJaCPacket} stanza Jingle handled stanza
*/
self.handle_description_info = function(stanza) {
self.get_debug().log('[JSJaCJingle] handle_description_info', 4);
try {
// Not implemented for now
self.send_error(stanza, XMPP_ERROR_FEATURE_NOT_IMPLEMENTED);
} catch(e) {
self.get_debug().log('[JSJaCJingle] handle_description_info > ' + e, 1);
}
};
/**
* Handles the Jingle security info
* @param {JSJaCPacket} stanza Jingle handled stanza
*/
self.handle_security_info = function(stanza) {
self.get_debug().log('[JSJaCJingle] handle_security_info', 4);
try {
// Not implemented for now
self.send_error(stanza, XMPP_ERROR_FEATURE_NOT_IMPLEMENTED);
} catch(e) {
self.get_debug().log('[JSJaCJingle] handle_security_info > ' + e, 1);
}
};
/**
* Handles the Jingle session accept
* @param {JSJaCPacket} stanza Jingle handled stanza
*/
self.handle_session_accept = function(stanza) {
self.get_debug().log('[JSJaCJingle] handle_session_accept', 4);
try {
// Security preconditions
if(!self.util_stanza_safe(stanza)) {
self.get_debug().log('[JSJaCJingle] handle_session_accept > Dropped unsafe stanza.', 0);
self.send_error(stanza, JSJAC_JINGLE_ERROR_UNKNOWN_SESSION);
return;
}
// Can now safely dispatch the stanza
switch(stanza.getType()) {
case JSJAC_JINGLE_STANZA_TYPE_RESULT:
(self._get_session_accept_success())(self, stanza);
self.handle_session_accept_success(stanza);
break;
case 'error':
(self._get_session_accept_error())(self, stanza);
self.handle_session_accept_error(stanza);
break;
case JSJAC_JINGLE_STANZA_TYPE_SET:
// External handler must be set before internal one here...
(self._get_session_accept_request())(self, stanza);
self.handle_session_accept_request(stanza);
break;
default:
self.send_error(stanza, XMPP_ERROR_FEATURE_NOT_IMPLEMENTED);
}
} catch(e) {
self.get_debug().log('[JSJaCJingle] handle_session_accept > ' + e, 1);
}
};
/**
* Handles the Jingle session accept success
* @param {JSJaCPacket} stanza Jingle handled stanza
*/
self.handle_session_accept_success = function(stanza) {
self.get_debug().log('[JSJaCJingle] handle_session_accept_success', 4);
try {
// Change session status
self._set_status(JSJAC_JINGLE_STATUS_ACCEPTED);
} catch(e) {
self.get_debug().log('[JSJaCJingle] handle_session_accept_success > ' + e, 1);
}
};
/**
* Handles the Jingle session accept error
* @param {JSJaCPacket} stanza Jingle handled stanza
*/
self.handle_session_accept_error = function(stanza) {
self.get_debug().log('[JSJaCJingle] handle_session_accept_error', 4);
try {
// Terminate the session (timeout)
self.terminate(JSJAC_JINGLE_REASON_TIMEOUT);
} catch(e) {
self.get_debug().log('[JSJaCJingle] handle_session_accept_error > ' + e, 1);
}
};
/**
* Handles the Jingle session accept request
* @param {JSJaCPacket} stanza Jingle handled stanza
*/
self.handle_session_accept_request = function(stanza) {
self.get_debug().log('[JSJaCJingle] handle_session_accept_request', 4);
try {
// Slot unavailable?
if(self.get_status() != JSJAC_JINGLE_STATUS_INITIATED) {
self.get_debug().log('[JSJaCJingle] handle_session_accept_request > Cannot handle, resource already accepted (status: ' + self.get_status() + ').', 0);
self.send_error(stanza, JSJAC_JINGLE_ERROR_OUT_OF_ORDER);
return;
}
// Common vars
var i, cur_candidate_obj;
// Change session status
self._set_status(JSJAC_JINGLE_STATUS_ACCEPTING);
var rd_sid = self.util_stanza_sid(stanza);
// Request is valid?
if(rd_sid && self.is_initiator() && self._util_stanza_parse_content(stanza)) {
// Handle additional data (optional)
self._util_stanza_parse_group(stanza);
// Generate and store content data
self._util_build_content_remote();
// Trigger accept success callback
(self._get_session_accept_success())(self, stanza);
self.handle_session_accept_success(stanza);
var sdp_remote = self._util_sdp_generate(
WEBRTC_SDP_TYPE_ANSWER,
self._get_group_remote(),
self._get_payloads_remote(),
self._get_candidates_queue_remote()
);
if(self.get_sdp_trace()) self.get_debug().log('[JSJaCJingle] SDP (remote)' + '\n\n' + sdp_remote.description.sdp, 4);
// Remote description
self._get_peer_connection().setRemoteDescription(
(new WEBRTC_SESSION_DESCRIPTION(sdp_remote.description)),
function() {
// Success (descriptions are compatible)
},
function(e) {
if(self.get_sdp_trace()) self.get_debug().log('[JSJaCJingle] SDP (remote:error)' + '\n\n' + (e.message || e.name || 'Unknown error'), 4);
// Error (descriptions are incompatible)
self.terminate(JSJAC_JINGLE_REASON_INCOMPATIBLE_PARAMETERS);
}
);
// ICE candidates
for(i in sdp_remote.candidates) {
cur_candidate_obj = sdp_remote.candidates[i];
self._get_peer_connection().addIceCandidate(
new WEBRTC_ICE_CANDIDATE({
sdpMLineIndex : cur_candidate_obj.id,
candidate : cur_candidate_obj.candidate
})
);
}
// Empty the unapplied candidates queue
self._set_candidates_queue_remote(null);
// Success reply
self.send(JSJAC_JINGLE_STANZA_TYPE_RESULT, { id: stanza.getID() });
} else {
// Trigger accept error callback
(self._get_session_accept_error())(self, stanza);
self.handle_session_accept_error(stanza);
// Send error reply
self.send_error(stanza, XMPP_ERROR_BAD_REQUEST);
self.get_debug().log('[JSJaCJingle] handle_session_accept_request > Error.', 1);
}
} catch(e) {
self.get_debug().log('[JSJaCJingle] handle_session_accept_request > ' + e, 1);
}
};
/**
* Handles the Jingle session info
* @param {JSJaCPacket} stanza Jingle handled stanza
*/
self.handle_session_info = function(stanza) {
self.get_debug().log('[JSJaCJingle] handle_session_info', 4);
try {
// Security preconditions
if(!self.util_stanza_safe(stanza)) {
self.get_debug().log('[JSJaCJingle] handle_session_info > Dropped unsafe stanza.', 0);
self.send_error(stanza, JSJAC_JINGLE_ERROR_UNKNOWN_SESSION);
return;
}
// Can now safely dispatch the stanza
switch(stanza.getType()) {
case JSJAC_JINGLE_STANZA_TYPE_RESULT:
(self._get_session_info_success())(self, stanza);
self.handle_session_info_success(stanza);
break;
case 'error':
(self._get_session_info_error())(self, stanza);
self.handle_session_info_error(stanza);
break;
case JSJAC_JINGLE_STANZA_TYPE_SET:
(self._get_session_info_request())(self, stanza);
self.handle_session_info_request(stanza);
break;
default:
self.send_error(stanza, XMPP_ERROR_FEATURE_NOT_IMPLEMENTED);
}
} catch(e) {
self.get_debug().log('[JSJaCJingle] handle_session_info > ' + e, 1);
}
};
/**
* Handles the Jingle session info success
* @param {JSJaCPacket} stanza Jingle handled stanza
*/
self.handle_session_info_success = function(stanza) {
self.get_debug().log('[JSJaCJingle] handle_session_info_success', 4);
};
/**
* Handles the Jingle session info error
* @param {JSJaCPacket} stanza Jingle handled stanza
*/
self.handle_session_info_error = function(stanza) {
self.get_debug().log('[JSJaCJingle] handle_session_info_error', 4);
};
/**
* Handles the Jingle session info request
* @param {JSJaCPacket} stanza Jingle handled stanza
*/
self.handle_session_info_request = function(stanza) {
self.get_debug().log('[JSJaCJingle] handle_session_info_request', 4);
try {
// Parse stanza
var info_name = self.util_stanza_session_info(stanza);
var info_result = false;
switch(info_name) {
case JSJAC_JINGLE_SESSION_INFO_ACTIVE:
case JSJAC_JINGLE_SESSION_INFO_RINGING:
case JSJAC_JINGLE_SESSION_INFO_MUTE:
case JSJAC_JINGLE_SESSION_INFO_UNMUTE:
info_result = true; break;
}
if(info_result) {
self.get_debug().log('[JSJaCJingle] handle_session_info_request > (name: ' + (info_name || 'undefined') + ').', 3);
// Process info actions
self.send(JSJAC_JINGLE_STANZA_TYPE_RESULT, { id: stanza.getID() });
// Trigger info success custom callback
(self._get_session_info_success())(self, stanza);
self.handle_session_info_success(stanza);
} else {
self.get_debug().log('[JSJaCJingle] handle_session_info_request > Error (name: ' + (info_name || 'undefined') + ').', 1);
// Send error reply
self.send_error(stanza, XMPP_ERROR_FEATURE_NOT_IMPLEMENTED);
// Trigger info error custom callback
(self._get_session_info_error())(self, stanza);
self.handle_session_info_error(stanza);
}
} catch(e) {
self.get_debug().log('[JSJaCJingle] handle_session_info_request > ' + e, 1);
}
};
/**
* Handles the Jingle session initiate
* @param {JSJaCPacket} stanza Jingle handled stanza
*/
self.handle_session_initiate = function(stanza) {
self.get_debug().log('[JSJaCJingle] handle_session_initiate', 4);
try {
switch(stanza.getType()) {
case JSJAC_JINGLE_STANZA_TYPE_RESULT:
(self._get_session_initiate_success())(self, stanza);
self.handle_session_initiate_success(stanza);
break;
case 'error':
(self._get_session_initiate_error())(self, stanza);
self.handle_session_initiate_error(stanza);
break;
case JSJAC_JINGLE_STANZA_TYPE_SET:
(self._get_session_initiate_request())(self, stanza);
self.handle_session_initiate_request(stanza);
break;
default:
self.send_error(stanza, XMPP_ERROR_FEATURE_NOT_IMPLEMENTED);
}
} catch(e) {
self.get_debug().log('[JSJaCJingle] handle_session_initiate > ' + e, 1);
}
};
/**
* Handles the Jingle session initiate success
* @param {JSJaCPacket} stanza Jingle handled stanza
*/
self.handle_session_initiate_success = function(stanza) {
self.get_debug().log('[JSJaCJingle] handle_session_initiate_success', 4);
try {
// Change session status
self._set_status(JSJAC_JINGLE_STATUS_INITIATED);
} catch(e) {
self.get_debug().log('[JSJaCJingle] handle_session_initiate_success > ' + e, 1);
}
};
/**
* Handles the Jingle session initiate error
* @param {JSJaCPacket} stanza Jingle handled stanza
*/
self.handle_session_initiate_error = function(stanza) {
self.get_debug().log('[JSJaCJingle] handle_session_initiate_error', 4);
try {
// Change session status
self._set_status(JSJAC_JINGLE_STATUS_INACTIVE);
// Stop WebRTC
self._peer_stop();
// Lock session (cannot be used later)
self._set_lock(true);
} catch(e) {
self.get_debug().log('[JSJaCJingle] handle_session_initiate_error > ' + e, 1);
}
};
/**
* Handles the Jingle session initiate request
* @param {JSJaCPacket} stanza Jingle handled stanza
*/
self.handle_session_initiate_request = function(stanza) {
self.get_debug().log('[JSJaCJingle] handle_session_initiate_request', 4);
try {
// Slot unavailable?
if(self.get_status() != JSJAC_JINGLE_STATUS_INACTIVE) {
self.get_debug().log('[JSJaCJingle] handle_session_initiate_request > Cannot handle, resource already initiated (status: ' + self.get_status() + ').', 0);
self.send_error(stanza, JSJAC_JINGLE_ERROR_OUT_OF_ORDER);
return;
}
// Change session status
self._set_status(JSJAC_JINGLE_STATUS_INITIATING);
// Common vars
var rd_from = self.util_stanza_from(stanza);
var rd_sid = self.util_stanza_sid(stanza);
// Request is valid?
if(rd_sid && self._util_stanza_parse_content(stanza)) {
// Handle additional data (optional)
self._util_stanza_parse_group(stanza);
// Set session values
self._set_sid(rd_sid);
self._set_to(rd_from);
self._set_initiator(rd_from);
self._set_responder(self.util_connection_jid());
// Register session to common router
JSJaCJingle_add(rd_sid, self);
// Generate and store content data
self._util_build_content_remote();
// Video or audio-only session?
if(JSJAC_JINGLE_MEDIA_VIDEO in self._get_content_remote()) {
self._set_media(JSJAC_JINGLE_MEDIA_VIDEO);
} else if(JSJAC_JINGLE_MEDIA_AUDIO in self._get_content_remote()) {
self._set_media(JSJAC_JINGLE_MEDIA_AUDIO);
} else {
// Session initiation not done
(self._get_session_initiate_error())(self, stanza);
self.handle_session_initiate_error(stanza);
// Error (no media is supported)
self.terminate(JSJAC_JINGLE_REASON_UNSUPPORTED_APPLICATIONS);
self.get_debug().log('[JSJaCJingle] handle_session_initiate_request > Error (unsupported media).', 1);
return;
}
// Session initiate done
(self._get_session_initiate_success())(self, stanza);
self.handle_session_initiate_success(stanza);
self.send(JSJAC_JINGLE_STANZA_TYPE_RESULT, { id: stanza.getID() });
} else {
// Session initiation not done
(self._get_session_initiate_error())(self, stanza);
self.handle_session_initiate_error(stanza);
// Send error reply
self.send_error(stanza, XMPP_ERROR_BAD_REQUEST);
self.get_debug().log('[JSJaCJingle] handle_session_initiate_request > Error (bad request).', 1);
}
} catch(e) {
self.get_debug().log('[JSJaCJingle] handle_session_initiate_request > ' + e, 1);
}
};
/**
* Handles the Jingle session terminate
* @param {JSJaCPacket} stanza Jingle handled stanza
*/
self.handle_session_terminate = function(stanza) {
self.get_debug().log('[JSJaCJingle] handle_session_terminate', 4);
try {
var type = stanza.getType();
// Security preconditions
if(!self.util_stanza_safe(stanza)) {
self.get_debug().log('[JSJaCJingle] handle_session_terminate > Dropped unsafe stanza.', 0);
self.send_error(stanza, JSJAC_JINGLE_ERROR_UNKNOWN_SESSION);
return;
}
// Can now safely dispatch the stanza
switch(stanza.getType()) {
case JSJAC_JINGLE_STANZA_TYPE_RESULT:
(self._get_session_terminate_success())(self, stanza);
self.handle_session_terminate_success(stanza);
break;
case 'error':
(self._get_session_terminate_error())(self, stanza);
self.handle_session_terminate_error(stanza);
break;
case JSJAC_JINGLE_STANZA_TYPE_SET:
(self._get_session_terminate_request())(self, stanza);
self.handle_session_terminate_request(stanza);
break;
default:
self.send_error(stanza, XMPP_ERROR_FEATURE_NOT_IMPLEMENTED);
}
} catch(e) {
self.get_debug().log('[JSJaCJingle] handle_session_terminate > ' + e, 1);
}
};
/**
* Handles the Jingle session terminate success
* @param {JSJaCPacket} stanza Jingle handled stanza
*/
self.handle_session_terminate_success = function(stanza) {
self.get_debug().log('[JSJaCJingle] handle_session_terminate_success', 4);
try {
// Change session status
self._set_status(JSJAC_JINGLE_STATUS_TERMINATED);
// Stop WebRTC
self._peer_stop();
} catch(e) {
self.get_debug().log('[JSJaCJingle] handle_session_terminate_success > ' + e, 1);
}
};
/**
* Handles the Jingle session terminate error
* @param {JSJaCPacket} stanza Jingle handled stanza
*/
self.handle_session_terminate_error = function(stanza) {
self.get_debug().log('[JSJaCJingle] handle_session_terminate_error', 4);
try {
// Change session status
self._set_status(JSJAC_JINGLE_STATUS_TERMINATED);
// Stop WebRTC
self._peer_stop();
// Lock session (cannot be used later)
self._set_lock(true);
self.get_debug().log('[JSJaCJingle] handle_session_terminate_error > Forced session termination locally.', 0);
} catch(e) {
self.get_debug().log('[JSJaCJingle] handle_session_terminate_error > ' + e, 1);
}
};
/**
* Handles the Jingle session terminate request
* @param {JSJaCPacket} stanza Jingle handled stanza
*/
self.handle_session_terminate_request = function(stanza) {
self.get_debug().log('[JSJaCJingle] handle_session_terminate_request', 4);
try {
// Slot unavailable?
if(self.get_status() == JSJAC_JINGLE_STATUS_INACTIVE || self.get_status() == JSJAC_JINGLE_STATUS_TERMINATED) {
self.get_debug().log('[JSJaCJingle] handle_session_terminate_request > Cannot handle, resource not active (status: ' + self.get_status() + ').', 0);
self.send_error(stanza, JSJAC_JINGLE_ERROR_OUT_OF_ORDER);
return;
}
// Change session status
self._set_status(JSJAC_JINGLE_STATUS_TERMINATING);
// Store termination reason
self._set_reason(self.util_stanza_terminate_reason(stanza));
// Trigger terminate success callbacks
(self._get_session_terminate_success())(self, stanza);
self.handle_session_terminate_success(stanza);
// Process terminate actions
self.send(JSJAC_JINGLE_STANZA_TYPE_RESULT, { id: stanza.getID() });
self.get_debug().log('[JSJaCJingle] handle_session_terminate_request > (reason: ' + self.get_reason() + ').', 3);
} catch(e) {
self.get_debug().log('[JSJaCJingle] handle_session_terminate_request > ' + e, 1);
}
};
/**
* Handles the Jingle transport accept
* @param {JSJaCPacket} stanza Jingle handled stanza
*/
self.handle_transport_accept = function(stanza) {
self.get_debug().log('[JSJaCJingle] handle_transport_accept', 4);
try {
// Not implemented for now
self.send_error(stanza, XMPP_ERROR_FEATURE_NOT_IMPLEMENTED);
} catch(e) {
self.get_debug().log('[JSJaCJingle] handle_content_accept > ' + e, 1);
}
};
/**
* Handles the Jingle transport info
* @param {JSJaCPacket} stanza Jingle handled stanza
*/
self.handle_transport_info = function(stanza) {
self.get_debug().log('[JSJaCJingle] handle_transport_info', 4);
try {
// Slot unavailable?
if(self.get_status() != JSJAC_JINGLE_STATUS_INITIATED && self.get_status() != JSJAC_JINGLE_STATUS_ACCEPTING && self.get_status() != JSJAC_JINGLE_STATUS_ACCEPTED) {
self.get_debug().log('[JSJaCJingle] handle_transport_info > Cannot handle, resource not initiated, nor accepting, nor accepted (status: ' + self.get_status() + ').', 0);
self.send_error(stanza, JSJAC_JINGLE_ERROR_OUT_OF_ORDER);
return;
}
// Common vars
var i, cur_candidate_obj;
// Parse the incoming transport
var rd_sid = self.util_stanza_sid(stanza);
// Request is valid?
if(rd_sid && self._util_stanza_parse_content(stanza)) {
// Handle additional data (optional)
// Still unsure if it is relevant to parse groups there... (are they allowed in such stanza?)
//self._util_stanza_parse_group(stanza);
// Re-generate and store new content data
self._util_build_content_remote();
var sdp_candidates_remote = self._util_sdp_generate_candidates(
self._get_candidates_queue_remote()
);
// ICE candidates
for(i in sdp_candidates_remote) {
cur_candidate_obj = sdp_candidates_remote[i];
self._get_peer_connection().addIceCandidate(
new WEBRTC_ICE_CANDIDATE({
sdpMLineIndex : cur_candidate_obj.id,
candidate : cur_candidate_obj.candidate
})
);
}
// Empty the unapplied candidates queue
self._set_candidates_queue_remote(null);
// Success reply
self.send(JSJAC_JINGLE_STANZA_TYPE_RESULT, { id: stanza.getID() });
} else {
// Send error reply
self.send_error(stanza, XMPP_ERROR_BAD_REQUEST);
self.get_debug().log('[JSJaCJingle] handle_transport_info > Error.', 1);
}
} catch(e) {
self.get_debug().log('[JSJaCJingle] handle_transport_info > ' + e, 1);
}
};
/**
* Handles the Jingle transport info success
* @param {JSJaCPacket} stanza Jingle handled stanza
*/
self.handle_transport_info_success = function(stanza) {
self.get_debug().log('[JSJaCJingle] handle_transport_info_success', 4);
};
/**
* Handles the Jingle transport info error
* @param {JSJaCPacket} stanza Jingle handled stanza
*/
self.handle_transport_info_error = function(stanza) {
self.get_debug().log('[JSJaCJingle] handle_transport_info_error', 4);
};
/**
* Handles the Jingle transport reject
* @param {JSJaCPacket} stanza Jingle handled stanza
*/
self.handle_transport_reject = function(stanza) {
self.get_debug().log('[JSJaCJingle] handle_transport_reject', 4);
try {
// Not implemented for now
self.send_error(stanza, XMPP_ERROR_FEATURE_NOT_IMPLEMENTED);
} catch(e) {
self.get_debug().log('[JSJaCJingle] handle_transport_reject > ' + e, 1);
}
};
/**
* Handles the Jingle transport replace
* @param {JSJaCPacket} stanza Jingle handled stanza
*/
self.handle_transport_replace = function(stanza) {
self.get_debug().log('[JSJaCJingle] handle_transport_replace', 4);
try {
// Not implemented for now
self.send_error(stanza, XMPP_ERROR_FEATURE_NOT_IMPLEMENTED);
} catch(e) {
self.get_debug().log('[JSJaCJingle] handle_transport_replace > ' + e, 1);
}
};
/**
* JSJSAC JINGLE GETTERS
*/
/**
* @private
*/
self._get_session_initiate_pending = function() {
if(typeof self._session_initiate_pending == 'function')
return self._session_initiate_pending;
return function() {};
};
/**
* @private
*/
self._get_session_initiate_success = function() {
if(typeof self._session_initiate_success == 'function')
return self._session_initiate_success;
return function(stanza) {};
};
/**
* @private
*/
self._get_session_initiate_error = function() {
if(typeof self._session_initiate_error == 'function')
return self._session_initiate_error;
return function(stanza) {};
};
/**
* @private
*/
self._get_session_initiate_request = function() {
if(typeof self._session_initiate_request == 'function')
return self._session_initiate_request;
return function(stanza) {};
};
/**
* @private
*/
self._get_session_accept_pending = function() {
if(typeof self._session_accept_pending == 'function')
return self._session_accept_pending;
return function() {};
};
/**
* @private
*/
self._get_session_accept_success = function() {
if(typeof self._session_accept_success == 'function')
return self._session_accept_success;
return function(stanza) {};
};
/**
* @private
*/
self._get_session_accept_error = function() {
if(typeof self._session_accept_error == 'function')
return self._session_accept_error;
return function(stanza) {};
};
/**
* @private
*/
self._get_session_accept_request = function() {
if(typeof self._session_accept_request == 'function')
return self._session_accept_request;
return function(stanza) {};
};
/**
* @private
*/
self._get_session_info_success = function() {
if(typeof self._session_info_success == 'function')
return self._session_info_success;
return function(stanza) {};
};
/**
* @private
*/
self._get_session_info_error = function() {
if(typeof self._session_info_error == 'function')
return self._session_info_error;
return function(stanza) {};
};
/**
* @private
*/
self._get_session_info_request = function() {
if(typeof self._session_info_request == 'function')
return self._session_info_request;
return function(stanza) {};
};
/**
* @private
*/
self._get_session_terminate_pending = function() {
if(typeof self._session_terminate_pending == 'function')
return self._session_terminate_pending;
return function() {};
};
/**
* @private
*/
self._get_session_terminate_success = function() {
if(typeof self._session_terminate_success == 'function')
return self._session_terminate_success;
return function(stanza) {};
};
/**
* @private
*/
self._get_session_terminate_error = function() {
if(typeof self._session_terminate_error == 'function')
return self._session_terminate_error;
return function(stanza) {};
};
/**
* @private
*/
self._get_session_terminate_request = function() {
if(typeof self._session_terminate_request == 'function')
return self._session_terminate_request;
return function(stanza) {};
};
/**
* @private
*/
self._get_local_stream = function() {
return self._local_stream;
};
/**
* @private
*/
self._get_remote_stream = function() {
return self._remote_stream;
};
/**
* @private
*/
self._get_payloads_local = function(name) {
if(name)
return (name in self._payloads_local) ? self._payloads_local[name] : {};
return self._payloads_local;
};
/**
* @private
*/
self._get_group_local = function(semantics) {
if(semantics)
return (semantics in self._group_local) ? self._group_local[semantics] : {};
return self._group_local;
};
/**
* @private
*/
self._get_candidates_local = function(name) {
if(name)
return (name in self._candidates_local) ? self._candidates_local[name] : {};
return self._candidates_local;
};
/**
* @private
*/
self._get_candidates_queue_local = function(name) {
if(name)
return (name in self._candidates_queue_local) ? self._candidates_queue_local[name] : {};
return self._candidates_queue_local;
};
/**
* @private
*/
self._get_payloads_remote = function(name) {
if(name)
return (name in self._payloads_remote) ? self._payloads_remote[name] : {};
return self._payloads_remote;
};
/**
* @private
*/
self._get_group_remote = function(semantics) {
if(semantics)
return (semantics in self._group_remote) ? self._group_remote[semantics] : {};
return self._group_remote;
};
/**
* @private
*/
self._get_candidates_remote = function(name) {
if(name)
return (name in self._candidates_remote) ? self._candidates_remote[name] : [];
return self._candidates_remote;
};
/**
* @private
*/
self._get_candidates_queue_remote = function(name) {
if(name)
return (name in self._candidates_queue_remote) ? self._candidates_queue_remote[name] : {};
return self._candidates_queue_remote;
};
/**
* @private
*/
self._get_content_local = function(name) {
if(name)
return (name in self._content_local) ? self._content_local[name] : {};
return self._content_local;
};
/**
* @private
*/
self._get_content_remote = function(name) {
if(name)
return (name in self._content_remote) ? self._content_remote[name] : {};
return self._content_remote;
};
/**
* @private
*/
self._get_handlers = function(type, id) {
type = type || JSJAC_JINGLE_STANZA_TYPE_ALL;
if(id) {
if(type != JSJAC_JINGLE_STANZA_TYPE_ALL && type in self._handlers && typeof self._handlers[type][id] == 'function')
return self._handlers[type][id];
if(JSJAC_JINGLE_STANZA_TYPE_ALL in self._handlers && typeof self._handlers[JSJAC_JINGLE_STANZA_TYPE_ALL][id] == 'function')
return self._handlers[type][id];
}
return null;
};
/**
* @private
*/
self._get_peer_connection = function() {
return self._peer_connection;
};
/**
* @private
*/
self._get_id = function() {
return self._id;
};
/**
* @private
*/
self._get_id_pre = function() {
return JSJAC_JINGLE_STANZA_ID_PRE + '_' + (self.get_sid() || '0') + '_';
};
/**
* @private
*/
self._get_id_new = function() {
var trans_id = self._get_id() + 1;
self._set_id(trans_id);
return self._get_id_pre() + trans_id;
};
/**
* @private
*/
self._get_sent_id = function() {
return self._sent_id;
};
/**
* @private
*/
self._get_received_id = function() {
return self._received_id;
};
/**
* Gets the mute state
* @return mute value
* @type boolean
*/
self.get_mute = function(name) {
if(!name) name = '*';
return (name in self._mute) ? self._mute[name] : false;
};
/**
* Gets the lock value
* @return lock value
* @type boolean
*/
self.get_lock = function() {
return self._lock || !JSJAC_JINGLE_AVAILABLE;
};
/**
* Gets the media busy value
* @return media busy value
* @type boolean
*/
self.get_media_busy = function() {
return self._media_busy;
};
/**
* Gets the sid value
* @return sid value
* @type string
*/
self.get_sid = function() {
return self._sid;
};
/**
* Gets the status value
* @return status value
* @type string
*/
self.get_status = function() {
return self._status;
};
/**
* Gets the reason value
* @return reason value
* @type string
*/
self.get_reason = function() {
return self._reason;
};
/**
* Gets the to value
* @return to value
* @type string
*/
self.get_to = function() {
return self._to;
};
/**
* Gets the media value
* @return media value
* @type string
*/
self.get_media = function() {
return (self._media && self._media in JSJAC_JINGLE_MEDIAS) ? self._media : JSJAC_JINGLE_MEDIA_VIDEO;
};
/**
* Gets a list of medias in use
* @return media list
* @type object
*/
self.get_media_all = function() {
if(self.get_media() == JSJAC_JINGLE_MEDIA_AUDIO)
return [JSJAC_JINGLE_MEDIA_AUDIO];
return [JSJAC_JINGLE_MEDIA_AUDIO, JSJAC_JINGLE_MEDIA_VIDEO];
};
/**
* Gets the video source value
* @return video source value
* @type string
*/
self.get_video_source = function() {
return (self._video_source && self._video_source in JSJAC_JINGLE_VIDEO_SOURCES) ? self._video_source : JSJAC_JINGLE_VIDEO_SOURCE_CAMERA;
};
/**
* Gets the resolution value
* @return resolution value
* @type string
*/
self.get_resolution = function() {
return self._resolution ? (self._resolution).toString() : null;
};
/**
* Gets the bandwidth value
* @return bandwidth value
* @type string
*/
self.get_bandwidth = function() {
return self._bandwidth ? (self._bandwidth).toString() : null;
};
/**
* Gets the fps value
* @return fps value
* @type string
*/
self.get_fps = function() {
return self._fps ? (self._fps).toString() : null;
};
/**
* Gets the name value
* @return name value
* @type string
*/
self.get_name = function(name) {
if(name)
return name in self._name;
return self._name;
};
/**
* Gets the senders value
* @return senders value
* @type string
*/
self.get_senders = function(name) {
if(name)
return (name in self._senders) ? self._senders[name] : null;
return self._senders;
};
/**
* Gets the creator value
* @return creator value
* @type string
*/
self.get_creator = function(name) {
if(name)
return (name in self._creator) ? self._creator[name] : null;
return self._creator;
};
/**
* Gets the creator value (for this)
* @return creator value
* @type string
*/
self.get_creator_this = function(name) {
return self.get_responder() == self.get_to() ? JSJAC_JINGLE_CREATOR_INITIATOR : JSJAC_JINGLE_CREATOR_RESPONDER;
};
/**
* Gets the initiator value
* @return initiator value
* @type string
*/
self.get_initiator = function() {
return self._initiator;
};
/**
* Gets the response value
* @return response value
* @type string
*/
self.get_responder = function() {
return self._responder;
};
/**
* Gets the local_view value
* @return local_view value
* @type DOM
*/
self.get_local_view = function() {
return (typeof self._local_view == 'object') ? self._local_view : [];
};
/**
* Gets the remote_view value
* @return remote_view value
* @type DOM
*/
self.get_remote_view = function() {
return (typeof self._remote_view == 'object') ? self._remote_view : [];
};
/**
* Gets the STUN servers
* @return STUN servers
* @type object
*/
self.get_stun = function() {
return (typeof self._stun == 'object') ? self._stun : {};
};
/**
* Gets the TURN servers
* @return TURN servers
* @type object
*/
self.get_turn = function() {
return (typeof self._turn == 'object') ? self._turn : {};
};
/**
* Gets the SDP trace value
* @return SDP trace value
* @type JSJaCsdp_traceger
*/
self.get_sdp_trace = function() {
return (self._sdp_trace === true);
};
/**
* Gets the debug value
* @return debug value
* @type JSJaCDebugger
*/
self.get_debug = function() {
return self._debug;
};
/**
* JSJSAC JINGLE SETTERS
*/
/**
* @private
*/
self._set_session_initiate_pending = function(session_initiate_pending) {
self._session_initiate_pending = session_initiate_pending;
};
/**
* @private
*/
self._set_initiate_success = function(initiate_success) {
self._session_initiate_success = initiate_success;
};
/**
* @private
*/
self._set_initiate_error = function(initiate_error) {
self._session_initiate_error = initiate_error;
};
/**
* @private
*/
self._set_initiate_request = function(initiate_request) {
self._session_initiate_request = initiate_request;
};
/**
* @private
*/
self._set_accept_pending = function(accept_pending) {
self._session_accept_pending = accept_pending;
};
/**
* @private
*/
self._set_accept_success = function(accept_success) {
self._session_accept_success = accept_success;
};
/**
* @private
*/
self._set_accept_error = function(accept_error) {
self._session_accept_error = accept_error;
};
/**
* @private
*/
self._set_accept_request = function(accept_request) {
self._session_accept_request = accept_request;
};
/**
* @private
*/
self._set_info_success = function(info_success) {
self._session_info_success = info_success;
};
/**
* @private
*/
self._set_info_error = function(info_error) {
self._session_info_error = info_error;
};
/**
* @private
*/
self._set_info_request = function(info_request) {
self._session_info_request = info_request;
};
/**
* @private
*/
self._set_terminate_pending = function(terminate_pending) {
self._session_terminate_pending = terminate_pending;
};
/**
* @private
*/
self._set_terminate_success = function(terminate_success) {
self._session_terminate_success = terminate_success;
};
/**
* @private
*/
self._set_terminate_error = function(terminate_error) {
self._session_terminate_error = terminate_error;
};
/**
* @private
*/
self._set_terminate_request = function(terminate_request) {
self._session_terminate_request = terminate_request;
};
/**
* @private
*/
self._set_local_stream = function(local_stream) {
try {
if(!local_stream && self._local_stream) {
(self._local_stream).stop();
self._util_peer_stream_detach(
self.get_local_view()
);
}
self._local_stream = local_stream;
if(local_stream) {
self._util_peer_stream_attach(
self.get_local_view(),
self._get_local_stream(),
true
);
} else {
self._util_peer_stream_detach(
self.get_local_view()
);
}
} catch(e) {
self.get_debug().log('[JSJaCJingle] _set_local_stream > ' + e, 1);
}
};
/**
* @private
*/
self._set_remote_stream = function(remote_stream) {
try {
if(!remote_stream && self._remote_stream) {
self._util_peer_stream_detach(
self.get_remote_view()
);
}
self._remote_stream = remote_stream;
if(remote_stream) {
self._util_peer_stream_attach(
self.get_remote_view(),
self._get_remote_stream(),
false
);
} else {
self._util_peer_stream_detach(
self.get_remote_view()
);
}
} catch(e) {
self.get_debug().log('[JSJaCJingle] _set_remote_stream > ' + e, 1);
}
};
/**
* @private
*/
self._set_local_view = function(local_view) {
if(typeof self._local_view !== 'object')
self._local_view = [];
self._local_view.push(local_view);
};
/**
* @private
*/
self._set_remote_view = function(remote_view) {
if(typeof self._remote_view !== 'object')
self._remote_view = [];
self._remote_view.push(remote_view);
};
/**
* @private
*/
self._set_payloads_local = function(name, payload_data) {
self._payloads_local[name] = payload_data;
};
/**
* @private
*/
self._set_group_local = function(semantics, group_data) {
self._group_local[semantics] = group_data;
};
/**
* @private
*/
self._set_candidates_local = function(name, candidate_data) {
if(!(name in self._candidates_local)) self._candidates_local[name] = [];
(self._candidates_local[name]).push(candidate_data);
};
/**
* @private
*/
self._set_candidates_queue_local = function(name, candidate_data) {
try {
if(name === null) {
self._candidates_queue_local = {};
} else {
if(!(name in self._candidates_queue_local)) self._candidates_queue_local[name] = [];
(self._candidates_queue_local[name]).push(candidate_data);
}
} catch(e) {
self.get_debug().log('[JSJaCJingle] _set_candidates_queue_local > ' + e, 1);
}
};
/**
* @private
*/
self._set_payloads_remote = function(name, payload_data) {
self._payloads_remote[name] = payload_data;
};
/**
* @private
*/
self._set_payloads_remote_add = function(name, payload_data) {
try {
if(!(name in self._payloads_remote)) {
self._set_payloads_remote(name, payload_data);
} else {
var key;
var payloads_store = self._payloads_remote[name].descriptions.payload;
var payloads_add = payload_data.descriptions.payload;
for(key in payloads_add) {
if(!(key in payloads_store))
payloads_store[key] = payloads_add[key];
}
}
} catch(e) {
self.get_debug().log('[JSJaCJingle] _set_payloads_remote_add > ' + e, 1);
}
};
/**
* @private
*/
self._set_group_remote = function(semantics, group_data) {
self._group_remote[semantics] = group_data;
};
/**
* @private
*/
self._set_candidates_remote = function(name, candidate_data) {
self._candidates_remote[name] = candidate_data;
};
/**
* @private
*/
self._set_candidates_queue_remote = function(name, candidate_data) {
if(name === null)
self._candidates_queue_remote = {};
else
self._candidates_queue_remote[name] = (candidate_data);
};
/**
* @private
*/
self._set_candidates_remote_add = function(name, candidate_data) {
try {
if(!name) return;
if(!(name in self._candidates_remote))
self._set_candidates_remote(name, []);
var c, i;
var candidate_ids = [];
for(c in self._get_candidates_remote(name))
candidate_ids.push(self._get_candidates_remote(name)[c].id);
for(i in candidate_data) {
if((candidate_data[i].id).indexOf(candidate_ids) !== -1)
self._get_candidates_remote(name).push(candidate_data[i]);
}
} catch(e) {
self.get_debug().log('[JSJaCJingle] _set_candidates_remote_add > ' + e, 1);
}
};
/**
* @private
*/
self._set_content_local = function(name, content_local) {
self._content_local[name] = content_local;
};
/**
* @private
*/
self._set_content_remote = function(name, content_remote) {
self._content_remote[name] = content_remote;
};
/**
* @private
*/
self._set_handlers = function(type, id, handler) {
if(!(type in self._handlers)) self._handlers[type] = {};
self._handlers[type][id] = handler;
};
/**
* @private
*/
self._set_peer_connection = function(peer_connection) {
self._peer_connection = peer_connection;
};
/**
* @private
*/
self._set_id = function(id) {
self._id = id;
};
/**
* @private
*/
self._set_sent_id = function(sent_id) {
self._sent_id[sent_id] = 1;
};
/**
* @private
*/
self._set_received_id = function(received_id) {
self._received_id[received_id] = 1;
};
/**
* @private
*/
self._set_mute = function(name, mute) {
if(!name || name == '*') {
self._mute = {};
name = '*';
}
self._mute[name] = mute;
};
/**
* @private
*/
self._set_lock = function(lock) {
self._lock = lock;
};
/**
* Gets the media busy value
* @return media busy value
* @type boolean
*/
self._set_media_busy = function(busy) {
self._media_busy = busy;
};
/**
* @private
*/
self._set_sid = function(sid) {
self._sid = sid;
};
/**
* @private
*/
self._set_status = function(status) {
self._status = status;
};
/**
* @private
*/
self._set_reason = function(reason) {
self._reason = reason || JSJAC_JINGLE_REASON_CANCEL;
};
/**
* @private
*/
self._set_to = function(to) {
self._to = to;
};
/**
* @private
*/
self._set_media = function(media) {
self._media = media;
};
/**
* @private
*/
self._set_video_source = function() {
self._video_source = video_source;
};
/**
* @private
*/
self._set_resolution = function(resolution) {
self._resolution = resolution;
};
/**
* @private
*/
self._set_bandwidth = function(bandwidth) {
self._bandwidth = bandwidth;
};
/**
* @private
*/
self._set_fps = function(fps) {
self._fps = fps;
};
/**
* @private
*/
self._set_name = function(name) {
self._name[name] = 1;
};
/**
* @private
*/
self._set_senders = function(name, senders) {
if(!(senders in JSJAC_JINGLE_SENDERS)) senders = JSJAC_JINGLE_SENDERS_BOTH.jingle;
self._senders[name] = senders;
};
/**
* @private
*/
self._set_creator = function(name, creator) {
if(!(creator in JSJAC_JINGLE_CREATORS)) creator = JSJAC_JINGLE_CREATOR_INITIATOR;
self._creator[name] = creator;
};
/**
* @private
*/
self._set_initiator = function(initiator) {
self._initiator = initiator;
};
/**
* @private
*/
self._set_responder = function(responder) {
self._responder = responder;
};
/**
* @private
*/
self._set_stun = function(stun_host, stun_data) {
self._stun[stun_server] = stun_data;
};
/**
* @private
*/
self._set_turn = function(turn_host, turn_data) {
self._turn[turn_server] = turn_data;
};
/**
* @private
*/
self._set_sdp_trace = function(sdp_trace) {
self._sdp_trace = sdp_trace;
};
/**
* @private
*/
self._set_debug = function(debug) {
self._debug = debug;
};
/**
* JSJSAC JINGLE SHORTCUTS
*/
/**
* Am I responder?
* @return Receiver state
* @type boolean
*/
self.is_responder = function() {
return self.util_negotiation_status() == JSJAC_JINGLE_SENDERS_RESPONDER.jingle;
};
/**
* Am I initiator?
* @return Initiator state
* @type boolean
*/
self.is_initiator = function() {
return self.util_negotiation_status() == JSJAC_JINGLE_SENDERS_INITIATOR.jingle;
};
/**
* JSJSAC JINGLE UTILITIES
*/
/**
* Removes a given array value
* @return new array
* @type object
*/
self.util_array_remove_value = function(array, value) {
try {
var i;
for(i = 0; i < array.length; i++) {
if(array[i] === value) {
array.splice(i, 1); i--;
}
}
} catch(e) {
self.get_debug().log('[JSJaCJingle] util_array_remove_value > ' + e, 1);
}
return array;
};
/**
* Returns whether an object is empty or not
* @return Empty value
* @type boolean
*/
self.util_object_length = function(object) {
var key;
var l = 0;
try {
for(key in object) {
if(object.hasOwnProperty(key)) l++;
}
} catch(e) {
self.get_debug().log('[JSJaCJingle] util_object_length > ' + e, 1);
}
return l;
};
/**
* Collects given objects
* @return Empty value
* @type object
*/
self.util_object_collect = function() {
var i, p;
var collect_obj = {};
var len = arguments.length;
for(i = 0; i < len; i++) {
for(p in arguments[i]) {
if(arguments[i].hasOwnProperty(p))
collect_obj[p] = arguments[i][p];
}
}
return collect_obj;
};
/**
* Clones a given object
* @return Cloned object
* @type object
*/
self.util_object_clone = function(object) {
try {
var copy, i, attr;
// Assert
if(object === null || typeof object !== 'object') return object;
// Handle Date
if(object instanceof Date) {
copy = new Date();
copy.setTime(object.getTime());
return copy;
}
// Handle Array
if(object instanceof Array) {
copy = [];
for(i = 0, len = object.length; i < len; i++)
copy[i] = self.util_object_clone(object[i]);
return copy;
}
// Handle Object
if(object instanceof Object) {
copy = {};
for(attr in object) {
if(object.hasOwnProperty(attr))
copy[attr] = self.util_object_clone(object[attr]);
}
return copy;
}
} catch(e) {
self.get_debug().log('[JSJaCJingle] util_object_clone > ' + e, 1);
}
self.get_debug().log('[JSJaCJingle] util_object_clone > Cannot clone this object.', 1);
};
/**
* Gets the browser info
* @return browser info
* @type object
*/
self._util_browser = function() {
var browser_info = {
name : 'Generic'
};
try {
var user_agent, detect_arr, cur_browser;
detect_arr = {
'firefox' : JSJAC_JINGLE_BROWSER_FIREFOX,
'chrome' : JSJAC_JINGLE_BROWSER_CHROME,
'safari' : JSJAC_JINGLE_BROWSER_SAFARI,
'opera' : JSJAC_JINGLE_BROWSER_OPERA,
'msie' : JSJAC_JINGLE_BROWSER_IE
};
user_agent = navigator.userAgent.toLowerCase();
for(cur_browser in detect_arr) {
if(user_agent.indexOf(cur_browser) > -1) {
browser_info.name = detect_arr[cur_browser];
break;
}
}
} catch(e) {
self.get_debug().log('[JSJaCJingle] _util_browser > ' + e, 1);
}
return browser_info;
};
/**
* Gets the ICE config
* @return ICE config
* @type object
*/
self._util_config_ice = function() {
try {
// Collect data (user + server)
var stun_config = self.util_object_collect(
self.get_stun(),
JSJAC_JINGLE_STORE_EXTDISCO.stun,
JSJAC_JINGLE_STORE_FALLBACK.stun
);
var turn_config = self.util_object_collect(
self.get_turn(),
JSJAC_JINGLE_STORE_EXTDISCO.turn,
JSJAC_JINGLE_STORE_FALLBACK.turn
);
// Can proceed?
if(stun_config && self.util_object_length(stun_config) ||
turn_config && self.util_object_length(turn_config) ) {
var config = {
iceServers : []
};
// STUN servers
var cur_stun_host, cur_stun_obj, cur_stun_config;
for(cur_stun_host in stun_config) {
if(cur_stun_host) {
cur_stun_obj = stun_config[cur_stun_host];
cur_stun_config = {};
cur_stun_config.url = 'stun:' + cur_stun_host;
if(cur_stun_obj.port)
cur_stun_config.url += ':' + cur_stun_obj.port;
if(cur_stun_obj.transport && self._util_browser().name != JSJAC_JINGLE_BROWSER_FIREFOX)
cur_stun_config.url += '?transport=' + cur_stun_obj.transport;
(config.iceServers).push(cur_stun_config);
}
}
// TURN servers
var cur_turn_host, cur_turn_obj, cur_turn_config;
for(cur_turn_host in turn_config) {
if(cur_turn_host) {
cur_turn_obj = turn_config[cur_turn_host];
cur_turn_config = {};
cur_turn_config.url = 'turn:' + cur_turn_host;
if(cur_turn_obj.port)
cur_turn_config.url += ':' + cur_turn_obj.port;
if(cur_turn_obj.transport)
cur_turn_config.url += '?transport=' + cur_turn_obj.transport;
if(cur_turn_obj.username)
cur_turn_config.username = cur_turn_obj.username;
if(cur_turn_obj.password)
cur_turn_config.password = cur_turn_obj.password;
(config.iceServers).push(cur_turn_config);
}
}
// Check we have at least a STUN server (if user can traverse NAT)
var i;
var has_stun = false;
for(i in config.iceServers) {
if((config.iceServers[i].url).match(/^stun:/i)) {
has_stun = true; break;
}
}
if(!has_stun) {
(config.iceServers).push({
url: (WEBRTC_CONFIGURATION.peer_connection.config.iceServers)[0].url
});
}
return config;
}
} catch(e) {
self.get_debug().log('[JSJaCJingle] _util_config_ice > ' + e, 1);
}
return WEBRTC_CONFIGURATION.peer_connection.config;
};
/**
* Gets the node value from a stanza element
* @return Node value
* @type string
*/
self.util_stanza_get_value = function(stanza) {
try {
return stanza.firstChild.nodeValue || null;
} catch(e) {
try {
return (stanza[0]).firstChild.nodeValue || null;
} catch(_e) {
self.get_debug().log('[JSJaCJingle] util_stanza_get_value > ' + _e, 1);
}
}
return null;
};
/**
* Gets the attribute value from a stanza element
* @return Attribute value
* @type string
*/
self.util_stanza_get_attribute = function(stanza, name) {
if(!name) return null;
try {
return stanza.getAttribute(name) || null;
} catch(e) {
try {
return (stanza[0]).getAttribute(name) || null;
} catch(_e) {
self.get_debug().log('[JSJaCJingle] util_stanza_get_attribute > ' + _e, 1);
}
}
return null;
};
/**
* Sets the attribute value to a stanza element
*/
self.util_stanza_set_attribute = function(stanza, name, value) {
if(!(name && value && stanza)) return;
try {
stanza.setAttribute(name, value);
} catch(e) {
try {
(stanza[0]).setAttribute(name, value);
} catch(_e) {
self.get_debug().log('[JSJaCJingle] util_stanza_set_attribute > ' + _e, 1);
}
}
};
/**
* Gets the Jingle node from a stanza
* @return Jingle node
* @type DOM
*/
self.util_stanza_get_element = function(stanza, name, ns) {
// Assert
if(!stanza) return [];
if(stanza.length) stanza = stanza[0];
try {
// Get only in lower level (not all sub-levels)
var matches = stanza.getElementsByTagNameNS(ns, name);
if(matches[0] && matches[0].parentNode == stanza) return matches;
return [];
} catch(e) {
self.get_debug().log('[JSJaCJingle] util_stanza_get_element > ' + e, 1);
}
return [];
};
/**
* Gets the Jingle node from a stanza
* @return Jingle node
* @type DOM
*/
self.util_stanza_jingle = function(stanza) {
try {
return stanza.getChild('jingle', NS_JINGLE);
} catch(e) {
self.get_debug().log('[JSJaCJingle] util_stanza_jingle > ' + e, 1);
}
return null;
};
/**
* Gets the from value from a stanza
* @return from value
* @type string
*/
self.util_stanza_from = function(stanza) {
try {
return stanza.getFrom() || null;
} catch(e) {
self.get_debug().log('[JSJaCJingle] util_stanza_from > ' + e, 1);
}
return null;
};
/**
* Gets the SID value from a stanza
* @return SID value
* @type string
*/
self.util_stanza_sid = function(stanza) {
try {
return self.util_stanza_get_attribute(
self.util_stanza_jingle(stanza),
'sid'
);
} catch(e) {
self.get_debug().log('[JSJaCJingle] util_stanza_sid > ' + e, 1);
}
};
/**
* Checks if a stanza is safe (known SID + sender)
* @return safety state
* @type boolean
*/
self.util_stanza_safe = function(stanza) {
try {
return !((stanza.getType() == JSJAC_JINGLE_STANZA_TYPE_SET && self.util_stanza_sid(stanza) != self.get_sid()) || self.util_stanza_from(stanza) != self.get_to());
} catch(e) {
self.get_debug().log('[JSJaCJingle] util_stanza_safe > ' + e, 1);
}
return false;
};
/**
* Gets a stanza terminate reason
* @return reason code
* @type string
*/
self.util_stanza_terminate_reason = function(stanza) {
try {
var jingle = self.util_stanza_jingle(stanza);
if(jingle) {
var reason = self.util_stanza_get_element(jingle, 'reason', NS_JINGLE);
if(reason.length) {
var cur_reason;
for(cur_reason in JSJAC_JINGLE_REASONS) {
if(self.util_stanza_get_element(reason[0], cur_reason, NS_JINGLE).length)
return cur_reason;
}
}
}
} catch(e) {
self.get_debug().log('[JSJaCJingle] util_stanza_terminate_reason > ' + e, 1);
}
return null;
};
/**
* Gets a stanza session info
* @return info code
* @type string
*/
self.util_stanza_session_info = function(stanza) {
try {
var jingle = self.util_stanza_jingle(stanza);
if(jingle) {
var cur_info;
for(cur_info in JSJAC_JINGLE_SESSION_INFOS) {
if(self.util_stanza_get_element(jingle, cur_info, NS_JINGLE_APPS_RTP_INFO).length)
return cur_info;
}
}
} catch(e) {
self.get_debug().log('[JSJaCJingle] util_stanza_session_info > ' + e, 1);
}
return null;
};
/**
* Set a timeout limit to a stanza
*/
self.util_stanza_timeout = function(t_type, t_id, handlers) {
try {
t_type = t_type || JSJAC_JINGLE_STANZA_TYPE_ALL;
var t_sid = self.get_sid();
var t_status = self.get_status();
self.get_debug().log('[JSJaCJingle] util_stanza_timeout > Registered (id: ' + t_id + ', status: ' + t_status + ').', 4);
setTimeout(function() {
self.get_debug().log('[JSJaCJingle] util_stanza_timeout > Cheking (id: ' + t_id + ', status: ' + t_status + '-' + self.get_status() + ').', 4);
// State did not change?
if(self.get_sid() == t_sid && self.get_status() == t_status && !(t_id in self._get_received_id())) {
self.get_debug().log('[JSJaCJingle] util_stanza_timeout > Stanza timeout.', 2);
self.unregister_handler(t_type, t_id);
if(handlers.external) (handlers.external)(self);
if(handlers.internal) (handlers.internal)();
} else {
self.get_debug().log('[JSJaCJingle] util_stanza_timeout > Stanza successful.', 4);
}
}, (JSJAC_JINGLE_STANZA_TIMEOUT * 1000));
} catch(e) {
self.get_debug().log('[JSJaCJingle] util_stanza_timeout > ' + e, 1);
}
};
/**
* @private
*/
self._util_stanza_parse_node = function(parent, name, ns, obj, attrs, value) {
try {
var i, j,
error, child, child_arr;
var children = self.util_stanza_get_element(parent, name, ns);
if(children.length) {
for(i = 0; i < children.length; i++) {
// Initialize
error = 0;
child = children[i];
child_arr = {};
// Parse attributes
for(j in attrs) {
child_arr[attrs[j].n] = self.util_stanza_get_attribute(child, attrs[j].n);
if(attrs[j].r && !child_arr[attrs[j].n]) {
error++; break;
}
}
// Parse value
if(value) {
child_arr[value.n] = self.util_stanza_get_value(child);
if(value.r && !child_arr[value.n]) error++;
}
if(error !== 0) continue;
// Push current children
obj.push(child_arr);
}
}
} catch(e) {
self.get_debug().log('[JSJaCJingle] _util_stanza_parse_node > ' + e, 1);
}
};
/**
* @private
*/
self._util_stanza_parse_content = function(stanza) {
try {
var i,
jingle, content, cur_content,
content_creator, content_name, content_senders,
cur_candidates;
// Parse initiate stanza
jingle = self.util_stanza_jingle(stanza);
if(jingle) {
// Childs
content = self.util_stanza_get_element(jingle, 'content', NS_JINGLE);
if(content && content.length) {
for(i = 0; i < content.length; i++) {
cur_content = content[i];
// Attrs (avoids senders & creators to be changed later in the flow)
content_name = self.util_stanza_get_attribute(cur_content, 'name');
content_senders = self.get_senders(content_name) || self.util_stanza_get_attribute(cur_content, 'senders');
content_creator = self.get_creator(content_name) || self.util_stanza_get_attribute(cur_content, 'creator');
self._set_name(content_name);
self._set_senders(content_name, content_senders);
self._set_creator(content_name, content_creator);
// Payloads (non-destructive setters / cumulative)
self._set_payloads_remote_add(
content_name,
self._util_stanza_parse_payload(cur_content)
);
// Candidates (enqueue them for ICE processing, too)
cur_candidate = self._util_stanza_parse_candidate(cur_content);
self._set_candidates_remote_add(
content_name,
cur_candidate
);
self._set_candidates_queue_remote(
content_name,
cur_candidate
);
}
return true;
}
}
} catch(e) {
self.get_debug().log('[JSJaCJingle] _util_stanza_parse_content > ' + e, 1);
}
return false;
};
/**
* @private
*/
self._util_stanza_parse_group = function(stanza) {
try {
var i, j,
jingle,
group, cur_group,
content, cur_content, group_content_names;
// Parse initiate stanza
jingle = self.util_stanza_jingle(stanza);
if(jingle) {
// Childs
group = self.util_stanza_get_element(jingle, 'group', NS_JINGLE_APPS_GROUPING);
if(group && group.length) {
for(i = 0; i < group.length; i++) {
cur_group = group[i];
group_content_names = [];
// Attrs
group_semantics = self.util_stanza_get_attribute(cur_group, 'semantics');
// Contents
content = self.util_stanza_get_element(cur_group, 'content', NS_JINGLE_APPS_GROUPING);
for(j = 0; j < content.length; j++) {
cur_content = content[j];
// Content attrs
group_content_names.push(
self.util_stanza_get_attribute(cur_content, 'name')
);
}
// Payloads (non-destructive setters / cumulative)
self._set_group_remote(
group_semantics,
group_content_names
);
}
}
}
return true;
} catch(e) {
self.get_debug().log('[JSJaCJingle] _util_stanza_parse_group > ' + e, 1);
}
return false;
};
/**
* @private
*/
self._util_stanza_parse_payload = function(stanza_content) {
var payload_obj = {
descriptions : {},
transports : {}
};
try {
// Common vars
var j, error,
cur_payload, cur_payload_arr, cur_payload_id;
// Common functions
var init_content = function() {
var ic_key;
var ic_arr = {
'attrs' : {},
'rtcp-fb' : [],
'bandwidth' : [],
'payload' : {},
'rtp-hdrext' : [],
'rtcp-mux' : 0,
'encryption' : {
'attrs' : {},
'crypto' : [],
'zrtp-hash' : []
}
};
for(ic_key in ic_arr)
if(!(ic_key in payload_obj.descriptions)) payload_obj.descriptions[ic_key] = ic_arr[ic_key];
};
var init_payload = function(id) {
var ip_key;
var ip_arr = {
'attrs' : {},
'parameter' : [],
'rtcp-fb' : [],
'rtcp-fb-trr-int' : []
};
if(!(id in payload_obj.descriptions.payload)) payload_obj.descriptions.payload[id] = {};
for(ip_key in ip_arr)
if(!(ip_key in payload_obj.descriptions.payload[id])) payload_obj.descriptions.payload[id][ip_key] = ip_arr[ip_key];
};
// Parse session description
var description = self.util_stanza_get_element(stanza_content, 'description', NS_JINGLE_APPS_RTP);
if(description.length) {
description = description[0];
var cd_media = self.util_stanza_get_attribute(description, 'media');
var cd_ssrc = self.util_stanza_get_attribute(description, 'ssrc');
if(!cd_media)
self.get_debug().log('[JSJaCJingle] util_stanza_parse_payload > No media attribute to ' + cc_name + ' stanza.', 1);
// Initialize current description
init_content();
payload_obj.descriptions.attrs.media = cd_media;
payload_obj.descriptions.attrs.ssrc = cd_ssrc;
// Loop on multiple payloads
var payload = self.util_stanza_get_element(description, 'payload-type', NS_JINGLE_APPS_RTP);
if(payload.length) {
for(j = 0; j < payload.length; j++) {
error = 0;
cur_payload = payload[j];
cur_payload_arr = {};
cur_payload_arr.channels = self.util_stanza_get_attribute(cur_payload, 'channels');
cur_payload_arr.clockrate = self.util_stanza_get_attribute(cur_payload, 'clockrate');
cur_payload_arr.id = self.util_stanza_get_attribute(cur_payload, 'id') || error++;
cur_payload_arr.name = self.util_stanza_get_attribute(cur_payload, 'name');
payload_obj.descriptions.attrs.ptime = self.util_stanza_get_attribute(cur_payload, 'ptime');
payload_obj.descriptions.attrs.maxptime = self.util_stanza_get_attribute(cur_payload, 'maxptime');
if(error !== 0) continue;
// Initialize current payload
cur_payload_id = cur_payload_arr.id;
init_payload(cur_payload_id);
// Push current payload
payload_obj.descriptions.payload[cur_payload_id].attrs = cur_payload_arr;
// Loop on multiple parameters
self._util_stanza_parse_node(
cur_payload,
'parameter',
NS_JINGLE_APPS_RTP,
payload_obj.descriptions.payload[cur_payload_id].parameter,
[ { n: 'name', r: 1 }, { n: 'value', r: 0 } ]
);
// Loop on multiple RTCP-FB
self._util_stanza_parse_node(
cur_payload,
'rtcp-fb',
NS_JINGLE_APPS_RTP_RTCP_FB,
payload_obj.descriptions.payload[cur_payload_id]['rtcp-fb'],
[ { n: 'type', r: 1 }, { n: 'subtype', r: 0 } ]
);
// Loop on multiple RTCP-FB-TRR-INT
self._util_stanza_parse_node(
cur_payload,
'rtcp-fb-trr-int',
NS_JINGLE_APPS_RTP_RTCP_FB,
payload_obj.descriptions.payload[cur_payload_id]['rtcp-fb-trr-int'],
[ { n: 'value', r: 1 } ]
);
}
}
// Parse the encryption element
var encryption = self.util_stanza_get_element(description, 'encryption', NS_JINGLE_APPS_RTP);
if(encryption.length) {
encryption = encryption[0];
payload_obj.descriptions.encryption.attrs.required = self.util_stanza_get_attribute(encryption, 'required') || '0';
// Loop on multiple cryptos
self._util_stanza_parse_node(
encryption,
'crypto',
NS_JINGLE_APPS_RTP,
payload_obj.descriptions.encryption.crypto,
[ { n: 'crypto-suite', r: 1 }, { n: 'key-params', r: 1 }, { n: 'session-params', r: 0 }, { n: 'tag', r: 1 } ]
);
// Loop on multiple zrtp-hash
self._util_stanza_parse_node(
encryption,
'zrtp-hash',
NS_JINGLE_APPS_RTP_ZRTP,
payload_obj.descriptions.encryption['zrtp-hash'],
[ { n: 'version', r: 1 } ],
{ n: 'value', r: 1 }
);
}
// Loop on common RTCP-FB
self._util_stanza_parse_node(
description,
'rtcp-fb',
NS_JINGLE_APPS_RTP_RTCP_FB,
payload_obj.descriptions['rtcp-fb'],
[ { n: 'type', r: 1 }, { n: 'subtype', r: 0 } ]
);
// Loop on bandwidth
self._util_stanza_parse_node(
description,
'bandwidth',
NS_JINGLE_APPS_RTP,
payload_obj.descriptions.bandwidth,
[ { n: 'type', r: 1 } ],
{ n: 'value', r: 1 }
);
// Parse the RTP-HDREXT element
self._util_stanza_parse_node(
description,
'rtp-hdrext',
NS_JINGLE_APPS_RTP_RTP_HDREXT,
payload_obj.descriptions['rtp-hdrext'],
[ { n: 'id', r: 1 }, { n: 'uri', r: 1 }, { n: 'senders', r: 0 } ]
);
// Parse the RTCP-MUX element
var rtcp_mux = self.util_stanza_get_element(description, 'rtcp-mux', NS_JINGLE_APPS_RTP);
if(rtcp_mux.length) {
payload_obj.descriptions['rtcp-mux'] = 1;
}
}
// Parse transport (need to get 'ufrag' and 'pwd' there)
var transport = self.util_stanza_get_element(stanza_content, 'transport', NS_JINGLE_TRANSPORTS_ICEUDP);
if(transport.length) {
payload_obj.transports.pwd = self.util_stanza_get_attribute(transport, 'pwd');
payload_obj.transports.ufrag = self.util_stanza_get_attribute(transport, 'ufrag');
var fingerprint = self.util_stanza_get_element(transport, 'fingerprint', NS_JINGLE_APPS_DTLS);
if(fingerprint.length) {
payload_obj.transports.fingerprint = {};
payload_obj.transports.fingerprint.setup = self.util_stanza_get_attribute(fingerprint, 'setup');
payload_obj.transports.fingerprint.hash = self.util_stanza_get_attribute(fingerprint, 'hash');
payload_obj.transports.fingerprint.value = self.util_stanza_get_value(fingerprint);
}
}
} catch(e) {
self.get_debug().log('[JSJaCJingle] _util_stanza_parse_payload > ' + e, 1);
}
return payload_obj;
};
/**
* @private
*/
self._util_stanza_parse_candidate = function(stanza_content) {
var candidate_arr = [];
try {
// Common vars
var i,
transport, candidate,
cur_candidate, cur_candidate_obj;
// Parse transport candidates
transport = self.util_stanza_get_element(stanza_content, 'transport', NS_JINGLE_TRANSPORTS_ICEUDP);
if(transport.length) {
self._util_stanza_parse_node(
transport,
'candidate',
NS_JINGLE_TRANSPORTS_ICEUDP,
candidate_arr,
[
{ n: 'component', r: 1 },
{ n: 'foundation', r: 1 },
{ n: 'generation', r: 1 },
{ n: 'id', r: 1 },
{ n: 'ip', r: 1 },
{ n: 'network', r: 1 },
{ n: 'port', r: 1 },
{ n: 'priority', r: 1 },
{ n: 'protocol', r: 1 },
{ n: 'rel-addr', r: 0 },
{ n: 'rel-port', r: 0 },
{ n: 'type', r: 1 }
]
);
}
} catch(e) {
self.get_debug().log('[JSJaCJingle] _util_stanza_parse_candidate > ' + e, 1);
}
return candidate_arr;
};
/*
* @private
*/
self._util_stanza_build_node = function(doc, parent, children, name, ns, value) {
var node = null;
try {
var i, child, attr;
if(children && children.length) {
for(i in children) {
child = children[i];
if(!child) continue;
node = parent.appendChild(doc.buildNode(
name,
{ 'xmlns': ns },
(value && child[value]) ? child[value] : null
));
for(attr in child)
if(attr != value) self.util_stanza_set_attribute(node, attr, child[attr]);
}
}
} catch(e) {
self.get_debug().log('[JSJaCJingle] _util_stanza_build_node > name: ' + name + ' > ' + e, 1);
}
return node;
};
/**
* @private
*/
self._util_stanza_generate_jingle = function(stanza, attrs) {
var jingle = null;
try {
var cur_attr;
jingle = stanza.getNode().appendChild(stanza.buildNode('jingle', { 'xmlns': NS_JINGLE }));
if(!attrs.sid) attrs.sid = self.get_sid();
for(cur_attr in attrs) self.util_stanza_set_attribute(jingle, cur_attr, attrs[cur_attr]);
} catch(e) {
self.get_debug().log('[JSJaCJingle] _util_stanza_generate_jingle > ' + e, 1);
}
return jingle;
};
/**
* @private
*/
self._util_stanza_generate_session_info = function(stanza, jingle, args) {
try {
var info = jingle.appendChild(stanza.buildNode(args.info, { 'xmlns': NS_JINGLE_APPS_RTP_INFO }));
// Info attributes
switch(args.info) {
case JSJAC_JINGLE_SESSION_INFO_MUTE:
case JSJAC_JINGLE_SESSION_INFO_UNMUTE:
self.util_stanza_set_attribute(info, 'creator', self.get_creator_this());
self.util_stanza_set_attribute(info, 'name', args.name);
break;
}
} catch(e) {
self.get_debug().log('[JSJaCJingle] _util_stanza_generate_session_info > ' + e, 1);
}
};
/**
* @private
*/
self._util_stanza_generate_content_local = function(stanza, jingle, override_content) {
try {
var cur_media;
var content_local = override_content ? override_content : self._get_content_local();
for(cur_media in content_local) {
var cur_content = content_local[cur_media];
var content = jingle.appendChild(stanza.buildNode('content', { 'xmlns': NS_JINGLE }));
self.util_stanza_set_attribute(content, 'creator', cur_content.creator);
self.util_stanza_set_attribute(content, 'name', cur_content.name);
self.util_stanza_set_attribute(content, 'senders', cur_content.senders);
// Build description (if action type allows that element)
if(self.util_stanza_get_attribute(jingle, 'action') != JSJAC_JINGLE_ACTION_TRANSPORT_INFO) {
var cs_description = cur_content.description;
var cs_d_attrs = cs_description.attrs;
var cs_d_rtcp_fb = cs_description['rtcp-fb'];
var cs_d_bandwidth = cs_description.bandwidth;
var cs_d_payload = cs_description.payload;
var cs_d_encryption = cs_description.encryption;
var cs_d_rtp_hdrext = cs_description['rtp-hdrext'];
var cs_d_rtcp_mux = cs_description['rtcp-mux'];
var description = self._util_stanza_build_node(
stanza, content,
[cs_d_attrs],
'description',
NS_JINGLE_APPS_RTP
);
// Payload-type
if(cs_d_payload) {
var i, cs_d_p, payload_type;
for(i in cs_d_payload) {
cs_d_p = cs_d_payload[i];
payload_type = self._util_stanza_build_node(
stanza,
description,
[cs_d_p.attrs],
'payload-type',
NS_JINGLE_APPS_RTP
);
// Parameter
self._util_stanza_build_node(
stanza,
payload_type,
cs_d_p.parameter,
'parameter',
NS_JINGLE_APPS_RTP
);
// RTCP-FB (sub)
self._util_stanza_build_node(
stanza,
payload_type,
cs_d_p['rtcp-fb'],
'rtcp-fb',
NS_JINGLE_APPS_RTP_RTCP_FB
);
// RTCP-FB-TRR-INT
self._util_stanza_build_node(
stanza,
payload_type,
cs_d_p['rtcp-fb-trr-int'],
'rtcp-fb-trr-int',
NS_JINGLE_APPS_RTP_RTCP_FB
);
}
// Encryption
if(cs_d_encryption &&
(cs_d_encryption.crypto && cs_d_encryption.crypto.length ||
cs_d_encryption['zrtp-hash'] && cs_d_encryption['zrtp-hash'].length)) {
var encryption = description.appendChild(stanza.buildNode('encryption', { 'xmlns': NS_JINGLE_APPS_RTP }));
self.util_stanza_set_attribute(encryption, 'required', (cs_d_encryption.attrs.required || '0'));
// Crypto
self._util_stanza_build_node(
stanza,
encryption,
cs_d_encryption.crypto,
'crypto',
NS_JINGLE_APPS_RTP
);
// ZRTP-HASH
self._util_stanza_build_node(
stanza,
encryption,
cs_d_encryption['zrtp-hash'],
'zrtp-hash',
NS_JINGLE_APPS_RTP_ZRTP,
'value'
);
}
// RTCP-FB (common)
self._util_stanza_build_node(
stanza,
description,
cs_d_rtcp_fb,
'rtcp-fb',
NS_JINGLE_APPS_RTP_RTCP_FB
);
// Bandwidth
self._util_stanza_build_node(
stanza,
description,
cs_d_bandwidth,
'bandwidth',
NS_JINGLE_APPS_RTP,
'value'
);
// RTP-HDREXT
self._util_stanza_build_node(
stanza,
description,
cs_d_rtp_hdrext,
'rtp-hdrext',
NS_JINGLE_APPS_RTP_RTP_HDREXT
);
// RTCP-MUX
if(cs_d_rtcp_mux)
description.appendChild(stanza.buildNode('rtcp-mux', { 'xmlns': NS_JINGLE_APPS_RTP }));
}
}
// Build transport
var cs_transport = cur_content.transport;
var transport = self._util_stanza_build_node(
stanza,
content,
[cs_transport.attrs],
'transport',
NS_JINGLE_TRANSPORTS_ICEUDP
);
// Fingerprint
self._util_stanza_build_node(
stanza,
transport,
[cs_transport.fingerprint],
'fingerprint',
NS_JINGLE_APPS_DTLS,
'value'
);
// Candidates
self._util_stanza_build_node(
stanza,
transport,
cs_transport.candidate,
'candidate',
NS_JINGLE_TRANSPORTS_ICEUDP
);
}
} catch(e) {
self.get_debug().log('[JSJaCJingle] _util_stanza_generate_content_local > ' + e, 1);
}
};
/**
* @private
*/
self._util_stanza_generate_group_local = function(stanza, jingle) {
try {
var i,
cur_semantics, cur_group, cur_group_name,
group;
var group_local = self._get_group_local();
for(cur_semantics in group_local) {
cur_group = group_local[cur_semantics];
group = jingle.appendChild(stanza.buildNode('group', {
'xmlns': NS_JINGLE_APPS_GROUPING,
'semantics': cur_semantics
}));
for(i in cur_group) {
cur_group_name = cur_group[i];
group.appendChild(stanza.buildNode('content', {
'xmlns': NS_JINGLE_APPS_GROUPING,
'name': cur_group_name
}));
}
}
} catch(e) {
self.get_debug().log('[JSJaCJingle] _util_stanza_generate_group_local > ' + e, 1);
}
};
/**
* @private
*/
self._util_generate_content = function(creator, name, senders, payloads, transports) {
var content_obj = {};
try {
// Generation process
content_obj.creator = creator;
content_obj.name = name;
content_obj.senders = senders;
content_obj.description = {};
content_obj.transport = {};
// Generate description
var i;
var description_cpy = self.util_object_clone(payloads.descriptions);
var description_ptime = description_cpy.attrs.ptime;
var description_maxptime = description_cpy.attrs.maxptime;
if(description_ptime) delete description_cpy.attrs.ptime;
if(description_maxptime) delete description_cpy.attrs.maxptime;
for(i in description_cpy.payload) {
if(!('attrs' in description_cpy.payload[i]))
description_cpy.payload[i].attrs = {};
description_cpy.payload[i].attrs.ptime = description_ptime;
description_cpy.payload[i].attrs.maxptime = description_maxptime;
}
content_obj.description = description_cpy;
// Generate transport
content_obj.transport.candidate = transports;
content_obj.transport.attrs = {};
content_obj.transport.attrs.pwd = payloads.transports ? payloads.transports.pwd : null;
content_obj.transport.attrs.ufrag = payloads.transports ? payloads.transports.ufrag : null;
if(payloads.transports && payloads.transports.fingerprint)
content_obj.transport.fingerprint = payloads.transports.fingerprint;
} catch(e) {
self.get_debug().log('[JSJaCJingle] _util_generate_content > ' + e, 1);
}
return content_obj;
};
/**
* @private
*/
self._util_build_content_local = function() {
try {
var cur_name;
for(cur_name in self.get_name()) {
self._set_content_local(
cur_name,
self._util_generate_content(
JSJAC_JINGLE_SENDERS_INITIATOR.jingle,
cur_name,
self.get_senders(cur_name),
self._get_payloads_local(cur_name),
self._get_candidates_local(cur_name)
)
);
}
} catch(e) {
self.get_debug().log('[JSJaCJingle] _util_build_content_local > ' + e, 1);
}
};
/**
* @private
*/
self._util_build_content_remote = function() {
try {
var cur_name;
for(cur_name in self.get_name()) {
self._set_content_remote(
cur_name,
self._util_generate_content(
self.get_creator(cur_name),
cur_name,
self.get_senders(cur_name),
self._get_payloads_remote(cur_name),
self._get_candidates_remote(cur_name)
)
);
}
} catch(e) {
self.get_debug().log('[JSJaCJingle] _util_build_content_remote > ' + e, 1);
}
};
/**
* @private
*/
self._util_name_generate = function(media) {
var name = null;
try {
var i, cur_name;
var content_all = [
self._get_content_remote(),
self._get_content_local()
];
for(i in content_all) {
for(cur_name in content_all[i]) {
try {
if(content_all[i][cur_name].description.attrs.media == media) {
name = cur_name; break;
}
} catch(e) {}
}
if(name) break;
}
if(!name) name = media;
} catch(e) {
self.get_debug().log('[JSJaCJingle] _util_name_generate > ' + e, 1);
}
return name;
};
/**
* @private
*/
self._util_media_generate = function(name) {
var cur_media;
var media = null;
try {
if(typeof name == 'number') {
for(cur_media in JSJAC_JINGLE_MEDIAS) {
if(name == parseInt(JSJAC_JINGLE_MEDIAS[cur_media].label, 10)) {
media = cur_media; break;
}
}
} else {
for(cur_media in JSJAC_JINGLE_MEDIAS) {
if(name == self._util_name_generate(cur_media)) {
media = cur_media; break;
}
}
}
if(!media) media = name;
} catch(e) {
self.get_debug().log('[JSJaCJingle] _util_media_generate > ' + e, 1);
}
return media;
};
/**
* @private
*/
self._util_sdp_generate = function(type, group, payloads, candidates) {
try {
var sdp_obj = {};
sdp_obj.candidates = self._util_sdp_generate_candidates(candidates);
sdp_obj.description = self._util_sdp_generate_description(type, group, payloads, sdp_obj.candidates);
return sdp_obj;
} catch(e) {
self.get_debug().log('[JSJaCJingle] _util_sdp_generate > ' + e, 1);
}
return {};
};
/**
* @private
*/
self._util_sdp_generate_candidates = function(candidates) {
var candidates_arr = [];
try {
// Parse candidates
var i,
cur_media, cur_name, cur_c_name, cur_candidate, cur_label, cur_id, cur_candidate_str;
for(cur_name in candidates) {
cur_c_name = candidates[cur_name];
cur_media = self._util_media_generate(cur_name);
for(i in cur_c_name) {
cur_candidate = cur_c_name[i];
cur_label = JSJAC_JINGLE_MEDIAS[cur_media].label;
cur_id = cur_label;
cur_candidate_str = '';
cur_candidate_str += 'a=candidate:';
cur_candidate_str += cur_candidate.foundation;
cur_candidate_str += ' ';
cur_candidate_str += cur_candidate.component;
cur_candidate_str += ' ';
cur_candidate_str += cur_candidate.protocol;
cur_candidate_str += ' ';
cur_candidate_str += cur_candidate.priority;
cur_candidate_str += ' ';
cur_candidate_str += cur_candidate.ip;
cur_candidate_str += ' ';
cur_candidate_str += cur_candidate.port;
cur_candidate_str += ' ';
cur_candidate_str += 'typ';
cur_candidate_str += ' ';
cur_candidate_str += cur_candidate.type;
if(cur_candidate['rel-addr'] && cur_candidate['rel-port']) {
cur_candidate_str += ' ';
cur_candidate_str += 'raddr';
cur_candidate_str += ' ';
cur_candidate_str += cur_candidate['rel-addr'];
cur_candidate_str += ' ';
cur_candidate_str += 'rport';
cur_candidate_str += ' ';
cur_candidate_str += cur_candidate['rel-port'];
}
if(cur_candidate.generation) {
cur_candidate_str += ' ';
cur_candidate_str += 'generation';
cur_candidate_str += ' ';
cur_candidate_str += cur_candidate.generation;
}
cur_candidate_str += WEBRTC_SDP_LINE_BREAK;
candidates_arr.push({
label : cur_label,
id : cur_id,
candidate : cur_candidate_str
});
}
}
} catch(e) {
self.get_debug().log('[JSJaCJingle] _util_sdp_generate_candidates > ' + e, 1);
}
return candidates_arr;
};
/**
* @private
*/
self._util_sdp_generate_description = function(type, group, payloads, sdp_candidates) {
var payloads_obj = {};
try {
var payloads_str = '';
// Common vars
var i, c, j, k, l, m, n, o, p, q, r, s, t,
cur_name, cur_name_obj,
cur_media, cur_senders,
cur_group_semantics, cur_group_names, cur_group_name,
cur_transports_obj, cur_description_obj,
cur_d_pwd, cur_d_ufrag, cur_d_fingerprint,
cur_d_attrs, cur_d_rtcp_fb, cur_d_bandwidth, cur_d_encryption, cur_d_ssrc,
cur_d_ssrc_obj, cur_d_rtcp_fb_obj,
cur_d_payload, cur_d_payload_obj, cur_d_payload_obj_attrs, cur_d_payload_obj_id,
cur_d_payload_obj_parameter, cur_d_payload_obj_parameter_obj, cur_d_payload_obj_parameter_str,
cur_d_payload_obj_rtcp_fb, cur_d_payload_obj_rtcp_fb_obj,
cur_d_payload_obj_rtcp_fb_ttr_int, cur_d_payload_obj_rtcp_fb_ttr_int_obj,
cur_d_crypto_obj, cur_d_zrtp_hash_obj,
cur_d_rtp_hdrext, cur_d_rtp_hdrext_obj,
cur_d_rtcp_mux;
// Payloads headers
payloads_str += self._util_sdp_generate_protocol_version();
payloads_str += WEBRTC_SDP_LINE_BREAK;
payloads_str += self._util_sdp_generate_origin();
payloads_str += WEBRTC_SDP_LINE_BREAK;
payloads_str += self._util_sdp_generate_session_name();
payloads_str += WEBRTC_SDP_LINE_BREAK;
payloads_str += self._util_sdp_generate_timing();
payloads_str += WEBRTC_SDP_LINE_BREAK;
// Add groups
for(cur_group_semantics in group) {
cur_group_names = group[cur_group_semantics];
payloads_str += 'a=group:' + cur_group_semantics;
for(t in cur_group_names) {
cur_group_name = cur_group_names[t];
payloads_str += ' ' + cur_group_name;
}
payloads_str += WEBRTC_SDP_LINE_BREAK;
}
// Add media groups
for(cur_name in payloads) {
cur_name_obj = payloads[cur_name];
cur_senders = self.get_senders(cur_name);
cur_media = self.get_name(cur_name) ? self._util_media_generate(cur_name) : null;
// No media?
if(!cur_media) continue;
// Transports
cur_transports_obj = cur_name_obj.transports || {};
cur_d_pwd = cur_transports_obj.pwd;
cur_d_ufrag = cur_transports_obj.ufrag;
cur_d_fingerprint = cur_transports_obj.fingerprint;
// Descriptions
cur_description_obj = cur_name_obj.descriptions;
cur_d_attrs = cur_description_obj.attrs;
cur_d_rtcp_fb = cur_description_obj['rtcp-fb'];
cur_d_bandwidth = cur_description_obj.bandwidth;
cur_d_payload = cur_description_obj.payload;
cur_d_encryption = cur_description_obj.encryption;
cur_d_ssrc = cur_description_obj.ssrc;
cur_d_rtp_hdrext = cur_description_obj['rtp-hdrext'];
cur_d_rtcp_mux = cur_description_obj['rtcp-mux'];
// Current media
payloads_str += self._util_sdp_generate_description_media(cur_media, cur_d_encryption, cur_d_fingerprint, cur_d_payload);
payloads_str += WEBRTC_SDP_LINE_BREAK;
payloads_str += 'c=IN IP4 0.0.0.0';
payloads_str += WEBRTC_SDP_LINE_BREAK;
payloads_str += 'a=rtcp:1 IN IP4 0.0.0.0';
payloads_str += WEBRTC_SDP_LINE_BREAK;
if(cur_d_ufrag) payloads_str += 'a=ice-ufrag:' + cur_d_ufrag + WEBRTC_SDP_LINE_BREAK;
if(cur_d_pwd) payloads_str += 'a=ice-pwd:' + cur_d_pwd + WEBRTC_SDP_LINE_BREAK;
// Fingerprint
if(cur_d_fingerprint) {
if(cur_d_fingerprint.hash && cur_d_fingerprint.value) {
payloads_str += 'a=fingerprint:' + cur_d_fingerprint.hash + ' ' + cur_d_fingerprint.value;
payloads_str += WEBRTC_SDP_LINE_BREAK;
}
if(cur_d_fingerprint.setup) {
payloads_str += 'a=setup:' + cur_d_fingerprint.setup;
payloads_str += WEBRTC_SDP_LINE_BREAK;
}
}
// RTP-HDREXT
if(cur_d_rtp_hdrext && cur_d_rtp_hdrext.length) {
for(i in cur_d_rtp_hdrext) {
cur_d_rtp_hdrext_obj = cur_d_rtp_hdrext[i];
payloads_str += 'a=extmap:' + cur_d_rtp_hdrext_obj.id;
if(cur_d_rtp_hdrext_obj.senders)
payloads_str += '/' + cur_d_rtp_hdrext_obj.senders;
payloads_str += ' ' + cur_d_rtp_hdrext_obj.uri;
payloads_str += WEBRTC_SDP_LINE_BREAK;
}
}
// Senders
if(cur_senders) {
payloads_str += 'a=' + JSJAC_JINGLE_SENDERS[cur_senders];
payloads_str += WEBRTC_SDP_LINE_BREAK;
}
// Name
if(cur_media && JSJAC_JINGLE_MEDIAS[cur_media]) {
payloads_str += 'a=mid:' + (JSJAC_JINGLE_MEDIAS[cur_media]).label;
payloads_str += WEBRTC_SDP_LINE_BREAK;
}
// RTCP-MUX
// WARNING: no spec!
// See: http://code.google.com/p/libjingle/issues/detail?id=309
// http://mail.jabber.org/pipermail/jingle/2011-December/001761.html
if(cur_d_rtcp_mux) {
payloads_str += 'a=rtcp-mux';
payloads_str += WEBRTC_SDP_LINE_BREAK;
}
// 'encryption'
if(cur_d_encryption) {
// 'crypto'
for(j in cur_d_encryption.crypto) {
cur_d_crypto_obj = cur_d_encryption.crypto[j];
payloads_str += 'a=crypto:' +
cur_d_crypto_obj.tag + ' ' +
cur_d_crypto_obj['crypto-suite'] + ' ' +
cur_d_crypto_obj['key-params'] +
(cur_d_crypto_obj['session-params'] ? (' ' + cur_d_crypto_obj['session-params']) : '');
payloads_str += WEBRTC_SDP_LINE_BREAK;
}
// 'zrtp-hash'
for(p in cur_d_encryption['zrtp-hash']) {
cur_d_zrtp_hash_obj = cur_d_encryption['zrtp-hash'][p];
payloads_str += 'a=zrtp-hash:' +
cur_d_zrtp_hash_obj.version + ' ' +
cur_d_zrtp_hash_obj.value;
payloads_str += WEBRTC_SDP_LINE_BREAK;
}
}
// 'rtcp-fb' (common)
for(n in cur_d_rtcp_fb) {
cur_d_rtcp_fb_obj = cur_d_rtcp_fb[n];
payloads_str += 'a=rtcp-fb:*';
payloads_str += ' ' + cur_d_rtcp_fb_obj.type;
if(cur_d_rtcp_fb_obj.subtype)
payloads_str += ' ' + cur_d_rtcp_fb_obj.subtype;
payloads_str += WEBRTC_SDP_LINE_BREAK;
}
// 'bandwidth' (common)
for(q in cur_d_bandwidth) {
cur_d_bandwidth_obj = cur_d_bandwidth[q];
payloads_str += 'b=' + cur_d_bandwidth_obj.type;
payloads_str += ':' + cur_d_bandwidth_obj.value;
payloads_str += WEBRTC_SDP_LINE_BREAK;
}
// 'payload-type'
for(k in cur_d_payload) {
cur_d_payload_obj = cur_d_payload[k];
cur_d_payload_obj_attrs = cur_d_payload_obj.attrs;
cur_d_payload_obj_parameter = cur_d_payload_obj.parameter;
cur_d_payload_obj_rtcp_fb = cur_d_payload_obj['rtcp-fb'];
cur_d_payload_obj_rtcp_fb_ttr_int = cur_d_payload_obj['rtcp-fb-trr-int'];
cur_d_payload_obj_id = cur_d_payload_obj_attrs.id;
payloads_str += 'a=rtpmap:' + cur_d_payload_obj_id;
// 'rtpmap'
if(cur_d_payload_obj_attrs.name) {
payloads_str += ' ' + cur_d_payload_obj_attrs.name;
if(cur_d_payload_obj_attrs.clockrate) {
payloads_str += '/' + cur_d_payload_obj_attrs.clockrate;
if(cur_d_payload_obj_attrs.channels)
payloads_str += '/' + cur_d_payload_obj_attrs.channels;
}
}
payloads_str += WEBRTC_SDP_LINE_BREAK;
// 'parameter'
if(cur_d_payload_obj_parameter.length) {
payloads_str += 'a=fmtp:' + cur_d_payload_obj_id + ' ';
cur_d_payload_obj_parameter_str = '';
for(o in cur_d_payload_obj_parameter) {
cur_d_payload_obj_parameter_obj = cur_d_payload_obj_parameter[o];
if(cur_d_payload_obj_parameter_str) cur_d_payload_obj_parameter_str += ';';
cur_d_payload_obj_parameter_str += cur_d_payload_obj_parameter_obj.name;
if(cur_d_payload_obj_parameter_obj.value !== null) {
cur_d_payload_obj_parameter_str += '=';
cur_d_payload_obj_parameter_str += cur_d_payload_obj_parameter_obj.value;
}
}
payloads_str += cur_d_payload_obj_parameter_str;
payloads_str += WEBRTC_SDP_LINE_BREAK;
}
// 'rtcp-fb' (sub)
for(l in cur_d_payload_obj_rtcp_fb) {
cur_d_payload_obj_rtcp_fb_obj = cur_d_payload_obj_rtcp_fb[l];
payloads_str += 'a=rtcp-fb:' + cur_d_payload_obj_id;
payloads_str += ' ' + cur_d_payload_obj_rtcp_fb_obj.type;
if(cur_d_payload_obj_rtcp_fb_obj.subtype)
payloads_str += ' ' + cur_d_payload_obj_rtcp_fb_obj.subtype;
payloads_str += WEBRTC_SDP_LINE_BREAK;
}
// 'rtcp-fb-ttr-int'
for(m in cur_d_payload_obj_rtcp_fb_ttr_int) {
cur_d_payload_obj_rtcp_fb_ttr_int_obj = cur_d_payload_obj_rtcp_fb_ttr_int[m];
payloads_str += 'a=rtcp-fb:' + cur_d_payload_obj_id;
payloads_str += ' ' + 'trr-int';
payloads_str += ' ' + cur_d_payload_obj_rtcp_fb_ttr_int_obj.value;
payloads_str += WEBRTC_SDP_LINE_BREAK;
}
}
if(cur_d_attrs.ptime) payloads_str += 'a=ptime:' + cur_d_attrs.ptime + WEBRTC_SDP_LINE_BREAK;
if(cur_d_attrs.maxptime) payloads_str += 'a=maxptime:' + cur_d_attrs.maxptime + WEBRTC_SDP_LINE_BREAK;
// 'ssrc' (not used in Jingle ATM)
for(r in cur_d_ssrc) {
for(s in cur_d_ssrc[r]) {
cur_d_ssrc_obj = cur_d_ssrc[r][s];
payloads_str += 'a=ssrc';
payloads_str += ':' + cur_d_ssrc_obj.id;
payloads_str += ' ' + cur_d_ssrc_obj.attribute;
if(cur_d_ssrc_obj.value)
payloads_str += ':' + cur_d_ssrc_obj.value;
if(cur_d_ssrc_obj.data)
payloads_str += ' ' + cur_d_ssrc_obj.data;
payloads_str += WEBRTC_SDP_LINE_BREAK;
}
}
// Candidates (some browsers require them there, too)
if(typeof sdp_candidates == 'object') {
for(c in sdp_candidates) {
if((sdp_candidates[c]).label == JSJAC_JINGLE_MEDIAS[cur_media].label)
payloads_str += (sdp_candidates[c]).candidate;
}
}
}
// Push to object
payloads_obj.type = type;
payloads_obj.sdp = payloads_str;
} catch(e) {
self.get_debug().log('[JSJaCJingle] _util_sdp_generate_description > ' + e, 1);
}
return payloads_obj;
};
/**
* @private
*/
self._util_sdp_generate_protocol_version = function() {
return 'v=0';
};
/**
* @private
*/
self._util_sdp_generate_origin = function() {
var sdp_origin = '';
try {
// Values
var jid = new JSJaCJID(self.get_initiator());
var username = jid.getNode() ? jid.getNode() : '-';
var session_id = '1';
var session_version = '1';
var nettype = 'IN';
var addrtype = 'IP4';
var unicast_address = jid.getDomain() ? jid.getDomain() : '127.0.0.1';
// Line content
sdp_origin += 'o=';
sdp_origin += username + ' ';
sdp_origin += session_id + ' ';
sdp_origin += session_version + ' ';
sdp_origin += nettype + ' ';
sdp_origin += addrtype + ' ';
sdp_origin += unicast_address;
} catch(e) {
self.get_debug().log('[JSJaCJingle] _util_sdp_generate_origin > ' + e, 1);
}
return sdp_origin;
};
/**
* @private
*/
self._util_sdp_generate_session_name = function() {
return 's=' + (self.get_sid() || '-');
};
/**
* @private
*/
self._util_sdp_generate_timing = function() {
return 't=0 0';
};
/**
* @private
*/
self._util_sdp_generate_description_media = function(media, crypto, fingerprint, payload) {
var sdp_media = '';
try {
var i;
var type_ids = [];
sdp_media += 'm=' + media + ' 1 ';
// Protocol
if((crypto && crypto.length) || (fingerprint && fingerprint.hash && fingerprint.value))
sdp_media += 'RTP/SAVPF';
else
sdp_media += 'RTP/AVPF';
// Payload type IDs
for(i in payload) type_ids.push(payload[i].attrs.id);
sdp_media += ' ' + type_ids.join(' ');
} catch(e) {
self.get_debug().log('[JSJaCJingle] _util_sdp_generate_description_media > ' + e, 1);
}
return sdp_media;
};
/**
* Generates a random SID value
* @return SID value
* @type string
*/
self.util_generate_sid = function() {
return cnonce(16);
};
/**
* Generates a random ID value
* @return ID value
* @type string
*/
self.util_generate_id = function() {
return cnonce(10);
};
/**
* Generates the constraints object
* @return constraints object
* @type object
*/
self.util_generate_constraints = function() {
var constraints = {
audio : false,
video : false
};
try {
// Medias?
constraints.audio = true;
constraints.video = (self.get_media() == JSJAC_JINGLE_MEDIA_VIDEO);
// Video configuration
if(constraints.video === true) {
// Resolution?
switch(self.get_resolution()) {
// 16:9
case '720':
case 'hd':
constraints.video = {
mandatory : {
minWidth : 1280,
minHeight : 720,
minAspectRatio : 1.77
}
};
break;
case '360':
case 'md':
constraints.video = {
mandatory : {
minWidth : 640,
minHeight : 360,
minAspectRatio : 1.77
}
};
break;
case '180':
case 'sd':
constraints.video = {
mandatory : {
minWidth : 320,
minHeight : 180,
minAspectRatio : 1.77
}
};
break;
// 4:3
case '960':
constraints.video = {
mandatory : {
minWidth : 960,
minHeight : 720
}
};
break;
case '640':
case 'vga':
constraints.video = {
mandatory : {
maxWidth : 640,
maxHeight : 480
}
};
break;
case '320':
constraints.video = {
mandatory : {
maxWidth : 320,
maxHeight : 240
}
};
break;
}
// Bandwidth?
if(self.get_bandwidth())
constraints.video.optional = [{ bandwidth: self.get_bandwidth() }];
// FPS?
if(self.get_fps())
constraints.video.mandatory.minFrameRate = self.get_fps();
// Custom video source? (screenshare)
if(self.get_media() == JSJAC_JINGLE_MEDIA_VIDEO &&
self.get_video_source() != JSJAC_JINGLE_VIDEO_SOURCE_CAMERA ) {
if(document.location.protocol !== 'https:')
self.get_debug().log('[JSJaCJingle] util_generate_constraints > HTTPS might be required to share screen, otherwise you may get a permission denied error.', 0);
// Unsupported browser? (for that feature)
if(self._util_browser().name != JSJAC_JINGLE_BROWSER_CHROME) {
self.get_debug().log('[JSJaCJingle] util_generate_constraints > Video source not supported by ' + self._util_browser().name + ' (source: ' + self.get_video_source() + ').', 1);
self.terminate(JSJAC_JINGLE_REASON_MEDIA_ERROR);
return;
}
constraints.audio = false;
constraints.video.mandatory = {
'chromeMediaSource': self.get_video_source()
};
}
}
} catch(e) {
self.get_debug().log('[JSJaCJingle] util_generate_constraints > ' + e, 1);
}
return constraints;
};
/**
* Returns our negotiation status
* @return Negotiation status
* @type string
*/
self.util_negotiation_status = function() {
return (self.get_initiator() == self.util_connection_jid()) ? JSJAC_JINGLE_SENDERS_INITIATOR.jingle : JSJAC_JINGLE_SENDERS_RESPONDER.jingle;
};
/**
* Get my connection JID
* @return JID value
* @type string
*/
self.util_connection_jid = function() {
return JSJAC_JINGLE_STORE_CONNECTION.username + '@' +
JSJAC_JINGLE_STORE_CONNECTION.domain + '/' +
JSJAC_JINGLE_STORE_CONNECTION.resource;
};
/**
* @private
*/
self._util_map_register_view = function(type) {
var fn = {
type : null,
mute : false,
view : {
get : null,
set : null
},
stream : {
get : null,
set : null
}
};
try {
switch(type) {
case 'local':
fn.type = type;
fn.mute = true;
fn.view.get = self.get_local_view;
fn.view.set = self._set_local_view;
fn.stream.get = self._get_local_stream;
fn.stream.set = self._set_local_stream;
break;
case 'remote':
fn.type = type;
fn.view.get = self.get_remote_view;
fn.view.set = self._set_remote_view;
fn.stream.get = self._get_remote_stream;
fn.stream.set = self._set_remote_stream;
break;
}
} catch(e) {
self.get_debug().log('[JSJaCJingle] _util_map_register_view > ' + e, 1);
}
return fn;
};
/**
* @private
*/
self._util_map_unregister_view = function(type) {
return self._util_map_register_view(type);
};
/**
* @private
*/
self._util_peer_stream_attach = function(element, stream, mute) {
try {
var i;
var stream_src = stream ? URL.createObjectURL(stream) : '';
for(i in element) {
element[i].src = stream_src;
if(navigator.mozGetUserMedia)
element[i].play();
else
element[i].autoplay = true;
if(typeof mute == 'boolean') element[i].muted = mute;
}
} catch(e) {
self.get_debug().log('[JSJaCJingle] _util_peer_stream_attach > ' + e, 1);
}
};
/**
* @private
*/
self._util_peer_stream_detach = function(element) {
try {
var i;
for(i in element) {
element[i].pause();
element[i].src = '';
}
} catch(e) {
self.get_debug().log('[JSJaCJingle] _util_peer_stream_detach > ' + e, 1);
}
};
/**
* @private
*/
self._util_sdp_parse_payload = function(sdp_payload) {
var payload = {};
try {
if(!sdp_payload || sdp_payload.indexOf('\n') == -1) return payload;
// Common vars
var lines = sdp_payload.split('\n');
var cur_name = null;
var cur_media = null;
var common_transports = {
'fingerprint' : {},
'pwd' : null,
'ufrag' : null
};
var error, i, j,
cur_line,
cur_fmtp, cur_fmtp_id, cur_fmtp_values, cur_fmtp_attrs, cur_fmtp_key, cur_fmtp_value,
cur_rtpmap, cur_rtcp_fb, cur_rtcp_fb_trr_int,
cur_crypto, cur_zrtp_hash, cur_fingerprint, cur_ssrc, cur_extmap,
cur_rtpmap_id, cur_rtcp_fb_id, cur_bandwidth,
m_rtpmap, m_fmtp, m_rtcp_fb, m_rtcp_fb_trr_int, m_crypto, m_zrtp_hash,
m_fingerprint, m_pwd, m_ufrag, m_ptime, m_maxptime, m_bandwidth, m_media, m_candidate,
cur_check_name, cur_transport_sub;
// Common functions
var init_content = function(name) {
if(!(name in payload)) payload[name] = {};
};
var init_descriptions = function(name, sub, sub_default) {
init_content(name);
if(!('descriptions' in payload[name])) payload[name].descriptions = {};
if(!(sub in payload[name].descriptions)) payload[name].descriptions[sub] = sub_default;
};
var init_transports = function(name, sub, sub_default) {
init_content(name);
if(!('transports' in payload[name])) payload[name].transports = {};
if(!(sub in payload[name].transports)) payload[name].transports[sub] = sub_default;
};
var init_ssrc = function(name, id) {
init_descriptions(name, 'ssrc', {});
if(!(id in payload[name].descriptions.ssrc))
payload[name].descriptions.ssrc[id] = [];
};
var init_payload = function(name, id) {
init_descriptions(name, 'payload', {});
if(!(id in payload[name].descriptions.payload)) {
payload[name].descriptions.payload[id] = {
'attrs' : {},
'parameter' : [],
'rtcp-fb' : [],
'rtcp-fb-trr-int' : []
};
}
};
var init_encryption = function(name) {
init_descriptions(name, 'encryption', {
'attrs' : {
'required' : '1'
},
'crypto' : [],
'zrtp-hash' : []
});
};
for(i in lines) {
cur_line = lines[i];
m_media = (R_WEBRTC_SDP_ICE_PAYLOAD.media).exec(cur_line);
// 'audio/video' line?
if(m_media) {
cur_media = m_media[1];
cur_name = self._util_name_generate(cur_media);
// Push it to parent array
init_descriptions(cur_name, 'attrs', {});
payload[cur_name].descriptions.attrs.media = cur_media;
continue;
}
m_bandwidth = (R_WEBRTC_SDP_ICE_PAYLOAD.bandwidth).exec(cur_line);
// 'bandwidth' line?
if(m_bandwidth) {
// Populate current object
error = 0;
cur_bandwidth = {};
cur_bandwidth.type = m_bandwidth[1] || error++;
cur_bandwidth.value = m_bandwidth[2] || error++;
// Incomplete?
if(error !== 0) continue;
// Push it to parent array
init_descriptions(cur_name, 'bandwidth', []);
payload[cur_name].descriptions.bandwidth.push(cur_bandwidth);
continue;
}
m_rtpmap = (R_WEBRTC_SDP_ICE_PAYLOAD.rtpmap).exec(cur_line);
// 'rtpmap' line?
if(m_rtpmap) {
// Populate current object
error = 0;
cur_rtpmap = {};
cur_rtpmap.channels = m_rtpmap[6];
cur_rtpmap.clockrate = m_rtpmap[4];
cur_rtpmap.id = m_rtpmap[1] || error++;
cur_rtpmap.name = m_rtpmap[3];
// Incomplete?
if(error !== 0) continue;
cur_rtpmap_id = cur_rtpmap.id;
// Push it to parent array
init_payload(cur_name, cur_rtpmap_id);
payload[cur_name].descriptions.payload[cur_rtpmap_id].attrs = cur_rtpmap;
continue;
}
m_fmtp = (R_WEBRTC_SDP_ICE_PAYLOAD.fmtp).exec(cur_line);
// 'fmtp' line?
if(m_fmtp) {
cur_fmtp_id = m_fmtp[1];
if(cur_fmtp_id) {
cur_fmtp_values = m_fmtp[2] ? (m_fmtp[2]).split(';') : [];
for(j in cur_fmtp_values) {
// Parse current attribute
if(cur_fmtp_values[j].indexOf('=') !== -1) {
cur_fmtp_attrs = cur_fmtp_values[j].split('=');
cur_fmtp_key = cur_fmtp_attrs[0];
cur_fmtp_value = cur_fmtp_attrs[1];
while(cur_fmtp_key.length && !cur_fmtp_key[0])
cur_fmtp_key = cur_fmtp_key.substring(1);
} else {
cur_fmtp_key = cur_fmtp_values[j];
cur_fmtp_value = null;
}
// Populate current object
error = 0;
cur_fmtp = {};
cur_fmtp.name = cur_fmtp_key || error++;
cur_fmtp.value = cur_fmtp_value;
// Incomplete?
if(error !== 0) continue;
// Push it to parent array
init_payload(cur_name, cur_fmtp_id);
payload[cur_name].descriptions.payload[cur_fmtp_id].parameter.push(cur_fmtp);
}
}
continue;
}
m_rtcp_fb = (R_WEBRTC_SDP_ICE_PAYLOAD.rtcp_fb).exec(cur_line);
// 'rtcp-fb' line?
if(m_rtcp_fb) {
// Populate current object
error = 0;
cur_rtcp_fb = {};
cur_rtcp_fb.id = m_rtcp_fb[1] || error++;
cur_rtcp_fb.type = m_rtcp_fb[2];
cur_rtcp_fb.subtype = m_rtcp_fb[4];
// Incomplete?
if(error !== 0) continue;
cur_rtcp_fb_id = cur_rtcp_fb.id;
// Push it to parent array
if(cur_rtcp_fb_id == '*') {
init_descriptions(cur_name, 'rtcp-fb', []);
(payload[cur_name].descriptions['rtcp-fb']).push(cur_rtcp_fb);
} else {
init_payload(cur_name, cur_rtcp_fb_id);
(payload[cur_name].descriptions.payload[cur_rtcp_fb_id]['rtcp-fb']).push(cur_rtcp_fb);
}
continue;
}
m_rtcp_fb_trr_int = (R_WEBRTC_SDP_ICE_PAYLOAD.rtcp_fb_trr_int).exec(cur_line);
// 'rtcp-fb-trr-int' line?
if(m_rtcp_fb_trr_int) {
// Populate current object
error = 0;
cur_rtcp_fb_trr_int = {};
cur_rtcp_fb_trr_int.id = m_rtcp_fb_trr_int[1] || error++;
cur_rtcp_fb_trr_int.value = m_rtcp_fb_trr_int[2] || error++;
// Incomplete?
if(error !== 0) continue;
cur_rtcp_fb_trr_int_id = cur_rtcp_fb_trr_int.id;
// Push it to parent array
init_payload(cur_name, cur_rtcp_fb_trr_int_id);
(payload[cur_name].descriptions.payload[cur_rtcp_fb_trr_int_id]['rtcp-fb-trr-int']).push(cur_rtcp_fb_trr_int);
continue;
}
m_crypto = (R_WEBRTC_SDP_ICE_PAYLOAD.crypto).exec(cur_line);
// 'crypto' line?
if(m_crypto) {
// Populate current object
error = 0;
cur_crypto = {};
cur_crypto['crypto-suite'] = m_crypto[2] || error++;
cur_crypto['key-params'] = m_crypto[3] || error++;
cur_crypto['session-params'] = m_crypto[5];
cur_crypto.tag = m_crypto[1] || error++;
// Incomplete?
if(error !== 0) continue;
// Push it to parent array
init_encryption(cur_name);
(payload[cur_name].descriptions.encryption.crypto).push(cur_crypto);
continue;
}
m_zrtp_hash = (R_WEBRTC_SDP_ICE_PAYLOAD.zrtp_hash).exec(cur_line);
// 'zrtp-hash' line?
if(m_zrtp_hash) {
// Populate current object
error = 0;
cur_zrtp_hash = {};
cur_zrtp_hash.version = m_zrtp_hash[1] || error++;
cur_zrtp_hash.value = m_zrtp_hash[2] || error++;
// Incomplete?
if(error !== 0) continue;
// Push it to parent array
init_encryption(cur_name);
(payload[cur_name].descriptions.encryption['zrtp-hash']).push(cur_zrtp_hash);
continue;
}
m_ptime = (R_WEBRTC_SDP_ICE_PAYLOAD.ptime).exec(cur_line);
// 'ptime' line?
if(m_ptime) {
// Push it to parent array
init_descriptions(cur_name, 'attrs', {});
payload[cur_name].descriptions.attrs.ptime = m_ptime[1];
continue;
}
m_maxptime = (R_WEBRTC_SDP_ICE_PAYLOAD.maxptime).exec(cur_line);
// 'maxptime' line?
if(m_maxptime) {
// Push it to parent array
init_descriptions(cur_name, 'attrs', {});
payload[cur_name].descriptions.attrs.maxptime = m_maxptime[1];
continue;
}
m_ssrc = (R_WEBRTC_SDP_ICE_PAYLOAD.ssrc).exec(cur_line);
// 'ssrc' line?
if(m_ssrc) {
// Populate current object
error = 0;
cur_ssrc = {};
cur_ssrc.id = m_ssrc[1] || error++;
cur_ssrc.attribute = m_ssrc[2] || error++;
cur_ssrc.value = m_ssrc[4];
cur_ssrc.data = m_ssrc[6];
// Incomplete?
if(error !== 0) continue;
// Push it to parent array (not used in Jingle ATM)
init_ssrc(cur_name, cur_ssrc.id);
(payload[cur_name].descriptions.ssrc[cur_ssrc.id]).push(cur_ssrc);
// Push it to parent array (common attr required for Jingle)
init_descriptions(cur_name, 'attrs', {});
payload[cur_name].descriptions.attrs.ssrc = m_ssrc[1];
continue;
}
m_rtcp_mux = (R_WEBRTC_SDP_ICE_PAYLOAD.rtcp_mux).exec(cur_line);
// 'rtcp-mux' line?
if(m_rtcp_mux) {
// Push it to parent array
init_descriptions(cur_name, 'rtcp-mux', 1);
continue;
}
m_extmap = (R_WEBRTC_SDP_ICE_PAYLOAD.extmap).exec(cur_line);
// 'extmap' line?
if(m_extmap) {
// Populate current object
error = 0;
cur_extmap = {};
cur_extmap.id = m_extmap[1] || error++;
cur_extmap.uri = m_extmap[4] || error++;
cur_extmap.senders = m_extmap[3];
// Incomplete?
if(error !== 0) continue;
// Push it to parent array
init_descriptions(cur_name, 'rtp-hdrext', []);
(payload[cur_name].descriptions['rtp-hdrext']).push(cur_extmap);
continue;
}
m_fingerprint = (R_WEBRTC_SDP_ICE_PAYLOAD.fingerprint).exec(cur_line);
// 'fingerprint' line?
if(m_fingerprint) {
// Populate current object
error = 0;
cur_fingerprint = common_transports.fingerprint || {};
cur_fingerprint.hash = m_fingerprint[1] || error++;
cur_fingerprint.value = m_fingerprint[2] || error++;
// Incomplete?
if(error !== 0) continue;
// Push it to parent array
init_transports(cur_name, 'fingerprint', cur_fingerprint);
common_transports.fingerprint = cur_fingerprint;
continue;
}
m_setup = (R_WEBRTC_SDP_ICE_PAYLOAD.setup).exec(cur_line);
// 'setup' line?
if(m_setup) {
// Populate current object
cur_fingerprint = common_transports.fingerprint || {};
cur_fingerprint.setup = m_setup[1];
// Push it to parent array
if(cur_fingerprint.setup) {
// Map it to fingerprint as XML-wise it is related
init_transports(cur_name, 'fingerprint', cur_fingerprint);
common_transports.fingerprint = cur_fingerprint;
}
continue;
}
m_pwd = (R_WEBRTC_SDP_ICE_PAYLOAD.pwd).exec(cur_line);
// 'pwd' line?
if(m_pwd) {
init_transports(cur_name, 'pwd', m_pwd[1]);
if(!common_transports.pwd)
common_transports.pwd = m_pwd[1];
continue;
}
m_ufrag = (R_WEBRTC_SDP_ICE_PAYLOAD.ufrag).exec(cur_line);
// 'ufrag' line?
if(m_ufrag) {
init_transports(cur_name, 'ufrag', m_ufrag[1]);
if(!common_transports.ufrag)
common_transports.ufrag = m_ufrag[1];
continue;
}
// 'candidate' line? (shouldn't be there)
m_candidate = R_WEBRTC_SDP_ICE_CANDIDATE.exec(cur_line);
if(m_candidate) {
self._util_sdp_parse_candidate_store({
media : cur_media,
candidate : cur_line
});
continue;
}
}
// Filter medias
for(cur_check_name in payload) {
// Undesired media?
if(!self.get_name()[cur_check_name]) {
delete payload[cur_check_name]; continue;
}
// Validate transports
if(typeof payload[cur_check_name].transports !== 'object')
payload[cur_check_name].transports = {};
for(cur_transport_sub in common_transports) {
if(!payload[cur_check_name].transports[cur_transport_sub])
payload[cur_check_name].transports[cur_transport_sub] = common_transports[cur_transport_sub];
}
}
} catch(e) {
self.get_debug().log('[JSJaCJingle] _util_sdp_parse_payload > ' + e, 1);
}
return payload;
};
/**
* @private
*/
self._util_sdp_parse_group = function(sdp_payload) {
var group = {};
try {
if(!sdp_payload || sdp_payload.indexOf('\n') == -1) return group;
// Common vars
var lines = sdp_payload.split('\n');
var i, cur_line,
m_group;
var init_group = function(semantics) {
if(!(semantics in group)) group[semantics] = [];
};
for(i in lines) {
cur_line = lines[i];
// 'group' line?
m_group = (R_WEBRTC_SDP_ICE_PAYLOAD.group).exec(cur_line);
if(m_group) {
if(m_group[1] && m_group[2]) {
init_group(m_group[1]);
group[m_group[1]] = (m_group[2].indexOf(' ') === -1 ? [m_group[2]] : m_group[2].split(' '));
}
continue;
}
}
} catch(e) {
self.get_debug().log('[JSJaCJingle] _util_sdp_parse_group > ' + e, 1);
}
return group;
};
/**
* @private
*/
self._util_sdp_resolution_payload = function(payload) {
try {
if(!payload || typeof payload !== 'object') return {};
// No video?
if(self.get_media_all().indexOf(JSJAC_JINGLE_MEDIA_VIDEO) === -1) return payload;
var i, j, k, cur_media;
var cur_payload, res_arr, constraints;
var res_height = null;
var res_width = null;
// Try local view? (more reliable)
for(i in self.get_local_view()) {
if(typeof self.get_local_view()[i].videoWidth == 'number' &&
typeof self.get_local_view()[i].videoHeight == 'number' ) {
res_height = self.get_local_view()[i].videoHeight;
res_width = self.get_local_view()[i].videoWidth;
if(res_height && res_width) break;
}
}
// Try media constraints? (less reliable)
if(!res_height || !res_width) {
self.get_debug().log('[JSJaCJingle] _util_sdp_resolution_payload > Could not get local video resolution, falling back on constraints (local video may not be ready).', 0);
constraints = self.util_generate_constraints();
// Still nothing?!
if(typeof constraints.video !== 'object' ||
typeof constraints.video.mandatory !== 'object' ||
typeof constraints.video.mandatory.minWidth !== 'number' ||
typeof constraints.video.mandatory.minHeight !== 'number' ) {
self.get_debug().log('[JSJaCJingle] _util_sdp_resolution_payload > Could not get local video resolution (not sending it).', 1);
return payload;
}
res_height = constraints.video.mandatory.minHeight;
res_width = constraints.video.mandatory.minWidth;
}
// Constraints to be used
res_arr = [
{
name : 'height',
value : res_height
},
{
name : 'width',
value : res_width
}
];
for(cur_media in payload) {
if(cur_media != JSJAC_JINGLE_MEDIA_VIDEO) continue;
cur_payload = payload[cur_media].descriptions.payload;
for(j in cur_payload) {
if(typeof cur_payload[j].parameter !== 'object') cur_payload[j].parameter = [];
for(k in res_arr)
(cur_payload[j].parameter).push(res_arr[k]);
}
}
self.get_debug().log('[JSJaCJingle] _util_sdp_resolution_payload > Got local video resolution (' + res_width + 'x' + res_height + ').', 2);
} catch(e) {
self.get_debug().log('[JSJaCJingle] _util_sdp_resolution_payload > ' + e, 1);
}
return payload;
};
/**
* @private
*/
self._util_sdp_parse_candidate = function(sdp_candidate) {
var candidate = {};
try {
if(!sdp_candidate) return candidate;
var error = 0;
var matches = R_WEBRTC_SDP_ICE_CANDIDATE.exec(sdp_candidate);
// Matches!
if(matches) {
candidate.component = matches[2] || error++;
candidate.foundation = matches[1] || error++;
candidate.generation = matches[16] || JSJAC_JINGLE_GENERATION;
candidate.id = self.util_generate_id();
candidate.ip = matches[5] || error++;
candidate.network = JSJAC_JINGLE_NETWORK;
candidate.port = matches[6] || error++;
candidate.priority = matches[4] || error++;
candidate.protocol = matches[3] || error++;
candidate['rel-addr'] = matches[11];
candidate['rel-port'] = matches[13];
candidate.type = matches[8] || error++;
}
// Incomplete?
if(error !== 0) return {};
} catch(e) {
self.get_debug().log('[JSJaCJingle] _util_sdp_parse_candidate > ' + e, 1);
}
return candidate;
};
/**
* @private
*/
self._util_sdp_parse_candidate_store = function(sdp_candidate) {
// Store received candidate
var candidate_media = sdp_candidate.media;
var candidate_data = sdp_candidate.candidate;
// Convert SDP raw data to an object
var candidate_obj = self._util_sdp_parse_candidate(candidate_data);
self._set_candidates_local(
self._util_name_generate(
candidate_media
),
candidate_obj
);
// Enqueue candidate
self._set_candidates_queue_local(
self._util_name_generate(
candidate_media
),
candidate_obj
);
};
/**
* JSJSAC JINGLE PEER API
*/
/**
* @private
*/
self._peer_connection_create = function(sdp_message_callback) {
self.get_debug().log('[JSJaCJingle] _peer_connection_create', 4);
try {
// Log STUN servers in use
var i;
var ice_config = self._util_config_ice();
if(typeof ice_config.iceServers == 'object') {
for(i = 0; i < (ice_config.iceServers).length; i++)
self.get_debug().log('[JSJaCJingle] _peer_connection_create > Using ICE server at: ' + ice_config.iceServers[i].url + ' (' + (i + 1) + ').', 2);
} else {
self.get_debug().log('[JSJaCJingle] _peer_connection_create > No ICE server configured. Network may not work properly.', 0);
}
// Create the RTCPeerConnection object
self._set_peer_connection(
new WEBRTC_PEER_CONNECTION(
ice_config,
WEBRTC_CONFIGURATION.peer_connection.constraints
)
);
// Event: onicecandidate
self._get_peer_connection().onicecandidate = function(e) {
if(e.candidate) {
self._util_sdp_parse_candidate_store({
media : (isNaN(e.candidate.sdpMid) ? e.candidate.sdpMid : self._util_media_generate(parseInt(e.candidate.sdpMid, 10))),
candidate : e.candidate.candidate
});
} else {
// Build or re-build content (local)
self._util_build_content_local();
// In which action stanza should candidates be sent?
if((self.is_initiator() && self.get_status() == JSJAC_JINGLE_STATUS_INITIATING) ||
(self.is_responder() && self.get_status() == JSJAC_JINGLE_STATUS_ACCEPTING)) {
self.get_debug().log('[JSJaCJingle] _peer_connection_create > onicecandidate > Got initial candidates.', 2);
// Execute what's next (initiate/accept session)
sdp_message_callback();
} else {
self.get_debug().log('[JSJaCJingle] _peer_connection_create > onicecandidate > Got more candidates (on the go).', 2);
// Send unsent candidates
var candidates_queue_local = self._get_candidates_queue_local();
if(self.util_object_length(candidates_queue_local) > 0)
self.send(JSJAC_JINGLE_STANZA_TYPE_SET, { action: JSJAC_JINGLE_ACTION_TRANSPORT_INFO, candidates: candidates_queue_local });
}
// Empty the unsent candidates queue
self._set_candidates_queue_local(null);
}
};
// Event: oniceconnectionstatechange
self._get_peer_connection().oniceconnectionstatechange = function(e) {
self.get_debug().log('[JSJaCJingle] _peer_connection_create > oniceconnectionstatechange', 2);
// Connection errors?
switch(this.iceConnectionState) {
case 'disconnected':
self._peer_timeout(this.iceConnectionState, {
timer : JSJAC_JINGLE_PEER_TIMEOUT_DISCONNECT,
reason : JSJAC_JINGLE_REASON_CONNECTIVITY_ERROR
});
break;
case 'checking':
self._peer_timeout(this.iceConnectionState); break;
}
self.get_debug().log('[JSJaCJingle] _peer_connection_create > oniceconnectionstatechange > (state: ' + this.iceConnectionState + ').', 2);
};
// Event: onaddstream
self._get_peer_connection().onaddstream = function(e) {
if (!e) return;
self.get_debug().log('[JSJaCJingle] _peer_connection_create > onaddstream', 2);
// Attach remote stream to DOM view
self._set_remote_stream(e.stream);
};
// Event: onremovestream
self._get_peer_connection().onremovestream = function(e) {
self.get_debug().log('[JSJaCJingle] _peer_connection_create > onremovestream', 2);
// Detach remote stream from DOM view
self._set_remote_stream(null);
};
// Add local stream
self._get_peer_connection().addStream(self._get_local_stream());
// Create offer
self.get_debug().log('[JSJaCJingle] _peer_connection_create > Getting local description...', 2);
if(self.is_initiator()) {
// Local description
self._get_peer_connection().createOffer(self._peer_got_description, self._peer_fail_description, WEBRTC_CONFIGURATION.create_offer);
// Then, wait for responder to send back its remote description
} else {
// Apply SDP data
sdp_remote = self._util_sdp_generate(
WEBRTC_SDP_TYPE_OFFER,
self._get_group_remote(),
self._get_payloads_remote(),
self._get_candidates_queue_remote()
);
if(self.get_sdp_trace()) self.get_debug().log('[JSJaCJingle] SDP (remote)' + '\n\n' + sdp_remote.description.sdp, 4);
// Remote description
self._get_peer_connection().setRemoteDescription(
(new WEBRTC_SESSION_DESCRIPTION(sdp_remote.description)),
function() {
// Success (descriptions are compatible)
},
function(e) {
if(self.get_sdp_trace()) self.get_debug().log('[JSJaCJingle] SDP (remote:error)' + '\n\n' + (e.message || e.name || 'Unknown error'), 4);
// Error (descriptions are incompatible)
self.terminate(JSJAC_JINGLE_REASON_INCOMPATIBLE_PARAMETERS);
}
);
// Local description
self._get_peer_connection().createAnswer(self._peer_got_description, self._peer_fail_description, WEBRTC_CONFIGURATION.create_answer);
// ICE candidates
var c;
var cur_candidate_obj;
for(c in sdp_remote.candidates) {
cur_candidate_obj = sdp_remote.candidates[c];
self._get_peer_connection().addIceCandidate(
new WEBRTC_ICE_CANDIDATE({
sdpMLineIndex : cur_candidate_obj.id,
candidate : cur_candidate_obj.candidate
})
);
}
// Empty the unapplied candidates queue
self._set_candidates_queue_remote(null);
}
} catch(e) {
self.get_debug().log('[JSJaCJingle] _peer_connection_create > ' + e, 1);
}
};
/**
* @private
*/
self._peer_get_user_media = function(callback) {
self.get_debug().log('[JSJaCJingle] _peer_get_user_media', 4);
try {
self.get_debug().log('[JSJaCJingle] _peer_get_user_media > Getting user media...', 2);
(WEBRTC_GET_MEDIA.bind(navigator))(
self.util_generate_constraints(),
self._peer_got_user_media_success.bind(this, callback),
self._peer_got_user_media_error.bind(this)
);
} catch(e) {
self.get_debug().log('[JSJaCJingle] _peer_get_user_media > ' + e, 1);
}
};
/**
* @private
*/
self._peer_got_user_media_success = function(callback, stream) {
self.get_debug().log('[JSJaCJingle] _peer_got_user_media_success', 4);
try {
self.get_debug().log('[JSJaCJingle] _peer_got_user_media_success > Got user media.', 2);
self._set_local_stream(stream);
if(callback && typeof callback == 'function') {
if((self.get_media() == JSJAC_JINGLE_MEDIA_VIDEO) && self.get_local_view().length) {
self.get_debug().log('[JSJaCJingle] _peer_got_user_media_success > Waiting for local video to be loaded...', 2);
var fn_loaded = function() {
self.get_debug().log('[JSJaCJingle] _peer_got_user_media_success > Local video loaded.', 2);
this.removeEventListener('loadeddata', fn_loaded, false);
callback();
};
self.get_local_view()[0].addEventListener('loadeddata', fn_loaded, false);
} else {
callback();
}
}
} catch(e) {
self.get_debug().log('[JSJaCJingle] _peer_got_user_media_success > ' + e, 1);
}
};
/**
* @private
*/
self._peer_got_user_media_error = function(error) {
self.get_debug().log('[JSJaCJingle] _peer_got_user_media_error', 4);
try {
(self._get_session_initiate_error())(self);
// Not needed in case we are the responder (breaks termination)
if(self.is_initiator()) self.handle_session_initiate_error();
// Not needed in case we are the initiator (no packet sent, ever)
if(self.is_responder()) self.terminate(JSJAC_JINGLE_REASON_MEDIA_ERROR);
self.get_debug().log('[JSJaCJingle] _peer_got_user_media_error > Failed (' + (error.PERMISSION_DENIED ? 'permission denied' : 'unknown' ) + ').', 1);
} catch(e) {
self.get_debug().log('[JSJaCJingle] _peer_got_user_media_error > ' + e, 1);
}
};
/**
* @private
*/
self._peer_got_description = function(sdp_local) {
self.get_debug().log('[JSJaCJingle] _peer_got_description', 4);
try {
self.get_debug().log('[JSJaCJingle] _peer_got_description > Got local description.', 2);
if(self.get_sdp_trace()) self.get_debug().log('[JSJaCJingle] SDP (local:raw)' + '\n\n' + sdp_local.sdp, 4);
// Convert SDP raw data to an object
var cur_name;
var payload_parsed = self._util_sdp_parse_payload(sdp_local.sdp);
self._util_sdp_resolution_payload(payload_parsed);
for(cur_name in payload_parsed) {
self._set_payloads_local(
cur_name,
payload_parsed[cur_name]
);
}
var cur_semantics;
var group_parsed = self._util_sdp_parse_group(sdp_local.sdp);
for(cur_semantics in group_parsed) {
self._set_group_local(
cur_semantics,
group_parsed[cur_semantics]
);
}
// Filter our local description (remove unused medias)
var sdp_local_desc = self._util_sdp_generate_description(
sdp_local.type,
self._get_group_local(),
self._get_payloads_local(),
self._util_sdp_generate_candidates(
self._get_candidates_local()
)
);
if(self.get_sdp_trace()) self.get_debug().log('[JSJaCJingle] SDP (local:gen)' + '\n\n' + sdp_local_desc.sdp, 4);
self._get_peer_connection().setLocalDescription(
(new WEBRTC_SESSION_DESCRIPTION(sdp_local_desc)),
function() {
// Success (descriptions are compatible)
},
function(e) {
if(self.get_sdp_trace()) self.get_debug().log('[JSJaCJingle] SDP (local:error)' + '\n\n' + (e.message || e.name || 'Unknown error'), 4);
// Error (descriptions are incompatible)
}
);
self.get_debug().log('[JSJaCJingle] _peer_got_description > Waiting for local candidates...', 2);
} catch(e) {
self.get_debug().log('[JSJaCJingle] _peer_got_description > ' + e, 1);
}
};
/**
* @private
*/
self._peer_fail_description = function() {
self.get_debug().log('[JSJaCJingle] _peer_fail_description', 4);
try {
self.get_debug().log('[JSJaCJingle] _peer_fail_description > Could not get local description!', 1);
} catch(e) {
self.get_debug().log('[JSJaCJingle] _peer_fail_description > ' + e, 1);
}
};
/**
* @private
*/
self._peer_sound = function(enable) {
self.get_debug().log('[JSJaCJingle] _peer_sound', 4);
try {
self.get_debug().log('[JSJaCJingle] _peer_sound > Enable: ' + enable + ' (current: ' + self.get_mute(JSJAC_JINGLE_MEDIA_AUDIO) + ').', 2);
var i;
var audio_tracks = self._get_local_stream().getAudioTracks();
for(i = 0; i < audio_tracks.length; i++)
audio_tracks[i].enabled = enable;
} catch(e) {
self.get_debug().log('[JSJaCJingle] _peer_sound > ' + e, 1);
}
};
/**
* Set a timeout limit to peer connection
*/
self._peer_timeout = function(state, args) {
try {
// Assert
if(typeof args !== 'object') args = {};
var t_sid = self.get_sid();
setTimeout(function() {
// State did not change?
if(self.get_sid() == t_sid && self._get_peer_connection().iceConnectionState == state) {
self.get_debug().log('[JSJaCJingle] util_stanza_timeout > Peer timeout.', 2);
// Error (transports are incompatible)
self.terminate(args.reason || JSJAC_JINGLE_REASON_FAILED_TRANSPORT);
}
}, ((args.timer || JSJAC_JINGLE_PEER_TIMEOUT_DEFAULT) * 1000));
} catch(e) {
self.get_debug().log('[JSJaCJingle] _peer_timeout > ' + e, 1);
}
};
/**
* @private
*/
self._peer_stop = function() {
self.get_debug().log('[JSJaCJingle] _peer_stop', 4);
// Detach media streams from DOM view
self._set_local_stream(null);
self._set_remote_stream(null);
// Close the media stream
if(self._get_peer_connection())
self._get_peer_connection().close();
// Remove this session from router
JSJaCJingle_remove(self.get_sid());
};
}
/**
* Listens for Jingle events
*/
function JSJaCJingle_listen(args) {
try {
if(args && args.connection)
JSJAC_JINGLE_STORE_CONNECTION = args.connection;
if(args && args.initiate)
JSJAC_JINGLE_STORE_INITIATE = args.initiate;
if(args && args.debug)
JSJAC_JINGLE_STORE_DEBUG = args.debug;
// Incoming IQs handler
JSJAC_JINGLE_STORE_CONNECTION.registerHandler('iq', JSJaCJingle_route);
JSJAC_JINGLE_STORE_DEBUG.log('[JSJaCJingle] lib:listen > Listening.', 2);
// Discover available network services
if(!args || args.extdisco !== false)
JSJaCJingle_extdisco();
if(args.fallback && typeof args.fallback === 'string')
JSJaCJingle_fallback(args.fallback);
} catch(e) {
JSJAC_JINGLE_STORE_DEBUG.log('[JSJaCJingle] lib:listen > ' + e, 1);
}
}
/**
* Routes Jingle stanzas
*/
function JSJaCJingle_route(stanza) {
try {
var action = null;
var sid = null;
// Route the incoming stanza
var jingle = stanza.getChild('jingle', NS_JINGLE);
if(jingle) {
sid = jingle.getAttribute('sid');
action = jingle.getAttribute('action');
} else {
var stanza_id = stanza.getID();
if(stanza_id) {
var is_jingle = stanza_id.indexOf(JSJAC_JINGLE_STANZA_ID_PRE + '_') !== -1;
if(is_jingle) {
var stanza_id_split = stanza_id.split('_');
sid = stanza_id_split[1];
}
}
}
// WebRTC not available ATM?
if(jingle && !JSJAC_JINGLE_AVAILABLE) {
JSJAC_JINGLE_STORE_DEBUG.log('[JSJaCJingle] lib:route > Dropped Jingle packet (WebRTC not available).', 0);
(new JSJaCJingle({ to: stanza.getFrom() })).send_error(stanza, XMPP_ERROR_SERVICE_UNAVAILABLE);
} else {
// New session? Or registered one?
var session_route = JSJaCJingle_read(sid);
if(action == JSJAC_JINGLE_ACTION_SESSION_INITIATE && session_route === null) {
JSJAC_JINGLE_STORE_DEBUG.log('[JSJaCJingle] lib:route > New Jingle session (sid: ' + sid + ').', 2);
JSJAC_JINGLE_STORE_INITIATE(stanza);
} else if(sid) {
if(session_route !== null) {
JSJAC_JINGLE_STORE_DEBUG.log('[JSJaCJingle] lib:route > Routed to Jingle session (sid: ' + sid + ').', 2);
session_route.handle(stanza);
} else if(stanza.getType() == JSJAC_JINGLE_STANZA_TYPE_SET && stanza.getFrom()) {
JSJAC_JINGLE_STORE_DEBUG.log('[JSJaCJingle] lib:route > Unknown Jingle session (sid: ' + sid + ').', 0);
(new JSJaCJingle({ to: stanza.getFrom() })).send_error(stanza, JSJAC_JINGLE_ERROR_UNKNOWN_SESSION);
}
}
}
} catch(e) {
JSJAC_JINGLE_STORE_DEBUG.log('[JSJaCJingle] lib:route > ' + e, 1);
}
}
/**
* Adds a new Jingle session
*/
function JSJaCJingle_add(sid, obj) {
JSJAC_JINGLE_STORE_SESSIONS[sid] = obj;
}
/**
* Reads a new Jingle session
* @return Session
* @type object
*/
function JSJaCJingle_read(sid) {
return (sid in JSJAC_JINGLE_STORE_SESSIONS) ? JSJAC_JINGLE_STORE_SESSIONS[sid] : null;
}
/**
* Removes a new Jingle session
*/
function JSJaCJingle_remove(sid) {
delete JSJAC_JINGLE_STORE_SESSIONS[sid];
}
/**
* Defer given task/execute deferred tasks
*/
function JSJaCJingle_defer(arg) {
try {
if(typeof arg == 'function') {
// Deferring?
if(JSJAC_JINGLE_STORE_DEFER.deferred) {
(JSJAC_JINGLE_STORE_DEFER.fn).push(arg);
JSJAC_JINGLE_STORE_DEBUG.log('[JSJaCJingle] lib:defer > Registered a function to be executed once ready.', 2);
}
return JSJAC_JINGLE_STORE_DEFER.deferred;
} else if(!arg || typeof arg == 'boolean') {
JSJAC_JINGLE_STORE_DEFER.deferred = (arg === true);
if(JSJAC_JINGLE_STORE_DEFER.deferred === false) {
// Execute deferred tasks?
if((--JSJAC_JINGLE_STORE_DEFER.count) <= 0) {
JSJAC_JINGLE_STORE_DEFER.count = 0;
JSJAC_JINGLE_STORE_DEBUG.log('[JSJaCJingle] lib:defer > Executing ' + JSJAC_JINGLE_STORE_DEFER.fn.length + ' deferred functions...', 2);
while(JSJAC_JINGLE_STORE_DEFER.fn.length)
((JSJAC_JINGLE_STORE_DEFER.fn).shift())();
JSJAC_JINGLE_STORE_DEBUG.log('[JSJaCJingle] lib:defer > Done executing deferred functions.', 2);
}
} else {
++JSJAC_JINGLE_STORE_DEFER.count;
}
}
} catch(e) {
JSJAC_JINGLE_STORE_DEBUG.log('[JSJaCJingle] lib:defer > ' + e, 1);
}
}
/**
* Maps the Jingle disco features
* @return Feature namespaces
* @type array
*/
function JSJaCJingle_disco() {
return JSJAC_JINGLE_AVAILABLE ? MAP_DISCO_JINGLE : [];
}
/**
* Query the server for external services
*/
function JSJaCJingle_extdisco() {
JSJAC_JINGLE_STORE_DEBUG.log('[JSJaCJingle] lib:extdisco > Discovering available services...', 2);
try {
// Pending state (defer other requests)
JSJaCJingle_defer(true);
// Build request
var request = new JSJaCIQ();
request.setTo(JSJAC_JINGLE_STORE_CONNECTION.domain);
request.setType(JSJAC_JINGLE_STANZA_TYPE_GET);
request.getNode().appendChild(request.buildNode('services', { 'xmlns': NS_EXTDISCO }));
JSJAC_JINGLE_STORE_CONNECTION.send(request, function(response) {
try {
// Parse response
if(response.getType() == JSJAC_JINGLE_STANZA_TYPE_RESULT) {
var i,
service_arr, cur_service,
cur_host, cur_password, cur_port, cur_transport, cur_type, cur_username;
var services = response.getChild('services', NS_EXTDISCO);
if(services) {
service_arr = services.getElementsByTagNameNS(NS_EXTDISCO, 'service');
for(i = 0; i < service_arr.length; i++) {
cur_service = service_arr[i];
cur_host = cur_service.getAttribute('host') || null;
cur_port = cur_service.getAttribute('port') || null;
cur_transport = cur_service.getAttribute('transport') || null;
cur_type = cur_service.getAttribute('type') || null;
cur_username = cur_service.getAttribute('username') || null;
cur_password = cur_service.getAttribute('password') || null;
if(!cur_host || !cur_type) continue;
if(!(cur_type in JSJAC_JINGLE_STORE_EXTDISCO)) {
JSJAC_JINGLE_STORE_DEBUG.log('[JSJaCJingle] lib:extdisco > handle > Service skipped (type: ' + cur_type + ', host: ' + cur_host + ', port: ' + cur_port + ', transport: ' + cur_transport + ').', 4);
continue;
}
JSJAC_JINGLE_STORE_EXTDISCO[cur_type][cur_host] = {
'port' : cur_port,
'transport' : cur_transport,
'type' : cur_type
};
if(cur_type == 'turn') {
JSJAC_JINGLE_STORE_EXTDISCO[cur_type][cur_host].username = cur_username;
JSJAC_JINGLE_STORE_EXTDISCO[cur_type][cur_host].password = cur_password;
}
JSJAC_JINGLE_STORE_DEBUG.log('[JSJaCJingle] lib:extdisco > handle > Service stored (type: ' + cur_type + ', host: ' + cur_host + ', port: ' + cur_port + ', transport: ' + cur_transport + ').', 4);
}
}
JSJAC_JINGLE_STORE_DEBUG.log('[JSJaCJingle] lib:extdisco > handle > Discovered available services.', 2);
} else {
JSJAC_JINGLE_STORE_DEBUG.log('[JSJaCJingle] lib:extdisco > handle > Could not discover services (server might not support XEP-0215).', 0);
}
} catch(e) {
JSJAC_JINGLE_STORE_DEBUG.log('[JSJaCJingle] lib:extdisco > handle > ' + e, 1);
}
JSJAC_JINGLE_STORE_DEBUG.log('[JSJaCJingle] lib:extdisco > Ready.', 2);
// Execute deferred requests
JSJaCJingle_defer(false);
});
} catch(e) {
JSJAC_JINGLE_STORE_DEBUG.log('[JSJaCJingle] lib:extdisco > ' + e, 1);
// Execute deferred requests
JSJaCJingle_defer(false);
}
}
/**
* Query some external APIs for fallback STUN/TURN (must be configured)
*/
function JSJaCJingle_fallback(fallback_url) {
JSJAC_JINGLE_STORE_DEBUG.log('[JSJaCJingle] lib:fallback > Discovering fallback services...', 2);
try {
// Pending state (defer other requests)
JSJaCJingle_defer(true);
// Generate fallback API URL
fallback_url += '?username=' +
encodeURIComponent(JSJAC_JINGLE_STORE_CONNECTION.username + '@' + JSJAC_JINGLE_STORE_CONNECTION.domain);
// Proceed request
var xhr = new XMLHttpRequest();
xhr.open('GET', fallback_url, true);
xhr.onreadystatechange = function() {
if(xhr.readyState === 4) {
// Success?
if(xhr.status === 200) {
var data = JSON.parse(xhr.responseText);
var cur_parse,
i, cur_url,
cur_type, cur_host, cur_port, cur_transport,
cur_username, cur_password;
if(data.uris && data.uris.length) {
JSJAC_JINGLE_STORE_DEBUG.log('[JSJaCJingle] lib:fallback > handle > Parsing ' + data.uris.length + ' URIs...', 2);
for(i in data.uris) {
cur_url = data.uris[i];
if(cur_url) {
// Parse current URL
cur_parse = R_JSJAC_JINGLE_SERVICE_URI.exec(cur_url);
if(cur_parse) {
cur_type = cur_parse[1] || null;
cur_host = cur_parse[2] || null;
cur_port = cur_parse[3] || null;
cur_transport = cur_parse[4] || null;
cur_username = data.username || null;
cur_password = data.password || null;
if(!cur_host || !cur_type) continue;
if(!(cur_type in JSJAC_JINGLE_STORE_FALLBACK)) {
JSJAC_JINGLE_STORE_DEBUG.log('[JSJaCJingle] lib:fallback > handle > Service skipped (type: ' + cur_type + ', host: ' + cur_host + ', port: ' + cur_port + ', transport: ' + cur_transport + ').', 4);
continue;
}
JSJAC_JINGLE_STORE_FALLBACK[cur_type][cur_host] = {
'port' : cur_port,
'transport' : cur_transport,
'type' : cur_type
};
if(cur_type == 'turn') {
JSJAC_JINGLE_STORE_FALLBACK[cur_type][cur_host].username = cur_username;
JSJAC_JINGLE_STORE_FALLBACK[cur_type][cur_host].password = cur_password;
}
JSJAC_JINGLE_STORE_DEBUG.log('[JSJaCJingle] lib:fallback > handle > Fallback service stored (type: ' + cur_type + ', host: ' + cur_host + ', port: ' + cur_port + ', transport: ' + cur_transport + ').', 4);
} else {
JSJAC_JINGLE_STORE_DEBUG.log('[JSJaCJingle] lib:fallback > handle > Fallback service not stored, weird URI (' + cur_url + ').', 0);
}
}
}
JSJAC_JINGLE_STORE_DEBUG.log('[JSJaCJingle] lib:fallback > handle > Finished parsing URIs.', 2);
} else {
JSJAC_JINGLE_STORE_DEBUG.log('[JSJaCJingle] lib:fallback > handle > No URI to parse.', 2);
}
JSJAC_JINGLE_STORE_DEBUG.log('[JSJaCJingle] lib:fallback > handle > Discovered fallback services.', 2);
} else {
JSJAC_JINGLE_STORE_DEBUG.log('[JSJaCJingle] lib:fallback > handle > Could not discover fallback services (API malfunction).', 0);
}
JSJAC_JINGLE_STORE_DEBUG.log('[JSJaCJingle] lib:fallback > Ready.', 2);
// Execute deferred requests
JSJaCJingle_defer(false);
}
};
xhr.send();
} catch(e) {
JSJAC_JINGLE_STORE_DEBUG.log('[JSJaCJingle] lib:fallback > ' + e, 1);
}
}