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

SdpTransformUtil.js 12KB

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