Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

SdpTransformUtil.js 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402
  1. import * as transform from 'sdp-transform';
  2. import { SSRC_GROUP_SEMANTICS } from '../../service/RTC/StandardVideoQualitySettings';
  3. /**
  4. * Parses the primary SSRC of given SSRC group.
  5. * @param {object} group the SSRC group object as defined by the 'sdp-transform'
  6. * @return {Number} the primary SSRC number
  7. */
  8. export function parsePrimarySSRC(group) {
  9. return parseInt(group.ssrcs.split(' ')[0], 10);
  10. }
  11. /**
  12. * Parses the secondary SSRC of given SSRC group.
  13. * @param {object} group the SSRC group object as defined by the 'sdp-transform'
  14. * @return {Number} the secondary SSRC number
  15. */
  16. export function parseSecondarySSRC(group) {
  17. return parseInt(group.ssrcs.split(' ')[1], 10);
  18. }
  19. /**
  20. * Tells how many distinct SSRCs are contained in given media line.
  21. * @param {Object} mLine the media line object as defined by 'sdp-transform' lib
  22. * @return {number}
  23. */
  24. function _getSSRCCount(mLine) {
  25. if (!mLine.ssrcs) {
  26. return 0;
  27. }
  28. return mLine.ssrcs
  29. .map(ssrcInfo => ssrcInfo.id)
  30. .filter((ssrc, index, array) => array.indexOf(ssrc) === index)
  31. .length;
  32. }
  33. /**
  34. * A wrapper around 'sdp-transform' media description object which provides
  35. * utility methods for common SDP/SSRC related operations.
  36. */
  37. class MLineWrap {
  38. /**
  39. * Creates new <tt>MLineWrap</t>>
  40. * @param {Object} mLine the media line object as defined by 'sdp-transform'
  41. * lib.
  42. */
  43. constructor(mLine) {
  44. if (!mLine) {
  45. throw new Error('mLine is undefined');
  46. }
  47. this.mLine = mLine;
  48. }
  49. /**
  50. * Getter for the mLine's "ssrcs" array. If the array was undefined an empty
  51. * one will be preassigned.
  52. *
  53. * @return {Array<Object>} an array of 'sdp-transform' SSRC attributes
  54. * objects.
  55. */
  56. get ssrcs() {
  57. if (!this.mLine.ssrcs) {
  58. this.mLine.ssrcs = [];
  59. }
  60. return this.mLine.ssrcs;
  61. }
  62. /**
  63. * Setter for the mLine's "ssrcs" array.
  64. *
  65. * @param {Array<Object>} ssrcs an array of 'sdp-transform' SSRC attributes
  66. * objects.
  67. */
  68. set ssrcs(ssrcs) {
  69. this.mLine.ssrcs = ssrcs;
  70. }
  71. /**
  72. * Returns the direction of the underlying media description.
  73. * @return {string} the media direction name as defined in the SDP.
  74. */
  75. get direction() {
  76. return this.mLine.direction;
  77. }
  78. /**
  79. * Modifies the direction of the underlying media description.
  80. * @param {string} direction the new direction to be set
  81. */
  82. set direction(direction) {
  83. this.mLine.direction = direction;
  84. }
  85. /**
  86. * Exposes the SSRC group array of the underlying media description object.
  87. * @return {Array.<Object>}
  88. */
  89. get ssrcGroups() {
  90. if (!this.mLine.ssrcGroups) {
  91. this.mLine.ssrcGroups = [];
  92. }
  93. return this.mLine.ssrcGroups;
  94. }
  95. /**
  96. * Modifies the SSRC groups array of the underlying media description
  97. * object.
  98. * @param {Array.<Object>} ssrcGroups
  99. */
  100. set ssrcGroups(ssrcGroups) {
  101. this.mLine.ssrcGroups = ssrcGroups;
  102. }
  103. /**
  104. * Obtains value from SSRC attribute.
  105. * @param {number} ssrcNumber the SSRC number for which attribute is to be
  106. * found
  107. * @param {string} attrName the name of the SSRC attribute to be found.
  108. * @return {string|undefined} the value of SSRC attribute or
  109. * <tt>undefined</tt> if no such attribute exists.
  110. */
  111. getSSRCAttrValue(ssrcNumber, attrName) {
  112. const attribute = this.ssrcs.find(
  113. ssrcObj => ssrcObj.id === ssrcNumber
  114. && ssrcObj.attribute === attrName);
  115. return attribute && attribute.value;
  116. }
  117. /**
  118. * Removes all attributes for given SSRC number.
  119. * @param {number} ssrcNum the SSRC number for which all attributes will be
  120. * removed.
  121. */
  122. removeSSRC(ssrcNum) {
  123. if (!this.mLine.ssrcs || !this.mLine.ssrcs.length) {
  124. return;
  125. }
  126. this.mLine.ssrcs
  127. = this.mLine.ssrcs.filter(ssrcObj => ssrcObj.id !== ssrcNum);
  128. }
  129. /**
  130. * Adds SSRC attribute
  131. * @param {object} ssrcObj the SSRC attribute object as defined in
  132. * the 'sdp-transform' lib.
  133. */
  134. addSSRCAttribute(ssrcObj) {
  135. this.ssrcs.push(ssrcObj);
  136. }
  137. /**
  138. * Finds a SSRC group matching both semantics and SSRCs in order.
  139. * @param {string} semantics the name of the semantics
  140. * @param {string} [ssrcs] group SSRCs as a string (like it's defined in
  141. * SSRC group object of the 'sdp-transform' lib) e.g. "1232546 342344 25434"
  142. * @return {object|undefined} the SSRC group object or <tt>undefined</tt> if
  143. * not found.
  144. */
  145. findGroup(semantics, ssrcs) {
  146. return this.ssrcGroups.find(
  147. group =>
  148. group.semantics === semantics
  149. && (!ssrcs || ssrcs === group.ssrcs));
  150. }
  151. /**
  152. * Finds all groups matching given semantic's name.
  153. * @param {string} semantics the name of the semantics
  154. * @return {Array.<object>} an array of SSRC group objects as defined by
  155. * the 'sdp-transform' lib.
  156. */
  157. findGroups(semantics) {
  158. return this.ssrcGroups.filter(
  159. group => group.semantics === semantics);
  160. }
  161. /**
  162. * Finds all groups matching given semantic's name and group's primary SSRC.
  163. * @param {string} semantics the name of the semantics
  164. * @param {number} primarySSRC the primary SSRC number to be matched
  165. * @return {Object} SSRC group object as defined by the 'sdp-transform' lib.
  166. */
  167. findGroupByPrimarySSRC(semantics, primarySSRC) {
  168. return this.ssrcGroups.find(
  169. group => group.semantics === semantics
  170. && parsePrimarySSRC(group) === primarySSRC);
  171. }
  172. /**
  173. * Gets the SSRC count for the underlying media description.
  174. * @return {number}
  175. */
  176. getSSRCCount() {
  177. return _getSSRCCount(this.mLine);
  178. }
  179. /**
  180. * Checks whether the underlying media description contains any SSRC groups.
  181. * @return {boolean} <tt>true</tt> if there are any SSRC groups or
  182. * <tt>false</tt> otherwise.
  183. */
  184. containsAnySSRCGroups() {
  185. return this.mLine.ssrcGroups !== undefined;
  186. }
  187. /**
  188. * Finds the primary video SSRC.
  189. * @returns {number|undefined} the primary video ssrc
  190. * @throws Error if the underlying media description is not a video
  191. */
  192. getPrimaryVideoSsrc() {
  193. const mediaType = this.mLine.type;
  194. if (mediaType !== 'video') {
  195. throw new Error(
  196. `getPrimarySsrc doesn't work with '${mediaType}'`);
  197. }
  198. const numSsrcs = _getSSRCCount(this.mLine);
  199. if (numSsrcs === 1) {
  200. // Not using "ssrcs" getter on purpose here
  201. return this.mLine.ssrcs[0].id;
  202. }
  203. // Look for a SIM, FID, or FEC-FR group
  204. if (this.mLine.ssrcGroups) {
  205. const simGroup = this.findGroup(SSRC_GROUP_SEMANTICS.SIM);
  206. if (simGroup) {
  207. return parsePrimarySSRC(simGroup);
  208. }
  209. const fidGroup = this.findGroup(SSRC_GROUP_SEMANTICS.FID);
  210. if (fidGroup) {
  211. return parsePrimarySSRC(fidGroup);
  212. }
  213. const fecGroup = this.findGroup('FEC-FR');
  214. if (fecGroup) {
  215. return parsePrimarySSRC(fecGroup);
  216. }
  217. }
  218. }
  219. /**
  220. * Obtains RTX SSRC from the underlying video description (the
  221. * secondary SSRC of the first "FID" group found)
  222. * @param {number} primarySsrc the video ssrc for which to find the
  223. * corresponding rtx ssrc
  224. * @returns {number|undefined} the rtx ssrc (or undefined if there isn't
  225. * one)
  226. */
  227. getRtxSSRC(primarySsrc) {
  228. const fidGroup = this.findGroupByPrimarySSRC(SSRC_GROUP_SEMANTICS.FID, primarySsrc);
  229. return fidGroup && parseSecondarySSRC(fidGroup);
  230. }
  231. /**
  232. * Obtains all SSRCs contained in the underlying media description.
  233. * @return {Array.<number>} an array with all SSRC as numbers.
  234. */
  235. getSSRCs() {
  236. return this.ssrcs
  237. .map(ssrcInfo => ssrcInfo.id)
  238. .filter((ssrc, index, array) => array.indexOf(ssrc) === index);
  239. }
  240. /**
  241. * Obtains primary video SSRCs.
  242. * @return {Array.<number>} an array of all primary video SSRCs as numbers.
  243. * @throws Error if the wrapped media description is not a video.
  244. */
  245. getPrimaryVideoSSRCs() {
  246. const mediaType = this.mLine.type;
  247. if (mediaType !== 'video') {
  248. throw new Error(
  249. `getPrimaryVideoSSRCs doesn't work with ${mediaType}`);
  250. }
  251. const videoSSRCs = this.getSSRCs();
  252. for (const ssrcGroupInfo of this.ssrcGroups) {
  253. // Right now, FID and FEC-FR groups are the only ones we parse to
  254. // disqualify streams. If/when others arise we'll
  255. // need to add support for them here
  256. if (ssrcGroupInfo.semantics === SSRC_GROUP_SEMANTICS.FID
  257. || ssrcGroupInfo.semantics === 'FEC-FR') {
  258. // secondary streams should be filtered out
  259. const secondarySsrc = parseSecondarySSRC(ssrcGroupInfo);
  260. videoSSRCs.splice(
  261. videoSSRCs.indexOf(secondarySsrc), 1);
  262. }
  263. }
  264. return videoSSRCs;
  265. }
  266. /**
  267. * Removes all SSRC groups which contain given SSRC number at any position.
  268. * @param {number} ssrc the SSRC for which all matching groups are to be
  269. * removed.
  270. */
  271. removeGroupsWithSSRC(ssrc) {
  272. if (!this.mLine.ssrcGroups) {
  273. return;
  274. }
  275. this.mLine.ssrcGroups = this.mLine.ssrcGroups
  276. .filter(groupInfo => groupInfo.ssrcs.indexOf(`${ssrc}`) === -1);
  277. }
  278. /**
  279. * Removes groups that match given semantics.
  280. * @param {string} semantics e.g. "SIM" or "FID"
  281. */
  282. removeGroupsBySemantics(semantics) {
  283. if (!this.mLine.ssrcGroups) {
  284. return;
  285. }
  286. this.mLine.ssrcGroups
  287. = this.mLine.ssrcGroups
  288. .filter(groupInfo => groupInfo.semantics !== semantics);
  289. }
  290. /**
  291. * Adds given SSRC group to this media description.
  292. * @param {object} group the SSRC group object as defined by
  293. * the 'sdp-transform' lib.
  294. */
  295. addSSRCGroup(group) {
  296. this.ssrcGroups.push(group);
  297. }
  298. }
  299. /**
  300. * Utility class for SDP manipulation using the 'sdp-transform' library.
  301. *
  302. * Typical use usage scenario:
  303. *
  304. * const transformer = new SdpTransformWrap(rawSdp);
  305. * const videoMLine = transformer.selectMedia('video);
  306. * if (videoMLine) {
  307. * videoMLiner.addSSRCAttribute({
  308. * id: 2342343,
  309. * attribute: "cname",
  310. * value: "someCname"
  311. * });
  312. * rawSdp = transformer.toRawSdp();
  313. * }
  314. */
  315. export class SdpTransformWrap {
  316. /**
  317. * Creates new instance and parses the raw SDP into objects using
  318. * 'sdp-transform' lib.
  319. * @param {string} rawSDP the SDP in raw text format.
  320. */
  321. constructor(rawSDP) {
  322. this.parsedSDP = transform.parse(rawSDP);
  323. }
  324. /**
  325. * Selects all the m-lines from the SDP for a given media type.
  326. *
  327. * @param {string} mediaType the name of the media e.g. 'audio', 'video', 'data'.
  328. * @return {MLineWrap|null} return {@link MLineWrap} instance for the media line or <tt>null</tt> if not found. The
  329. * object returned references the underlying SDP state held by this <tt>SdpTransformWrap</tt> instance (it's not a
  330. * copy).
  331. */
  332. selectMedia(mediaType) {
  333. const selectedMLines = this.parsedSDP.media
  334. .filter(mLine => mLine.type === mediaType)
  335. .map(mLine => new MLineWrap(mLine));
  336. return selectedMLines ?? null;
  337. }
  338. /**
  339. * Converts the currently stored SDP state in this instance to raw text SDP
  340. * format.
  341. * @return {string}
  342. */
  343. toRawSDP() {
  344. return transform.write(this.parsedSDP);
  345. }
  346. }