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.

SDPUtil.js 25KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775
  1. import { getLogger } from '@jitsi/logger';
  2. const logger = getLogger('modules/sdp/SDPUtil');
  3. import { CodecMimeType } from '../../service/RTC/CodecMimeType';
  4. import { MediaDirection } from '../../service/RTC/MediaDirection';
  5. import { SSRC_GROUP_SEMANTICS } from '../../service/RTC/StandardVideoQualitySettings';
  6. import browser from '../browser';
  7. import RandomUtil from '../util/RandomUtil';
  8. const SDPUtil = {
  9. filterSpecialChars(text) {
  10. // XXX Neither one of the falsy values (e.g. null, undefined, false,
  11. // "", etc.) "contain" special chars.
  12. // eslint-disable-next-line no-useless-escape
  13. return text ? text.replace(/[\\\/\{,\}\+]/g, '') : text;
  14. },
  15. iceparams(mediadesc, sessiondesc) {
  16. let data = null;
  17. let pwd, ufrag;
  18. if ((ufrag = SDPUtil.findLine(mediadesc, 'a=ice-ufrag:', sessiondesc))
  19. && (pwd
  20. = SDPUtil.findLine(
  21. mediadesc,
  22. 'a=ice-pwd:',
  23. sessiondesc))) {
  24. data = {
  25. ufrag: SDPUtil.parseICEUfrag(ufrag),
  26. pwd: SDPUtil.parseICEPwd(pwd)
  27. };
  28. }
  29. return data;
  30. },
  31. parseICEUfrag(line) {
  32. return line.substring(12);
  33. },
  34. buildICEUfrag(frag) {
  35. return `a=ice-ufrag:${frag}`;
  36. },
  37. parseICEPwd(line) {
  38. return line.substring(10);
  39. },
  40. buildICEPwd(pwd) {
  41. return `a=ice-pwd:${pwd}`;
  42. },
  43. parseMID(line) {
  44. return line.substring(6);
  45. },
  46. /**
  47. * Finds the MSID attribute in the given array of SSRC attribute lines and returns the value.
  48. *
  49. * @param {string[]} ssrcLines - an array of lines similar to 'a:213123 msid:stream-id track-id'.
  50. * @returns {undefined|string}
  51. */
  52. parseMSIDAttribute(ssrcLines) {
  53. const msidLine = ssrcLines.find(line => line.indexOf(' msid:') > 0);
  54. if (!msidLine) {
  55. return undefined;
  56. }
  57. const v = msidLine.substring(msidLine.indexOf(' msid:') + 6 /* the length of ' msid:' */);
  58. return SDPUtil.filterSpecialChars(v);
  59. },
  60. parseMLine(line) {
  61. const data = {};
  62. const parts = line.substring(2).split(' ');
  63. data.media = parts.shift();
  64. data.port = parts.shift();
  65. data.proto = parts.shift();
  66. if (parts[parts.length - 1] === '') { // trailing whitespace
  67. parts.pop();
  68. }
  69. data.fmt = parts;
  70. return data;
  71. },
  72. buildMLine(mline) {
  73. return (
  74. `m=${mline.media} ${mline.port} ${mline.proto} ${
  75. mline.fmt.join(' ')}`);
  76. },
  77. parseRTPMap(line) {
  78. const data = {};
  79. let parts = line.substring(9).split(' ');
  80. data.id = parts.shift();
  81. parts = parts[0].split('/');
  82. data.name = parts.shift();
  83. data.clockrate = parts.shift();
  84. data.channels = parts.length ? parts.shift() : '1';
  85. return data;
  86. },
  87. /**
  88. * Parses SDP line "a=sctpmap:..." and extracts SCTP port from it.
  89. * @param line eg. "a=sctpmap:5000 webrtc-datachannel"
  90. * @returns [SCTP port number, protocol, streams]
  91. */
  92. parseSCTPMap(line) {
  93. const parts = line.substring(10).split(' ');
  94. const sctpPort = parts[0];
  95. const protocol = parts[1];
  96. // Stream count is optional
  97. const streamCount = parts.length > 2 ? parts[2] : null;
  98. return [ sctpPort, protocol, streamCount ];// SCTP port
  99. },
  100. parseSCTPPort(line) {
  101. return line.substring(12);
  102. },
  103. buildRTPMap(el) {
  104. let line
  105. = `a=rtpmap:${el.getAttribute('id')} ${el.getAttribute('name')}/${
  106. el.getAttribute('clockrate')}`;
  107. if (el.getAttribute('channels')
  108. && el.getAttribute('channels') !== '1') {
  109. line += `/${el.getAttribute('channels')}`;
  110. }
  111. return line;
  112. },
  113. parseCrypto(line) {
  114. const data = {};
  115. const parts = line.substring(9).split(' ');
  116. data.tag = parts.shift();
  117. data['crypto-suite'] = parts.shift();
  118. data['key-params'] = parts.shift();
  119. if (parts.length) {
  120. data['session-params'] = parts.join(' ');
  121. }
  122. return data;
  123. },
  124. parseFingerprint(line) { // RFC 4572
  125. const data = {};
  126. const parts = line.substring(14).split(' ');
  127. data.hash = parts.shift();
  128. data.fingerprint = parts.shift();
  129. // TODO assert that fingerprint satisfies 2UHEX *(":" 2UHEX) ?
  130. return data;
  131. },
  132. parseFmtp(line) {
  133. const data = [];
  134. let parts = line.split(' ');
  135. parts.shift();
  136. parts = parts.join(' ').split(';');
  137. for (let i = 0; i < parts.length; i++) {
  138. let key = parts[i].split('=')[0];
  139. while (key.length && key[0] === ' ') {
  140. key = key.substring(1);
  141. }
  142. const value = parts[i].split('=')[1];
  143. if (key && value) {
  144. data.push({ name: key,
  145. value });
  146. } else if (key) {
  147. // rfc 4733 (DTMF) style stuff
  148. data.push({ name: '',
  149. value: key });
  150. }
  151. }
  152. return data;
  153. },
  154. parseICECandidate(line) {
  155. const candidate = {};
  156. const elems = line.split(' ');
  157. candidate.foundation = elems[0].substring(12);
  158. candidate.component = elems[1];
  159. candidate.protocol = elems[2].toLowerCase();
  160. candidate.priority = elems[3];
  161. candidate.ip = elems[4];
  162. candidate.port = elems[5];
  163. // elems[6] => "typ"
  164. candidate.type = elems[7];
  165. candidate.generation = 0; // default value, may be overwritten below
  166. for (let i = 8; i < elems.length; i += 2) {
  167. switch (elems[i]) {
  168. case 'raddr':
  169. candidate['rel-addr'] = elems[i + 1];
  170. break;
  171. case 'rport':
  172. candidate['rel-port'] = elems[i + 1];
  173. break;
  174. case 'generation':
  175. candidate.generation = elems[i + 1];
  176. break;
  177. case 'tcptype':
  178. candidate.tcptype = elems[i + 1];
  179. break;
  180. default: // TODO
  181. logger.debug(
  182. `parseICECandidate not translating "${
  183. elems[i]}" = "${elems[i + 1]}"`);
  184. }
  185. }
  186. candidate.network = '1';
  187. // not applicable to SDP -- FIXME: should be unique, not just random
  188. // eslint-disable-next-line newline-per-chained-call
  189. candidate.id = Math.random().toString(36).substr(2, 10);
  190. return candidate;
  191. },
  192. buildICECandidate(cand) {
  193. let line = [
  194. `a=candidate:${cand.foundation}`,
  195. cand.component,
  196. cand.protocol,
  197. cand.priority,
  198. cand.ip,
  199. cand.port,
  200. 'typ',
  201. cand.type
  202. ].join(' ');
  203. line += ' ';
  204. switch (cand.type) {
  205. case 'srflx':
  206. case 'prflx':
  207. case 'relay':
  208. if (cand.hasOwnAttribute('rel-addr')
  209. && cand.hasOwnAttribute('rel-port')) {
  210. line += 'raddr';
  211. line += ' ';
  212. line += cand['rel-addr'];
  213. line += ' ';
  214. line += 'rport';
  215. line += ' ';
  216. line += cand['rel-port'];
  217. line += ' ';
  218. }
  219. break;
  220. }
  221. if (cand.hasOwnAttribute('tcptype')) {
  222. line += 'tcptype';
  223. line += ' ';
  224. line += cand.tcptype;
  225. line += ' ';
  226. }
  227. line += 'generation';
  228. line += ' ';
  229. line += cand.hasOwnAttribute('generation') ? cand.generation : '0';
  230. return line;
  231. },
  232. parseSSRC(desc) {
  233. // proprietary mapping of a=ssrc lines
  234. // TODO: see "Jingle RTP Source Description" by Juberti and P. Thatcher
  235. // on google docs and parse according to that
  236. const data = new Map();
  237. const lines = desc.split('\r\n');
  238. for (let i = 0; i < lines.length; i++) {
  239. if (lines[i].substring(0, 7) === 'a=ssrc:') {
  240. // FIXME: Use regex to smartly find the ssrc.
  241. const ssrc = lines[i].split('a=ssrc:')[1].split(' ')[0];
  242. if (!data.get(ssrc)) {
  243. data.set(ssrc, []);
  244. }
  245. data.get(ssrc).push(lines[i]);
  246. }
  247. }
  248. return data;
  249. },
  250. /**
  251. * Parses the 'a=ssrc-group' line.
  252. *
  253. * @param {string} line - The media line to parse.
  254. * @returns {object}
  255. */
  256. parseSSRCGroupLine(line) {
  257. const parts = line.substr(13).split(' ');
  258. return {
  259. semantics: parts.shift(),
  260. ssrcs: parts
  261. };
  262. },
  263. /**
  264. * Gets the source name out of the name attribute "a=ssrc:254321 name:name1".
  265. *
  266. * @param {string[]} ssrcLines
  267. * @returns {string | undefined}
  268. */
  269. parseSourceNameLine(ssrcLines) {
  270. const sourceNameLine = ssrcLines.find(ssrcSdpLine => ssrcSdpLine.indexOf(' name:') > 0);
  271. // Everything past the "name:" part
  272. return sourceNameLine?.substring(sourceNameLine.indexOf(' name:') + 6);
  273. },
  274. /**
  275. * Parse the "videoType" attribute encoded in a set of SSRC attributes (e.g.
  276. * "a=ssrc:1234 videoType:desktop")
  277. *
  278. * @param {string[]} ssrcLines
  279. * @returns {string | undefined}
  280. */
  281. parseVideoTypeLine(ssrcLines) {
  282. const s = ' videoType:';
  283. const videoTypeLine = ssrcLines.find(ssrcSdpLine => ssrcSdpLine.indexOf(s) > 0);
  284. return videoTypeLine?.substring(videoTypeLine.indexOf(s) + s.length);
  285. },
  286. parseRTCPFB(line) {
  287. const parts = line.substr(10).split(' ');
  288. const data = {};
  289. data.pt = parts.shift();
  290. data.type = parts.shift();
  291. data.params = parts;
  292. return data;
  293. },
  294. parseExtmap(line) {
  295. const parts = line.substr(9).split(' ');
  296. const data = {};
  297. data.value = parts.shift();
  298. if (data.value.indexOf('/') === -1) {
  299. data.direction = 'both';
  300. } else {
  301. data.direction = data.value.substr(data.value.indexOf('/') + 1);
  302. data.value = data.value.substr(0, data.value.indexOf('/'));
  303. }
  304. data.uri = parts.shift();
  305. data.params = parts;
  306. return data;
  307. },
  308. findLine(haystack, needle, sessionpart) {
  309. let lines = haystack.split('\r\n');
  310. for (let i = 0; i < lines.length; i++) {
  311. if (lines[i].substring(0, needle.length) === needle) {
  312. return lines[i];
  313. }
  314. }
  315. if (!sessionpart) {
  316. return false;
  317. }
  318. // search session part
  319. lines = sessionpart.split('\r\n');
  320. for (let j = 0; j < lines.length; j++) {
  321. if (lines[j].substring(0, needle.length) === needle) {
  322. return lines[j];
  323. }
  324. }
  325. return false;
  326. },
  327. findLines(haystack, needle, sessionpart) {
  328. let lines = haystack.split('\r\n');
  329. const needles = [];
  330. for (let i = 0; i < lines.length; i++) {
  331. if (lines[i].substring(0, needle.length) === needle) {
  332. needles.push(lines[i]);
  333. }
  334. }
  335. if (needles.length || !sessionpart) {
  336. return needles;
  337. }
  338. // search session part
  339. lines = sessionpart.split('\r\n');
  340. for (let j = 0; j < lines.length; j++) {
  341. if (lines[j].substring(0, needle.length) === needle) {
  342. needles.push(lines[j]);
  343. }
  344. }
  345. return needles;
  346. },
  347. candidateToJingle(line) {
  348. // a=candidate:2979166662 1 udp 2113937151 192.168.2.100 57698 typ host
  349. // generation 0
  350. // <candidate component=... foundation=... generation=... id=...
  351. // ip=... network=... port=... priority=... protocol=... type=.../>
  352. if (line.indexOf('candidate:') === 0) {
  353. // eslint-disable-next-line no-param-reassign
  354. line = `a=${line}`;
  355. } else if (line.substring(0, 12) !== 'a=candidate:') {
  356. logger.warn(
  357. 'parseCandidate called with a line that is not a candidate'
  358. + ' line');
  359. logger.warn(line);
  360. return null;
  361. }
  362. if (line.substring(line.length - 2) === '\r\n') { // chomp it
  363. // eslint-disable-next-line no-param-reassign
  364. line = line.substring(0, line.length - 2);
  365. }
  366. const candidate = {};
  367. const elems = line.split(' ');
  368. if (elems[6] !== 'typ') {
  369. logger.warn('did not find typ in the right place');
  370. logger.warn(line);
  371. return null;
  372. }
  373. candidate.foundation = elems[0].substring(12);
  374. candidate.component = elems[1];
  375. candidate.protocol = elems[2].toLowerCase();
  376. candidate.priority = elems[3];
  377. candidate.ip = elems[4];
  378. candidate.port = elems[5];
  379. // elems[6] => "typ"
  380. candidate.type = elems[7];
  381. candidate.generation = '0'; // default, may be overwritten below
  382. for (let i = 8; i < elems.length; i += 2) {
  383. switch (elems[i]) {
  384. case 'raddr':
  385. candidate['rel-addr'] = elems[i + 1];
  386. break;
  387. case 'rport':
  388. candidate['rel-port'] = elems[i + 1];
  389. break;
  390. case 'generation':
  391. candidate.generation = elems[i + 1];
  392. break;
  393. case 'tcptype':
  394. candidate.tcptype = elems[i + 1];
  395. break;
  396. default: // TODO
  397. logger.debug(`not translating "${elems[i]}" = "${elems[i + 1]}"`);
  398. }
  399. }
  400. candidate.network = '1';
  401. // not applicable to SDP -- FIXME: should be unique, not just random
  402. // eslint-disable-next-line newline-per-chained-call
  403. candidate.id = Math.random().toString(36).substr(2, 10);
  404. return candidate;
  405. },
  406. candidateFromJingle(cand) {
  407. let line = 'a=candidate:';
  408. line += cand.getAttribute('foundation');
  409. line += ' ';
  410. line += cand.getAttribute('component');
  411. line += ' ';
  412. let protocol = cand.getAttribute('protocol');
  413. // use tcp candidates for FF
  414. if (browser.isFirefox() && protocol.toLowerCase() === 'ssltcp') {
  415. protocol = 'tcp';
  416. }
  417. line += protocol; // .toUpperCase(); // chrome M23 doesn't like this
  418. line += ' ';
  419. line += cand.getAttribute('priority');
  420. line += ' ';
  421. line += cand.getAttribute('ip');
  422. line += ' ';
  423. line += cand.getAttribute('port');
  424. line += ' ';
  425. line += 'typ';
  426. line += ` ${cand.getAttribute('type')}`;
  427. line += ' ';
  428. switch (cand.getAttribute('type')) {
  429. case 'srflx':
  430. case 'prflx':
  431. case 'relay':
  432. if (cand.getAttribute('rel-addr')
  433. && cand.getAttribute('rel-port')) {
  434. line += 'raddr';
  435. line += ' ';
  436. line += cand.getAttribute('rel-addr');
  437. line += ' ';
  438. line += 'rport';
  439. line += ' ';
  440. line += cand.getAttribute('rel-port');
  441. line += ' ';
  442. }
  443. break;
  444. }
  445. if (protocol.toLowerCase() === 'tcp') {
  446. line += 'tcptype';
  447. line += ' ';
  448. line += cand.getAttribute('tcptype');
  449. line += ' ';
  450. }
  451. line += 'generation';
  452. line += ' ';
  453. line += cand.getAttribute('generation') || '0';
  454. return `${line}\r\n`;
  455. },
  456. /**
  457. * Parse the 'most' primary video ssrc from the given m line
  458. * @param {object} mLine object as parsed from transform.parse
  459. * @return {number} the primary video ssrc from the given m line
  460. */
  461. parsePrimaryVideoSsrc(videoMLine) {
  462. const numSsrcs = videoMLine.ssrcs
  463. .map(ssrcInfo => ssrcInfo.id)
  464. .filter((ssrc, index, array) => array.indexOf(ssrc) === index)
  465. .length;
  466. const numGroups
  467. = (videoMLine.ssrcGroups && videoMLine.ssrcGroups.length) || 0;
  468. if (numSsrcs > 1 && numGroups === 0) {
  469. // Ambiguous, can't figure out the primary
  470. return;
  471. }
  472. let primarySsrc = null;
  473. if (numSsrcs === 1) {
  474. primarySsrc = videoMLine.ssrcs[0].id;
  475. } else if (numSsrcs === 2) {
  476. // Can figure it out if there's an FID group
  477. const fidGroup
  478. = videoMLine.ssrcGroups.find(
  479. group => group.semantics === SSRC_GROUP_SEMANTICS.FID);
  480. if (fidGroup) {
  481. primarySsrc = fidGroup.ssrcs.split(' ')[0];
  482. }
  483. } else if (numSsrcs >= 3) {
  484. // Can figure it out if there's a sim group
  485. const simGroup
  486. = videoMLine.ssrcGroups.find(
  487. group => group.semantics === SSRC_GROUP_SEMANTICS.SIM);
  488. if (simGroup) {
  489. primarySsrc = simGroup.ssrcs.split(' ')[0];
  490. }
  491. }
  492. return primarySsrc;
  493. },
  494. /**
  495. * Generate an ssrc
  496. * @returns {number} an ssrc
  497. */
  498. generateSsrc() {
  499. return RandomUtil.randomInt(1, 0xffffffff);
  500. },
  501. /**
  502. * Get an attribute for the given ssrc with the given attributeName
  503. * from the given mline
  504. * @param {object} mLine an mLine object as parsed from transform.parse
  505. * @param {number} ssrc the ssrc for which an attribute is desired
  506. * @param {string} attributeName the name of the desired attribute
  507. * @returns {string} the value corresponding to the given ssrc
  508. * and attributeName
  509. */
  510. getSsrcAttribute(mLine, ssrc, attributeName) {
  511. for (let i = 0; i < mLine.ssrcs.length; ++i) {
  512. const ssrcLine = mLine.ssrcs[i];
  513. if (ssrcLine.id === ssrc
  514. && ssrcLine.attribute === attributeName) {
  515. return ssrcLine.value;
  516. }
  517. }
  518. },
  519. /**
  520. * Parses the ssrcs from the group sdp line and
  521. * returns them as a list of numbers
  522. * @param {object} the ssrcGroup object as parsed from
  523. * sdp-transform
  524. * @returns {list<number>} a list of the ssrcs in the group
  525. * parsed as numbers
  526. */
  527. parseGroupSsrcs(ssrcGroup) {
  528. return ssrcGroup
  529. .ssrcs
  530. .split(' ')
  531. .map(ssrcStr => parseInt(ssrcStr, 10));
  532. },
  533. /**
  534. * Get the mline of the given type from the given sdp
  535. * @param {object} sdp sdp as parsed from transform.parse
  536. * @param {string} type the type of the desired mline (e.g. "video")
  537. * @returns {object} a media object
  538. */
  539. getMedia(sdp, type) {
  540. return sdp.media.find(m => m.type === type);
  541. },
  542. /**
  543. * Extracts the ICE username fragment from an SDP string.
  544. * @param {string} sdp the SDP in raw text format
  545. */
  546. getUfrag(sdp) {
  547. const ufragLines
  548. = sdp.split('\n').filter(line => line.startsWith('a=ice-ufrag:'));
  549. if (ufragLines.length > 0) {
  550. return ufragLines[0].substr('a=ice-ufrag:'.length);
  551. }
  552. },
  553. /**
  554. * Sets the given codecName as the preferred codec by moving it to the beginning
  555. * of the payload types list (modifies the given mline in place). All instances
  556. * of the codec are moved up.
  557. * @param {object} mLine the mline object from an sdp as parsed by transform.parse.
  558. * @param {string} codecName the name of the preferred codec.
  559. * @param {boolean} sortPayloadTypes whether the payloadtypes need to be sorted for a given codec.
  560. */
  561. preferCodec(mline, codecName, sortPayloadTypes = false) {
  562. if (!mline || !codecName) {
  563. return;
  564. }
  565. const matchingPayloadTypes = mline.rtp
  566. .filter(rtp => rtp.codec && rtp.codec.toLowerCase() === codecName.toLowerCase())
  567. .map(rtp => rtp.payload);
  568. if (matchingPayloadTypes) {
  569. if (sortPayloadTypes && codecName === CodecMimeType.H264) {
  570. // Move all the H.264 codecs with packetization-mode=0 to top of the list.
  571. const payloadsWithMode0 = matchingPayloadTypes.filter(payload => {
  572. const fmtp = mline.fmtp.find(item => item.payload === payload);
  573. if (fmtp) {
  574. return fmtp.config.includes('packetization-mode=0');
  575. }
  576. return false;
  577. });
  578. for (const pt of payloadsWithMode0.reverse()) {
  579. const idx = matchingPayloadTypes.findIndex(payloadType => payloadType === pt);
  580. if (idx >= 0) {
  581. matchingPayloadTypes.splice(idx, 1);
  582. matchingPayloadTypes.unshift(pt);
  583. }
  584. }
  585. }
  586. // Call toString() on payloads to get around an issue within SDPTransform that sets
  587. // payloads as a number, instead of a string, when there is only one payload.
  588. const payloadTypes
  589. = mline.payloads
  590. .toString()
  591. .split(' ')
  592. .map(p => parseInt(p, 10));
  593. for (const pt of matchingPayloadTypes.reverse()) {
  594. const payloadIndex = payloadTypes.indexOf(pt);
  595. payloadTypes.splice(payloadIndex, 1);
  596. payloadTypes.unshift(pt);
  597. }
  598. mline.payloads = payloadTypes.join(' ');
  599. } else {
  600. logger.error(`No matching RTP payload type found for ${codecName}, failed to set preferred codecs`);
  601. }
  602. },
  603. /**
  604. * Strips the given codec from the given mline. All related RTX payload
  605. * types are also stripped. If the resulting mline would have no codecs,
  606. * it's disabled.
  607. *
  608. * @param {object} mLine the mline object from an sdp as parsed by transform.parse.
  609. * @param {string} codecName the name of the codec which will be stripped.
  610. * @param {boolean} highProfile determines if only the high profile codec needs to be stripped from the sdp for a
  611. * given codec type.
  612. */
  613. stripCodec(mLine, codecName, highProfile = false) {
  614. if (!mLine || !codecName) {
  615. return;
  616. }
  617. const highProfileCodecs = new Map();
  618. let removePts = [];
  619. for (const rtp of mLine.rtp) {
  620. if (rtp.codec && rtp.codec.toLowerCase() === codecName.toLowerCase()) {
  621. if (highProfile) {
  622. highProfileCodecs.set(rtp.payload, rtp.codec);
  623. } else {
  624. removePts.push(rtp.payload);
  625. }
  626. }
  627. }
  628. if (highProfile) {
  629. removePts = mLine.fmtp
  630. .filter(item => {
  631. const codec = highProfileCodecs.get(item.payload);
  632. if (codec) {
  633. return codec.toLowerCase() === CodecMimeType.VP9
  634. ? !item.config.includes('profile-id=0')
  635. : !item.config.includes('profile-level-id=42');
  636. }
  637. return false;
  638. })
  639. .map(item => item.payload);
  640. }
  641. if (removePts.length > 0) {
  642. // We also need to remove the payload types that are related to RTX
  643. // for the codecs we want to disable.
  644. const rtxApts = removePts.map(item => `apt=${item}`);
  645. const rtxPts = mLine.fmtp.filter(
  646. item => rtxApts.indexOf(item.config) !== -1);
  647. removePts.push(...rtxPts.map(item => item.payload));
  648. // Call toString() on payloads to get around an issue within
  649. // SDPTransform that sets payloads as a number, instead of a string,
  650. // when there is only one payload.
  651. const allPts = mLine.payloads
  652. .toString()
  653. .split(' ')
  654. .map(Number);
  655. const keepPts = allPts.filter(pt => removePts.indexOf(pt) === -1);
  656. if (keepPts.length === 0) {
  657. // There are no other codecs, disable the stream.
  658. mLine.port = 0;
  659. mLine.direction = MediaDirection.INACTIVE;
  660. mLine.payloads = '*';
  661. } else {
  662. mLine.payloads = keepPts.join(' ');
  663. }
  664. mLine.rtp = mLine.rtp.filter(
  665. item => keepPts.indexOf(item.payload) !== -1);
  666. mLine.fmtp = mLine.fmtp.filter(
  667. item => keepPts.indexOf(item.payload) !== -1);
  668. if (mLine.rtcpFb) {
  669. mLine.rtcpFb = mLine.rtcpFb.filter(
  670. item => keepPts.indexOf(item.payload) !== -1);
  671. }
  672. }
  673. }
  674. };
  675. export default SDPUtil;