diff --git a/modules/sdp/SDP.js b/modules/sdp/SDP.js index 80066832b8..e2d2d24f4c 100644 --- a/modules/sdp/SDP.js +++ b/modules/sdp/SDP.js @@ -88,7 +88,7 @@ export default class SDP { containsSSRC(ssrc) { const souceMap = this.getMediaSsrcMap(); - return Object.values(souceMap).some(media => media.ssrcs[ssrc]); + return Array.from(Object.values(souceMap)).some(media => media.ssrcs[ssrc]); } /** @@ -141,7 +141,7 @@ export default class SDP { * @returns {*} */ getMediaSsrcMap() { - const mediaSSRCs = {}; + const sourceInfo = new Map(); this.media.forEach((mediaItem, mediaindex) => { const mid = SDPUtil.parseMID(SDPUtil.findLine(mediaItem, 'a=mid:')); @@ -152,8 +152,6 @@ export default class SDP { ssrcGroups: [] }; - mediaSSRCs[mediaindex] = media; - SDPUtil.findLines(mediaItem, 'a=ssrc:').forEach(line => { const linessrc = line.substring(7).split(' ')[0]; @@ -179,9 +177,11 @@ export default class SDP { }); } }); + + sourceInfo.set(mediaindex, media); }); - return mediaSSRCs; + return sourceInfo; } /** diff --git a/modules/sdp/SDPDiffer.js b/modules/sdp/SDPDiffer.js index 0abec4597b..14ac2a659c 100644 --- a/modules/sdp/SDPDiffer.js +++ b/modules/sdp/SDPDiffer.js @@ -1,220 +1,112 @@ +import { isEqual } from 'lodash-es'; import { XEP } from '../../service/xmpp/XMPPExtensioProtocols'; import SDPUtil from './SDPUtil'; -// this could be useful in Array.prototype. /** - * - * @param array1 - * @param array2 + * A class that provides methods for comparing the source information present in two different SDPs so that the delta + * can be signaled to Jicofo via 'source-remove' or 'source-add'. */ -function arrayEquals(array1, array2) { - // if the other array is a falsy value, return - if (!array2) { - return false; +export class SDPDiffer { + /** + * Constructor. + * + * @param {SDP} mySdp - the new SDP. + * @param {SDP} othersSdp - the old SDP. + */ + constructor(mySdp, othersSdp) { + this.mySdp = mySdp; + this.othersSdp = othersSdp; } - // compare lengths - can save a lot of time - if (array1.length !== array2.length) { - return false; - } - - for (let i = 0, l = array1.length; i < l; i++) { - // Check if we have nested arrays - if (array1[i] instanceof Array && array2[i] instanceof Array) { - // recurse into the nested arrays - if (!array1[i].equals(array2[i])) { - return false; + /** + * Returns a map of the sources that are present in 'othersSdp' but not in 'mySdp'. + * + * @returns {*} + */ + getNewMedia() { + const mySources = this.mySdp.getMediaSsrcMap(); + const othersSources = this.othersSdp.getMediaSsrcMap(); + const diff = {}; + + for (const [ index, othersSource ] of othersSources.entries()) { + const mySource = mySources.get(index); + + if (!mySource || !isEqual(mySource, othersSource)) { + diff[index] = othersSource; } - } else if (array1[i] !== array2[i]) { - // Warning - two different object instances will never be - // equal: {x:20} != {x:20} - return false; } - } - - return true; -} -/** - * - * @param mySDP - * @param otherSDP - */ -export default function SDPDiffer(mySDP, otherSDP) { - this.mySDP = mySDP; - this.otherSDP = otherSDP; - if (!mySDP) { - throw new Error('"mySDP" is undefined!'); - } else if (!otherSDP) { - throw new Error('"otherSDP" is undefined!'); + return diff; } -} - -/** - * Returns map of MediaChannel that contains media contained in - * 'mySDP', but not contained in 'otherSdp'. Mapped by channel idx. - */ -SDPDiffer.prototype.getNewMedia = function() { - - const myMedias = this.mySDP.getMediaSsrcMap(); - const othersMedias = this.otherSDP.getMediaSsrcMap(); - const newMedia = {}; - Object.keys(othersMedias).forEach(othersMediaIdx => { - const myMedia = myMedias[othersMediaIdx]; - const othersMedia = othersMedias[othersMediaIdx]; + /** + * Adds the diff source info to the provided IQ stanza. + * + * @param {*} modify - Stanza IQ. + * @returns {boolean} + */ + toJingle(modify) { + let modified = false; + const diffSourceInfo = this.getNewMedia(); + + for (const media of Object.values(diffSourceInfo)) { + modified = true; + modify.c('content', { name: media.mid }); + + modify.c('description', { + xmlns: XEP.RTP_MEDIA, + media: media.mid + }); - if (!myMedia && othersMedia) { - // Add whole channel - newMedia[othersMediaIdx] = othersMedia; + Object.keys(media.ssrcs).forEach(ssrcNum => { + const mediaSsrc = media.ssrcs[ssrcNum]; + const ssrcLines = mediaSsrc.lines; + const sourceName = SDPUtil.parseSourceNameLine(ssrcLines); + const videoType = SDPUtil.parseVideoTypeLine(ssrcLines); + + modify.c('source', { xmlns: XEP.SOURCE_ATTRIBUTES }); + modify.attrs({ + name: sourceName, + videoType, + ssrc: mediaSsrc.ssrc + }); - return; - } + // Only MSID attribute is sent + const msid = SDPUtil.parseMSIDAttribute(ssrcLines); - // Look for new ssrcs across the channel - Object.keys(othersMedia.ssrcs).forEach(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: [] - }; + if (msid) { + modify.c('parameter'); + modify.attrs({ name: 'msid' }); + modify.attrs({ value: msid }); + modify.up(); } - newMedia[othersMediaIdx].ssrcs[ssrc] = othersMedia.ssrcs[ssrc]; - } else if (othersMedia.ssrcs[ssrc].lines - && myMedia.ssrcs[ssrc].lines) { - // we want to detect just changes in adding/removing msid - const myContainMsid = myMedia.ssrcs[ssrc].lines.find( - line => line.indexOf('msid') !== -1) !== undefined; - const newContainMsid = othersMedia.ssrcs[ssrc].lines.find( - line => line.indexOf('msid') !== -1) !== undefined; - - if (myContainMsid !== newContainMsid) { - 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(otherSsrcGroup => { - // try to match the other ssrc-group with an ssrc-group of ours - let matched = false; - - for (let i = 0; i < myMedia.ssrcGroups.length; i++) { - const mySsrcGroup = myMedia.ssrcGroups[i]; + modify.up(); // end of source + }); - if (otherSsrcGroup.semantics === mySsrcGroup.semantics - && arrayEquals(otherSsrcGroup.ssrcs, mySsrcGroup.ssrcs)) { + // generate source groups from lines + media.ssrcGroups.forEach(ssrcGroup => { + if (ssrcGroup.ssrcs.length) { - matched = true; - break; - } - } + modify.c('ssrc-group', { + semantics: ssrcGroup.semantics, + xmlns: XEP.SOURCE_ATTRIBUTES + }); - 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: [] - }; + ssrcGroup.ssrcs.forEach(ssrc => { + modify.c('source', { ssrc }) + .up(); // end of source + }); + modify.up(); // end of ssrc-group } - newMedia[othersMediaIdx].ssrcGroups.push(otherSsrcGroup); - } - }); - }); - - return newMedia; -}; - -/** - * TODO: document! - */ -SDPDiffer.prototype.toJingle = function(modify) { - const sdpMediaSsrcs = this.getNewMedia(); - - let modified = false; - - Object.keys(sdpMediaSsrcs).forEach(mediaindex => { - modified = true; - const media = sdpMediaSsrcs[mediaindex]; - - modify.c('content', { name: media.mid }); - - modify.c('description', { - xmlns: XEP.RTP_MEDIA, - media: media.mid - }); - - // FIXME: not completely sure this operates on blocks and / or handles - // different ssrcs correctly - // generate sources from lines - Object.keys(media.ssrcs).forEach(ssrcNum => { - const mediaSsrc = media.ssrcs[ssrcNum]; - const ssrcLines = mediaSsrc.lines; - const sourceName = SDPUtil.parseSourceNameLine(ssrcLines); - const videoType = SDPUtil.parseVideoTypeLine(ssrcLines); - - modify.c('source', { xmlns: XEP.SOURCE_ATTRIBUTES }); - modify.attrs({ - name: sourceName, - videoType, - ssrc: mediaSsrc.ssrc }); - // Only MSID attribute is sent - const msid = SDPUtil.parseMSIDAttribute(ssrcLines); - - if (msid) { - modify.c('parameter'); - modify.attrs({ name: 'msid' }); - modify.attrs({ value: msid }); - modify.up(); - } - - modify.up(); // end of source - }); - - // generate source groups from lines - media.ssrcGroups.forEach(ssrcGroup => { - if (ssrcGroup.ssrcs.length) { - - modify.c('ssrc-group', { - semantics: ssrcGroup.semantics, - xmlns: XEP.SOURCE_ATTRIBUTES - }); - - ssrcGroup.ssrcs.forEach(ssrc => { - modify.c('source', { ssrc }) - .up(); // end of source - }); - modify.up(); // end of ssrc-group - } - }); - - modify.up(); // end of description - modify.up(); // end of content - }); + modify.up(); // end of description + modify.up(); // end of content + } - return modified; -}; + return modified; + } +} diff --git a/modules/sdp/SDPDiffer.spec.js b/modules/sdp/SDPDiffer.spec.js index 8d7961a2d1..30fb4e344e 100644 --- a/modules/sdp/SDPDiffer.spec.js +++ b/modules/sdp/SDPDiffer.spec.js @@ -3,54 +3,57 @@ import { $iq } from 'strophe.js'; import FeatureFlags from '../flags/FeatureFlags'; import SDP from './SDP'; -import SDPDiffer from './SDPDiffer'; +import { SDPDiffer } from './SDPDiffer'; + +/* eslint-disable max-len*/ describe('SDPDiffer', () => { beforeEach(() => { FeatureFlags.init({ }); }); describe('toJingle', () => { - /* eslint-disable max-len*/ - const testSdpOld = [ - 'v=0\r\n', - 'o=thisisadapterortc 2719486166053431 0 IN IP4 127.0.0.1\r\n', - 's=-\r\n', - 't=0 0\r\n', - 'a=group:BUNDLE audio video\r\n', - 'm=audio 9 UDP/TLS/RTP/SAVPF 111 126\r\n', - 'a=mid:audio\r\n', - 'a=ssrc:2002 msid:26D16D51-503A-420B-8274-3DD1174E498F 8205D1FC-50B4-407C-87D5-9C45F1B779F0\r\n', - 'a=ssrc:2002 cname:juejgy8a01\r\n', - 'a=ssrc:2002 name:a8f7g30-a0\r\n', - 'm=video 9 UDP/TLS/RTP/SAVPF 107 100 99 96\r\n', - 'a=mid:video\r\n' - ].join(''); - const testSdpNew = [ - 'm=audio 9 UDP/TLS/RTP/SAVPF 111 126\r\n', - 'a=mid:audio\r\n', - 'm=video 9 UDP/TLS/RTP/SAVPF 107 100 99 96\r\n', - 'a=mid:video\r\n', - 'a=ssrc:4004 msid:7C0035E5-2DA1-4AEA-804A-9E75BF9B3768 225E9CDA-0384-4C92-92DD-E74C1153EC68\r\n', - 'a=ssrc:4005 msid:7C0035E5-2DA1-4AEA-804A-9E75BF9B3768 225E9CDA-0384-4C92-92DD-E74C1153EC68\r\n', - 'a=ssrc:4004 cname:juejgy8a01\r\n', - 'a=ssrc:4005 cname:juejgy8a01\r\n', - 'a=ssrc:4004 name:a8f7g30-v0\r\n', - 'a=ssrc:4005 name:a8f7g30-v0\r\n', - 'a=ssrc-group:FID 4004 4005\r\n' - ].join(''); - /* eslint-enable max-len*/ - it('should include source names in added/removed sources', () => { FeatureFlags.init({ }); + const testSdpOld = [ + 'v=0\r\n', + 'o=thisisadapterortc 2719486166053431 0 IN IP4 127.0.0.1\r\n', + 's=-\r\n', + 't=0 0\r\n', + 'a=group:BUNDLE audio video\r\n', + 'm=audio 9 UDP/TLS/RTP/SAVPF 111 126\r\n', + 'a=mid:audio\r\n', + 'a=ssrc:2002 msid:26D16D51-503A-420B-8274-3DD1174E498F 8205D1FC-50B4-407C-87D5-9C45F1B779F0\r\n', + 'a=ssrc:2002 cname:juejgy8a01\r\n', + 'a=ssrc:2002 name:a8f7g30-a0\r\n', + 'm=video 9 UDP/TLS/RTP/SAVPF 107 100 99 96\r\n', + 'a=mid:video\r\n' + ].join(''); + const testSdpNew = [ + 'v=0\r\n', + 'o=thisisadapterortc 2719486166053431 0 IN IP4 127.0.0.1\r\n', + 's=-\r\n', + 't=0 0\r\n', + 'a=group:BUNDLE audio video\r\n', + 'm=audio 9 UDP/TLS/RTP/SAVPF 111 126\r\n', + 'a=mid:audio\r\n', + 'm=video 9 UDP/TLS/RTP/SAVPF 107 100 99 96\r\n', + 'a=mid:video\r\n', + 'a=ssrc:4004 msid:7C0035E5-2DA1-4AEA-804A-9E75BF9B3768 225E9CDA-0384-4C92-92DD-E74C1153EC68\r\n', + 'a=ssrc:4005 msid:7C0035E5-2DA1-4AEA-804A-9E75BF9B3768 225E9CDA-0384-4C92-92DD-E74C1153EC68\r\n', + 'a=ssrc:4004 cname:juejgy8a01\r\n', + 'a=ssrc:4005 cname:juejgy8a01\r\n', + 'a=ssrc:4004 name:a8f7g30-v0\r\n', + 'a=ssrc:4005 name:a8f7g30-v0\r\n', + 'a=ssrc-group:FID 4004 4005\r\n' + ].join(''); const newToOldDiff = new SDPDiffer(new SDP(testSdpNew), new SDP(testSdpOld)); const sourceRemoveIq = $iq({}) .c('jingle', { action: 'source-remove' }); newToOldDiff.toJingle(sourceRemoveIq); - const removedAudioSources = sourceRemoveIq.nodeTree - .querySelectorAll('description[media=\'audio\']>source'); + const removedAudioSources = sourceRemoveIq.nodeTree.querySelectorAll('description[media=\'audio\']>source'); expect(removedAudioSources[0].getAttribute('name')).toBe('a8f7g30-a0'); @@ -60,12 +63,219 @@ describe('SDPDiffer', () => { oldToNewDiff.toJingle(sourceAddIq); - const addedVideoSources = sourceAddIq.nodeTree - .querySelectorAll('description[media=\'video\']>source'); + const addedVideoSources = sourceAddIq.nodeTree.querySelectorAll('description[media=\'video\']>source'); + const addedVideoSourceGroups = sourceAddIq.nodeTree.querySelectorAll('description[media=\'video\']>ssrc-group'); expect(addedVideoSources.length).toBe(2); expect(addedVideoSources[0].getAttribute('name')).toBe('a8f7g30-v0'); expect(addedVideoSources[1].getAttribute('name')).toBe('a8f7g30-v0'); + expect(addedVideoSourceGroups.length).toBe(1); + }); + + it('should send source-remove/source-add when ssrc changes', () => { + FeatureFlags.init({ }); + + const testSdpOld = [ + 'v=0\r\n', + 'o=thisisadapterortc 2719486166053431 0 IN IP4 127.0.0.1\r\n', + 's=-\r\n', + 't=0 0\r\n', + 'a=group:BUNDLE audio video\r\n', + 'm=audio 9 UDP/TLS/RTP/SAVPF 111 126\r\n', + 'a=mid:audio\r\n', + 'a=ssrc:2002 msid:26D16D51-503A-420B-8274-3DD1174E498F 8205D1FC-50B4-407C-87D5-9C45F1B779F0\r\n', + 'a=ssrc:2002 cname:juejgy8a01\r\n', + 'a=ssrc:2002 name:a8f7g30-a0\r\n', + 'm=video 9 UDP/TLS/RTP/SAVPF 107 100 99 96\r\n', + 'a=mid:video\r\n', + 'a=ssrc:4004 msid:7C0035E5-2DA1-4AEA-804A-9E75BF9B3768 225E9CDA-0384-4C92-92DD-E74C1153EC68\r\n', + 'a=ssrc:4005 msid:7C0035E5-2DA1-4AEA-804A-9E75BF9B3768 225E9CDA-0384-4C92-92DD-E74C1153EC68\r\n', + 'a=ssrc:4004 cname:juejgy8a01\r\n', + 'a=ssrc:4005 cname:juejgy8a01\r\n', + 'a=ssrc:4004 name:a8f7g30-v0\r\n', + 'a=ssrc:4005 name:a8f7g30-v0\r\n', + 'a=ssrc-group:FID 4004 4005\r\n' + ].join(''); + const testSdpNew = [ + 'v=0\r\n', + 'o=thisisadapterortc 2719486166053431 0 IN IP4 127.0.0.1\r\n', + 's=-\r\n', + 't=0 0\r\n', + 'a=group:BUNDLE audio video\r\n', + 'm=audio 9 UDP/TLS/RTP/SAVPF 111 126\r\n', + 'a=mid:audio\r\n', + 'a=ssrc:2003 msid:26D16D51-503A-420B-8274-3DD1174E498F 8205D1FC-50B4-407C-87D5-9C45F1B779F0\r\n', + 'a=ssrc:2003 cname:juejgy8a01\r\n', + 'a=ssrc:2003 name:a8f7g30-a0\r\n', + 'm=video 9 UDP/TLS/RTP/SAVPF 107 100 99 96\r\n', + 'a=mid:video\r\n', + 'a=ssrc:4004 msid:7C0035E5-2DA1-4AEA-804A-9E75BF9B3768 225E9CDA-0384-4C92-92DD-E74C1153EC68\r\n', + 'a=ssrc:4005 msid:7C0035E5-2DA1-4AEA-804A-9E75BF9B3768 225E9CDA-0384-4C92-92DD-E74C1153EC68\r\n', + 'a=ssrc:4004 cname:juejgy8a01\r\n', + 'a=ssrc:4005 cname:juejgy8a01\r\n', + 'a=ssrc:4004 name:a8f7g30-v0\r\n', + 'a=ssrc:4005 name:a8f7g30-v0\r\n', + 'a=ssrc-group:FID 4004 4005\r\n' + ].join(''); + const newToOldDiff = new SDPDiffer(new SDP(testSdpNew), new SDP(testSdpOld)); + const sourceRemoveIq = $iq({}) + .c('jingle', { action: 'source-remove' }); + + newToOldDiff.toJingle(sourceRemoveIq); + + const removedAudioSources = sourceRemoveIq.nodeTree.querySelectorAll('description[media=\'audio\']>source'); + const removedVideoSources = sourceRemoveIq.nodeTree.querySelectorAll('description[media=\'video\']>source'); + + expect(removedAudioSources.length).toBe(1); + expect(removedAudioSources[0].getAttribute('name')).toBe('a8f7g30-a0'); + expect(removedAudioSources[0].getAttribute('ssrc')).toBe('2002'); + expect(removedVideoSources.length).toBe(0); + + const oldToNewDiff = new SDPDiffer(new SDP(testSdpOld), new SDP(testSdpNew)); + const sourceAddIq = $iq({}) + .c('jingle', { action: 'source-add' }); + + oldToNewDiff.toJingle(sourceAddIq); + + const addedAudioSources = sourceAddIq.nodeTree.querySelectorAll('description[media=\'audio\']>source'); + const addedVideoSources = sourceAddIq.nodeTree.querySelectorAll('description[media=\'video\']>source'); + + expect(addedAudioSources.length).toBe(1); + expect(addedAudioSources[0].getAttribute('name')).toBe('a8f7g30-a0'); + expect(addedAudioSources[0].getAttribute('ssrc')).toBe('2003'); + expect(addedVideoSources.length).toBe(0); + }); + + it('should not send source-remove/source-add when nothing changes', () => { + FeatureFlags.init({ }); + + const testSdpOld = [ + 'v=0\r\n', + 'o=thisisadapterortc 2719486166053431 0 IN IP4 127.0.0.1\r\n', + 's=-\r\n', + 't=0 0\r\n', + 'a=group:BUNDLE audio video\r\n', + 'm=audio 9 UDP/TLS/RTP/SAVPF 111 126\r\n', + 'a=mid:audio\r\n', + 'a=ssrc:2002 msid:26D16D51-503A-420B-8274-3DD1174E498F 8205D1FC-50B4-407C-87D5-9C45F1B779F0\r\n', + 'a=ssrc:2002 cname:juejgy8a01\r\n', + 'a=ssrc:2002 name:a8f7g30-a0\r\n', + 'm=video 9 UDP/TLS/RTP/SAVPF 107 100 99 96\r\n', + 'a=mid:video\r\n', + 'a=ssrc:4004 msid:7C0035E5-2DA1-4AEA-804A-9E75BF9B3768 225E9CDA-0384-4C92-92DD-E74C1153EC68\r\n', + 'a=ssrc:4005 msid:7C0035E5-2DA1-4AEA-804A-9E75BF9B3768 225E9CDA-0384-4C92-92DD-E74C1153EC68\r\n', + 'a=ssrc:4004 cname:juejgy8a01\r\n', + 'a=ssrc:4005 cname:juejgy8a01\r\n', + 'a=ssrc:4004 name:a8f7g30-v0\r\n', + 'a=ssrc:4005 name:a8f7g30-v0\r\n', + 'a=ssrc-group:FID 4004 4005\r\n' + ].join(''); + const testSdpNew = [ + 'v=0\r\n', + 'o=thisisadapterortc 2719486166053431 0 IN IP4 127.0.0.1\r\n', + 's=-\r\n', + 't=0 0\r\n', + 'a=group:BUNDLE audio video\r\n', + 'm=audio 9 UDP/TLS/RTP/SAVPF 111 126\r\n', + 'a=mid:audio\r\n', + 'a=ssrc:2002 msid:26D16D51-503A-420B-8274-3DD1174E498F 8205D1FC-50B4-407C-87D5-9C45F1B779F0\r\n', + 'a=ssrc:2002 cname:juejgy8a01\r\n', + 'a=ssrc:2002 name:a8f7g30-a0\r\n', + 'm=video 9 UDP/TLS/RTP/SAVPF 107 100 99 96\r\n', + 'a=mid:video\r\n', + 'a=ssrc:4004 msid:7C0035E5-2DA1-4AEA-804A-9E75BF9B3768 225E9CDA-0384-4C92-92DD-E74C1153EC68\r\n', + 'a=ssrc:4005 msid:7C0035E5-2DA1-4AEA-804A-9E75BF9B3768 225E9CDA-0384-4C92-92DD-E74C1153EC68\r\n', + 'a=ssrc:4004 cname:juejgy8a01\r\n', + 'a=ssrc:4005 cname:juejgy8a01\r\n', + 'a=ssrc:4004 name:a8f7g30-v0\r\n', + 'a=ssrc:4005 name:a8f7g30-v0\r\n', + 'a=ssrc-group:FID 4004 4005\r\n' + ].join(''); + const newToOldDiff = new SDPDiffer(new SDP(testSdpNew), new SDP(testSdpOld)); + const sourceRemoveIq = $iq({}) + .c('jingle', { action: 'source-remove' }); + + newToOldDiff.toJingle(sourceRemoveIq); + + const removedAudioSources = sourceRemoveIq.nodeTree.querySelectorAll('description[media=\'audio\']>source'); + const removedVideoSources = sourceRemoveIq.nodeTree.querySelectorAll('description[media=\'video\']>source'); + + expect(removedAudioSources.length).toBe(0); + expect(removedVideoSources.length).toBe(0); + + const oldToNewDiff = new SDPDiffer(new SDP(testSdpOld), new SDP(testSdpNew)); + const sourceAddIq = $iq({}) + .c('jingle', { action: 'source-add' }); + + oldToNewDiff.toJingle(sourceAddIq); + + const addedAudioSources = sourceAddIq.nodeTree.querySelectorAll('description[media=\'audio\']>source'); + const addedVideoSources = sourceAddIq.nodeTree.querySelectorAll('description[media=\'video\']>source'); + + expect(addedAudioSources.length).toBe(0); + expect(addedVideoSources.length).toBe(0); + }); + + it('should send source-adds for 2 sources', () => { + FeatureFlags.init({ }); + + const testSdpOld = [ + 'v=0\r\n', + 'o=thisisadapterortc 2719486166053431 0 IN IP4 127.0.0.1\r\n', + 's=-\r\n', + 't=0 0\r\n', + 'a=group:BUNDLE audio video\r\n', + 'm=audio 9 UDP/TLS/RTP/SAVPF 111 126\r\n', + 'a=mid:audio\r\n', + 'm=video 9 UDP/TLS/RTP/SAVPF 107 100 99 96\r\n', + 'a=mid:video\r\n' + ].join(''); + const testSdpNew = [ + 'v=0\r\n', + 'o=thisisadapterortc 2719486166053431 0 IN IP4 127.0.0.1\r\n', + 's=-\r\n', + 't=0 0\r\n', + 'a=group:BUNDLE audio video\r\n', + 'm=audio 9 UDP/TLS/RTP/SAVPF 111 126\r\n', + 'a=mid:audio\r\n', + 'a=ssrc:2002 msid:26D16D51-503A-420B-8274-3DD1174E498F 8205D1FC-50B4-407C-87D5-9C45F1B779F0\r\n', + 'a=ssrc:2002 cname:juejgy8a01\r\n', + 'a=ssrc:2002 name:a8f7g30-a0\r\n', + 'm=video 9 UDP/TLS/RTP/SAVPF 107 100 99 96\r\n', + 'a=mid:video\r\n', + 'a=ssrc:4004 msid:7C0035E5-2DA1-4AEA-804A-9E75BF9B3768 225E9CDA-0384-4C92-92DD-E74C1153EC68\r\n', + 'a=ssrc:4005 msid:7C0035E5-2DA1-4AEA-804A-9E75BF9B3768 225E9CDA-0384-4C92-92DD-E74C1153EC68\r\n', + 'a=ssrc:4004 cname:juejgy8a01\r\n', + 'a=ssrc:4005 cname:juejgy8a01\r\n', + 'a=ssrc:4004 name:a8f7g30-v0\r\n', + 'a=ssrc:4005 name:a8f7g30-v0\r\n', + 'a=ssrc-group:FID 4004 4005\r\n' + ].join(''); + const newToOldDiff = new SDPDiffer(new SDP(testSdpNew), new SDP(testSdpOld)); + const sourceRemoveIq = $iq({}) + .c('jingle', { action: 'source-remove' }); + + newToOldDiff.toJingle(sourceRemoveIq); + + const removedAudioSources = sourceRemoveIq.nodeTree.querySelectorAll('description[media=\'audio\']>source'); + const removedVideoSources = sourceRemoveIq.nodeTree.querySelectorAll('description[media=\'video\']>source'); + + expect(removedAudioSources.length).toBe(0); + expect(removedVideoSources.length).toBe(0); + + const oldToNewDiff = new SDPDiffer(new SDP(testSdpOld), new SDP(testSdpNew)); + const sourceAddIq = $iq({}) + .c('jingle', { action: 'source-add' }); + + oldToNewDiff.toJingle(sourceAddIq); + + const addedAudioSources = sourceAddIq.nodeTree.querySelectorAll('description[media=\'audio\']>source'); + const addedVideoSources = sourceAddIq.nodeTree.querySelectorAll('description[media=\'video\']>source'); + const addedVideoSourceGroups = sourceAddIq.nodeTree.querySelectorAll('description[media=\'video\']>ssrc-group'); + + expect(addedAudioSources.length).toBe(1); + expect(addedVideoSources.length).toBe(2); + expect(addedVideoSourceGroups.length).toBe(1); }); }); }); diff --git a/modules/xmpp/JingleSessionPC.js b/modules/xmpp/JingleSessionPC.js index af8411182e..4190626396 100644 --- a/modules/xmpp/JingleSessionPC.js +++ b/modules/xmpp/JingleSessionPC.js @@ -17,7 +17,7 @@ import { XEP } from '../../service/xmpp/XMPPExtensioProtocols'; import { SS_DEFAULT_FRAME_RATE } from '../RTC/ScreenObtainer'; import FeatureFlags from '../flags/FeatureFlags'; import SDP from '../sdp/SDP'; -import SDPDiffer from '../sdp/SDPDiffer'; +import { SDPDiffer } from '../sdp/SDPDiffer'; import SDPUtil from '../sdp/SDPUtil'; import Statistics from '../statistics/statistics'; import AsyncQueue, { ClearedQueueError } from '../util/AsyncQueue'; @@ -2562,7 +2562,7 @@ export default class JingleSessionPC extends JingleSession { Object.keys(newMedia).forEach(mediaIndex => { const signaledSsrcs = Object.keys(newMedia[mediaIndex].ssrcs); - mediaType = newMedia[mediaIndex].mid; + mediaType = newMedia[mediaIndex].mediaType; if (signaledSsrcs?.length) { ssrcs = ssrcs.concat(signaledSsrcs); }