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.

SdpTransformUtil.js 12KB

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