您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

SdpTransformUtil.js 13KB

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