modified lib-jitsi-meet dev repo
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486
  1. import {getLogger} from "jitsi-meet-logger";
  2. const logger = getLogger(__filename);
  3. import RandomUtil from "../util/RandomUtil";
  4. var RTCBrowserType = require("../RTC/RTCBrowserType");
  5. var SDPUtil = {
  6. filter_special_chars: function (text) {
  7. // XXX Neither one of the falsy values (e.g. null, undefined, false,
  8. // "", etc.) "contain" special chars.
  9. return text ? text.replace(/[\\\/\{,\}\+]/g, "") : text;
  10. },
  11. iceparams: function (mediadesc, sessiondesc) {
  12. var data = null;
  13. var ufrag, pwd;
  14. if ((ufrag = SDPUtil.find_line(mediadesc, 'a=ice-ufrag:', sessiondesc))
  15. && (pwd = SDPUtil.find_line(mediadesc, 'a=ice-pwd:', sessiondesc))) {
  16. data = {
  17. ufrag: SDPUtil.parse_iceufrag(ufrag),
  18. pwd: SDPUtil.parse_icepwd(pwd)
  19. };
  20. }
  21. return data;
  22. },
  23. parse_iceufrag: function (line) {
  24. return line.substring(12);
  25. },
  26. build_iceufrag: function (frag) {
  27. return 'a=ice-ufrag:' + frag;
  28. },
  29. parse_icepwd: function (line) {
  30. return line.substring(10);
  31. },
  32. build_icepwd: function (pwd) {
  33. return 'a=ice-pwd:' + pwd;
  34. },
  35. parse_mid: function (line) {
  36. return line.substring(6);
  37. },
  38. parse_mline: function (line) {
  39. var parts = line.substring(2).split(' '),
  40. data = {};
  41. data.media = parts.shift();
  42. data.port = parts.shift();
  43. data.proto = parts.shift();
  44. if (parts[parts.length - 1] === '') { // trailing whitespace
  45. parts.pop();
  46. }
  47. data.fmt = parts;
  48. return data;
  49. },
  50. build_mline: function (mline) {
  51. return 'm=' + mline.media + ' ' + mline.port + ' ' + mline.proto + ' ' + mline.fmt.join(' ');
  52. },
  53. parse_rtpmap: function (line) {
  54. var parts = line.substring(9).split(' '),
  55. data = {};
  56. data.id = parts.shift();
  57. parts = parts[0].split('/');
  58. data.name = parts.shift();
  59. data.clockrate = parts.shift();
  60. data.channels = parts.length ? parts.shift() : '1';
  61. return data;
  62. },
  63. /**
  64. * Parses SDP line "a=sctpmap:..." and extracts SCTP port from it.
  65. * @param line eg. "a=sctpmap:5000 webrtc-datachannel"
  66. * @returns [SCTP port number, protocol, streams]
  67. */
  68. parse_sctpmap: function (line)
  69. {
  70. var parts = line.substring(10).split(' ');
  71. var sctpPort = parts[0];
  72. var protocol = parts[1];
  73. // Stream count is optional
  74. var streamCount = parts.length > 2 ? parts[2] : null;
  75. return [sctpPort, protocol, streamCount];// SCTP port
  76. },
  77. build_rtpmap: function (el) {
  78. var line = 'a=rtpmap:' + el.getAttribute('id') + ' ' + el.getAttribute('name') + '/' + el.getAttribute('clockrate');
  79. if (el.getAttribute('channels') && el.getAttribute('channels') != '1') {
  80. line += '/' + el.getAttribute('channels');
  81. }
  82. return line;
  83. },
  84. parse_crypto: function (line) {
  85. var parts = line.substring(9).split(' '),
  86. data = {};
  87. data.tag = parts.shift();
  88. data['crypto-suite'] = parts.shift();
  89. data['key-params'] = parts.shift();
  90. if (parts.length) {
  91. data['session-params'] = parts.join(' ');
  92. }
  93. return data;
  94. },
  95. parse_fingerprint: function (line) { // RFC 4572
  96. var parts = line.substring(14).split(' '),
  97. data = {};
  98. data.hash = parts.shift();
  99. data.fingerprint = parts.shift();
  100. // TODO assert that fingerprint satisfies 2UHEX *(":" 2UHEX) ?
  101. return data;
  102. },
  103. parse_fmtp: function (line) {
  104. var parts = line.split(' '),
  105. i, key, value,
  106. data = [];
  107. parts.shift();
  108. parts = parts.join(' ').split(';');
  109. for (i = 0; i < parts.length; i++) {
  110. key = parts[i].split('=')[0];
  111. while (key.length && key[0] == ' ') {
  112. key = key.substring(1);
  113. }
  114. value = parts[i].split('=')[1];
  115. if (key && value) {
  116. data.push({name: key, value: value});
  117. } else if (key) {
  118. // rfc 4733 (DTMF) style stuff
  119. data.push({name: '', value: key});
  120. }
  121. }
  122. return data;
  123. },
  124. parse_icecandidate: function (line) {
  125. var candidate = {},
  126. elems = line.split(' ');
  127. candidate.foundation = elems[0].substring(12);
  128. candidate.component = elems[1];
  129. candidate.protocol = elems[2].toLowerCase();
  130. candidate.priority = elems[3];
  131. candidate.ip = elems[4];
  132. candidate.port = elems[5];
  133. // elems[6] => "typ"
  134. candidate.type = elems[7];
  135. candidate.generation = 0; // default value, may be overwritten below
  136. for (var i = 8; i < elems.length; i += 2) {
  137. switch (elems[i]) {
  138. case 'raddr':
  139. candidate['rel-addr'] = elems[i + 1];
  140. break;
  141. case 'rport':
  142. candidate['rel-port'] = elems[i + 1];
  143. break;
  144. case 'generation':
  145. candidate.generation = elems[i + 1];
  146. break;
  147. case 'tcptype':
  148. candidate.tcptype = elems[i + 1];
  149. break;
  150. default: // TODO
  151. logger.log('parse_icecandidate not translating "' + elems[i] + '" = "' + elems[i + 1] + '"');
  152. }
  153. }
  154. candidate.network = '1';
  155. candidate.id = Math.random().toString(36).substr(2, 10); // not applicable to SDP -- FIXME: should be unique, not just random
  156. return candidate;
  157. },
  158. build_icecandidate: function (cand) {
  159. var line = ['a=candidate:' + cand.foundation, cand.component, cand.protocol, cand.priority, cand.ip, cand.port, 'typ', cand.type].join(' ');
  160. line += ' ';
  161. switch (cand.type) {
  162. case 'srflx':
  163. case 'prflx':
  164. case 'relay':
  165. if (cand.hasOwnAttribute('rel-addr') && cand.hasOwnAttribute('rel-port')) {
  166. line += 'raddr';
  167. line += ' ';
  168. line += cand['rel-addr'];
  169. line += ' ';
  170. line += 'rport';
  171. line += ' ';
  172. line += cand['rel-port'];
  173. line += ' ';
  174. }
  175. break;
  176. }
  177. if (cand.hasOwnAttribute('tcptype')) {
  178. line += 'tcptype';
  179. line += ' ';
  180. line += cand.tcptype;
  181. line += ' ';
  182. }
  183. line += 'generation';
  184. line += ' ';
  185. line += cand.hasOwnAttribute('generation') ? cand.generation : '0';
  186. return line;
  187. },
  188. parse_ssrc: function (desc) {
  189. // proprietary mapping of a=ssrc lines
  190. // TODO: see "Jingle RTP Source Description" by Juberti and P. Thatcher on google docs
  191. // and parse according to that
  192. var lines = desc.split('\r\n'),
  193. data = {};
  194. for (var i = 0; i < lines.length; i++) {
  195. if (lines[i].substring(0, 7) == 'a=ssrc:') {
  196. var idx = lines[i].indexOf(' ');
  197. data[lines[i].substr(idx + 1).split(':', 2)[0]] = lines[i].substr(idx + 1).split(':', 2)[1];
  198. }
  199. }
  200. return data;
  201. },
  202. parse_rtcpfb: function (line) {
  203. var parts = line.substr(10).split(' ');
  204. var data = {};
  205. data.pt = parts.shift();
  206. data.type = parts.shift();
  207. data.params = parts;
  208. return data;
  209. },
  210. parse_extmap: function (line) {
  211. var parts = line.substr(9).split(' ');
  212. var data = {};
  213. data.value = parts.shift();
  214. if (data.value.indexOf('/') != -1) {
  215. data.direction = data.value.substr(data.value.indexOf('/') + 1);
  216. data.value = data.value.substr(0, data.value.indexOf('/'));
  217. } else {
  218. data.direction = 'both';
  219. }
  220. data.uri = parts.shift();
  221. data.params = parts;
  222. return data;
  223. },
  224. find_line: function (haystack, needle, sessionpart) {
  225. var lines = haystack.split('\r\n');
  226. for (var i = 0; i < lines.length; i++) {
  227. if (lines[i].substring(0, needle.length) == needle) {
  228. return lines[i];
  229. }
  230. }
  231. if (!sessionpart) {
  232. return false;
  233. }
  234. // search session part
  235. lines = sessionpart.split('\r\n');
  236. for (var j = 0; j < lines.length; j++) {
  237. if (lines[j].substring(0, needle.length) == needle) {
  238. return lines[j];
  239. }
  240. }
  241. return false;
  242. },
  243. find_lines: function (haystack, needle, sessionpart) {
  244. var lines = haystack.split('\r\n'),
  245. needles = [];
  246. for (var i = 0; i < lines.length; i++) {
  247. if (lines[i].substring(0, needle.length) == needle)
  248. needles.push(lines[i]);
  249. }
  250. if (needles.length || !sessionpart) {
  251. return needles;
  252. }
  253. // search session part
  254. lines = sessionpart.split('\r\n');
  255. for (var j = 0; j < lines.length; j++) {
  256. if (lines[j].substring(0, needle.length) == needle) {
  257. needles.push(lines[j]);
  258. }
  259. }
  260. return needles;
  261. },
  262. candidateToJingle: function (line) {
  263. // a=candidate:2979166662 1 udp 2113937151 192.168.2.100 57698 typ host generation 0
  264. // <candidate component=... foundation=... generation=... id=... ip=... network=... port=... priority=... protocol=... type=.../>
  265. if (line.indexOf('candidate:') === 0) {
  266. line = 'a=' + line;
  267. } else if (line.substring(0, 12) != 'a=candidate:') {
  268. logger.log('parseCandidate called with a line that is not a candidate line');
  269. logger.log(line);
  270. return null;
  271. }
  272. if (line.substring(line.length - 2) == '\r\n') // chomp it
  273. line = line.substring(0, line.length - 2);
  274. var candidate = {},
  275. elems = line.split(' '),
  276. i;
  277. if (elems[6] != 'typ') {
  278. logger.log('did not find typ in the right place');
  279. logger.log(line);
  280. return null;
  281. }
  282. candidate.foundation = elems[0].substring(12);
  283. candidate.component = elems[1];
  284. candidate.protocol = elems[2].toLowerCase();
  285. candidate.priority = elems[3];
  286. candidate.ip = elems[4];
  287. candidate.port = elems[5];
  288. // elems[6] => "typ"
  289. candidate.type = elems[7];
  290. candidate.generation = '0'; // default, may be overwritten below
  291. for (i = 8; i < elems.length; i += 2) {
  292. switch (elems[i]) {
  293. case 'raddr':
  294. candidate['rel-addr'] = elems[i + 1];
  295. break;
  296. case 'rport':
  297. candidate['rel-port'] = elems[i + 1];
  298. break;
  299. case 'generation':
  300. candidate.generation = elems[i + 1];
  301. break;
  302. case 'tcptype':
  303. candidate.tcptype = elems[i + 1];
  304. break;
  305. default: // TODO
  306. logger.log('not translating "' + elems[i] + '" = "' + elems[i + 1] + '"');
  307. }
  308. }
  309. candidate.network = '1';
  310. candidate.id = Math.random().toString(36).substr(2, 10); // not applicable to SDP -- FIXME: should be unique, not just random
  311. return candidate;
  312. },
  313. candidateFromJingle: function (cand) {
  314. var line = 'a=candidate:';
  315. line += cand.getAttribute('foundation');
  316. line += ' ';
  317. line += cand.getAttribute('component');
  318. line += ' ';
  319. var protocol = cand.getAttribute('protocol');
  320. // use tcp candidates for FF
  321. if (RTCBrowserType.isFirefox() && protocol.toLowerCase() == 'ssltcp') {
  322. protocol = 'tcp';
  323. }
  324. line += protocol; //.toUpperCase(); // chrome M23 doesn't like this
  325. line += ' ';
  326. line += cand.getAttribute('priority');
  327. line += ' ';
  328. line += cand.getAttribute('ip');
  329. line += ' ';
  330. line += cand.getAttribute('port');
  331. line += ' ';
  332. line += 'typ';
  333. line += ' ' + cand.getAttribute('type');
  334. line += ' ';
  335. switch (cand.getAttribute('type')) {
  336. case 'srflx':
  337. case 'prflx':
  338. case 'relay':
  339. if (cand.getAttribute('rel-addr') && cand.getAttribute('rel-port')) {
  340. line += 'raddr';
  341. line += ' ';
  342. line += cand.getAttribute('rel-addr');
  343. line += ' ';
  344. line += 'rport';
  345. line += ' ';
  346. line += cand.getAttribute('rel-port');
  347. line += ' ';
  348. }
  349. break;
  350. }
  351. if (protocol.toLowerCase() == 'tcp') {
  352. line += 'tcptype';
  353. line += ' ';
  354. line += cand.getAttribute('tcptype');
  355. line += ' ';
  356. }
  357. line += 'generation';
  358. line += ' ';
  359. line += cand.getAttribute('generation') || '0';
  360. return line + '\r\n';
  361. },
  362. /**
  363. * Parse the 'most' primary video ssrc from the given m line
  364. * @param {object} mLine object as parsed from transform.parse
  365. * @return {number} the primary video ssrc from the given m line
  366. */
  367. parsePrimaryVideoSsrc: function(videoMLine) {
  368. const numSsrcs = videoMLine.ssrcs
  369. .map(ssrcInfo => ssrcInfo.id)
  370. .filter((ssrc, index, array) => array.indexOf(ssrc) === index)
  371. .length;
  372. const numGroups = (videoMLine.ssrcGroups && videoMLine.ssrcGroups.length) || 0;
  373. if (numSsrcs > 1 && numGroups === 0) {
  374. // Ambiguous, can't figure out the primary
  375. return;
  376. }
  377. let primarySsrc = null;
  378. if (numSsrcs === 1) {
  379. primarySsrc = videoMLine.ssrcs[0].id;
  380. } else {
  381. if (numSsrcs === 2) {
  382. // Can figure it out if there's an FID group
  383. const fidGroup = videoMLine.ssrcGroups
  384. .find(group => group.semantics === "FID");
  385. if (fidGroup) {
  386. primarySsrc = fidGroup.ssrcs.split(" ")[0];
  387. }
  388. } else if (numSsrcs >= 3) {
  389. // Can figure it out if there's a sim group
  390. const simGroup = videoMLine.ssrcGroups
  391. .find(group => group.semantics === "SIM");
  392. if (simGroup) {
  393. primarySsrc = simGroup.ssrcs.split(" ")[0];
  394. }
  395. }
  396. }
  397. return primarySsrc;
  398. },
  399. /**
  400. * Generate an ssrc
  401. * @returns {number} an ssrc
  402. */
  403. generateSsrc: function() {
  404. return RandomUtil.randomInt(1, 0xffffffff);
  405. },
  406. /**
  407. * Get an attribute for the given ssrc with the given attributeName
  408. * from the given mline
  409. * @param {object} mLine an mLine object as parsed from transform.parse
  410. * @param {number} ssrc the ssrc for which an attribtue is desired
  411. * @param {string} attributeName the name of the desired attribute
  412. * @returns {string} the value corresponding to the given ssrc
  413. * and attributeName
  414. */
  415. getSsrcAttribute: function (mLine, ssrc, attributeName) {
  416. for (let i = 0; i < mLine.ssrcs.length; ++i) {
  417. const ssrcLine = mLine.ssrcs[i];
  418. if (ssrcLine.id === ssrc &&
  419. ssrcLine.attribute === attributeName) {
  420. return ssrcLine.value;
  421. }
  422. }
  423. },
  424. /**
  425. * Parses the ssrcs from the group sdp line and
  426. * returns them as a list of numbers
  427. * @param {object} the ssrcGroup object as parsed from
  428. * sdp-transform
  429. * @returns {list<number>} a list of the ssrcs in the group
  430. * parsed as numbers
  431. */
  432. parseGroupSsrcs: function (ssrcGroup) {
  433. return ssrcGroup
  434. .ssrcs
  435. .split(" ")
  436. .map(ssrcStr => parseInt(ssrcStr));
  437. },
  438. /**
  439. * Get the mline of the given type from the given sdp
  440. * @param {object} sdp sdp as parsed from transform.parse
  441. * @param {string} type the type of the desired mline (e.g. "video")
  442. * @returns {object} a media object
  443. */
  444. getMedia: function (sdp, type) {
  445. return sdp.media.find(m => m.type === type);
  446. },
  447. /**
  448. * Sets the given codecName as the preferred codec by
  449. * moving it to the beginning of the payload types
  450. * list (modifies the given mline in place). If there
  451. * are multiple options within the same codec (multiple h264
  452. * profiles, for instance), this will prefer the first one
  453. * that is found.
  454. * @param {object} videoMLine the video mline object from
  455. * an sdp as parsed by transform.parse
  456. * @param {string} the name of the preferred codec
  457. */
  458. preferVideoCodec: function(videoMLine, codecName) {
  459. let payloadType = null;
  460. for (let i = 0; i < videoMLine.rtp.length; ++i) {
  461. const rtp = videoMLine.rtp[i];
  462. if (rtp.codec === codecName) {
  463. payloadType = rtp.payload;
  464. break;
  465. }
  466. }
  467. if (payloadType) {
  468. const payloadTypes = videoMLine.payloads.split(" ").map(p => parseInt(p));
  469. const payloadIndex = payloadTypes.indexOf(payloadType);
  470. payloadTypes.splice(payloadIndex, 1);
  471. payloadTypes.unshift(payloadType);
  472. videoMLine.payloads = payloadTypes.join(" ");
  473. }
  474. },
  475. };
  476. module.exports = SDPUtil;