function SDPDiffer(mySDP, otherSDP) { this.mySDP = mySDP; this.otherSDP = otherSDP; } /** * Returns map of MediaChannel that contains only media not contained in otherSdp. Mapped by channel idx. * @param otherSdp the other SDP to check ssrc with. */ SDPDiffer.prototype.getNewMedia = function() { // this could be useful in Array.prototype. function arrayEquals(array) { // if the other array is a falsy value, return if (!array) return false; // compare lengths - can save a lot of time if (this.length != array.length) return false; for (var i = 0, l=this.length; i < l; i++) { // Check if we have nested arrays if (this[i] instanceof Array && array[i] instanceof Array) { // recurse into the nested arrays if (!this[i].equals(array[i])) return false; } else if (this[i] != array[i]) { // Warning - two different object instances will never be equal: {x:20} != {x:20} return false; } } return true; } var myMedias = this.mySDP.getMediaSsrcMap(); var othersMedias = this.otherSDP.getMediaSsrcMap(); var newMedia = {}; Object.keys(othersMedias).forEach(function(othersMediaIdx) { var myMedia = myMedias[othersMediaIdx]; var othersMedia = othersMedias[othersMediaIdx]; if(!myMedia && othersMedia) { // Add whole channel newMedia[othersMediaIdx] = othersMedia; return; } // Look for new ssrcs accross the channel Object.keys(othersMedia.ssrcs).forEach(function(ssrc) { if(Object.keys(myMedia.ssrcs).indexOf(ssrc) === -1) { // Allocate channel if we've found ssrc that doesn't exist in our channel if(!newMedia[othersMediaIdx]){ newMedia[othersMediaIdx] = { mediaindex: othersMedia.mediaindex, mid: othersMedia.mid, ssrcs: {}, ssrcGroups: [] }; } newMedia[othersMediaIdx].ssrcs[ssrc] = othersMedia.ssrcs[ssrc]; } }); // Look for new ssrc groups across the channels othersMedia.ssrcGroups.forEach(function(otherSsrcGroup){ // try to match the other ssrc-group with an ssrc-group of ours var matched = false; for (var i = 0; i < myMedia.ssrcGroups.length; i++) { var mySsrcGroup = myMedia.ssrcGroups[i]; if (otherSsrcGroup.semantics == mySsrcGroup.semantics && arrayEquals.apply(otherSsrcGroup.ssrcs, [mySsrcGroup.ssrcs])) { matched = true; break; } } if (!matched) { // Allocate channel if we've found an ssrc-group that doesn't // exist in our channel if(!newMedia[othersMediaIdx]){ newMedia[othersMediaIdx] = { mediaindex: othersMedia.mediaindex, mid: othersMedia.mid, ssrcs: {}, ssrcGroups: [] }; } newMedia[othersMediaIdx].ssrcGroups.push(otherSsrcGroup); } }); }); 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; }; module.exports = SDPDiffer;