mirror of
https://github.com/YunoHost-Apps/jappix_ynh.git
synced 2024-09-03 19:26:19 +02:00
7247 lines
212 KiB
JavaScript
7247 lines
212 KiB
JavaScript
/**
|
|
* @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);
|
|
}
|
|
}
|