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

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