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

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