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.

JingleHelperFunctions.js 6.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. import { getLogger } from '@jitsi/logger';
  2. import $ from 'jquery';
  3. import { $build } from 'strophe.js';
  4. import { MediaType } from '../../service/RTC/MediaType';
  5. import FeatureFlags from '../flags/FeatureFlags';
  6. const logger = getLogger(__filename);
  7. /**
  8. * Creates a "source" XML element for the source described in compact JSON format in [sourceCompactJson].
  9. * @param {*} owner the endpoint ID of the owner of the source.
  10. * @param {*} sourceCompactJson the compact JSON representation of the source.
  11. * @returns the created "source" XML element.
  12. */
  13. function _createSourceExtension(owner, sourceCompactJson) {
  14. const node = $build('source', {
  15. xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0',
  16. ssrc: sourceCompactJson.s,
  17. name: FeatureFlags.isSourceNameSignalingEnabled() ? sourceCompactJson.n : undefined
  18. });
  19. if (sourceCompactJson.m) {
  20. node.c('parameter', {
  21. name: 'msid',
  22. value: sourceCompactJson.m
  23. }).up();
  24. }
  25. node.c('ssrc-info', {
  26. xmlns: 'http://jitsi.org/jitmeet',
  27. owner
  28. }).up();
  29. return node.node;
  30. }
  31. /**
  32. * Creates an "ssrc-group" XML element for the SSRC group described in compact JSON format in [ssrcGroupCompactJson].
  33. * @param {*} ssrcGroupCompactJson the compact JSON representation of the SSRC group.
  34. * @returns the created "ssrc-group" element.
  35. */
  36. function _createSsrcGroupExtension(ssrcGroupCompactJson) {
  37. const node = $build('ssrc-group', {
  38. xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0',
  39. semantics: _getSemantics(ssrcGroupCompactJson[0])
  40. });
  41. for (let i = 1; i < ssrcGroupCompactJson.length; i++) {
  42. node.c('source', {
  43. xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0',
  44. ssrc: ssrcGroupCompactJson[i]
  45. }).up();
  46. }
  47. return node.node;
  48. }
  49. /**
  50. * Finds in a Jingle IQ the RTP description element with the given media type. If one does not exists, create it (as
  51. * well as the required "content" parent element) and adds it to the IQ.
  52. * @param {*} iq
  53. * @param {*} mediaType The media type, "audio" or "video".
  54. * @returns the RTP description element with the given media type.
  55. */
  56. function _getOrCreateRtpDescription(iq, mediaType) {
  57. const jingle = $(iq).find('jingle')[0];
  58. let content = $(jingle).find(`content[name="${mediaType}"]`);
  59. let description;
  60. if (content.length) {
  61. content = content[0];
  62. } else {
  63. // I'm not suree if "creator" and "senders" are required.
  64. content = $build('content', {
  65. name: mediaType
  66. }).node;
  67. jingle.appendChild(content);
  68. }
  69. description = $(content).find('description');
  70. if (description.length) {
  71. description = description[0];
  72. } else {
  73. description = $build('description', {
  74. xmlns: 'urn:xmpp:jingle:apps:rtp:1',
  75. media: mediaType
  76. }).node;
  77. content.appendChild(description);
  78. }
  79. return description;
  80. }
  81. /**
  82. * Converts the short string representing SSRC group semantics in compact JSON format to the standard representation
  83. * (i.e. convert "f" to "FID" and "s" to "SIM").
  84. * @param {*} str the compact JSON format representation of an SSRC group's semantics.
  85. * @returns the SSRC group semantics corresponding to [str].
  86. */
  87. function _getSemantics(str) {
  88. if (str === 'f') {
  89. return 'FID';
  90. } else if (str === 's') {
  91. return 'SIM';
  92. }
  93. return null;
  94. }
  95. /**
  96. * Reads a JSON-encoded message (from a "json-message" element) and extracts source descriptions. Adds the extracted
  97. * source descriptions to the given Jingle IQ in the standard Jingle format.
  98. *
  99. * Encoding sources in this compact JSON format instead of standard Jingle was introduced in order to reduce the
  100. * network traffic and load on the XMPP server. The format is described in Jicofo [TODO: insert link].
  101. *
  102. * @param {*} iq the IQ to which source descriptions will be added.
  103. * @param {*} jsonMessageXml The XML node for the "json-message" element.
  104. * @returns {Map<string, Array<string>} The audio and video ssrcs extracted from the JSON-encoded message with remote
  105. * endpoint id as the key.
  106. */
  107. export function expandSourcesFromJson(iq, jsonMessageXml) {
  108. let json;
  109. try {
  110. json = JSON.parse(jsonMessageXml.textContent);
  111. } catch (error) {
  112. logger.error(`json-message XML contained invalid JSON, ignoring: ${jsonMessageXml.textContent}`);
  113. return null;
  114. }
  115. if (!json?.sources) {
  116. // It might be a message of a different type, no need to log.
  117. return null;
  118. }
  119. // This is where we'll add "source" and "ssrc-group" elements. Create them elements if they don't exist.
  120. const audioRtpDescription = _getOrCreateRtpDescription(iq, MediaType.AUDIO);
  121. const videoRtpDescription = _getOrCreateRtpDescription(iq, MediaType.VIDEO);
  122. const ssrcMap = new Map();
  123. for (const owner in json.sources) {
  124. if (json.sources.hasOwnProperty(owner)) {
  125. const ssrcs = [];
  126. const ownerSources = json.sources[owner];
  127. // The video sources, video ssrc-groups, audio sources and audio ssrc-groups are encoded in that order in
  128. // the elements of the array.
  129. const videoSources = ownerSources?.length && ownerSources[0];
  130. const videoSsrcGroups = ownerSources?.length > 1 && ownerSources[1];
  131. const audioSources = ownerSources?.length > 2 && ownerSources[2];
  132. const audioSsrcGroups = ownerSources?.length > 3 && ownerSources[3];
  133. if (videoSources?.length) {
  134. for (let i = 0; i < videoSources.length; i++) {
  135. videoRtpDescription.appendChild(_createSourceExtension(owner, videoSources[i]));
  136. ssrcs.push(videoSources[i]?.s);
  137. }
  138. }
  139. if (videoSsrcGroups?.length) {
  140. for (let i = 0; i < videoSsrcGroups.length; i++) {
  141. videoRtpDescription.appendChild(_createSsrcGroupExtension(videoSsrcGroups[i]));
  142. }
  143. }
  144. if (audioSources?.length) {
  145. for (let i = 0; i < audioSources.length; i++) {
  146. audioRtpDescription.appendChild(_createSourceExtension(owner, audioSources[i]));
  147. ssrcs.push(audioSources[i]?.s);
  148. }
  149. }
  150. if (audioSsrcGroups?.length) {
  151. for (let i = 0; i < audioSsrcGroups.length; i++) {
  152. audioRtpDescription.appendChild(_createSsrcGroupExtension(audioSsrcGroups[i]));
  153. }
  154. }
  155. ssrcMap.set(owner, ssrcs);
  156. }
  157. }
  158. return ssrcMap;
  159. }