Makes it possible to switch video streams during the session. Adds desktop sharing feature for chrome.
This commit is contained in:
parent
a5951df0d9
commit
3e34df8730
14
app.js
14
app.js
|
@ -166,6 +166,7 @@ $(document).bind('remotestreamadded.jingle', function (event, data, sid) {
|
||||||
var sess = connection.jingle.sessions[sid];
|
var sess = connection.jingle.sessions[sid];
|
||||||
if (data.stream.id === 'mixedmslabel') return;
|
if (data.stream.id === 'mixedmslabel') return;
|
||||||
videoTracks = data.stream.getVideoTracks();
|
videoTracks = data.stream.getVideoTracks();
|
||||||
|
console.log("waiting..", videoTracks, selector[0]);
|
||||||
if (videoTracks.length === 0 || selector[0].currentTime > 0) {
|
if (videoTracks.length === 0 || selector[0].currentTime > 0) {
|
||||||
RTC.attachMediaStream(selector, data.stream); // FIXME: why do i have to do this for FF?
|
RTC.attachMediaStream(selector, data.stream); // FIXME: why do i have to do this for FF?
|
||||||
$(document).trigger('callactive.jingle', [selector, sid]);
|
$(document).trigger('callactive.jingle', [selector, sid]);
|
||||||
|
@ -437,6 +438,8 @@ $(document).bind('setLocalDescription.jingle', function (event, sid) {
|
||||||
});
|
});
|
||||||
console.log('new ssrcs', newssrcs);
|
console.log('new ssrcs', newssrcs);
|
||||||
|
|
||||||
|
// Have to clear presence map to get rid of removed streams
|
||||||
|
connection.emuc.clearPresenceMedia();
|
||||||
var i = 0;
|
var i = 0;
|
||||||
Object.keys(newssrcs).forEach(function (mtype) {
|
Object.keys(newssrcs).forEach(function (mtype) {
|
||||||
i++;
|
i++;
|
||||||
|
@ -542,6 +545,14 @@ $(document).bind('left.muc', function (event, jid) {
|
||||||
});
|
});
|
||||||
|
|
||||||
$(document).bind('presence.muc', function (event, jid, info, pres) {
|
$(document).bind('presence.muc', function (event, jid, info, pres) {
|
||||||
|
|
||||||
|
// Remove old ssrcs coming from the jid
|
||||||
|
Object.keys(ssrc2jid).forEach(function(ssrc){
|
||||||
|
if(ssrc2jid[ssrc] == jid){
|
||||||
|
delete ssrc2jid[ssrc];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
$(pres).find('>media[xmlns="http://estos.de/ns/mjs"]>source').each(function (idx, ssrc) {
|
$(pres).find('>media[xmlns="http://estos.de/ns/mjs"]>source').each(function (idx, ssrc) {
|
||||||
//console.log(jid, 'assoc ssrc', ssrc.getAttribute('type'), ssrc.getAttribute('ssrc'));
|
//console.log(jid, 'assoc ssrc', ssrc.getAttribute('type'), ssrc.getAttribute('ssrc'));
|
||||||
ssrc2jid[ssrc.getAttribute('ssrc')] = jid;
|
ssrc2jid[ssrc.getAttribute('ssrc')] = jid;
|
||||||
|
@ -1191,6 +1202,9 @@ function showToolbar() {
|
||||||
// TODO: Enable settings functionality. Need to uncomment the settings button in index.html.
|
// TODO: Enable settings functionality. Need to uncomment the settings button in index.html.
|
||||||
// $('#settingsButton').css({visibility:"visible"});
|
// $('#settingsButton').css({visibility:"visible"});
|
||||||
}
|
}
|
||||||
|
if(isDesktopSharingEnabled()) {
|
||||||
|
$('#desktopsharing').css({display:"inline"});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -8,5 +8,6 @@ var config = {
|
||||||
// useStunTurn: true, // use XEP-0215 to fetch STUN and TURN server
|
// useStunTurn: true, // use XEP-0215 to fetch STUN and TURN server
|
||||||
// useIPv6: true, // ipv6 support. use at your own risk
|
// useIPv6: true, // ipv6 support. use at your own risk
|
||||||
useNicks: false,
|
useNicks: false,
|
||||||
bosh: '//lambada.jitsi.net/http-bind' // FIXME: use xep-0156 for that
|
bosh: '//lambada.jitsi.net/http-bind', // FIXME: use xep-0156 for that
|
||||||
|
chromeDesktopSharing: false // Desktop sharing is disabled by default
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
/**
|
||||||
|
* Indicates that desktop stream is currently in use(for toggle purpose).
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
var isUsingScreenStream = false;
|
||||||
|
/**
|
||||||
|
* Indicates that switch stream operation is in progress and prevent from triggering new events.
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
var switchInProgress = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {boolean} <tt>true</tt> if desktop sharing feature is available and enabled.
|
||||||
|
*/
|
||||||
|
function isDesktopSharingEnabled() {
|
||||||
|
// Desktop sharing must be enabled in config and works on chrome only.
|
||||||
|
// Flag 'chrome://flags/#enable-usermedia-screen-capture' must be enabled.
|
||||||
|
return config.chromeDesktopSharing && RTC.browser == 'chrome';
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Toggles screen sharing.
|
||||||
|
*/
|
||||||
|
function toggleScreenSharing() {
|
||||||
|
if (!(connection && connection.connected
|
||||||
|
&& !switchInProgress
|
||||||
|
&& getConferenceHandler().peerconnection.signalingState == 'stable'
|
||||||
|
&& getConferenceHandler().peerconnection.iceConnectionState == 'connected')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
switchInProgress = true;
|
||||||
|
|
||||||
|
// Only the focus is able to set a shared key.
|
||||||
|
if(!isUsingScreenStream)
|
||||||
|
{
|
||||||
|
// Enable screen stream
|
||||||
|
getUserMediaWithConstraints(
|
||||||
|
['screen'],
|
||||||
|
function(stream){
|
||||||
|
isUsingScreenStream = true;
|
||||||
|
gotScreenStream(stream);
|
||||||
|
},
|
||||||
|
getSwitchStreamFailed
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Disable screen stream
|
||||||
|
getUserMediaWithConstraints(
|
||||||
|
['video'],
|
||||||
|
function(stream) {
|
||||||
|
isUsingScreenStream = false;
|
||||||
|
gotScreenStream(stream);
|
||||||
|
},
|
||||||
|
getSwitchStreamFailed, config.resolution || '360'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSwitchStreamFailed(error) {
|
||||||
|
console.error("Failed to obtain the stream to switch to", error);
|
||||||
|
switchInProgress = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function gotScreenStream(stream) {
|
||||||
|
var oldStream = connection.jingle.localVideo;
|
||||||
|
|
||||||
|
change_local_video(stream);
|
||||||
|
|
||||||
|
// FIXME: will block switchInProgress on true value in case of exception
|
||||||
|
getConferenceHandler().switchStreams(stream, oldStream, onDesktopStreamEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDesktopStreamEnabled() {
|
||||||
|
// Wait a moment before enabling the button
|
||||||
|
window.setTimeout(function() {
|
||||||
|
switchInProgress = false;
|
||||||
|
}, 3000);
|
||||||
|
}
|
|
@ -6,6 +6,7 @@
|
||||||
<script src="libs/strophe/strophe.jingle.bundle.js?v=8"></script>
|
<script src="libs/strophe/strophe.jingle.bundle.js?v=8"></script>
|
||||||
<script src="libs/strophe/strophe.jingle.js?v=1"></script>
|
<script src="libs/strophe/strophe.jingle.js?v=1"></script>
|
||||||
<script src="libs/strophe/strophe.jingle.sdp.js?v=1"></script>
|
<script src="libs/strophe/strophe.jingle.sdp.js?v=1"></script>
|
||||||
|
<script src="libs/strophe/strophe.jingle.sdp.util.js?v=1"></script>
|
||||||
<script src="libs/strophe/strophe.jingle.sessionbase.js?v=1"></script>
|
<script src="libs/strophe/strophe.jingle.sessionbase.js?v=1"></script>
|
||||||
<script src="libs/strophe/strophe.jingle.session.js?v=1"></script>
|
<script src="libs/strophe/strophe.jingle.session.js?v=1"></script>
|
||||||
<script src="libs/colibri/colibri.focus.js?v=8"></script><!-- colibri focus implementation -->
|
<script src="libs/colibri/colibri.focus.js?v=8"></script><!-- colibri focus implementation -->
|
||||||
|
@ -15,6 +16,7 @@
|
||||||
<script src="estos_log.js?v=2"></script><!-- simple stanza logger -->
|
<script src="estos_log.js?v=2"></script><!-- simple stanza logger -->
|
||||||
<script src="app.js?v=23"></script><!-- application logic -->
|
<script src="app.js?v=23"></script><!-- application logic -->
|
||||||
<script src="chat.js?v=3"></script><!-- chat logic -->
|
<script src="chat.js?v=3"></script><!-- chat logic -->
|
||||||
|
<script src="desktopsharing.js?v=1"></script><!-- desktop sharing logic -->
|
||||||
<script src="util.js?v=2"></script><!-- utility functions -->
|
<script src="util.js?v=2"></script><!-- utility functions -->
|
||||||
<script src="etherpad.js?v=5"></script><!-- etherpad plugin -->
|
<script src="etherpad.js?v=5"></script><!-- etherpad plugin -->
|
||||||
<script src="smileys.js?v=1"></script><!-- smiley images -->
|
<script src="smileys.js?v=1"></script><!-- smiley images -->
|
||||||
|
@ -54,6 +56,10 @@
|
||||||
<a class="button" onclick='Etherpad.toggleEtherpad(0);'><i title="Open shared document" class="fa fa-file-text fa-lg"></i></a>
|
<a class="button" onclick='Etherpad.toggleEtherpad(0);'><i title="Open shared document" class="fa fa-file-text fa-lg"></i></a>
|
||||||
</span>
|
</span>
|
||||||
<div class="header_button_separator"></div>
|
<div class="header_button_separator"></div>
|
||||||
|
<span id="desktopsharing" style="display: none">
|
||||||
|
<a class="button" onclick="toggleScreenSharing();"><i title="Share screen" class="fa fa-desktop fa-lg"></i></a>
|
||||||
|
<div class="header_button_separator"></div>
|
||||||
|
</span>
|
||||||
<a class="button" onclick='toggleFullScreen();'><i title="Enter / Exit Full Screen" class="fa fa-arrows-alt fa-lg"></i></a>
|
<a class="button" onclick='toggleFullScreen();'><i title="Enter / Exit Full Screen" class="fa fa-arrows-alt fa-lg"></i></a>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -38,7 +38,7 @@
|
||||||
ColibriFocus.prototype = Object.create(SessionBase.prototype);
|
ColibriFocus.prototype = Object.create(SessionBase.prototype);
|
||||||
function ColibriFocus(connection, bridgejid) {
|
function ColibriFocus(connection, bridgejid) {
|
||||||
|
|
||||||
SessionBase.call(this, connection);
|
SessionBase.call(this, connection, Math.random().toString(36).substr(2, 12));
|
||||||
|
|
||||||
this.bridgejid = bridgejid;
|
this.bridgejid = bridgejid;
|
||||||
this.peers = [];
|
this.peers = [];
|
||||||
|
@ -47,7 +47,6 @@ function ColibriFocus(connection, bridgejid) {
|
||||||
// media types of the conference
|
// media types of the conference
|
||||||
this.media = ['audio', 'video'];
|
this.media = ['audio', 'video'];
|
||||||
|
|
||||||
this.sid = Math.random().toString(36).substr(2, 12);
|
|
||||||
this.connection.jingle.sessions[this.sid] = this;
|
this.connection.jingle.sessions[this.sid] = this;
|
||||||
this.mychannel = [];
|
this.mychannel = [];
|
||||||
this.channels = [];
|
this.channels = [];
|
||||||
|
@ -556,67 +555,86 @@ ColibriFocus.prototype.updateChannel = function (remoteSDP, participant) {
|
||||||
|
|
||||||
// tell everyone about a new participants a=ssrc lines (isadd is true)
|
// tell everyone about a new participants a=ssrc lines (isadd is true)
|
||||||
// or a leaving participants a=ssrc lines
|
// or a leaving participants a=ssrc lines
|
||||||
// FIXME: should not take an SDP, but rather the a=ssrc lines and probably a=mid
|
ColibriFocus.prototype.sendSSRCUpdate = function (sdpMediaSsrcs, fromJid, isadd) {
|
||||||
ColibriFocus.prototype.sendSSRCUpdate = function (sdp, jid, isadd) {
|
|
||||||
var self = this;
|
var self = this;
|
||||||
this.peers.forEach(function (peerjid) {
|
this.peers.forEach(function (peerjid) {
|
||||||
if (peerjid == jid) return;
|
if (peerjid == fromJid) return;
|
||||||
console.log('tell', peerjid, 'about ' + (isadd ? 'new' : 'removed') + ' ssrcs from', jid);
|
console.log('tell', peerjid, 'about ' + (isadd ? 'new' : 'removed') + ' ssrcs from', fromJid);
|
||||||
if (!self.remotessrc[peerjid]) {
|
if (!self.remotessrc[peerjid]) {
|
||||||
// FIXME: this should only send to participants that are stable, i.e. who have sent a session-accept
|
// FIXME: this should only send to participants that are stable, i.e. who have sent a session-accept
|
||||||
// possibly, this.remoteSSRC[session.peerjid] does not exist yet
|
// possibly, this.remoteSSRC[session.peerjid] does not exist yet
|
||||||
console.warn('do we really want to bother', peerjid, 'with updates yet?');
|
console.warn('do we really want to bother', peerjid, 'with updates yet?');
|
||||||
}
|
}
|
||||||
var channel;
|
|
||||||
var peersess = self.connection.jingle.jid2session[peerjid];
|
var peersess = self.connection.jingle.jid2session[peerjid];
|
||||||
var modify = $iq({to: peerjid, type: 'set'})
|
if(!peersess){
|
||||||
.c('jingle', {
|
console.warn('no session with peer: '+peerjid+' yet...');
|
||||||
xmlns: 'urn:xmpp:jingle:1',
|
return;
|
||||||
action: isadd ? 'addsource' : 'removesource',
|
}
|
||||||
initiator: peersess.initiator,
|
|
||||||
sid: peersess.sid
|
|
||||||
}
|
|
||||||
);
|
|
||||||
// FIXME: only announce video ssrcs since we mix audio and dont need
|
|
||||||
// the audio ssrcs therefore
|
|
||||||
var modified = false;
|
|
||||||
for (channel = 0; channel < sdp.media.length; channel++) {
|
|
||||||
modified = true;
|
|
||||||
tmp = SDPUtil.find_lines(sdp.media[channel], 'a=ssrc:');
|
|
||||||
modify.c('content', {name: SDPUtil.parse_mid(SDPUtil.find_line(sdp.media[channel], 'a=mid:'))});
|
|
||||||
modify.c('source', { xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
|
|
||||||
// FIXME: not completly sure this operates on blocks and / or handles different ssrcs correctly
|
|
||||||
tmp.forEach(function (line) {
|
|
||||||
var idx = line.indexOf(' ');
|
|
||||||
var linessrc = line.substr(0, idx).substr(7);
|
|
||||||
modify.attrs({ssrc: linessrc});
|
|
||||||
|
|
||||||
var kv = line.substr(idx + 1);
|
self.sendSSRCUpdateIq(sdpMediaSsrcs, peersess.sid, peersess.initiator, peerjid, isadd);
|
||||||
modify.c('parameter');
|
});
|
||||||
if (kv.indexOf(':') == -1) {
|
};
|
||||||
modify.attrs({ name: kv });
|
|
||||||
} else {
|
/**
|
||||||
modify.attrs({ name: kv.split(':', 2)[0] });
|
* Overrides SessionBase.addSource.
|
||||||
modify.attrs({ value: kv.split(':', 2)[1] });
|
*
|
||||||
}
|
* @param elem proprietary 'add source' Jingle request(XML node).
|
||||||
modify.up();
|
* @param fromJid JID of the participant to whom new ssrcs belong.
|
||||||
});
|
*/
|
||||||
modify.up(); // end of source
|
ColibriFocus.prototype.addSource = function (elem, fromJid) {
|
||||||
modify.up(); // end of content
|
|
||||||
}
|
var self = this;
|
||||||
if (modified) {
|
this.peerconnection.addSource(elem);
|
||||||
self.connection.sendIQ(modify,
|
|
||||||
function (res) {
|
var peerSsrc = this.remotessrc[fromJid];
|
||||||
console.warn('got modify result');
|
//console.log("On ADD", self.addssrc, peerSsrc);
|
||||||
},
|
this.peerconnection.addssrc.forEach(function(val, idx){
|
||||||
function (err) {
|
if(!peerSsrc[idx]){
|
||||||
console.warn('got modify error', err);
|
// add ssrc
|
||||||
}
|
peerSsrc[idx] = val;
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
console.log('modification not necessary');
|
if(peerSsrc[idx].indexOf(val) == -1){
|
||||||
|
peerSsrc[idx] = peerSsrc[idx]+val;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var oldRemoteSdp = new SDP(this.peerconnection.remoteDescription.sdp);
|
||||||
|
this.modifySources(function(){
|
||||||
|
// Notify other participants about added ssrc
|
||||||
|
var remoteSDP = new SDP(self.peerconnection.remoteDescription.sdp);
|
||||||
|
var newSSRCs = oldRemoteSdp.getNewMedia(remoteSDP);
|
||||||
|
self.sendSSRCUpdate(newSSRCs, fromJid, true);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overrides SessionBase.removeSource.
|
||||||
|
*
|
||||||
|
* @param elem proprietary 'remove source' Jingle request(XML node).
|
||||||
|
* @param fromJid JID of the participant to whom removed ssrcs belong.
|
||||||
|
*/
|
||||||
|
ColibriFocus.prototype.removeSource = function (elem, fromJid) {
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
this.peerconnection.removeSource(elem);
|
||||||
|
|
||||||
|
var peerSsrc = this.remotessrc[fromJid];
|
||||||
|
//console.log("On REMOVE", self.removessrc, peerSsrc);
|
||||||
|
this.peerconnection.removessrc.forEach(function(val, idx){
|
||||||
|
if(peerSsrc[idx]){
|
||||||
|
// Remove ssrc
|
||||||
|
peerSsrc[idx] = peerSsrc[idx].replace(val, '');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var oldSDP = new SDP(self.peerconnection.remoteDescription.sdp);
|
||||||
|
this.modifySources(function(){
|
||||||
|
// Notify other participants about removed ssrc
|
||||||
|
var remoteSDP = new SDP(self.peerconnection.remoteDescription.sdp);
|
||||||
|
var removedSSRCs = remoteSDP.getNewMedia(oldSDP);
|
||||||
|
self.sendSSRCUpdate(removedSSRCs, fromJid, false);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
ColibriFocus.prototype.setRemoteDescription = function (session, elem, desctype) {
|
ColibriFocus.prototype.setRemoteDescription = function (session, elem, desctype) {
|
||||||
|
@ -630,7 +648,7 @@ ColibriFocus.prototype.setRemoteDescription = function (session, elem, desctype)
|
||||||
this.updateChannel(remoteSDP, participant);
|
this.updateChannel(remoteSDP, participant);
|
||||||
|
|
||||||
// ACT 2: tell anyone else about the new SSRCs
|
// ACT 2: tell anyone else about the new SSRCs
|
||||||
this.sendSSRCUpdate(remoteSDP, session.peerjid, true);
|
this.sendSSRCUpdate(remoteSDP.getMediaSsrcMap(), session.peerjid, true);
|
||||||
|
|
||||||
// ACT 3: note the SSRCs
|
// ACT 3: note the SSRCs
|
||||||
this.remotessrc[session.peerjid] = [];
|
this.remotessrc[session.peerjid] = [];
|
||||||
|
@ -792,7 +810,7 @@ ColibriFocus.prototype.terminate = function (session, reason) {
|
||||||
sdp.media[j] += ssrcs[j];
|
sdp.media[j] += ssrcs[j];
|
||||||
this.peerconnection.enqueueRemoveSsrc(j, ssrcs[j]);
|
this.peerconnection.enqueueRemoveSsrc(j, ssrcs[j]);
|
||||||
}
|
}
|
||||||
this.sendSSRCUpdate(sdp, session.peerjid, false);
|
this.sendSSRCUpdate(sdp.getMediaSsrcMap(), session.peerjid, false);
|
||||||
|
|
||||||
delete this.remotessrc[session.peerjid];
|
delete this.remotessrc[session.peerjid];
|
||||||
this.modifySources();
|
this.modifySources();
|
||||||
|
|
|
@ -61,6 +61,14 @@ ColibriSession.prototype.accept = function () {
|
||||||
console.log('ColibriSession.accept');
|
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) {
|
ColibriSession.prototype.terminate = function (reason) {
|
||||||
this.colibri.terminate(this, reason);
|
this.colibri.terminate(this, reason);
|
||||||
};
|
};
|
||||||
|
|
|
@ -25,6 +25,12 @@ function TraceablePeerConnection(ice_config, constraints) {
|
||||||
*/
|
*/
|
||||||
this.pendingop = null;
|
this.pendingop = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag indicates that peer connection stream have changed and modifySources should proceed.
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
this.switchstreams = false;
|
||||||
|
|
||||||
// override as desired
|
// override as desired
|
||||||
this.trace = function(what, info) {
|
this.trace = function(what, info) {
|
||||||
//console.warn('WTRACE', what, info);
|
//console.warn('WTRACE', what, info);
|
||||||
|
@ -197,6 +203,7 @@ TraceablePeerConnection.prototype.addSource = function (elem) {
|
||||||
console.log('addssrc', new Date().getTime());
|
console.log('addssrc', new Date().getTime());
|
||||||
console.log('ice', this.iceConnectionState);
|
console.log('ice', this.iceConnectionState);
|
||||||
var sdp = new SDP(this.remoteDescription.sdp);
|
var sdp = new SDP(this.remoteDescription.sdp);
|
||||||
|
var mySdp = new SDP(this.peerconnection.localDescription.sdp);
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
$(elem).each(function (idx, content) {
|
$(elem).each(function (idx, content) {
|
||||||
|
@ -205,6 +212,15 @@ TraceablePeerConnection.prototype.addSource = function (elem) {
|
||||||
tmp = $(content).find('>source[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]');
|
tmp = $(content).find('>source[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]');
|
||||||
tmp.each(function () {
|
tmp.each(function () {
|
||||||
var ssrc = $(this).attr('ssrc');
|
var ssrc = $(this).attr('ssrc');
|
||||||
|
if(mySdp.containsSSRC(ssrc)){
|
||||||
|
/**
|
||||||
|
* This happens when multiple participants change their streams at the same time and
|
||||||
|
* ColibriFocus.modifySources have to wait for stable state. In the meantime multiple
|
||||||
|
* addssrc are scheduled for update IQ. See
|
||||||
|
*/
|
||||||
|
console.warn("Got add stream request for my own ssrc: "+ssrc);
|
||||||
|
return;
|
||||||
|
}
|
||||||
$(this).find('>parameter').each(function () {
|
$(this).find('>parameter').each(function () {
|
||||||
lines += 'a=ssrc:' + ssrc + ' ' + $(this).attr('name');
|
lines += 'a=ssrc:' + ssrc + ' ' + $(this).attr('name');
|
||||||
if ($(this).attr('value') && $(this).attr('value').length)
|
if ($(this).attr('value') && $(this).attr('value').length)
|
||||||
|
@ -233,6 +249,7 @@ TraceablePeerConnection.prototype.removeSource = function (elem) {
|
||||||
console.log('removessrc', new Date().getTime());
|
console.log('removessrc', new Date().getTime());
|
||||||
console.log('ice', this.iceConnectionState);
|
console.log('ice', this.iceConnectionState);
|
||||||
var sdp = new SDP(this.remoteDescription.sdp);
|
var sdp = new SDP(this.remoteDescription.sdp);
|
||||||
|
var mySdp = new SDP(this.peerconnection.localDescription.sdp);
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
$(elem).each(function (idx, content) {
|
$(elem).each(function (idx, content) {
|
||||||
|
@ -241,6 +258,11 @@ TraceablePeerConnection.prototype.removeSource = function (elem) {
|
||||||
tmp = $(content).find('>source[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]');
|
tmp = $(content).find('>source[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]');
|
||||||
tmp.each(function () {
|
tmp.each(function () {
|
||||||
var ssrc = $(this).attr('ssrc');
|
var ssrc = $(this).attr('ssrc');
|
||||||
|
// This should never happen, but can be useful for bug detection
|
||||||
|
if(mySdp.containsSSRC(ssrc)){
|
||||||
|
console.error("Got remove stream request for my own ssrc: "+ssrc);
|
||||||
|
return;
|
||||||
|
}
|
||||||
$(this).find('>parameter').each(function () {
|
$(this).find('>parameter').each(function () {
|
||||||
lines += 'a=ssrc:' + ssrc + ' ' + $(this).attr('name');
|
lines += 'a=ssrc:' + ssrc + ' ' + $(this).attr('name');
|
||||||
if ($(this).attr('value') && $(this).attr('value').length)
|
if ($(this).attr('value') && $(this).attr('value').length)
|
||||||
|
@ -261,7 +283,8 @@ TraceablePeerConnection.prototype.removeSource = function (elem) {
|
||||||
TraceablePeerConnection.prototype.modifySources = function(successCallback) {
|
TraceablePeerConnection.prototype.modifySources = function(successCallback) {
|
||||||
var self = this;
|
var self = this;
|
||||||
if (this.signalingState == 'closed') return;
|
if (this.signalingState == 'closed') return;
|
||||||
if (!(this.addssrc.length || this.removessrc.length || this.pendingop !== null)){
|
if (!(this.addssrc.length || this.removessrc.length || this.pendingop !== null || this.switchstreams)){
|
||||||
|
// There is nothing to do since scheduled job might have been executed by another succeeding call
|
||||||
if(successCallback){
|
if(successCallback){
|
||||||
successCallback();
|
successCallback();
|
||||||
}
|
}
|
||||||
|
@ -282,6 +305,9 @@ TraceablePeerConnection.prototype.modifySources = function(successCallback) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset switch streams flag
|
||||||
|
this.switchstreams = false;
|
||||||
|
|
||||||
var sdp = new SDP(this.remoteDescription.sdp);
|
var sdp = new SDP(this.remoteDescription.sdp);
|
||||||
|
|
||||||
// add sources
|
// add sources
|
||||||
|
@ -486,8 +512,11 @@ function getUserMediaWithConstraints(um, success_callback, failure_callback, res
|
||||||
}
|
}
|
||||||
if (um.indexOf('screen') >= 0) {
|
if (um.indexOf('screen') >= 0) {
|
||||||
constraints.video = {
|
constraints.video = {
|
||||||
"mandatory": {
|
mandatory: {
|
||||||
"chromeMediaSource": "screen"
|
chromeMediaSource: "screen",
|
||||||
|
maxWidth: window.screen.width,
|
||||||
|
maxHeight: window.screen.height,
|
||||||
|
maxFrameRate: 3
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -141,10 +141,10 @@ Strophe.addConnectionPlugin('jingle', {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'addsource': // FIXME: proprietary
|
case 'addsource': // FIXME: proprietary
|
||||||
sess.addSource($(iq).find('>jingle>content'));
|
sess.addSource($(iq).find('>jingle>content'), fromJid);
|
||||||
break;
|
break;
|
||||||
case 'removesource': // FIXME: proprietary
|
case 'removesource': // FIXME: proprietary
|
||||||
sess.removeSource($(iq).find('>jingle>content'));
|
sess.removeSource($(iq).find('>jingle>content'), fromJid);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
console.warn('jingle action not implemented', action);
|
console.warn('jingle action not implemented', action);
|
||||||
|
|
|
@ -11,7 +11,75 @@ function SDP(sdp) {
|
||||||
this.session = this.media.shift() + '\r\n';
|
this.session = this.media.shift() + '\r\n';
|
||||||
this.raw = this.session + this.media.join('');
|
this.raw = this.session + this.media.join('');
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Returns map of MediaChannel mapped per channel idx.
|
||||||
|
*/
|
||||||
|
SDP.prototype.getMediaSsrcMap = function() {
|
||||||
|
var self = this;
|
||||||
|
var media_ssrcs = {};
|
||||||
|
for (channelNum = 0; channelNum < self.media.length; channelNum++) {
|
||||||
|
modified = true;
|
||||||
|
tmp = SDPUtil.find_lines(self.media[channelNum], 'a=ssrc:');
|
||||||
|
var type = SDPUtil.parse_mid(SDPUtil.find_line(self.media[channelNum], 'a=mid:'));
|
||||||
|
var channel = new MediaChannel(channelNum, type);
|
||||||
|
media_ssrcs[channelNum] = channel;
|
||||||
|
tmp.forEach(function (line) {
|
||||||
|
var linessrc = line.substring(7).split(' ')[0];
|
||||||
|
// allocate new ChannelSsrc
|
||||||
|
if(!channel.ssrcs[linessrc]) {
|
||||||
|
channel.ssrcs[linessrc] = new ChannelSsrc(linessrc, type);
|
||||||
|
}
|
||||||
|
channel.ssrcs[linessrc].lines.push(line);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return media_ssrcs;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Returns <tt>true</tt> if this SDP contains given SSRC.
|
||||||
|
* @param ssrc the ssrc to check.
|
||||||
|
* @returns {boolean} <tt>true</tt> if this SDP contains given SSRC.
|
||||||
|
*/
|
||||||
|
SDP.prototype.containsSSRC = function(ssrc) {
|
||||||
|
var channels = this.getMediaSsrcMap();
|
||||||
|
var contains = false;
|
||||||
|
Object.keys(channels).forEach(function(chNumber){
|
||||||
|
var channel = channels[chNumber];
|
||||||
|
//console.log("Check", channel, ssrc);
|
||||||
|
if(Object.keys(channel.ssrcs).indexOf(ssrc) != -1){
|
||||||
|
contains = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return contains;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Returns map of MediaChannel that contains only media not contained in <tt>otherSdp</tt>. Mapped by channel idx.
|
||||||
|
* @param otherSdp the other SDP to check ssrc with.
|
||||||
|
*/
|
||||||
|
SDP.prototype.getNewMedia = function(otherSdp) {
|
||||||
|
var myMedia = this.getMediaSsrcMap();
|
||||||
|
var othersMedia = otherSdp.getMediaSsrcMap();
|
||||||
|
var newMedia = {};
|
||||||
|
Object.keys(othersMedia).forEach(function(channelNum) {
|
||||||
|
var myChannel = myMedia[channelNum];
|
||||||
|
var othersChannel = othersMedia[channelNum];
|
||||||
|
if(!myChannel && othersChannel) {
|
||||||
|
// Add whole channel
|
||||||
|
newMedia[channelNum] = othersChannel;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Look for new ssrcs accross the channel
|
||||||
|
Object.keys(othersChannel.ssrcs).forEach(function(ssrc) {
|
||||||
|
if(Object.keys(myChannel.ssrcs).indexOf(ssrc) === -1) {
|
||||||
|
// Allocate channel if we've found ssrc that doesn't exist in our channel
|
||||||
|
if(!newMedia[channelNum]){
|
||||||
|
newMedia[channelNum] = new MediaChannel(othersChannel.chNumber, othersChannel.mediaType);
|
||||||
|
}
|
||||||
|
newMedia[channelNum].ssrcs[ssrc] = othersChannel.ssrcs[ssrc];
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
return newMedia;
|
||||||
|
}
|
||||||
// remove iSAC and CN from SDP
|
// remove iSAC and CN from SDP
|
||||||
SDP.prototype.mangle = function () {
|
SDP.prototype.mangle = function () {
|
||||||
var i, j, mline, lines, rtpmap, newdesc;
|
var i, j, mline, lines, rtpmap, newdesc;
|
||||||
|
@ -486,316 +554,3 @@ SDP.prototype.jingle2media = function (content) {
|
||||||
}
|
}
|
||||||
return media;
|
return media;
|
||||||
};
|
};
|
||||||
|
|
||||||
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;
|
|
||||||
},
|
|
||||||
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;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
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.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];
|
|
||||||
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;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
line += 'generation';
|
|
||||||
line += ' ';
|
|
||||||
line += cand.getAttribute('generation') || '0';
|
|
||||||
return line + '\r\n';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
|
@ -0,0 +1,353 @@
|
||||||
|
/**
|
||||||
|
* 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 = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
},
|
||||||
|
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;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
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.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];
|
||||||
|
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;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
line += 'generation';
|
||||||
|
line += ' ';
|
||||||
|
line += cand.getAttribute('generation') || '0';
|
||||||
|
return line + '\r\n';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
|
@ -3,10 +3,9 @@
|
||||||
JingleSession.prototype = Object.create(SessionBase.prototype);
|
JingleSession.prototype = Object.create(SessionBase.prototype);
|
||||||
function JingleSession(me, sid, connection) {
|
function JingleSession(me, sid, connection) {
|
||||||
|
|
||||||
SessionBase.call(this, connection);
|
SessionBase.call(this, connection, sid);
|
||||||
|
|
||||||
this.me = me;
|
this.me = me;
|
||||||
this.sid = sid;
|
|
||||||
this.initiator = null;
|
this.initiator = null;
|
||||||
this.responder = null;
|
this.responder = null;
|
||||||
this.isInitiator = null;
|
this.isInitiator = null;
|
||||||
|
@ -155,6 +154,22 @@ 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;
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
/**
|
/**
|
||||||
* Base class for ColibriFocus and JingleSession.
|
* Base class for ColibriFocus and JingleSession.
|
||||||
* @param connection Strophe connection object
|
* @param connection Strophe connection object
|
||||||
|
* @param sid my session identifier(resource)
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
function SessionBase(connection){
|
function SessionBase(connection, sid){
|
||||||
|
|
||||||
this.connection = connection;
|
this.connection = connection;
|
||||||
|
this.sid = sid;
|
||||||
this.peerconnection
|
this.peerconnection
|
||||||
= new TraceablePeerConnection(
|
= new TraceablePeerConnection(
|
||||||
connection.jingle.ice_config,
|
connection.jingle.ice_config,
|
||||||
|
@ -13,26 +15,158 @@ function SessionBase(connection){
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
SessionBase.prototype.modifySources = function() {
|
SessionBase.prototype.modifySources = function (successCallback) {
|
||||||
var self = this;
|
var self = this;
|
||||||
this.peerconnection.modifySources(function(){
|
this.peerconnection.modifySources(function(){
|
||||||
$(document).trigger('setLocalDescription.jingle', [self.sid]);
|
$(document).trigger('setLocalDescription.jingle', [self.sid]);
|
||||||
|
if(successCallback) {
|
||||||
|
successCallback();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
SessionBase.prototype.addSource = function (elem) {
|
SessionBase.prototype.addSource = function (elem, fromJid) {
|
||||||
|
|
||||||
this.peerconnection.addSource(elem);
|
this.peerconnection.addSource(elem);
|
||||||
|
|
||||||
this.modifySources();
|
this.modifySources();
|
||||||
};
|
};
|
||||||
|
|
||||||
SessionBase.prototype.removeSource = function (elem) {
|
SessionBase.prototype.removeSource = function (elem, fromJid) {
|
||||||
|
|
||||||
this.peerconnection.removeSource(elem);
|
this.peerconnection.removeSource(elem);
|
||||||
|
|
||||||
this.modifySources();
|
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;
|
||||||
|
|
||||||
|
// Remember SDP to figure out added/removed SSRCs
|
||||||
|
var oldSdp = new SDP(self.peerconnection.localDescription.sdp);
|
||||||
|
|
||||||
|
self.peerconnection.removeStream(oldStream);
|
||||||
|
|
||||||
|
self.connection.jingle.localVideo = new_stream;
|
||||||
|
self.peerconnection.addStream(self.connection.jingle.localVideo);
|
||||||
|
|
||||||
|
self.connection.jingle.localStreams = [];
|
||||||
|
self.connection.jingle.localStreams.push(self.connection.jingle.localAudio);
|
||||||
|
self.connection.jingle.localStreams.push(self.connection.jingle.localVideo);
|
||||||
|
|
||||||
|
self.peerconnection.switchstreams = true;
|
||||||
|
self.modifySources(function() {
|
||||||
|
console.log('modify sources done');
|
||||||
|
|
||||||
|
var newSdp = new SDP(self.peerconnection.localDescription.sdp);
|
||||||
|
console.log("SDPs", oldSdp, newSdp);
|
||||||
|
self.notifyMySSRCUpdate(oldSdp, newSdp);
|
||||||
|
|
||||||
|
success_callback();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 ? 'addsource' : 'removesource',
|
||||||
|
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});
|
||||||
|
// 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
|
||||||
|
});
|
||||||
|
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');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// SDP-based mute by going recvonly/sendrecv
|
// SDP-based mute by going recvonly/sendrecv
|
||||||
// FIXME: should probably black out the screen as well
|
// FIXME: should probably black out the screen as well
|
||||||
|
|
8
muc.js
8
muc.js
|
@ -232,6 +232,14 @@ Strophe.addConnectionPlugin('emuc', {
|
||||||
this.presMap['source' + sourceNumber + '_ssrc'] = ssrcs;
|
this.presMap['source' + sourceNumber + '_ssrc'] = ssrcs;
|
||||||
this.presMap['source' + sourceNumber + '_direction'] = direction;
|
this.presMap['source' + sourceNumber + '_direction'] = direction;
|
||||||
},
|
},
|
||||||
|
clearPresenceMedia: function () {
|
||||||
|
var self = this;
|
||||||
|
Object.keys(this.presMap).forEach( function(key) {
|
||||||
|
if(key.indexOf('source') != -1) {
|
||||||
|
delete self.presMap[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
addPreziToPresence: function (url, currentSlide) {
|
addPreziToPresence: function (url, currentSlide) {
|
||||||
this.presMap['prezins'] = 'http://jitsi.org/jitmeet/prezi';
|
this.presMap['prezins'] = 'http://jitsi.org/jitmeet/prezi';
|
||||||
this.presMap['preziurl'] = url;
|
this.presMap['preziurl'] = url;
|
||||||
|
|
Loading…
Reference in New Issue