-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
ref(SDPDiffer) Convert to ES6 class.
Make it work directly with unified plan SDP that has multiple m-lines and add more unit tests.
- Loading branch information
1 parent
4be1919
commit f10b346
Showing
4 changed files
with
338 additions
and
236 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} | ||
} |
Oops, something went wrong.