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

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