You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

SDPDiffer.js 6.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. import SDPUtil from './SDPUtil';
  2. // this could be useful in Array.prototype.
  3. /**
  4. *
  5. * @param array1
  6. * @param array2
  7. */
  8. function arrayEquals(array1, array2) {
  9. // if the other array is a falsy value, return
  10. if (!array2) {
  11. return false;
  12. }
  13. // compare lengths - can save a lot of time
  14. if (array1.length !== array2.length) {
  15. return false;
  16. }
  17. for (let i = 0, l = array1.length; i < l; i++) {
  18. // Check if we have nested arrays
  19. if (array1[i] instanceof Array && array2[i] instanceof Array) {
  20. // recurse into the nested arrays
  21. if (!array1[i].equals(array2[i])) {
  22. return false;
  23. }
  24. } else if (array1[i] !== array2[i]) {
  25. // Warning - two different object instances will never be
  26. // equal: {x:20} != {x:20}
  27. return false;
  28. }
  29. }
  30. return true;
  31. }
  32. /**
  33. *
  34. * @param mySDP
  35. * @param otherSDP
  36. */
  37. export default function SDPDiffer(mySDP, otherSDP) {
  38. this.mySDP = mySDP;
  39. this.otherSDP = otherSDP;
  40. if (!mySDP) {
  41. throw new Error('"mySDP" is undefined!');
  42. } else if (!otherSDP) {
  43. throw new Error('"otherSDP" is undefined!');
  44. }
  45. }
  46. /**
  47. * Returns map of MediaChannel that contains media contained in
  48. * 'mySDP', but not contained in 'otherSdp'. Mapped by channel idx.
  49. */
  50. SDPDiffer.prototype.getNewMedia = function() {
  51. const myMedias = this.mySDP.getMediaSsrcMap();
  52. const othersMedias = this.otherSDP.getMediaSsrcMap();
  53. const newMedia = {};
  54. Object.keys(othersMedias).forEach(othersMediaIdx => {
  55. const myMedia = myMedias[othersMediaIdx];
  56. const othersMedia = othersMedias[othersMediaIdx];
  57. if (!myMedia && othersMedia) {
  58. // Add whole channel
  59. newMedia[othersMediaIdx] = othersMedia;
  60. return;
  61. }
  62. // Look for new ssrcs across the channel
  63. Object.keys(othersMedia.ssrcs).forEach(ssrc => {
  64. if (Object.keys(myMedia.ssrcs).indexOf(ssrc) === -1) {
  65. // Allocate channel if we've found ssrc that doesn't exist in
  66. // our channel
  67. if (!newMedia[othersMediaIdx]) {
  68. newMedia[othersMediaIdx] = {
  69. mediaindex: othersMedia.mediaindex,
  70. mid: othersMedia.mid,
  71. ssrcs: {},
  72. ssrcGroups: []
  73. };
  74. }
  75. newMedia[othersMediaIdx].ssrcs[ssrc] = othersMedia.ssrcs[ssrc];
  76. } else if (othersMedia.ssrcs[ssrc].lines
  77. && myMedia.ssrcs[ssrc].lines) {
  78. // we want to detect just changes in adding/removing msid
  79. const myContainMsid = myMedia.ssrcs[ssrc].lines.find(
  80. line => line.indexOf('msid') !== -1) !== undefined;
  81. const newContainMsid = othersMedia.ssrcs[ssrc].lines.find(
  82. line => line.indexOf('msid') !== -1) !== undefined;
  83. if (myContainMsid !== newContainMsid) {
  84. if (!newMedia[othersMediaIdx]) {
  85. newMedia[othersMediaIdx] = {
  86. mediaindex: othersMedia.mediaindex,
  87. mid: othersMedia.mid,
  88. ssrcs: {},
  89. ssrcGroups: []
  90. };
  91. }
  92. newMedia[othersMediaIdx].ssrcs[ssrc]
  93. = othersMedia.ssrcs[ssrc];
  94. }
  95. }
  96. });
  97. // Look for new ssrc groups across the channels
  98. othersMedia.ssrcGroups.forEach(otherSsrcGroup => {
  99. // try to match the other ssrc-group with an ssrc-group of ours
  100. let matched = false;
  101. for (let i = 0; i < myMedia.ssrcGroups.length; i++) {
  102. const mySsrcGroup = myMedia.ssrcGroups[i];
  103. if (otherSsrcGroup.semantics === mySsrcGroup.semantics
  104. && arrayEquals(otherSsrcGroup.ssrcs, mySsrcGroup.ssrcs)) {
  105. matched = true;
  106. break;
  107. }
  108. }
  109. if (!matched) {
  110. // Allocate channel if we've found an ssrc-group that doesn't
  111. // exist in our channel
  112. if (!newMedia[othersMediaIdx]) {
  113. newMedia[othersMediaIdx] = {
  114. mediaindex: othersMedia.mediaindex,
  115. mid: othersMedia.mid,
  116. ssrcs: {},
  117. ssrcGroups: []
  118. };
  119. }
  120. newMedia[othersMediaIdx].ssrcGroups.push(otherSsrcGroup);
  121. }
  122. });
  123. });
  124. return newMedia;
  125. };
  126. /**
  127. * TODO: document!
  128. */
  129. SDPDiffer.prototype.toJingle = function(modify) {
  130. const sdpMediaSsrcs = this.getNewMedia();
  131. let modified = false;
  132. Object.keys(sdpMediaSsrcs).forEach(mediaindex => {
  133. modified = true;
  134. const media = sdpMediaSsrcs[mediaindex];
  135. modify.c('content', { name: media.mid });
  136. modify.c('description',
  137. { xmlns: 'urn:xmpp:jingle:apps:rtp:1',
  138. media: media.mid });
  139. // FIXME: not completely sure this operates on blocks and / or handles
  140. // different ssrcs correctly
  141. // generate sources from lines
  142. Object.keys(media.ssrcs).forEach(ssrcNum => {
  143. const mediaSsrc = media.ssrcs[ssrcNum];
  144. modify.c('source', { xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
  145. modify.attrs({ ssrc: mediaSsrc.ssrc });
  146. // iterate over ssrc lines
  147. mediaSsrc.lines.forEach(line => {
  148. const idx = line.indexOf(' ');
  149. const kv = line.substr(idx + 1);
  150. modify.c('parameter');
  151. if (kv.indexOf(':') === -1) {
  152. modify.attrs({ name: kv });
  153. } else {
  154. const nv = kv.split(':', 2);
  155. const name = nv[0];
  156. const value = SDPUtil.filterSpecialChars(nv[1]);
  157. modify.attrs({ name });
  158. modify.attrs({ value });
  159. }
  160. modify.up(); // end of parameter
  161. });
  162. modify.up(); // end of source
  163. });
  164. // generate source groups from lines
  165. media.ssrcGroups.forEach(ssrcGroup => {
  166. if (ssrcGroup.ssrcs.length) {
  167. modify.c('ssrc-group', {
  168. semantics: ssrcGroup.semantics,
  169. xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0'
  170. });
  171. ssrcGroup.ssrcs.forEach(ssrc => {
  172. modify.c('source', { ssrc })
  173. .up(); // end of source
  174. });
  175. modify.up(); // end of ssrc-group
  176. }
  177. });
  178. modify.up(); // end of description
  179. modify.up(); // end of content
  180. });
  181. return modified;
  182. };