Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

JingleHelperFunctions.ts 7.7KB

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