Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

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