Nukes colibri.*.js and restores original file structure of strophe.jingle.
This commit is contained in:
parent
efc161dacd
commit
dc5d5f8436
|
@ -17,13 +17,9 @@
|
||||||
<script src="libs/strophe/strophe.disco.min.js?v=1"></script>
|
<script src="libs/strophe/strophe.disco.min.js?v=1"></script>
|
||||||
<script src="libs/strophe/strophe.caps.jsonly.min.js?v=1"></script>
|
<script src="libs/strophe/strophe.caps.jsonly.min.js?v=1"></script>
|
||||||
<script src="libs/strophe/strophe.jingle.js?v=2"></script>
|
<script src="libs/strophe/strophe.jingle.js?v=2"></script>
|
||||||
<script src="libs/strophe/strophe.jingle.sdp.js?v=2"></script>
|
<script src="libs/strophe/strophe.jingle.sdp.js?v=3"></script>
|
||||||
<script src="libs/strophe/strophe.jingle.sdp.util.js?v=1"></script>
|
<script src="libs/strophe/strophe.jingle.session.js?v=3"></script>
|
||||||
<script src="libs/strophe/strophe.jingle.sessionbase.js?v=1"></script>
|
|
||||||
<script src="libs/strophe/strophe.jingle.session.js?v=2"></script>
|
|
||||||
<script src="libs/strophe/strophe.util.js"></script>
|
<script src="libs/strophe/strophe.util.js"></script>
|
||||||
<script src="libs/colibri/colibri.focus.js?v=12"></script><!-- colibri focus implementation -->
|
|
||||||
<script src="libs/colibri/colibri.session.js?v=1"></script>
|
|
||||||
<script src="libs/jquery-ui.js"></script>
|
<script src="libs/jquery-ui.js"></script>
|
||||||
<script src="libs/rayo.js?v=1"></script>
|
<script src="libs/rayo.js?v=1"></script>
|
||||||
<script src="libs/tooltip.js?v=1"></script><!-- bootstrap tooltip lib -->
|
<script src="libs/tooltip.js?v=1"></script><!-- bootstrap tooltip lib -->
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,94 +0,0 @@
|
||||||
/* colibri.js -- a COLIBRI focus
|
|
||||||
* The colibri spec has been submitted to the XMPP Standards Foundation
|
|
||||||
* for publications as a XMPP extensions:
|
|
||||||
* http://xmpp.org/extensions/inbox/colibri.html
|
|
||||||
*
|
|
||||||
* colibri.js is a participating focus, i.e. the focus participates
|
|
||||||
* in the conference. The conference itself can be ad-hoc, through a
|
|
||||||
* MUC, through PubSub, etc.
|
|
||||||
*
|
|
||||||
* colibri.js relies heavily on the strophe.jingle library available
|
|
||||||
* from https://github.com/ESTOS/strophe.jingle
|
|
||||||
* and interoperates with the Jitsi videobridge available from
|
|
||||||
* https://jitsi.org/Projects/JitsiVideobridge
|
|
||||||
*/
|
|
||||||
/*
|
|
||||||
Copyright (c) 2013 ESTOS GmbH
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
|
||||||
all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
THE SOFTWARE.
|
|
||||||
*/
|
|
||||||
// A colibri session is similar to a jingle session, it just implements some things differently
|
|
||||||
// FIXME: inherit jinglesession, see https://github.com/legastero/Jingle-RTCPeerConnection/blob/master/index.js
|
|
||||||
function ColibriSession(me, sid, connection) {
|
|
||||||
this.me = me;
|
|
||||||
this.sid = sid;
|
|
||||||
this.connection = connection;
|
|
||||||
//this.peerconnection = null;
|
|
||||||
//this.mychannel = null;
|
|
||||||
//this.channels = null;
|
|
||||||
this.peerjid = null;
|
|
||||||
|
|
||||||
this.colibri = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// implementation of JingleSession interface
|
|
||||||
ColibriSession.prototype.initiate = function (peerjid, isInitiator) {
|
|
||||||
this.peerjid = peerjid;
|
|
||||||
};
|
|
||||||
|
|
||||||
ColibriSession.prototype.sendOffer = function (offer) {
|
|
||||||
console.log('ColibriSession.sendOffer');
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
ColibriSession.prototype.accept = function () {
|
|
||||||
console.log('ColibriSession.accept');
|
|
||||||
};
|
|
||||||
|
|
||||||
ColibriSession.prototype.addSource = function (elem, fromJid) {
|
|
||||||
this.colibri.addSource(elem, fromJid);
|
|
||||||
};
|
|
||||||
|
|
||||||
ColibriSession.prototype.removeSource = function (elem, fromJid) {
|
|
||||||
this.colibri.removeSource(elem, fromJid);
|
|
||||||
};
|
|
||||||
|
|
||||||
ColibriSession.prototype.terminate = function (reason) {
|
|
||||||
this.colibri.terminate(this, reason);
|
|
||||||
};
|
|
||||||
|
|
||||||
ColibriSession.prototype.active = function () {
|
|
||||||
console.log('ColibriSession.active');
|
|
||||||
};
|
|
||||||
|
|
||||||
ColibriSession.prototype.setRemoteDescription = function (elem, desctype) {
|
|
||||||
this.colibri.setRemoteDescription(this, elem, desctype);
|
|
||||||
};
|
|
||||||
|
|
||||||
ColibriSession.prototype.addIceCandidate = function (elem) {
|
|
||||||
this.colibri.addIceCandidate(this, elem);
|
|
||||||
};
|
|
||||||
|
|
||||||
ColibriSession.prototype.sendAnswer = function (sdp, provisional) {
|
|
||||||
console.log('ColibriSession.sendAnswer');
|
|
||||||
};
|
|
||||||
|
|
||||||
ColibriSession.prototype.sendTerminate = function (reason, text) {
|
|
||||||
this.colibri.sendTerminate(this, reason, text);
|
|
||||||
};
|
|
|
@ -683,3 +683,409 @@ SDP.prototype.jingle2media = function (content) {
|
||||||
|
|
||||||
return media;
|
return media;
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
|
* Contains utility classes used in SDP class.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class holds a=ssrc lines and media type a=mid
|
||||||
|
* @param ssrc synchronization source identifier number(a=ssrc lines from SDP)
|
||||||
|
* @param type media type eg. "audio" or "video"(a=mid frm SDP)
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
function ChannelSsrc(ssrc, type) {
|
||||||
|
this.ssrc = ssrc;
|
||||||
|
this.type = type;
|
||||||
|
this.lines = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class holds a=ssrc-group: lines
|
||||||
|
* @param semantics
|
||||||
|
* @param ssrcs
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
function ChannelSsrcGroup(semantics, ssrcs, line) {
|
||||||
|
this.semantics = semantics;
|
||||||
|
this.ssrcs = ssrcs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper class represents media channel. Is a container for ChannelSsrc, holds channel idx and media type.
|
||||||
|
* @param channelNumber channel idx in SDP media array.
|
||||||
|
* @param mediaType media type(a=mid)
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
function MediaChannel(channelNumber, mediaType) {
|
||||||
|
/**
|
||||||
|
* SDP channel number
|
||||||
|
* @type {*}
|
||||||
|
*/
|
||||||
|
this.chNumber = channelNumber;
|
||||||
|
/**
|
||||||
|
* Channel media type(a=mid)
|
||||||
|
* @type {*}
|
||||||
|
*/
|
||||||
|
this.mediaType = mediaType;
|
||||||
|
/**
|
||||||
|
* The maps of ssrc numbers to ChannelSsrc objects.
|
||||||
|
*/
|
||||||
|
this.ssrcs = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The array of ChannelSsrcGroup objects.
|
||||||
|
* @type {Array}
|
||||||
|
*/
|
||||||
|
this.ssrcGroups = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
SDPUtil = {
|
||||||
|
iceparams: function (mediadesc, sessiondesc) {
|
||||||
|
var data = null;
|
||||||
|
if (SDPUtil.find_line(mediadesc, 'a=ice-ufrag:', sessiondesc) &&
|
||||||
|
SDPUtil.find_line(mediadesc, 'a=ice-pwd:', sessiondesc)) {
|
||||||
|
data = {
|
||||||
|
ufrag: SDPUtil.parse_iceufrag(SDPUtil.find_line(mediadesc, 'a=ice-ufrag:', sessiondesc)),
|
||||||
|
pwd: SDPUtil.parse_icepwd(SDPUtil.find_line(mediadesc, 'a=ice-pwd:', sessiondesc))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
parse_iceufrag: function (line) {
|
||||||
|
return line.substring(12);
|
||||||
|
},
|
||||||
|
build_iceufrag: function (frag) {
|
||||||
|
return 'a=ice-ufrag:' + frag;
|
||||||
|
},
|
||||||
|
parse_icepwd: function (line) {
|
||||||
|
return line.substring(10);
|
||||||
|
},
|
||||||
|
build_icepwd: function (pwd) {
|
||||||
|
return 'a=ice-pwd:' + pwd;
|
||||||
|
},
|
||||||
|
parse_mid: function (line) {
|
||||||
|
return line.substring(6);
|
||||||
|
},
|
||||||
|
parse_mline: function (line) {
|
||||||
|
var parts = line.substring(2).split(' '),
|
||||||
|
data = {};
|
||||||
|
data.media = parts.shift();
|
||||||
|
data.port = parts.shift();
|
||||||
|
data.proto = parts.shift();
|
||||||
|
if (parts[parts.length - 1] === '') { // trailing whitespace
|
||||||
|
parts.pop();
|
||||||
|
}
|
||||||
|
data.fmt = parts;
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
build_mline: function (mline) {
|
||||||
|
return 'm=' + mline.media + ' ' + mline.port + ' ' + mline.proto + ' ' + mline.fmt.join(' ');
|
||||||
|
},
|
||||||
|
parse_rtpmap: function (line) {
|
||||||
|
var parts = line.substring(9).split(' '),
|
||||||
|
data = {};
|
||||||
|
data.id = parts.shift();
|
||||||
|
parts = parts[0].split('/');
|
||||||
|
data.name = parts.shift();
|
||||||
|
data.clockrate = parts.shift();
|
||||||
|
data.channels = parts.length ? parts.shift() : '1';
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Parses SDP line "a=sctpmap:..." and extracts SCTP port from it.
|
||||||
|
* @param line eg. "a=sctpmap:5000 webrtc-datachannel"
|
||||||
|
* @returns [SCTP port number, protocol, streams]
|
||||||
|
*/
|
||||||
|
parse_sctpmap: function (line)
|
||||||
|
{
|
||||||
|
var parts = line.substring(10).split(' ');
|
||||||
|
var sctpPort = parts[0];
|
||||||
|
var protocol = parts[1];
|
||||||
|
// Stream count is optional
|
||||||
|
var streamCount = parts.length > 2 ? parts[2] : null;
|
||||||
|
return [sctpPort, protocol, streamCount];// SCTP port
|
||||||
|
},
|
||||||
|
build_rtpmap: function (el) {
|
||||||
|
var line = 'a=rtpmap:' + el.getAttribute('id') + ' ' + el.getAttribute('name') + '/' + el.getAttribute('clockrate');
|
||||||
|
if (el.getAttribute('channels') && el.getAttribute('channels') != '1') {
|
||||||
|
line += '/' + el.getAttribute('channels');
|
||||||
|
}
|
||||||
|
return line;
|
||||||
|
},
|
||||||
|
parse_crypto: function (line) {
|
||||||
|
var parts = line.substring(9).split(' '),
|
||||||
|
data = {};
|
||||||
|
data.tag = parts.shift();
|
||||||
|
data['crypto-suite'] = parts.shift();
|
||||||
|
data['key-params'] = parts.shift();
|
||||||
|
if (parts.length) {
|
||||||
|
data['session-params'] = parts.join(' ');
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
parse_fingerprint: function (line) { // RFC 4572
|
||||||
|
var parts = line.substring(14).split(' '),
|
||||||
|
data = {};
|
||||||
|
data.hash = parts.shift();
|
||||||
|
data.fingerprint = parts.shift();
|
||||||
|
// TODO assert that fingerprint satisfies 2UHEX *(":" 2UHEX) ?
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
parse_fmtp: function (line) {
|
||||||
|
var parts = line.split(' '),
|
||||||
|
i, key, value,
|
||||||
|
data = [];
|
||||||
|
parts.shift();
|
||||||
|
parts = parts.join(' ').split(';');
|
||||||
|
for (i = 0; i < parts.length; i++) {
|
||||||
|
key = parts[i].split('=')[0];
|
||||||
|
while (key.length && key[0] == ' ') {
|
||||||
|
key = key.substring(1);
|
||||||
|
}
|
||||||
|
value = parts[i].split('=')[1];
|
||||||
|
if (key && value) {
|
||||||
|
data.push({name: key, value: value});
|
||||||
|
} else if (key) {
|
||||||
|
// rfc 4733 (DTMF) style stuff
|
||||||
|
data.push({name: '', value: key});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
parse_icecandidate: function (line) {
|
||||||
|
var candidate = {},
|
||||||
|
elems = line.split(' ');
|
||||||
|
candidate.foundation = elems[0].substring(12);
|
||||||
|
candidate.component = elems[1];
|
||||||
|
candidate.protocol = elems[2].toLowerCase();
|
||||||
|
candidate.priority = elems[3];
|
||||||
|
candidate.ip = elems[4];
|
||||||
|
candidate.port = elems[5];
|
||||||
|
// elems[6] => "typ"
|
||||||
|
candidate.type = elems[7];
|
||||||
|
candidate.generation = 0; // default value, may be overwritten below
|
||||||
|
for (var i = 8; i < elems.length; i += 2) {
|
||||||
|
switch (elems[i]) {
|
||||||
|
case 'raddr':
|
||||||
|
candidate['rel-addr'] = elems[i + 1];
|
||||||
|
break;
|
||||||
|
case 'rport':
|
||||||
|
candidate['rel-port'] = elems[i + 1];
|
||||||
|
break;
|
||||||
|
case 'generation':
|
||||||
|
candidate.generation = elems[i + 1];
|
||||||
|
break;
|
||||||
|
case 'tcptype':
|
||||||
|
candidate.tcptype = elems[i + 1];
|
||||||
|
break;
|
||||||
|
default: // TODO
|
||||||
|
console.log('parse_icecandidate not translating "' + elems[i] + '" = "' + elems[i + 1] + '"');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
candidate.network = '1';
|
||||||
|
candidate.id = Math.random().toString(36).substr(2, 10); // not applicable to SDP -- FIXME: should be unique, not just random
|
||||||
|
return candidate;
|
||||||
|
},
|
||||||
|
build_icecandidate: function (cand) {
|
||||||
|
var line = ['a=candidate:' + cand.foundation, cand.component, cand.protocol, cand.priority, cand.ip, cand.port, 'typ', cand.type].join(' ');
|
||||||
|
line += ' ';
|
||||||
|
switch (cand.type) {
|
||||||
|
case 'srflx':
|
||||||
|
case 'prflx':
|
||||||
|
case 'relay':
|
||||||
|
if (cand.hasOwnAttribute('rel-addr') && cand.hasOwnAttribute('rel-port')) {
|
||||||
|
line += 'raddr';
|
||||||
|
line += ' ';
|
||||||
|
line += cand['rel-addr'];
|
||||||
|
line += ' ';
|
||||||
|
line += 'rport';
|
||||||
|
line += ' ';
|
||||||
|
line += cand['rel-port'];
|
||||||
|
line += ' ';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (cand.hasOwnAttribute('tcptype')) {
|
||||||
|
line += 'tcptype';
|
||||||
|
line += ' ';
|
||||||
|
line += cand.tcptype;
|
||||||
|
line += ' ';
|
||||||
|
}
|
||||||
|
line += 'generation';
|
||||||
|
line += ' ';
|
||||||
|
line += cand.hasOwnAttribute('generation') ? cand.generation : '0';
|
||||||
|
return line;
|
||||||
|
},
|
||||||
|
parse_ssrc: function (desc) {
|
||||||
|
// proprietary mapping of a=ssrc lines
|
||||||
|
// TODO: see "Jingle RTP Source Description" by Juberti and P. Thatcher on google docs
|
||||||
|
// and parse according to that
|
||||||
|
var lines = desc.split('\r\n'),
|
||||||
|
data = {};
|
||||||
|
for (var i = 0; i < lines.length; i++) {
|
||||||
|
if (lines[i].substring(0, 7) == 'a=ssrc:') {
|
||||||
|
var idx = lines[i].indexOf(' ');
|
||||||
|
data[lines[i].substr(idx + 1).split(':', 2)[0]] = lines[i].substr(idx + 1).split(':', 2)[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
parse_rtcpfb: function (line) {
|
||||||
|
var parts = line.substr(10).split(' ');
|
||||||
|
var data = {};
|
||||||
|
data.pt = parts.shift();
|
||||||
|
data.type = parts.shift();
|
||||||
|
data.params = parts;
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
parse_extmap: function (line) {
|
||||||
|
var parts = line.substr(9).split(' ');
|
||||||
|
var data = {};
|
||||||
|
data.value = parts.shift();
|
||||||
|
if (data.value.indexOf('/') != -1) {
|
||||||
|
data.direction = data.value.substr(data.value.indexOf('/') + 1);
|
||||||
|
data.value = data.value.substr(0, data.value.indexOf('/'));
|
||||||
|
} else {
|
||||||
|
data.direction = 'both';
|
||||||
|
}
|
||||||
|
data.uri = parts.shift();
|
||||||
|
data.params = parts;
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
find_line: function (haystack, needle, sessionpart) {
|
||||||
|
var lines = haystack.split('\r\n');
|
||||||
|
for (var i = 0; i < lines.length; i++) {
|
||||||
|
if (lines[i].substring(0, needle.length) == needle) {
|
||||||
|
return lines[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!sessionpart) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// search session part
|
||||||
|
lines = sessionpart.split('\r\n');
|
||||||
|
for (var j = 0; j < lines.length; j++) {
|
||||||
|
if (lines[j].substring(0, needle.length) == needle) {
|
||||||
|
return lines[j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
find_lines: function (haystack, needle, sessionpart) {
|
||||||
|
var lines = haystack.split('\r\n'),
|
||||||
|
needles = [];
|
||||||
|
for (var i = 0; i < lines.length; i++) {
|
||||||
|
if (lines[i].substring(0, needle.length) == needle)
|
||||||
|
needles.push(lines[i]);
|
||||||
|
}
|
||||||
|
if (needles.length || !sessionpart) {
|
||||||
|
return needles;
|
||||||
|
}
|
||||||
|
// search session part
|
||||||
|
lines = sessionpart.split('\r\n');
|
||||||
|
for (var j = 0; j < lines.length; j++) {
|
||||||
|
if (lines[j].substring(0, needle.length) == needle) {
|
||||||
|
needles.push(lines[j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return needles;
|
||||||
|
},
|
||||||
|
candidateToJingle: function (line) {
|
||||||
|
// a=candidate:2979166662 1 udp 2113937151 192.168.2.100 57698 typ host generation 0
|
||||||
|
// <candidate component=... foundation=... generation=... id=... ip=... network=... port=... priority=... protocol=... type=.../>
|
||||||
|
if (line.indexOf('candidate:') === 0) {
|
||||||
|
line = 'a=' + line;
|
||||||
|
} else if (line.substring(0, 12) != 'a=candidate:') {
|
||||||
|
console.log('parseCandidate called with a line that is not a candidate line');
|
||||||
|
console.log(line);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (line.substring(line.length - 2) == '\r\n') // chomp it
|
||||||
|
line = line.substring(0, line.length - 2);
|
||||||
|
var candidate = {},
|
||||||
|
elems = line.split(' '),
|
||||||
|
i;
|
||||||
|
if (elems[6] != 'typ') {
|
||||||
|
console.log('did not find typ in the right place');
|
||||||
|
console.log(line);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
candidate.foundation = elems[0].substring(12);
|
||||||
|
candidate.component = elems[1];
|
||||||
|
candidate.protocol = elems[2].toLowerCase();
|
||||||
|
candidate.priority = elems[3];
|
||||||
|
candidate.ip = elems[4];
|
||||||
|
candidate.port = elems[5];
|
||||||
|
// elems[6] => "typ"
|
||||||
|
candidate.type = elems[7];
|
||||||
|
|
||||||
|
candidate.generation = '0'; // default, may be overwritten below
|
||||||
|
for (i = 8; i < elems.length; i += 2) {
|
||||||
|
switch (elems[i]) {
|
||||||
|
case 'raddr':
|
||||||
|
candidate['rel-addr'] = elems[i + 1];
|
||||||
|
break;
|
||||||
|
case 'rport':
|
||||||
|
candidate['rel-port'] = elems[i + 1];
|
||||||
|
break;
|
||||||
|
case 'generation':
|
||||||
|
candidate.generation = elems[i + 1];
|
||||||
|
break;
|
||||||
|
case 'tcptype':
|
||||||
|
candidate.tcptype = elems[i + 1];
|
||||||
|
break;
|
||||||
|
default: // TODO
|
||||||
|
console.log('not translating "' + elems[i] + '" = "' + elems[i + 1] + '"');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
candidate.network = '1';
|
||||||
|
candidate.id = Math.random().toString(36).substr(2, 10); // not applicable to SDP -- FIXME: should be unique, not just random
|
||||||
|
return candidate;
|
||||||
|
},
|
||||||
|
candidateFromJingle: function (cand) {
|
||||||
|
var line = 'a=candidate:';
|
||||||
|
line += cand.getAttribute('foundation');
|
||||||
|
line += ' ';
|
||||||
|
line += cand.getAttribute('component');
|
||||||
|
line += ' ';
|
||||||
|
line += cand.getAttribute('protocol'); //.toUpperCase(); // chrome M23 doesn't like this
|
||||||
|
line += ' ';
|
||||||
|
line += cand.getAttribute('priority');
|
||||||
|
line += ' ';
|
||||||
|
line += cand.getAttribute('ip');
|
||||||
|
line += ' ';
|
||||||
|
line += cand.getAttribute('port');
|
||||||
|
line += ' ';
|
||||||
|
line += 'typ';
|
||||||
|
line += ' ' + cand.getAttribute('type');
|
||||||
|
line += ' ';
|
||||||
|
switch (cand.getAttribute('type')) {
|
||||||
|
case 'srflx':
|
||||||
|
case 'prflx':
|
||||||
|
case 'relay':
|
||||||
|
if (cand.getAttribute('rel-addr') && cand.getAttribute('rel-port')) {
|
||||||
|
line += 'raddr';
|
||||||
|
line += ' ';
|
||||||
|
line += cand.getAttribute('rel-addr');
|
||||||
|
line += ' ';
|
||||||
|
line += 'rport';
|
||||||
|
line += ' ';
|
||||||
|
line += cand.getAttribute('rel-port');
|
||||||
|
line += ' ';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (cand.getAttribute('protocol').toLowerCase() == 'tcp') {
|
||||||
|
line += 'tcptype';
|
||||||
|
line += ' ';
|
||||||
|
line += cand.getAttribute('tcptype');
|
||||||
|
line += ' ';
|
||||||
|
}
|
||||||
|
line += 'generation';
|
||||||
|
line += ' ';
|
||||||
|
line += cand.getAttribute('generation') || '0';
|
||||||
|
return line + '\r\n';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,406 +0,0 @@
|
||||||
/**
|
|
||||||
* Contains utility classes used in SDP class.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class holds a=ssrc lines and media type a=mid
|
|
||||||
* @param ssrc synchronization source identifier number(a=ssrc lines from SDP)
|
|
||||||
* @param type media type eg. "audio" or "video"(a=mid frm SDP)
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
function ChannelSsrc(ssrc, type) {
|
|
||||||
this.ssrc = ssrc;
|
|
||||||
this.type = type;
|
|
||||||
this.lines = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class holds a=ssrc-group: lines
|
|
||||||
* @param semantics
|
|
||||||
* @param ssrcs
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
function ChannelSsrcGroup(semantics, ssrcs, line) {
|
|
||||||
this.semantics = semantics;
|
|
||||||
this.ssrcs = ssrcs;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper class represents media channel. Is a container for ChannelSsrc, holds channel idx and media type.
|
|
||||||
* @param channelNumber channel idx in SDP media array.
|
|
||||||
* @param mediaType media type(a=mid)
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
function MediaChannel(channelNumber, mediaType) {
|
|
||||||
/**
|
|
||||||
* SDP channel number
|
|
||||||
* @type {*}
|
|
||||||
*/
|
|
||||||
this.chNumber = channelNumber;
|
|
||||||
/**
|
|
||||||
* Channel media type(a=mid)
|
|
||||||
* @type {*}
|
|
||||||
*/
|
|
||||||
this.mediaType = mediaType;
|
|
||||||
/**
|
|
||||||
* The maps of ssrc numbers to ChannelSsrc objects.
|
|
||||||
*/
|
|
||||||
this.ssrcs = {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The array of ChannelSsrcGroup objects.
|
|
||||||
* @type {Array}
|
|
||||||
*/
|
|
||||||
this.ssrcGroups = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
SDPUtil = {
|
|
||||||
iceparams: function (mediadesc, sessiondesc) {
|
|
||||||
var data = null;
|
|
||||||
if (SDPUtil.find_line(mediadesc, 'a=ice-ufrag:', sessiondesc) &&
|
|
||||||
SDPUtil.find_line(mediadesc, 'a=ice-pwd:', sessiondesc)) {
|
|
||||||
data = {
|
|
||||||
ufrag: SDPUtil.parse_iceufrag(SDPUtil.find_line(mediadesc, 'a=ice-ufrag:', sessiondesc)),
|
|
||||||
pwd: SDPUtil.parse_icepwd(SDPUtil.find_line(mediadesc, 'a=ice-pwd:', sessiondesc))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
},
|
|
||||||
parse_iceufrag: function (line) {
|
|
||||||
return line.substring(12);
|
|
||||||
},
|
|
||||||
build_iceufrag: function (frag) {
|
|
||||||
return 'a=ice-ufrag:' + frag;
|
|
||||||
},
|
|
||||||
parse_icepwd: function (line) {
|
|
||||||
return line.substring(10);
|
|
||||||
},
|
|
||||||
build_icepwd: function (pwd) {
|
|
||||||
return 'a=ice-pwd:' + pwd;
|
|
||||||
},
|
|
||||||
parse_mid: function (line) {
|
|
||||||
return line.substring(6);
|
|
||||||
},
|
|
||||||
parse_mline: function (line) {
|
|
||||||
var parts = line.substring(2).split(' '),
|
|
||||||
data = {};
|
|
||||||
data.media = parts.shift();
|
|
||||||
data.port = parts.shift();
|
|
||||||
data.proto = parts.shift();
|
|
||||||
if (parts[parts.length - 1] === '') { // trailing whitespace
|
|
||||||
parts.pop();
|
|
||||||
}
|
|
||||||
data.fmt = parts;
|
|
||||||
return data;
|
|
||||||
},
|
|
||||||
build_mline: function (mline) {
|
|
||||||
return 'm=' + mline.media + ' ' + mline.port + ' ' + mline.proto + ' ' + mline.fmt.join(' ');
|
|
||||||
},
|
|
||||||
parse_rtpmap: function (line) {
|
|
||||||
var parts = line.substring(9).split(' '),
|
|
||||||
data = {};
|
|
||||||
data.id = parts.shift();
|
|
||||||
parts = parts[0].split('/');
|
|
||||||
data.name = parts.shift();
|
|
||||||
data.clockrate = parts.shift();
|
|
||||||
data.channels = parts.length ? parts.shift() : '1';
|
|
||||||
return data;
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Parses SDP line "a=sctpmap:..." and extracts SCTP port from it.
|
|
||||||
* @param line eg. "a=sctpmap:5000 webrtc-datachannel"
|
|
||||||
* @returns [SCTP port number, protocol, streams]
|
|
||||||
*/
|
|
||||||
parse_sctpmap: function (line)
|
|
||||||
{
|
|
||||||
var parts = line.substring(10).split(' ');
|
|
||||||
var sctpPort = parts[0];
|
|
||||||
var protocol = parts[1];
|
|
||||||
// Stream count is optional
|
|
||||||
var streamCount = parts.length > 2 ? parts[2] : null;
|
|
||||||
return [sctpPort, protocol, streamCount];// SCTP port
|
|
||||||
},
|
|
||||||
build_rtpmap: function (el) {
|
|
||||||
var line = 'a=rtpmap:' + el.getAttribute('id') + ' ' + el.getAttribute('name') + '/' + el.getAttribute('clockrate');
|
|
||||||
if (el.getAttribute('channels') && el.getAttribute('channels') != '1') {
|
|
||||||
line += '/' + el.getAttribute('channels');
|
|
||||||
}
|
|
||||||
return line;
|
|
||||||
},
|
|
||||||
parse_crypto: function (line) {
|
|
||||||
var parts = line.substring(9).split(' '),
|
|
||||||
data = {};
|
|
||||||
data.tag = parts.shift();
|
|
||||||
data['crypto-suite'] = parts.shift();
|
|
||||||
data['key-params'] = parts.shift();
|
|
||||||
if (parts.length) {
|
|
||||||
data['session-params'] = parts.join(' ');
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
},
|
|
||||||
parse_fingerprint: function (line) { // RFC 4572
|
|
||||||
var parts = line.substring(14).split(' '),
|
|
||||||
data = {};
|
|
||||||
data.hash = parts.shift();
|
|
||||||
data.fingerprint = parts.shift();
|
|
||||||
// TODO assert that fingerprint satisfies 2UHEX *(":" 2UHEX) ?
|
|
||||||
return data;
|
|
||||||
},
|
|
||||||
parse_fmtp: function (line) {
|
|
||||||
var parts = line.split(' '),
|
|
||||||
i, key, value,
|
|
||||||
data = [];
|
|
||||||
parts.shift();
|
|
||||||
parts = parts.join(' ').split(';');
|
|
||||||
for (i = 0; i < parts.length; i++) {
|
|
||||||
key = parts[i].split('=')[0];
|
|
||||||
while (key.length && key[0] == ' ') {
|
|
||||||
key = key.substring(1);
|
|
||||||
}
|
|
||||||
value = parts[i].split('=')[1];
|
|
||||||
if (key && value) {
|
|
||||||
data.push({name: key, value: value});
|
|
||||||
} else if (key) {
|
|
||||||
// rfc 4733 (DTMF) style stuff
|
|
||||||
data.push({name: '', value: key});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
},
|
|
||||||
parse_icecandidate: function (line) {
|
|
||||||
var candidate = {},
|
|
||||||
elems = line.split(' ');
|
|
||||||
candidate.foundation = elems[0].substring(12);
|
|
||||||
candidate.component = elems[1];
|
|
||||||
candidate.protocol = elems[2].toLowerCase();
|
|
||||||
candidate.priority = elems[3];
|
|
||||||
candidate.ip = elems[4];
|
|
||||||
candidate.port = elems[5];
|
|
||||||
// elems[6] => "typ"
|
|
||||||
candidate.type = elems[7];
|
|
||||||
candidate.generation = 0; // default value, may be overwritten below
|
|
||||||
for (var i = 8; i < elems.length; i += 2) {
|
|
||||||
switch (elems[i]) {
|
|
||||||
case 'raddr':
|
|
||||||
candidate['rel-addr'] = elems[i + 1];
|
|
||||||
break;
|
|
||||||
case 'rport':
|
|
||||||
candidate['rel-port'] = elems[i + 1];
|
|
||||||
break;
|
|
||||||
case 'generation':
|
|
||||||
candidate.generation = elems[i + 1];
|
|
||||||
break;
|
|
||||||
case 'tcptype':
|
|
||||||
candidate.tcptype = elems[i + 1];
|
|
||||||
break;
|
|
||||||
default: // TODO
|
|
||||||
console.log('parse_icecandidate not translating "' + elems[i] + '" = "' + elems[i + 1] + '"');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
candidate.network = '1';
|
|
||||||
candidate.id = Math.random().toString(36).substr(2, 10); // not applicable to SDP -- FIXME: should be unique, not just random
|
|
||||||
return candidate;
|
|
||||||
},
|
|
||||||
build_icecandidate: function (cand) {
|
|
||||||
var line = ['a=candidate:' + cand.foundation, cand.component, cand.protocol, cand.priority, cand.ip, cand.port, 'typ', cand.type].join(' ');
|
|
||||||
line += ' ';
|
|
||||||
switch (cand.type) {
|
|
||||||
case 'srflx':
|
|
||||||
case 'prflx':
|
|
||||||
case 'relay':
|
|
||||||
if (cand.hasOwnAttribute('rel-addr') && cand.hasOwnAttribute('rel-port')) {
|
|
||||||
line += 'raddr';
|
|
||||||
line += ' ';
|
|
||||||
line += cand['rel-addr'];
|
|
||||||
line += ' ';
|
|
||||||
line += 'rport';
|
|
||||||
line += ' ';
|
|
||||||
line += cand['rel-port'];
|
|
||||||
line += ' ';
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (cand.hasOwnAttribute('tcptype')) {
|
|
||||||
line += 'tcptype';
|
|
||||||
line += ' ';
|
|
||||||
line += cand.tcptype;
|
|
||||||
line += ' ';
|
|
||||||
}
|
|
||||||
line += 'generation';
|
|
||||||
line += ' ';
|
|
||||||
line += cand.hasOwnAttribute('generation') ? cand.generation : '0';
|
|
||||||
return line;
|
|
||||||
},
|
|
||||||
parse_ssrc: function (desc) {
|
|
||||||
// proprietary mapping of a=ssrc lines
|
|
||||||
// TODO: see "Jingle RTP Source Description" by Juberti and P. Thatcher on google docs
|
|
||||||
// and parse according to that
|
|
||||||
var lines = desc.split('\r\n'),
|
|
||||||
data = {};
|
|
||||||
for (var i = 0; i < lines.length; i++) {
|
|
||||||
if (lines[i].substring(0, 7) == 'a=ssrc:') {
|
|
||||||
var idx = lines[i].indexOf(' ');
|
|
||||||
data[lines[i].substr(idx + 1).split(':', 2)[0]] = lines[i].substr(idx + 1).split(':', 2)[1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
},
|
|
||||||
parse_rtcpfb: function (line) {
|
|
||||||
var parts = line.substr(10).split(' ');
|
|
||||||
var data = {};
|
|
||||||
data.pt = parts.shift();
|
|
||||||
data.type = parts.shift();
|
|
||||||
data.params = parts;
|
|
||||||
return data;
|
|
||||||
},
|
|
||||||
parse_extmap: function (line) {
|
|
||||||
var parts = line.substr(9).split(' ');
|
|
||||||
var data = {};
|
|
||||||
data.value = parts.shift();
|
|
||||||
if (data.value.indexOf('/') != -1) {
|
|
||||||
data.direction = data.value.substr(data.value.indexOf('/') + 1);
|
|
||||||
data.value = data.value.substr(0, data.value.indexOf('/'));
|
|
||||||
} else {
|
|
||||||
data.direction = 'both';
|
|
||||||
}
|
|
||||||
data.uri = parts.shift();
|
|
||||||
data.params = parts;
|
|
||||||
return data;
|
|
||||||
},
|
|
||||||
find_line: function (haystack, needle, sessionpart) {
|
|
||||||
var lines = haystack.split('\r\n');
|
|
||||||
for (var i = 0; i < lines.length; i++) {
|
|
||||||
if (lines[i].substring(0, needle.length) == needle) {
|
|
||||||
return lines[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!sessionpart) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// search session part
|
|
||||||
lines = sessionpart.split('\r\n');
|
|
||||||
for (var j = 0; j < lines.length; j++) {
|
|
||||||
if (lines[j].substring(0, needle.length) == needle) {
|
|
||||||
return lines[j];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
find_lines: function (haystack, needle, sessionpart) {
|
|
||||||
var lines = haystack.split('\r\n'),
|
|
||||||
needles = [];
|
|
||||||
for (var i = 0; i < lines.length; i++) {
|
|
||||||
if (lines[i].substring(0, needle.length) == needle)
|
|
||||||
needles.push(lines[i]);
|
|
||||||
}
|
|
||||||
if (needles.length || !sessionpart) {
|
|
||||||
return needles;
|
|
||||||
}
|
|
||||||
// search session part
|
|
||||||
lines = sessionpart.split('\r\n');
|
|
||||||
for (var j = 0; j < lines.length; j++) {
|
|
||||||
if (lines[j].substring(0, needle.length) == needle) {
|
|
||||||
needles.push(lines[j]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return needles;
|
|
||||||
},
|
|
||||||
candidateToJingle: function (line) {
|
|
||||||
// a=candidate:2979166662 1 udp 2113937151 192.168.2.100 57698 typ host generation 0
|
|
||||||
// <candidate component=... foundation=... generation=... id=... ip=... network=... port=... priority=... protocol=... type=.../>
|
|
||||||
if (line.indexOf('candidate:') === 0) {
|
|
||||||
line = 'a=' + line;
|
|
||||||
} else if (line.substring(0, 12) != 'a=candidate:') {
|
|
||||||
console.log('parseCandidate called with a line that is not a candidate line');
|
|
||||||
console.log(line);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (line.substring(line.length - 2) == '\r\n') // chomp it
|
|
||||||
line = line.substring(0, line.length - 2);
|
|
||||||
var candidate = {},
|
|
||||||
elems = line.split(' '),
|
|
||||||
i;
|
|
||||||
if (elems[6] != 'typ') {
|
|
||||||
console.log('did not find typ in the right place');
|
|
||||||
console.log(line);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
candidate.foundation = elems[0].substring(12);
|
|
||||||
candidate.component = elems[1];
|
|
||||||
candidate.protocol = elems[2].toLowerCase();
|
|
||||||
candidate.priority = elems[3];
|
|
||||||
candidate.ip = elems[4];
|
|
||||||
candidate.port = elems[5];
|
|
||||||
// elems[6] => "typ"
|
|
||||||
candidate.type = elems[7];
|
|
||||||
|
|
||||||
candidate.generation = '0'; // default, may be overwritten below
|
|
||||||
for (i = 8; i < elems.length; i += 2) {
|
|
||||||
switch (elems[i]) {
|
|
||||||
case 'raddr':
|
|
||||||
candidate['rel-addr'] = elems[i + 1];
|
|
||||||
break;
|
|
||||||
case 'rport':
|
|
||||||
candidate['rel-port'] = elems[i + 1];
|
|
||||||
break;
|
|
||||||
case 'generation':
|
|
||||||
candidate.generation = elems[i + 1];
|
|
||||||
break;
|
|
||||||
case 'tcptype':
|
|
||||||
candidate.tcptype = elems[i + 1];
|
|
||||||
break;
|
|
||||||
default: // TODO
|
|
||||||
console.log('not translating "' + elems[i] + '" = "' + elems[i + 1] + '"');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
candidate.network = '1';
|
|
||||||
candidate.id = Math.random().toString(36).substr(2, 10); // not applicable to SDP -- FIXME: should be unique, not just random
|
|
||||||
return candidate;
|
|
||||||
},
|
|
||||||
candidateFromJingle: function (cand) {
|
|
||||||
var line = 'a=candidate:';
|
|
||||||
line += cand.getAttribute('foundation');
|
|
||||||
line += ' ';
|
|
||||||
line += cand.getAttribute('component');
|
|
||||||
line += ' ';
|
|
||||||
line += cand.getAttribute('protocol'); //.toUpperCase(); // chrome M23 doesn't like this
|
|
||||||
line += ' ';
|
|
||||||
line += cand.getAttribute('priority');
|
|
||||||
line += ' ';
|
|
||||||
line += cand.getAttribute('ip');
|
|
||||||
line += ' ';
|
|
||||||
line += cand.getAttribute('port');
|
|
||||||
line += ' ';
|
|
||||||
line += 'typ';
|
|
||||||
line += ' ' + cand.getAttribute('type');
|
|
||||||
line += ' ';
|
|
||||||
switch (cand.getAttribute('type')) {
|
|
||||||
case 'srflx':
|
|
||||||
case 'prflx':
|
|
||||||
case 'relay':
|
|
||||||
if (cand.getAttribute('rel-addr') && cand.getAttribute('rel-port')) {
|
|
||||||
line += 'raddr';
|
|
||||||
line += ' ';
|
|
||||||
line += cand.getAttribute('rel-addr');
|
|
||||||
line += ' ';
|
|
||||||
line += 'rport';
|
|
||||||
line += ' ';
|
|
||||||
line += cand.getAttribute('rel-port');
|
|
||||||
line += ' ';
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (cand.getAttribute('protocol').toLowerCase() == 'tcp') {
|
|
||||||
line += 'tcptype';
|
|
||||||
line += ' ';
|
|
||||||
line += cand.getAttribute('tcptype');
|
|
||||||
line += ' ';
|
|
||||||
}
|
|
||||||
line += 'generation';
|
|
||||||
line += ' ';
|
|
||||||
line += cand.getAttribute('generation') || '0';
|
|
||||||
return line + '\r\n';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
/* jshint -W117 */
|
/* jshint -W117 */
|
||||||
// Jingle stuff
|
// Jingle stuff
|
||||||
JingleSession.prototype = Object.create(SessionBase.prototype);
|
|
||||||
function JingleSession(me, sid, connection) {
|
function JingleSession(me, sid, connection) {
|
||||||
|
|
||||||
SessionBase.call(this, connection, sid);
|
|
||||||
|
|
||||||
this.me = me;
|
this.me = me;
|
||||||
|
this.sid = sid;
|
||||||
|
this.connection = connection;
|
||||||
this.initiator = null;
|
this.initiator = null;
|
||||||
this.responder = null;
|
this.responder = null;
|
||||||
this.isInitiator = null;
|
this.isInitiator = null;
|
||||||
|
@ -37,6 +35,13 @@ function JingleSession(me, sid, connection) {
|
||||||
|
|
||||||
this.wait = true;
|
this.wait = true;
|
||||||
this.localStreamsSSRC = null;
|
this.localStreamsSSRC = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The indicator which determines whether the (local) video has been muted
|
||||||
|
* in response to a user command in contrast to an automatic decision made
|
||||||
|
* by the application logic.
|
||||||
|
*/
|
||||||
|
this.videoMuteByUser = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
JingleSession.prototype.initiate = function (peerjid, isInitiator) {
|
JingleSession.prototype.initiate = function (peerjid, isInitiator) {
|
||||||
|
@ -163,22 +168,6 @@ JingleSession.prototype.accept = function () {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Implements SessionBase.sendSSRCUpdate.
|
|
||||||
*/
|
|
||||||
JingleSession.prototype.sendSSRCUpdate = function(sdpMediaSsrcs, fromJid, isadd) {
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
console.log('tell', self.peerjid, 'about ' + (isadd ? 'new' : 'removed') + ' ssrcs from' + self.me);
|
|
||||||
|
|
||||||
if (!(this.peerconnection.signalingState == 'stable' && this.peerconnection.iceConnectionState == 'connected')){
|
|
||||||
console.log("Too early to send updates");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.sendSSRCUpdateIq(sdpMediaSsrcs, self.sid, self.initiator, self.peerjid, isadd);
|
|
||||||
};
|
|
||||||
|
|
||||||
JingleSession.prototype.terminate = function (reason) {
|
JingleSession.prototype.terminate = function (reason) {
|
||||||
this.state = 'ended';
|
this.state = 'ended';
|
||||||
this.reason = reason;
|
this.reason = reason;
|
||||||
|
@ -675,6 +664,317 @@ JingleSession.prototype.sendTerminate = function (reason, text) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
JingleSession.prototype.addSource = function (elem, fromJid) {
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
// FIXME: dirty waiting
|
||||||
|
if (!this.peerconnection.localDescription)
|
||||||
|
{
|
||||||
|
console.warn("addSource - localDescription not ready yet")
|
||||||
|
setTimeout(function()
|
||||||
|
{
|
||||||
|
self.addSource(elem, fromJid);
|
||||||
|
},
|
||||||
|
200
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.peerconnection.addSource(elem);
|
||||||
|
|
||||||
|
this.modifySources();
|
||||||
|
};
|
||||||
|
|
||||||
|
JingleSession.prototype.removeSource = function (elem, fromJid) {
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
// FIXME: dirty waiting
|
||||||
|
if (!this.peerconnection.localDescription)
|
||||||
|
{
|
||||||
|
console.warn("removeSource - localDescription not ready yet")
|
||||||
|
setTimeout(function()
|
||||||
|
{
|
||||||
|
self.removeSource(elem, fromJid);
|
||||||
|
},
|
||||||
|
200
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.peerconnection.removeSource(elem);
|
||||||
|
|
||||||
|
this.modifySources();
|
||||||
|
};
|
||||||
|
|
||||||
|
JingleSession.prototype.modifySources = function (successCallback) {
|
||||||
|
var self = this;
|
||||||
|
if(this.peerconnection)
|
||||||
|
this.peerconnection.modifySources(function(){
|
||||||
|
$(document).trigger('setLocalDescription.jingle', [self.sid]);
|
||||||
|
if(successCallback) {
|
||||||
|
successCallback();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Switches video streams.
|
||||||
|
* @param new_stream new stream that will be used as video of this session.
|
||||||
|
* @param oldStream old video stream of this session.
|
||||||
|
* @param success_callback callback executed after successful stream switch.
|
||||||
|
*/
|
||||||
|
JingleSession.prototype.switchStreams = function (new_stream, oldStream, success_callback) {
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
// Stop the stream to trigger onended event for old stream
|
||||||
|
oldStream.stop();
|
||||||
|
|
||||||
|
// Remember SDP to figure out added/removed SSRCs
|
||||||
|
var oldSdp = null;
|
||||||
|
if(self.peerconnection) {
|
||||||
|
if(self.peerconnection.localDescription) {
|
||||||
|
oldSdp = new SDP(self.peerconnection.localDescription.sdp);
|
||||||
|
}
|
||||||
|
self.peerconnection.removeStream(oldStream, true);
|
||||||
|
self.peerconnection.addStream(new_stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.connection.jingle.localVideo = new_stream;
|
||||||
|
|
||||||
|
self.connection.jingle.localStreams = [];
|
||||||
|
|
||||||
|
//in firefox we have only one stream object
|
||||||
|
if(self.connection.jingle.localAudio != self.connection.jingle.localVideo)
|
||||||
|
self.connection.jingle.localStreams.push(self.connection.jingle.localAudio);
|
||||||
|
self.connection.jingle.localStreams.push(self.connection.jingle.localVideo);
|
||||||
|
|
||||||
|
// Conference is not active
|
||||||
|
if(!oldSdp || !self.peerconnection) {
|
||||||
|
success_callback();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.peerconnection.switchstreams = true;
|
||||||
|
self.modifySources(function() {
|
||||||
|
console.log('modify sources done');
|
||||||
|
|
||||||
|
success_callback();
|
||||||
|
|
||||||
|
var newSdp = new SDP(self.peerconnection.localDescription.sdp);
|
||||||
|
console.log("SDPs", oldSdp, newSdp);
|
||||||
|
self.notifyMySSRCUpdate(oldSdp, newSdp);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Figures out added/removed ssrcs and send update IQs.
|
||||||
|
* @param old_sdp SDP object for old description.
|
||||||
|
* @param new_sdp SDP object for new description.
|
||||||
|
*/
|
||||||
|
JingleSession.prototype.notifyMySSRCUpdate = function (old_sdp, new_sdp) {
|
||||||
|
|
||||||
|
var old_media = old_sdp.getMediaSsrcMap();
|
||||||
|
var new_media = new_sdp.getMediaSsrcMap();
|
||||||
|
//console.log("old/new medias: ", old_media, new_media);
|
||||||
|
|
||||||
|
var toAdd = old_sdp.getNewMedia(new_sdp);
|
||||||
|
var toRemove = new_sdp.getNewMedia(old_sdp);
|
||||||
|
//console.log("to add", toAdd);
|
||||||
|
//console.log("to remove", toRemove);
|
||||||
|
if(Object.keys(toRemove).length > 0){
|
||||||
|
this.sendSSRCUpdate(toRemove, null, false);
|
||||||
|
}
|
||||||
|
if(Object.keys(toAdd).length > 0){
|
||||||
|
this.sendSSRCUpdate(toAdd, null, true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Empty method that does nothing by default. It should send SSRC update IQs to session participants.
|
||||||
|
* @param sdpMediaSsrcs array of
|
||||||
|
* @param fromJid
|
||||||
|
* @param isAdd
|
||||||
|
*/
|
||||||
|
JingleSession.prototype.sendSSRCUpdate = function(sdpMediaSsrcs, fromJid, isAdd) {
|
||||||
|
var self = this;
|
||||||
|
console.log('tell', self.peerjid, 'about ' + (isadd ? 'new' : 'removed') + ' ssrcs from' + self.me);
|
||||||
|
|
||||||
|
if (!(this.peerconnection.signalingState == 'stable' && this.peerconnection.iceConnectionState == 'connected')){
|
||||||
|
console.log("Too early to send updates");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sendSSRCUpdateIq(sdpMediaSsrcs, self.sid, self.initiator, self.peerjid, isadd);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends SSRC update IQ.
|
||||||
|
* @param sdpMediaSsrcs SSRCs map obtained from SDP.getNewMedia. Cntains SSRCs to add/remove.
|
||||||
|
* @param sid session identifier that will be put into the IQ.
|
||||||
|
* @param initiator initiator identifier.
|
||||||
|
* @param toJid destination Jid
|
||||||
|
* @param isAdd indicates if this is remove or add operation.
|
||||||
|
*/
|
||||||
|
JingleSession.prototype.sendSSRCUpdateIq = function(sdpMediaSsrcs, sid, initiator, toJid, isAdd) {
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
var modify = $iq({to: toJid, type: 'set'})
|
||||||
|
.c('jingle', {
|
||||||
|
xmlns: 'urn:xmpp:jingle:1',
|
||||||
|
action: isAdd ? 'source-add' : 'source-remove',
|
||||||
|
initiator: initiator,
|
||||||
|
sid: sid
|
||||||
|
}
|
||||||
|
);
|
||||||
|
// FIXME: only announce video ssrcs since we mix audio and dont need
|
||||||
|
// the audio ssrcs therefore
|
||||||
|
var modified = false;
|
||||||
|
Object.keys(sdpMediaSsrcs).forEach(function(channelNum){
|
||||||
|
modified = true;
|
||||||
|
var channel = sdpMediaSsrcs[channelNum];
|
||||||
|
modify.c('content', {name: channel.mediaType});
|
||||||
|
|
||||||
|
modify.c('description', {xmlns:'urn:xmpp:jingle:apps:rtp:1', media: channel.mediaType});
|
||||||
|
// FIXME: not completly sure this operates on blocks and / or handles different ssrcs correctly
|
||||||
|
// generate sources from lines
|
||||||
|
Object.keys(channel.ssrcs).forEach(function(ssrcNum) {
|
||||||
|
var mediaSsrc = channel.ssrcs[ssrcNum];
|
||||||
|
modify.c('source', { xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
|
||||||
|
modify.attrs({ssrc: mediaSsrc.ssrc});
|
||||||
|
// iterate over ssrc lines
|
||||||
|
mediaSsrc.lines.forEach(function (line) {
|
||||||
|
var idx = line.indexOf(' ');
|
||||||
|
var kv = line.substr(idx + 1);
|
||||||
|
modify.c('parameter');
|
||||||
|
if (kv.indexOf(':') == -1) {
|
||||||
|
modify.attrs({ name: kv });
|
||||||
|
} else {
|
||||||
|
modify.attrs({ name: kv.split(':', 2)[0] });
|
||||||
|
modify.attrs({ value: kv.split(':', 2)[1] });
|
||||||
|
}
|
||||||
|
modify.up(); // end of parameter
|
||||||
|
});
|
||||||
|
modify.up(); // end of source
|
||||||
|
});
|
||||||
|
|
||||||
|
// generate source groups from lines
|
||||||
|
channel.ssrcGroups.forEach(function(ssrcGroup) {
|
||||||
|
if (ssrcGroup.ssrcs.length != 0) {
|
||||||
|
|
||||||
|
modify.c('ssrc-group', {
|
||||||
|
semantics: ssrcGroup.semantics,
|
||||||
|
xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0'
|
||||||
|
});
|
||||||
|
|
||||||
|
ssrcGroup.ssrcs.forEach(function (ssrc) {
|
||||||
|
modify.c('source', { ssrc: ssrc })
|
||||||
|
.up(); // end of source
|
||||||
|
});
|
||||||
|
modify.up(); // end of ssrc-group
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
modify.up(); // end of description
|
||||||
|
modify.up(); // end of content
|
||||||
|
});
|
||||||
|
if (modified) {
|
||||||
|
self.connection.sendIQ(modify,
|
||||||
|
function (res) {
|
||||||
|
console.info('got modify result', res);
|
||||||
|
},
|
||||||
|
function (err) {
|
||||||
|
console.error('got modify error', err);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.log('modification not necessary');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether the (local) video is mute i.e. all video tracks are
|
||||||
|
* disabled.
|
||||||
|
*
|
||||||
|
* @return <tt>true</tt> if the (local) video is mute i.e. all video tracks are
|
||||||
|
* disabled; otherwise, <tt>false</tt>
|
||||||
|
*/
|
||||||
|
JingleSession.prototype.isVideoMute = function () {
|
||||||
|
var tracks = connection.jingle.localVideo.getVideoTracks();
|
||||||
|
var mute = true;
|
||||||
|
|
||||||
|
for (var i = 0; i < tracks.length; ++i) {
|
||||||
|
if (tracks[i].enabled) {
|
||||||
|
mute = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mute;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mutes/unmutes the (local) video i.e. enables/disables all video tracks.
|
||||||
|
*
|
||||||
|
* @param mute <tt>true</tt> to mute the (local) video i.e. to disable all video
|
||||||
|
* tracks; otherwise, <tt>false</tt>
|
||||||
|
* @param callback a function to be invoked with <tt>mute</tt> after all video
|
||||||
|
* tracks have been enabled/disabled. The function may, optionally, return
|
||||||
|
* another function which is to be invoked after the whole mute/unmute operation
|
||||||
|
* has completed successfully.
|
||||||
|
* @param options an object which specifies optional arguments such as the
|
||||||
|
* <tt>boolean</tt> key <tt>byUser</tt> with default value <tt>true</tt> which
|
||||||
|
* specifies whether the method was initiated in response to a user command (in
|
||||||
|
* contrast to an automatic decision made by the application logic)
|
||||||
|
*/
|
||||||
|
JingleSession.prototype.setVideoMute = function (mute, callback, options) {
|
||||||
|
var byUser;
|
||||||
|
|
||||||
|
if (options) {
|
||||||
|
byUser = options.byUser;
|
||||||
|
if (typeof byUser === 'undefined') {
|
||||||
|
byUser = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
byUser = true;
|
||||||
|
}
|
||||||
|
// The user's command to mute the (local) video takes precedence over any
|
||||||
|
// automatic decision made by the application logic.
|
||||||
|
if (byUser) {
|
||||||
|
this.videoMuteByUser = mute;
|
||||||
|
} else if (this.videoMuteByUser) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (mute == this.isVideoMute())
|
||||||
|
{
|
||||||
|
// Even if no change occurs, the specified callback is to be executed.
|
||||||
|
// The specified callback may, optionally, return a successCallback
|
||||||
|
// which is to be executed as well.
|
||||||
|
var successCallback = callback(mute);
|
||||||
|
|
||||||
|
if (successCallback) {
|
||||||
|
successCallback();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var tracks = connection.jingle.localVideo.getVideoTracks();
|
||||||
|
|
||||||
|
for (var i = 0; i < tracks.length; ++i) {
|
||||||
|
tracks[i].enabled = !mute;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.peerconnection) {
|
||||||
|
this.peerconnection.hardMuteVideo(mute);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.modifySources(callback(mute));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// SDP-based mute by going recvonly/sendrecv
|
||||||
|
// FIXME: should probably black out the screen as well
|
||||||
|
JingleSession.prototype.toggleVideoMute = function (callback) {
|
||||||
|
setVideoMute(isVideoMute(), callback);
|
||||||
|
};
|
||||||
|
|
||||||
JingleSession.prototype.sendMute = function (muted, content) {
|
JingleSession.prototype.sendMute = function (muted, content) {
|
||||||
var info = $iq({to: this.peerjid,
|
var info = $iq({to: this.peerjid,
|
||||||
type: 'set'})
|
type: 'set'})
|
||||||
|
|
|
@ -1,321 +0,0 @@
|
||||||
/**
|
|
||||||
* Base class for ColibriFocus and JingleSession.
|
|
||||||
* @param connection Strophe connection object
|
|
||||||
* @param sid my session identifier(resource)
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
function SessionBase(connection, sid) {
|
|
||||||
this.connection = connection;
|
|
||||||
this.sid = sid;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The indicator which determines whether the (local) video has been muted
|
|
||||||
* in response to a user command in contrast to an automatic decision made
|
|
||||||
* by the application logic.
|
|
||||||
*/
|
|
||||||
this.videoMuteByUser = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
SessionBase.prototype.modifySources = function (successCallback) {
|
|
||||||
var self = this;
|
|
||||||
if(this.peerconnection)
|
|
||||||
this.peerconnection.modifySources(function(){
|
|
||||||
$(document).trigger('setLocalDescription.jingle', [self.sid]);
|
|
||||||
if(successCallback) {
|
|
||||||
successCallback();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
SessionBase.prototype.addSource = function (elem, fromJid) {
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
// FIXME: dirty waiting
|
|
||||||
if (!this.peerconnection.localDescription)
|
|
||||||
{
|
|
||||||
console.warn("addSource - localDescription not ready yet")
|
|
||||||
setTimeout(function()
|
|
||||||
{
|
|
||||||
self.addSource(elem, fromJid);
|
|
||||||
},
|
|
||||||
200
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.peerconnection.addSource(elem);
|
|
||||||
|
|
||||||
this.modifySources();
|
|
||||||
};
|
|
||||||
|
|
||||||
SessionBase.prototype.removeSource = function (elem, fromJid) {
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
// FIXME: dirty waiting
|
|
||||||
if (!this.peerconnection.localDescription)
|
|
||||||
{
|
|
||||||
console.warn("removeSource - localDescription not ready yet")
|
|
||||||
setTimeout(function()
|
|
||||||
{
|
|
||||||
self.removeSource(elem, fromJid);
|
|
||||||
},
|
|
||||||
200
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.peerconnection.removeSource(elem);
|
|
||||||
|
|
||||||
this.modifySources();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Switches video streams.
|
|
||||||
* @param new_stream new stream that will be used as video of this session.
|
|
||||||
* @param oldStream old video stream of this session.
|
|
||||||
* @param success_callback callback executed after successful stream switch.
|
|
||||||
*/
|
|
||||||
SessionBase.prototype.switchStreams = function (new_stream, oldStream, success_callback) {
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
// Stop the stream to trigger onended event for old stream
|
|
||||||
oldStream.stop();
|
|
||||||
|
|
||||||
// Remember SDP to figure out added/removed SSRCs
|
|
||||||
var oldSdp = null;
|
|
||||||
if(self.peerconnection) {
|
|
||||||
if(self.peerconnection.localDescription) {
|
|
||||||
oldSdp = new SDP(self.peerconnection.localDescription.sdp);
|
|
||||||
}
|
|
||||||
self.peerconnection.removeStream(oldStream, true);
|
|
||||||
self.peerconnection.addStream(new_stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.connection.jingle.localVideo = new_stream;
|
|
||||||
|
|
||||||
self.connection.jingle.localStreams = [];
|
|
||||||
|
|
||||||
//in firefox we have only one stream object
|
|
||||||
if(self.connection.jingle.localAudio != self.connection.jingle.localVideo)
|
|
||||||
self.connection.jingle.localStreams.push(self.connection.jingle.localAudio);
|
|
||||||
self.connection.jingle.localStreams.push(self.connection.jingle.localVideo);
|
|
||||||
|
|
||||||
// Conference is not active
|
|
||||||
if(!oldSdp || !self.peerconnection) {
|
|
||||||
success_callback();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.peerconnection.switchstreams = true;
|
|
||||||
self.modifySources(function() {
|
|
||||||
console.log('modify sources done');
|
|
||||||
|
|
||||||
success_callback();
|
|
||||||
|
|
||||||
var newSdp = new SDP(self.peerconnection.localDescription.sdp);
|
|
||||||
console.log("SDPs", oldSdp, newSdp);
|
|
||||||
self.notifyMySSRCUpdate(oldSdp, newSdp);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Figures out added/removed ssrcs and send update IQs.
|
|
||||||
* @param old_sdp SDP object for old description.
|
|
||||||
* @param new_sdp SDP object for new description.
|
|
||||||
*/
|
|
||||||
SessionBase.prototype.notifyMySSRCUpdate = function (old_sdp, new_sdp) {
|
|
||||||
|
|
||||||
var old_media = old_sdp.getMediaSsrcMap();
|
|
||||||
var new_media = new_sdp.getMediaSsrcMap();
|
|
||||||
//console.log("old/new medias: ", old_media, new_media);
|
|
||||||
|
|
||||||
var toAdd = old_sdp.getNewMedia(new_sdp);
|
|
||||||
var toRemove = new_sdp.getNewMedia(old_sdp);
|
|
||||||
//console.log("to add", toAdd);
|
|
||||||
//console.log("to remove", toRemove);
|
|
||||||
if(Object.keys(toRemove).length > 0){
|
|
||||||
this.sendSSRCUpdate(toRemove, null, false);
|
|
||||||
}
|
|
||||||
if(Object.keys(toAdd).length > 0){
|
|
||||||
this.sendSSRCUpdate(toAdd, null, true);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Empty method that does nothing by default. It should send SSRC update IQs to session participants.
|
|
||||||
* @param sdpMediaSsrcs array of
|
|
||||||
* @param fromJid
|
|
||||||
* @param isAdd
|
|
||||||
*/
|
|
||||||
SessionBase.prototype.sendSSRCUpdate = function(sdpMediaSsrcs, fromJid, isAdd) {
|
|
||||||
//FIXME: put default implementation here(maybe from JingleSession?)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends SSRC update IQ.
|
|
||||||
* @param sdpMediaSsrcs SSRCs map obtained from SDP.getNewMedia. Cntains SSRCs to add/remove.
|
|
||||||
* @param sid session identifier that will be put into the IQ.
|
|
||||||
* @param initiator initiator identifier.
|
|
||||||
* @param toJid destination Jid
|
|
||||||
* @param isAdd indicates if this is remove or add operation.
|
|
||||||
*/
|
|
||||||
SessionBase.prototype.sendSSRCUpdateIq = function(sdpMediaSsrcs, sid, initiator, toJid, isAdd) {
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
var modify = $iq({to: toJid, type: 'set'})
|
|
||||||
.c('jingle', {
|
|
||||||
xmlns: 'urn:xmpp:jingle:1',
|
|
||||||
action: isAdd ? 'source-add' : 'source-remove',
|
|
||||||
initiator: initiator,
|
|
||||||
sid: sid
|
|
||||||
}
|
|
||||||
);
|
|
||||||
// FIXME: only announce video ssrcs since we mix audio and dont need
|
|
||||||
// the audio ssrcs therefore
|
|
||||||
var modified = false;
|
|
||||||
Object.keys(sdpMediaSsrcs).forEach(function(channelNum){
|
|
||||||
modified = true;
|
|
||||||
var channel = sdpMediaSsrcs[channelNum];
|
|
||||||
modify.c('content', {name: channel.mediaType});
|
|
||||||
|
|
||||||
modify.c('description', {xmlns:'urn:xmpp:jingle:apps:rtp:1', media: channel.mediaType});
|
|
||||||
// FIXME: not completly sure this operates on blocks and / or handles different ssrcs correctly
|
|
||||||
// generate sources from lines
|
|
||||||
Object.keys(channel.ssrcs).forEach(function(ssrcNum) {
|
|
||||||
var mediaSsrc = channel.ssrcs[ssrcNum];
|
|
||||||
modify.c('source', { xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
|
|
||||||
modify.attrs({ssrc: mediaSsrc.ssrc});
|
|
||||||
// iterate over ssrc lines
|
|
||||||
mediaSsrc.lines.forEach(function (line) {
|
|
||||||
var idx = line.indexOf(' ');
|
|
||||||
var kv = line.substr(idx + 1);
|
|
||||||
modify.c('parameter');
|
|
||||||
if (kv.indexOf(':') == -1) {
|
|
||||||
modify.attrs({ name: kv });
|
|
||||||
} else {
|
|
||||||
modify.attrs({ name: kv.split(':', 2)[0] });
|
|
||||||
modify.attrs({ value: kv.split(':', 2)[1] });
|
|
||||||
}
|
|
||||||
modify.up(); // end of parameter
|
|
||||||
});
|
|
||||||
modify.up(); // end of source
|
|
||||||
});
|
|
||||||
|
|
||||||
// generate source groups from lines
|
|
||||||
channel.ssrcGroups.forEach(function(ssrcGroup) {
|
|
||||||
if (ssrcGroup.ssrcs.length != 0) {
|
|
||||||
|
|
||||||
modify.c('ssrc-group', {
|
|
||||||
semantics: ssrcGroup.semantics,
|
|
||||||
xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0'
|
|
||||||
});
|
|
||||||
|
|
||||||
ssrcGroup.ssrcs.forEach(function (ssrc) {
|
|
||||||
modify.c('source', { ssrc: ssrc })
|
|
||||||
.up(); // end of source
|
|
||||||
});
|
|
||||||
modify.up(); // end of ssrc-group
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
modify.up(); // end of description
|
|
||||||
modify.up(); // end of content
|
|
||||||
});
|
|
||||||
if (modified) {
|
|
||||||
self.connection.sendIQ(modify,
|
|
||||||
function (res) {
|
|
||||||
console.info('got modify result', res);
|
|
||||||
},
|
|
||||||
function (err) {
|
|
||||||
console.error('got modify error', err);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
console.log('modification not necessary');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines whether the (local) video is mute i.e. all video tracks are
|
|
||||||
* disabled.
|
|
||||||
*
|
|
||||||
* @return <tt>true</tt> if the (local) video is mute i.e. all video tracks are
|
|
||||||
* disabled; otherwise, <tt>false</tt>
|
|
||||||
*/
|
|
||||||
SessionBase.prototype.isVideoMute = function () {
|
|
||||||
var tracks = connection.jingle.localVideo.getVideoTracks();
|
|
||||||
var mute = true;
|
|
||||||
|
|
||||||
for (var i = 0; i < tracks.length; ++i) {
|
|
||||||
if (tracks[i].enabled) {
|
|
||||||
mute = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return mute;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mutes/unmutes the (local) video i.e. enables/disables all video tracks.
|
|
||||||
*
|
|
||||||
* @param mute <tt>true</tt> to mute the (local) video i.e. to disable all video
|
|
||||||
* tracks; otherwise, <tt>false</tt>
|
|
||||||
* @param callback a function to be invoked with <tt>mute</tt> after all video
|
|
||||||
* tracks have been enabled/disabled. The function may, optionally, return
|
|
||||||
* another function which is to be invoked after the whole mute/unmute operation
|
|
||||||
* has completed successfully.
|
|
||||||
* @param options an object which specifies optional arguments such as the
|
|
||||||
* <tt>boolean</tt> key <tt>byUser</tt> with default value <tt>true</tt> which
|
|
||||||
* specifies whether the method was initiated in response to a user command (in
|
|
||||||
* contrast to an automatic decision made by the application logic)
|
|
||||||
*/
|
|
||||||
SessionBase.prototype.setVideoMute = function (mute, callback, options) {
|
|
||||||
var byUser;
|
|
||||||
|
|
||||||
if (options) {
|
|
||||||
byUser = options.byUser;
|
|
||||||
if (typeof byUser === 'undefined') {
|
|
||||||
byUser = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
byUser = true;
|
|
||||||
}
|
|
||||||
// The user's command to mute the (local) video takes precedence over any
|
|
||||||
// automatic decision made by the application logic.
|
|
||||||
if (byUser) {
|
|
||||||
this.videoMuteByUser = mute;
|
|
||||||
} else if (this.videoMuteByUser) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (mute == this.isVideoMute())
|
|
||||||
{
|
|
||||||
// Even if no change occurs, the specified callback is to be executed.
|
|
||||||
// The specified callback may, optionally, return a successCallback
|
|
||||||
// which is to be executed as well.
|
|
||||||
var successCallback = callback(mute);
|
|
||||||
|
|
||||||
if (successCallback) {
|
|
||||||
successCallback();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
var tracks = connection.jingle.localVideo.getVideoTracks();
|
|
||||||
|
|
||||||
for (var i = 0; i < tracks.length; ++i) {
|
|
||||||
tracks[i].enabled = !mute;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.peerconnection) {
|
|
||||||
this.peerconnection.hardMuteVideo(mute);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.modifySources(callback(mute));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// SDP-based mute by going recvonly/sendrecv
|
|
||||||
// FIXME: should probably black out the screen as well
|
|
||||||
SessionBase.prototype.toggleVideoMute = function (callback) {
|
|
||||||
setVideoMute(isVideoMute(), callback);
|
|
||||||
};
|
|
Loading…
Reference in New Issue