Moves add/remove source element creation in strophe.jingle.sdp.js.

This commit is contained in:
George Politis 2014-12-26 13:17:25 +01:00
parent 1938280e27
commit f95d5f36bb
2 changed files with 163 additions and 191 deletions

View File

@ -17,27 +17,37 @@ function SDP(sdp) {
SDP.prototype.getMediaSsrcMap = function() { SDP.prototype.getMediaSsrcMap = function() {
var self = this; var self = this;
var media_ssrcs = {}; var media_ssrcs = {};
for (channelNum = 0; channelNum < self.media.length; channelNum++) { var tmp;
modified = true; for (var mediaindex = 0; mediaindex < self.media.length; mediaindex++) {
tmp = SDPUtil.find_lines(self.media[channelNum], 'a=ssrc:'); tmp = SDPUtil.find_lines(self.media[mediaindex], 'a=ssrc:');
var type = SDPUtil.parse_mid(SDPUtil.find_line(self.media[channelNum], 'a=mid:')); var mid = SDPUtil.parse_mid(SDPUtil.find_line(self.media[mediaindex], 'a=mid:'));
var channel = new MediaChannel(channelNum, type); var media = {
media_ssrcs[channelNum] = channel; mediaindex: mediaindex,
mid: mid,
ssrcs: {},
ssrcGroups: []
};
media_ssrcs[mediaindex] = media;
tmp.forEach(function (line) { tmp.forEach(function (line) {
var linessrc = line.substring(7).split(' ')[0]; var linessrc = line.substring(7).split(' ')[0];
// allocate new ChannelSsrc // allocate new ChannelSsrc
if(!channel.ssrcs[linessrc]) { if(!media.ssrcs[linessrc]) {
channel.ssrcs[linessrc] = new ChannelSsrc(linessrc, type); media.ssrcs[linessrc] = {
ssrc: linessrc,
lines: []
};
} }
channel.ssrcs[linessrc].lines.push(line); media.ssrcs[linessrc].lines.push(line);
}); });
tmp = SDPUtil.find_lines(self.media[channelNum], 'a=ssrc-group:'); tmp = SDPUtil.find_lines(self.media[mediaindex], 'a=ssrc-group:');
tmp.forEach(function(line){ tmp.forEach(function(line){
var semantics = line.substr(0, idx).substr(13); var semantics = line.substr(0, idx).substr(13);
var ssrcs = line.substr(14 + semantics.length).split(' '); var ssrcs = line.substr(14 + semantics.length).split(' ');
if (ssrcs.length != 0) { if (ssrcs.length != 0) {
var ssrcGroup = new ChannelSsrcGroup(semantics, ssrcs); media.ssrcGroups.push({
channel.ssrcGroups.push(ssrcGroup); semantics: semantics,
ssrcs: ssrcs
});
} }
}); });
} }
@ -49,23 +59,28 @@ SDP.prototype.getMediaSsrcMap = function() {
* @returns {boolean} <tt>true</tt> if this SDP contains given SSRC. * @returns {boolean} <tt>true</tt> if this SDP contains given SSRC.
*/ */
SDP.prototype.containsSSRC = function(ssrc) { SDP.prototype.containsSSRC = function(ssrc) {
var channels = this.getMediaSsrcMap(); var medias = this.getMediaSsrcMap();
var contains = false; var contains = false;
Object.keys(channels).forEach(function(chNumber){ Object.keys(medias).forEach(function(mediaindex){
var channel = channels[chNumber]; var media = medias[mediaindex];
//console.log("Check", channel, ssrc); //console.log("Check", channel, ssrc);
if(Object.keys(channel.ssrcs).indexOf(ssrc) != -1){ if(Object.keys(media.ssrcs).indexOf(ssrc) != -1){
contains = true; contains = true;
} }
}); });
return contains; return contains;
}; };
function SDPDiffer(mySDP, otherSDP) {
this.mySDP = new SDP(mySDP);
this.otherSDP = new SDP(otherSDP);
}
/** /**
* Returns map of MediaChannel that contains only media not contained in <tt>otherSdp</tt>. Mapped by channel idx. * 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. * @param otherSdp the other SDP to check ssrc with.
*/ */
SDP.prototype.getNewMedia = function(otherSdp) { SDPDiffer.prototype.getNewMedia = function() {
// this could be useful in Array.prototype. // this could be useful in Array.prototype.
function arrayEquals(array) { function arrayEquals(array) {
@ -92,35 +107,40 @@ SDP.prototype.getNewMedia = function(otherSdp) {
return true; return true;
} }
var myMedia = this.getMediaSsrcMap(); var myMedias = this.mySDP.getMediaSsrcMap();
var othersMedia = otherSdp.getMediaSsrcMap(); var othersMedias = this.otherSDP.getMediaSsrcMap();
var newMedia = {}; var newMedia = {};
Object.keys(othersMedia).forEach(function(channelNum) { Object.keys(othersMedias).forEach(function(othersMediaIdx) {
var myChannel = myMedia[channelNum]; var myMedia = myMedias[othersMediaIdx];
var othersChannel = othersMedia[channelNum]; var othersMedia = othersMedias[othersMediaIdx];
if(!myChannel && othersChannel) { if(!myMedia && othersMedia) {
// Add whole channel // Add whole channel
newMedia[channelNum] = othersChannel; newMedia[othersMediaIdx] = othersMedia;
return; return;
} }
// Look for new ssrcs accross the channel // Look for new ssrcs accross the channel
Object.keys(othersChannel.ssrcs).forEach(function(ssrc) { Object.keys(othersMedia.ssrcs).forEach(function(ssrc) {
if(Object.keys(myChannel.ssrcs).indexOf(ssrc) === -1) { if(Object.keys(myMedia.ssrcs).indexOf(ssrc) === -1) {
// Allocate channel if we've found ssrc that doesn't exist in our channel // Allocate channel if we've found ssrc that doesn't exist in our channel
if(!newMedia[channelNum]){ if(!newMedia[othersMediaIdx]){
newMedia[channelNum] = new MediaChannel(othersChannel.chNumber, othersChannel.mediaType); newMedia[othersMediaIdx] = {
mediaindex: othersMedia.mediaindex,
mid: othersMedia.mid,
ssrcs: {},
ssrcGroups: []
};
} }
newMedia[channelNum].ssrcs[ssrc] = othersChannel.ssrcs[ssrc]; newMedia[othersMediaIdx].ssrcs[ssrc] = othersMedia.ssrcs[ssrc];
} }
}); });
// Look for new ssrc groups across the channels // Look for new ssrc groups across the channels
othersChannel.ssrcGroups.forEach(function(otherSsrcGroup){ othersMedia.ssrcGroups.forEach(function(otherSsrcGroup){
// try to match the other ssrc-group with an ssrc-group of ours // try to match the other ssrc-group with an ssrc-group of ours
var matched = false; var matched = false;
for (var i = 0; i < myChannel.ssrcGroups.length; i++) { for (var i = 0; i < myMedia.ssrcGroups.length; i++) {
var mySsrcGroup = myChannel.ssrcGroups[i]; var mySsrcGroup = myMedia.ssrcGroups[i];
if (otherSsrcGroup.semantics == mySsrcGroup.semantics if (otherSsrcGroup.semantics == mySsrcGroup.semantics
&& arrayEquals.apply(otherSsrcGroup.ssrcs, [mySsrcGroup.ssrcs])) { && arrayEquals.apply(otherSsrcGroup.ssrcs, [mySsrcGroup.ssrcs])) {
@ -133,16 +153,88 @@ SDP.prototype.getNewMedia = function(otherSdp) {
// Allocate channel if we've found an ssrc-group that doesn't // Allocate channel if we've found an ssrc-group that doesn't
// exist in our channel // exist in our channel
if(!newMedia[channelNum]){ if(!newMedia[othersMediaIdx]){
newMedia[channelNum] = new MediaChannel(othersChannel.chNumber, othersChannel.mediaType); newMedia[othersMediaIdx] = {
mediaindex: othersMedia.mediaindex,
mid: othersMedia.mid,
ssrcs: {},
ssrcGroups: []
};
} }
newMedia[channelNum].ssrcGroups.push(otherSsrcGroup); newMedia[othersMediaIdx].ssrcGroups.push(otherSsrcGroup);
} }
}); });
}); });
return newMedia; return newMedia;
}; };
/**
* 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.
*/
SDPDiffer.prototype.toJingle = function(modify) {
var sdpMediaSsrcs = this.getNewMedia();
var self = this;
// FIXME: only announce video ssrcs since we mix audio and dont need
// the audio ssrcs therefore
var modified = false;
Object.keys(sdpMediaSsrcs).forEach(function(mediaindex){
modified = true;
var media = sdpMediaSsrcs[mediaindex];
modify.c('content', {name: media.mid});
modify.c('description', {xmlns:'urn:xmpp:jingle:apps:rtp:1', media: media.mid});
// FIXME: not completly sure this operates on blocks and / or handles different ssrcs correctly
// generate sources from lines
Object.keys(media.ssrcs).forEach(function(ssrcNum) {
var mediaSsrc = media.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
media.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
});
return modified;
};
// 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;
@ -683,62 +775,6 @@ SDP.prototype.jingle2media = function (content) {
return media; return media;
}; };
/**
* Contains utility classes used in SDP class.
*
*/
/**
* Class holds a=ssrc lines and media type a=mid
* @param ssrc synchronization source identifier number(a=ssrc lines from SDP)
* @param type media type eg. "audio" or "video"(a=mid frm SDP)
* @constructor
*/
function ChannelSsrc(ssrc, type) {
this.ssrc = ssrc;
this.type = type;
this.lines = [];
}
/**
* Class holds a=ssrc-group: lines
* @param semantics
* @param ssrcs
* @constructor
*/
function ChannelSsrcGroup(semantics, ssrcs, line) {
this.semantics = semantics;
this.ssrcs = ssrcs;
}
/**
* Helper class represents media channel. Is a container for ChannelSsrc, holds channel idx and media type.
* @param channelNumber channel idx in SDP media array.
* @param mediaType media type(a=mid)
* @constructor
*/
function MediaChannel(channelNumber, mediaType) {
/**
* SDP channel number
* @type {*}
*/
this.chNumber = channelNumber;
/**
* Channel media type(a=mid)
* @type {*}
*/
this.mediaType = mediaType;
/**
* The maps of ssrc numbers to ChannelSsrc objects.
*/
this.ssrcs = {};
/**
* The array of ChannelSsrcGroup objects.
* @type {Array}
*/
this.ssrcGroups = [];
}
SDPUtil = { SDPUtil = {
iceparams: function (mediadesc, sessiondesc) { iceparams: function (mediadesc, sessiondesc) {

View File

@ -968,121 +968,57 @@ JingleSession.prototype.switchStreams = function (new_stream, oldStream, success
*/ */
JingleSession.prototype.notifyMySSRCUpdate = function (old_sdp, new_sdp) { JingleSession.prototype.notifyMySSRCUpdate = function (old_sdp, new_sdp) {
var old_media = old_sdp.getMediaSsrcMap();
var new_media = new_sdp.getMediaSsrcMap();
//console.log("old/new medias: ", old_media, new_media);
var toAdd = old_sdp.getNewMedia(new_sdp);
var toRemove = new_sdp.getNewMedia(old_sdp);
//console.log("to add", toAdd);
//console.log("to remove", toRemove);
if(Object.keys(toRemove).length > 0){
this.sendSSRCUpdate(toRemove, null, false);
}
if(Object.keys(toAdd).length > 0){
this.sendSSRCUpdate(toAdd, null, true);
}
};
/**
* Empty method that does nothing by default. It should send SSRC update IQs to session participants.
* @param sdpMediaSsrcs array of
* @param fromJid
* @param isAdd
*/
JingleSession.prototype.sendSSRCUpdate = function(sdpMediaSsrcs, fromJid, isAdd) {
var self = this;
console.log('tell', self.peerjid, 'about ' + (isadd ? 'new' : 'removed') + ' ssrcs from' + self.me);
if (!(this.peerconnection.signalingState == 'stable' && this.peerconnection.iceConnectionState == 'connected')){ if (!(this.peerconnection.signalingState == 'stable' && this.peerconnection.iceConnectionState == 'connected')){
console.log("Too early to send updates"); console.log("Too early to send updates");
return; return;
} }
this.sendSSRCUpdateIq(sdpMediaSsrcs, self.sid, self.initiator, self.peerjid, isadd); // send source-add IQ.
} var sdpDiffer = new SDPDiffer(old_sdp, new_sdp);
var add = $iq({to: self.peerjid, type: 'set'})
/**
* Sends SSRC update IQ.
* @param sdpMediaSsrcs SSRCs map obtained from SDP.getNewMedia. Cntains SSRCs to add/remove.
* @param sid session identifier that will be put into the IQ.
* @param initiator initiator identifier.
* @param toJid destination Jid
* @param isAdd indicates if this is remove or add operation.
*/
JingleSession.prototype.sendSSRCUpdateIq = function(sdpMediaSsrcs, sid, initiator, toJid, isAdd) {
var self = this;
var modify = $iq({to: toJid, type: 'set'})
.c('jingle', { .c('jingle', {
xmlns: 'urn:xmpp:jingle:1', xmlns: 'urn:xmpp:jingle:1',
action: isAdd ? 'source-add' : 'source-remove', action: 'source-add',
initiator: initiator, initiator: self.initiator,
sid: sid sid: self.sid
} }
); );
// FIXME: only announce video ssrcs since we mix audio and dont need var added = sdpDiffer.toJingle(add);
// the audio ssrcs therefore if (added) {
var modified = false; this.connection.sendIQ(add,
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) { function (res) {
console.info('got modify result', res); console.info('got add result', res);
}, },
function (err) { function (err) {
console.error('got modify error', err); console.error('got add error', err);
} }
); );
} else { } else {
console.log('modification not necessary'); console.log('addition not necessary');
}
// send source-remove IQ.
sdpDiffer = new SDPDiffer(new_sdp, old_sdp);
var remove = $iq({to: self.peerjid, type: 'set'})
.c('jingle', {
xmlns: 'urn:xmpp:jingle:1',
action: 'source-remove',
initiator: self.initiator,
sid: self.sid
}
);
var removed = sdpDiffer.toJingle(remove);
if (removed) {
this.connection.sendIQ(remove,
function (res) {
console.info('got remove result', res);
},
function (err) {
console.error('got remove error', err);
}
);
} else {
console.log('removal not necessary');
} }
}; };