mirror of https://github.com/jitsi/jitsi-meet
Conflicts: app.js index.html libs/colibri/colibri.focus.js libs/modules/statistics.bundle.js moderator.js modules/UI/videolayout/VideoLayout.js muc.jspull/220/head
commit
6ce48a5b7b
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); |
||||
}; |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -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,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