選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

strophe.jingle.sdp.js 30KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801
  1. /* jshint -W117 */
  2. // SDP STUFF
  3. function SDP(sdp) {
  4. this.media = sdp.split('\r\nm=');
  5. for (var i = 1; i < this.media.length; i++) {
  6. this.media[i] = 'm=' + this.media[i];
  7. if (i != this.media.length - 1) {
  8. this.media[i] += '\r\n';
  9. }
  10. }
  11. this.session = this.media.shift() + '\r\n';
  12. this.raw = this.session + this.media.join('');
  13. }
  14. // remove iSAC and CN from SDP
  15. SDP.prototype.mangle = function () {
  16. var i, j, mline, lines, rtpmap, newdesc;
  17. for (i = 0; i < this.media.length; i++) {
  18. lines = this.media[i].split('\r\n');
  19. lines.pop(); // remove empty last element
  20. mline = SDPUtil.parse_mline(lines.shift());
  21. if (mline.media != 'audio')
  22. continue;
  23. newdesc = '';
  24. mline.fmt.length = 0;
  25. for (j = 0; j < lines.length; j++) {
  26. if (lines[j].substr(0, 9) == 'a=rtpmap:') {
  27. rtpmap = SDPUtil.parse_rtpmap(lines[j]);
  28. if (rtpmap.name == 'CN' || rtpmap.name == 'ISAC')
  29. continue;
  30. mline.fmt.push(rtpmap.id);
  31. newdesc += lines[j] + '\r\n';
  32. } else {
  33. newdesc += lines[j] + '\r\n';
  34. }
  35. }
  36. this.media[i] = SDPUtil.build_mline(mline) + '\r\n';
  37. this.media[i] += newdesc;
  38. }
  39. this.raw = this.session + this.media.join('');
  40. };
  41. // remove lines matching prefix from session section
  42. SDP.prototype.removeSessionLines = function(prefix) {
  43. var self = this;
  44. var lines = SDPUtil.find_lines(this.session, prefix);
  45. lines.forEach(function(line) {
  46. self.session = self.session.replace(line + '\r\n', '');
  47. });
  48. this.raw = this.session + this.media.join('');
  49. return lines;
  50. }
  51. // remove lines matching prefix from a media section specified by mediaindex
  52. // TODO: non-numeric mediaindex could match mid
  53. SDP.prototype.removeMediaLines = function(mediaindex, prefix) {
  54. var self = this;
  55. var lines = SDPUtil.find_lines(this.media[mediaindex], prefix);
  56. lines.forEach(function(line) {
  57. self.media[mediaindex] = self.media[mediaindex].replace(line + '\r\n', '');
  58. });
  59. this.raw = this.session + this.media.join('');
  60. return lines;
  61. }
  62. // add content's to a jingle element
  63. SDP.prototype.toJingle = function (elem, thecreator) {
  64. var i, j, k, mline, ssrc, rtpmap, tmp, line, lines;
  65. var self = this;
  66. // new bundle plan
  67. if (SDPUtil.find_line(this.session, 'a=group:')) {
  68. lines = SDPUtil.find_lines(this.session, 'a=group:');
  69. for (i = 0; i < lines.length; i++) {
  70. tmp = lines[i].split(' ');
  71. var semantics = tmp.shift().substr(8);
  72. elem.c('group', {xmlns: 'urn:xmpp:jingle:apps:grouping:0', semantics:semantics});
  73. for (j = 0; j < tmp.length; j++) {
  74. elem.c('content', {name: tmp[j]}).up();
  75. }
  76. elem.up();
  77. }
  78. }
  79. // old bundle plan, to be removed
  80. var bundle = [];
  81. if (SDPUtil.find_line(this.session, 'a=group:BUNDLE')) {
  82. bundle = SDPUtil.find_line(this.session, 'a=group:BUNDLE ').split(' ');
  83. bundle.shift();
  84. }
  85. for (i = 0; i < this.media.length; i++) {
  86. mline = SDPUtil.parse_mline(this.media[i].split('\r\n')[0]);
  87. if (!(mline.media == 'audio' || mline.media == 'video')) {
  88. continue;
  89. }
  90. if (SDPUtil.find_line(this.media[i], 'a=ssrc:')) {
  91. ssrc = SDPUtil.find_line(this.media[i], 'a=ssrc:').substring(7).split(' ')[0]; // take the first
  92. } else {
  93. ssrc = false;
  94. }
  95. elem.c('content', {creator: thecreator, name: mline.media});
  96. if (SDPUtil.find_line(this.media[i], 'a=mid:')) {
  97. // prefer identifier from a=mid if present
  98. var mid = SDPUtil.parse_mid(SDPUtil.find_line(this.media[i], 'a=mid:'));
  99. elem.attrs({ name: mid });
  100. // old BUNDLE plan, to be removed
  101. if (bundle.indexOf(mid) != -1) {
  102. elem.c('bundle', {xmlns: 'http://estos.de/ns/bundle'}).up();
  103. bundle.splice(bundle.indexOf(mid), 1);
  104. }
  105. }
  106. if (SDPUtil.find_line(this.media[i], 'a=rtpmap:').length) {
  107. elem.c('description',
  108. {xmlns: 'urn:xmpp:jingle:apps:rtp:1',
  109. media: mline.media });
  110. if (ssrc) {
  111. elem.attrs({ssrc: ssrc});
  112. }
  113. for (j = 0; j < mline.fmt.length; j++) {
  114. rtpmap = SDPUtil.find_line(this.media[i], 'a=rtpmap:' + mline.fmt[j]);
  115. elem.c('payload-type', SDPUtil.parse_rtpmap(rtpmap));
  116. // put any 'a=fmtp:' + mline.fmt[j] lines into <param name=foo value=bar/>
  117. if (SDPUtil.find_line(this.media[i], 'a=fmtp:' + mline.fmt[j])) {
  118. tmp = SDPUtil.parse_fmtp(SDPUtil.find_line(this.media[i], 'a=fmtp:' + mline.fmt[j]));
  119. for (k = 0; k < tmp.length; k++) {
  120. elem.c('parameter', tmp[k]).up();
  121. }
  122. }
  123. this.RtcpFbToJingle(i, elem, mline.fmt[j]); // XEP-0293 -- map a=rtcp-fb
  124. elem.up();
  125. }
  126. if (SDPUtil.find_line(this.media[i], 'a=crypto:', this.session)) {
  127. elem.c('encryption', {required: 1});
  128. var crypto = SDPUtil.find_lines(this.media[i], 'a=crypto:', this.session);
  129. crypto.forEach(function(line) {
  130. elem.c('crypto', SDPUtil.parse_crypto(line)).up();
  131. });
  132. elem.up(); // end of encryption
  133. }
  134. if (ssrc) {
  135. // new style mapping
  136. elem.c('source', { ssrc: ssrc, xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
  137. // FIXME: group by ssrc and support multiple different ssrcs
  138. var ssrclines = SDPUtil.find_lines(this.media[i], 'a=ssrc:');
  139. ssrclines.forEach(function(line) {
  140. idx = line.indexOf(' ');
  141. var linessrc = line.substr(0, idx).substr(7);
  142. if (linessrc != ssrc) {
  143. elem.up();
  144. ssrc = linessrc;
  145. elem.c('source', { ssrc: ssrc, xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
  146. }
  147. var kv = line.substr(idx + 1);
  148. elem.c('parameter');
  149. if (kv.indexOf(':') == -1) {
  150. elem.attrs({ name: kv });
  151. } else {
  152. elem.attrs({ name: kv.split(':', 2)[0] });
  153. elem.attrs({ value: kv.split(':', 2)[1] });
  154. }
  155. elem.up();
  156. });
  157. elem.up();
  158. // old proprietary mapping, to be removed at some point
  159. tmp = SDPUtil.parse_ssrc(this.media[i]);
  160. tmp.xmlns = 'http://estos.de/ns/ssrc';
  161. tmp.ssrc = ssrc;
  162. elem.c('ssrc', tmp).up(); // ssrc is part of description
  163. }
  164. if (SDPUtil.find_line(this.media[i], 'a=rtcp-mux')) {
  165. elem.c('rtcp-mux').up();
  166. }
  167. // XEP-0293 -- map a=rtcp-fb:*
  168. this.RtcpFbToJingle(i, elem, '*');
  169. // XEP-0294
  170. if (SDPUtil.find_line(this.media[i], 'a=extmap:')) {
  171. lines = SDPUtil.find_lines(this.media[i], 'a=extmap:');
  172. for (j = 0; j < lines.length; j++) {
  173. tmp = SDPUtil.parse_extmap(lines[j]);
  174. elem.c('rtp-hdrext', { xmlns: 'urn:xmpp:jingle:apps:rtp:rtp-hdrext:0',
  175. uri: tmp.uri,
  176. id: tmp.value });
  177. if (tmp.hasOwnProperty('direction')) {
  178. switch (tmp.direction) {
  179. case 'sendonly':
  180. elem.attrs({senders: 'responder'});
  181. break;
  182. case 'recvonly':
  183. elem.attrs({senders: 'initiator'});
  184. break;
  185. case 'sendrecv':
  186. elem.attrs({senders: 'both'});
  187. break;
  188. case 'inactive':
  189. elem.attrs({senders: 'none'});
  190. break;
  191. }
  192. }
  193. // TODO: handle params
  194. elem.up();
  195. }
  196. }
  197. elem.up(); // end of description
  198. }
  199. // map ice-ufrag/pwd, dtls fingerprint, candidates
  200. this.TransportToJingle(i, elem);
  201. if (SDPUtil.find_line(this.media[i], 'a=sendrecv', this.session)) {
  202. elem.attrs({senders: 'both'});
  203. } else if (SDPUtil.find_line(this.media[i], 'a=sendonly', this.session)) {
  204. elem.attrs({senders: 'initiator'});
  205. } else if (SDPUtil.find_line(this.media[i], 'a=recvonly', this.session)) {
  206. elem.attrs({senders: 'responder'});
  207. } else if (SDPUtil.find_line(this.media[i], 'a=inactive', this.session)) {
  208. elem.attrs({senders: 'none'});
  209. }
  210. if (mline.port == '0') {
  211. // estos hack to reject an m-line
  212. elem.attrs({senders: 'rejected'});
  213. }
  214. elem.up(); // end of content
  215. }
  216. elem.up();
  217. return elem;
  218. };
  219. SDP.prototype.TransportToJingle = function (mediaindex, elem) {
  220. var i = mediaindex;
  221. var tmp;
  222. var self = this;
  223. elem.c('transport');
  224. // XEP-0320
  225. var fingerprints = SDPUtil.find_lines(this.media[mediaindex], 'a=fingerprint:', this.session);
  226. fingerprints.forEach(function(line) {
  227. tmp = SDPUtil.parse_fingerprint(line);
  228. tmp.xmlns = 'urn:xmpp:tmp:jingle:apps:dtls:0';
  229. // tmp.xmlns = 'urn:xmpp:jingle:apps:dtls:0'; -- FIXME: update receivers first
  230. elem.c('fingerprint').t(tmp.fingerprint);
  231. delete tmp.fingerprint;
  232. line = SDPUtil.find_line(self.media[mediaindex], 'a=setup:', self.session);
  233. if (line) {
  234. tmp.setup = line.substr(8);
  235. }
  236. elem.attrs(tmp);
  237. elem.up(); // end of fingerprint
  238. });
  239. tmp = SDPUtil.iceparams(this.media[mediaindex], this.session);
  240. if (tmp) {
  241. tmp.xmlns = 'urn:xmpp:jingle:transports:ice-udp:1';
  242. elem.attrs(tmp);
  243. // XEP-0176
  244. if (SDPUtil.find_line(this.media[mediaindex], 'a=candidate:', this.session)) { // add any a=candidate lines
  245. var lines = SDPUtil.find_lines(this.media[mediaindex], 'a=candidate:', this.session);
  246. lines.forEach(function (line) {
  247. elem.c('candidate', SDPUtil.candidateToJingle(line)).up();
  248. });
  249. }
  250. }
  251. elem.up(); // end of transport
  252. }
  253. SDP.prototype.RtcpFbToJingle = function (mediaindex, elem, payloadtype) { // XEP-0293
  254. var lines = SDPUtil.find_lines(this.media[mediaindex], 'a=rtcp-fb:' + payloadtype);
  255. lines.forEach(function (line) {
  256. var tmp = SDPUtil.parse_rtcpfb(line);
  257. if (tmp.type == 'trr-int') {
  258. elem.c('rtcp-fb-trr-int', {xmlns: 'urn:xmpp:jingle:apps:rtp:rtcp-fb:0', value: tmp.params[0]});
  259. elem.up();
  260. } else {
  261. elem.c('rtcp-fb', {xmlns: 'urn:xmpp:jingle:apps:rtp:rtcp-fb:0', type: tmp.type});
  262. if (tmp.params.length > 0) {
  263. elem.attrs({'subtype': tmp.params[0]});
  264. }
  265. elem.up();
  266. }
  267. });
  268. };
  269. SDP.prototype.RtcpFbFromJingle = function (elem, payloadtype) { // XEP-0293
  270. var media = '';
  271. var tmp = elem.find('>rtcp-fb-trr-int[xmlns="urn:xmpp:jingle:apps:rtp:rtcp-fb:0"]');
  272. if (tmp.length) {
  273. media += 'a=rtcp-fb:' + '*' + ' ' + 'trr-int' + ' ';
  274. if (tmp.attr('value')) {
  275. media += tmp.attr('value');
  276. } else {
  277. media += '0';
  278. }
  279. media += '\r\n';
  280. }
  281. tmp = elem.find('>rtcp-fb[xmlns="urn:xmpp:jingle:apps:rtp:rtcp-fb:0"]');
  282. tmp.each(function () {
  283. media += 'a=rtcp-fb:' + payloadtype + ' ' + $(this).attr('type');
  284. if ($(this).attr('subtype')) {
  285. media += ' ' + $(this).attr('subtype');
  286. }
  287. media += '\r\n';
  288. });
  289. return media;
  290. };
  291. // construct an SDP from a jingle stanza
  292. SDP.prototype.fromJingle = function (jingle) {
  293. var self = this;
  294. this.raw = 'v=0\r\n' +
  295. 'o=- ' + '1923518516' + ' 2 IN IP4 0.0.0.0\r\n' +// FIXME
  296. 's=-\r\n' +
  297. 't=0 0\r\n';
  298. // http://tools.ietf.org/html/draft-ietf-mmusic-sdp-bundle-negotiation-04#section-8
  299. if ($(jingle).find('>group[xmlns="urn:xmpp:jingle:apps:grouping:0"]').length) {
  300. $(jingle).find('>group[xmlns="urn:xmpp:jingle:apps:grouping:0"]').each(function (idx, group) {
  301. var contents = $(group).find('>content').map(function (idx, content) {
  302. return content.getAttribute('name');
  303. }).get();
  304. if (contents.length > 0) {
  305. self.raw += 'a=group:' + (group.getAttribute('semantics') || group.getAttribute('type')) + ' ' + contents.join(' ') + '\r\n';
  306. }
  307. });
  308. } else if ($(jingle).find('>group[xmlns="urn:ietf:rfc:5888"]').length) {
  309. // temporary namespace, not to be used. to be removed soon.
  310. $(jingle).find('>group[xmlns="urn:ietf:rfc:5888"]').each(function (idx, group) {
  311. var contents = $(group).find('>content').map(function (idx, content) {
  312. return content.getAttribute('name');
  313. }).get();
  314. if (group.getAttribute('type') !== null && contents.length > 0) {
  315. self.raw += 'a=group:' + group.getAttribute('type') + ' ' + contents.join(' ') + '\r\n';
  316. }
  317. });
  318. } else {
  319. // for backward compability, to be removed soon
  320. // assume all contents are in the same bundle group, can be improved upon later
  321. var bundle = $(jingle).find('>content').filter(function (idx, content) {
  322. //elem.c('bundle', {xmlns:'http://estos.de/ns/bundle'});
  323. return $(content).find('>bundle').length > 0;
  324. }).map(function (idx, content) {
  325. return content.getAttribute('name');
  326. }).get();
  327. if (bundle.length) {
  328. this.raw += 'a=group:BUNDLE ' + bundle.join(' ') + '\r\n';
  329. }
  330. }
  331. this.session = this.raw;
  332. jingle.find('>content').each(function () {
  333. var m = self.jingle2media($(this));
  334. self.media.push(m);
  335. });
  336. // reconstruct msid-semantic -- apparently not necessary
  337. /*
  338. var msid = SDPUtil.parse_ssrc(this.raw);
  339. if (msid.hasOwnProperty('mslabel')) {
  340. this.session += "a=msid-semantic: WMS " + msid.mslabel + "\r\n";
  341. }
  342. */
  343. this.raw = this.session + this.media.join('');
  344. };
  345. // translate a jingle content element into an an SDP media part
  346. SDP.prototype.jingle2media = function (content) {
  347. var media = '',
  348. desc = content.find('description'),
  349. ssrc = desc.attr('ssrc'),
  350. self = this,
  351. tmp;
  352. tmp = { media: desc.attr('media') };
  353. tmp.port = '1';
  354. if (content.attr('senders') == 'rejected') {
  355. // estos hack to reject an m-line.
  356. tmp.port = '0';
  357. }
  358. if (content.find('>transport>fingerprint').length || desc.find('encryption').length) {
  359. tmp.proto = 'RTP/SAVPF';
  360. } else {
  361. tmp.proto = 'RTP/AVPF';
  362. }
  363. tmp.fmt = desc.find('payload-type').map(function () { return this.getAttribute('id'); }).get();
  364. media += SDPUtil.build_mline(tmp) + '\r\n';
  365. media += 'c=IN IP4 0.0.0.0\r\n';
  366. media += 'a=rtcp:1 IN IP4 0.0.0.0\r\n';
  367. tmp = content.find('>transport[xmlns="urn:xmpp:jingle:transports:ice-udp:1"]');
  368. if (tmp.length) {
  369. if (tmp.attr('ufrag')) {
  370. media += SDPUtil.build_iceufrag(tmp.attr('ufrag')) + '\r\n';
  371. }
  372. if (tmp.attr('pwd')) {
  373. media += SDPUtil.build_icepwd(tmp.attr('pwd')) + '\r\n';
  374. }
  375. tmp.find('>fingerprint').each(function () {
  376. // FIXME: check namespace at some point
  377. media += 'a=fingerprint:' + this.getAttribute('hash');
  378. media += ' ' + $(this).text();
  379. media += '\r\n';
  380. if (this.getAttribute('setup')) {
  381. media += 'a=setup:' + this.getAttribute('setup') + '\r\n';
  382. }
  383. });
  384. }
  385. switch (content.attr('senders')) {
  386. case 'initiator':
  387. media += 'a=sendonly\r\n';
  388. break;
  389. case 'responder':
  390. media += 'a=recvonly\r\n';
  391. break;
  392. case 'none':
  393. media += 'a=inactive\r\n';
  394. break;
  395. case 'both':
  396. media += 'a=sendrecv\r\n';
  397. break;
  398. }
  399. media += 'a=mid:' + content.attr('name') + '\r\n';
  400. // <description><rtcp-mux/></description>
  401. // see http://code.google.com/p/libjingle/issues/detail?id=309 -- no spec though
  402. // and http://mail.jabber.org/pipermail/jingle/2011-December/001761.html
  403. if (desc.find('rtcp-mux').length) {
  404. media += 'a=rtcp-mux\r\n';
  405. }
  406. if (desc.find('encryption').length) {
  407. desc.find('encryption>crypto').each(function () {
  408. media += 'a=crypto:' + this.getAttribute('tag');
  409. media += ' ' + this.getAttribute('crypto-suite');
  410. media += ' ' + this.getAttribute('key-params');
  411. if (this.getAttribute('session-params')) {
  412. media += ' ' + this.getAttribute('session-params');
  413. }
  414. media += '\r\n';
  415. });
  416. }
  417. desc.find('payload-type').each(function () {
  418. media += SDPUtil.build_rtpmap(this) + '\r\n';
  419. if ($(this).find('>parameter').length) {
  420. media += 'a=fmtp:' + this.getAttribute('id') + ' ';
  421. media += $(this).find('parameter').map(function () { return (this.getAttribute('name') ? (this.getAttribute('name') + '=') : '') + this.getAttribute('value'); }).get().join(';');
  422. media += '\r\n';
  423. }
  424. // xep-0293
  425. media += self.RtcpFbFromJingle($(this), this.getAttribute('id'));
  426. });
  427. // xep-0293
  428. media += self.RtcpFbFromJingle(desc, '*');
  429. // xep-0294
  430. tmp = desc.find('>rtp-hdrext[xmlns="urn:xmpp:jingle:apps:rtp:rtp-hdrext:0"]');
  431. tmp.each(function () {
  432. media += 'a=extmap:' + this.getAttribute('id') + ' ' + this.getAttribute('uri') + '\r\n';
  433. });
  434. content.find('>transport[xmlns="urn:xmpp:jingle:transports:ice-udp:1"]>candidate').each(function () {
  435. media += SDPUtil.candidateFromJingle(this);
  436. });
  437. tmp = content.find('description>source[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]');
  438. tmp.each(function () {
  439. var ssrc = this.getAttribute('ssrc');
  440. $(this).find('>parameter').each(function () {
  441. media += 'a=ssrc:' + ssrc + ' ' + this.getAttribute('name');
  442. if (this.getAttribute('value') && this.getAttribute('value').length)
  443. media += ':' + this.getAttribute('value');
  444. media += '\r\n';
  445. });
  446. });
  447. if (tmp.length === 0) {
  448. // fallback to proprietary mapping of a=ssrc lines
  449. tmp = content.find('description>ssrc[xmlns="http://estos.de/ns/ssrc"]');
  450. if (tmp.length) {
  451. media += 'a=ssrc:' + ssrc + ' cname:' + tmp.attr('cname') + '\r\n';
  452. media += 'a=ssrc:' + ssrc + ' msid:' + tmp.attr('msid') + '\r\n';
  453. media += 'a=ssrc:' + ssrc + ' mslabel:' + tmp.attr('mslabel') + '\r\n';
  454. media += 'a=ssrc:' + ssrc + ' label:' + tmp.attr('label') + '\r\n';
  455. }
  456. }
  457. return media;
  458. };
  459. SDPUtil = {
  460. iceparams: function (mediadesc, sessiondesc) {
  461. var data = null;
  462. if (SDPUtil.find_line(mediadesc, 'a=ice-ufrag:', sessiondesc) &&
  463. SDPUtil.find_line(mediadesc, 'a=ice-pwd:', sessiondesc)) {
  464. data = {
  465. ufrag: SDPUtil.parse_iceufrag(SDPUtil.find_line(mediadesc, 'a=ice-ufrag:', sessiondesc)),
  466. pwd: SDPUtil.parse_icepwd(SDPUtil.find_line(mediadesc, 'a=ice-pwd:', sessiondesc))
  467. };
  468. }
  469. return data;
  470. },
  471. parse_iceufrag: function (line) {
  472. return line.substring(12);
  473. },
  474. build_iceufrag: function (frag) {
  475. return 'a=ice-ufrag:' + frag;
  476. },
  477. parse_icepwd: function (line) {
  478. return line.substring(10);
  479. },
  480. build_icepwd: function (pwd) {
  481. return 'a=ice-pwd:' + pwd;
  482. },
  483. parse_mid: function (line) {
  484. return line.substring(6);
  485. },
  486. parse_mline: function (line) {
  487. var parts = line.substring(2).split(' '),
  488. data = {};
  489. data.media = parts.shift();
  490. data.port = parts.shift();
  491. data.proto = parts.shift();
  492. if (parts[parts.length - 1] === '') { // trailing whitespace
  493. parts.pop();
  494. }
  495. data.fmt = parts;
  496. return data;
  497. },
  498. build_mline: function (mline) {
  499. return 'm=' + mline.media + ' ' + mline.port + ' ' + mline.proto + ' ' + mline.fmt.join(' ');
  500. },
  501. parse_rtpmap: function (line) {
  502. var parts = line.substring(9).split(' '),
  503. data = {};
  504. data.id = parts.shift();
  505. parts = parts[0].split('/');
  506. data.name = parts.shift();
  507. data.clockrate = parts.shift();
  508. data.channels = parts.length ? parts.shift() : '1';
  509. return data;
  510. },
  511. build_rtpmap: function (el) {
  512. var line = 'a=rtpmap:' + el.getAttribute('id') + ' ' + el.getAttribute('name') + '/' + el.getAttribute('clockrate');
  513. if (el.getAttribute('channels') && el.getAttribute('channels') != '1') {
  514. line += '/' + el.getAttribute('channels');
  515. }
  516. return line;
  517. },
  518. parse_crypto: function (line) {
  519. var parts = line.substring(9).split(' '),
  520. data = {};
  521. data.tag = parts.shift();
  522. data['crypto-suite'] = parts.shift();
  523. data['key-params'] = parts.shift();
  524. if (parts.length) {
  525. data['session-params'] = parts.join(' ');
  526. }
  527. return data;
  528. },
  529. parse_fingerprint: function (line) { // RFC 4572
  530. var parts = line.substring(14).split(' '),
  531. data = {};
  532. data.hash = parts.shift();
  533. data.fingerprint = parts.shift();
  534. // TODO assert that fingerprint satisfies 2UHEX *(":" 2UHEX) ?
  535. return data;
  536. },
  537. parse_fmtp: function (line) {
  538. var parts = line.split(' '),
  539. i, key, value,
  540. data = [];
  541. parts.shift();
  542. parts = parts.join(' ').split(';');
  543. for (i = 0; i < parts.length; i++) {
  544. key = parts[i].split('=')[0];
  545. while (key.length && key[0] == ' ') {
  546. key = key.substring(1);
  547. }
  548. value = parts[i].split('=')[1];
  549. if (key && value) {
  550. data.push({name: key, value: value});
  551. } else if (key) {
  552. // rfc 4733 (DTMF) style stuff
  553. data.push({name: '', value: key});
  554. }
  555. }
  556. return data;
  557. },
  558. parse_icecandidate: function (line) {
  559. var candidate = {},
  560. elems = line.split(' ');
  561. candidate.foundation = elems[0].substring(12);
  562. candidate.component = elems[1];
  563. candidate.protocol = elems[2].toLowerCase();
  564. candidate.priority = elems[3];
  565. candidate.ip = elems[4];
  566. candidate.port = elems[5];
  567. // elems[6] => "typ"
  568. candidate.type = elems[7];
  569. candidate.generation = 0; // default value, may be overwritten below
  570. for (var i = 8; i < elems.length; i += 2) {
  571. switch (elems[i]) {
  572. case 'raddr':
  573. candidate['rel-addr'] = elems[i + 1];
  574. break;
  575. case 'rport':
  576. candidate['rel-port'] = elems[i + 1];
  577. break;
  578. case 'generation':
  579. candidate.generation = elems[i + 1];
  580. break;
  581. default: // TODO
  582. console.log('parse_icecandidate not translating "' + elems[i] + '" = "' + elems[i + 1] + '"');
  583. }
  584. }
  585. candidate.network = '1';
  586. candidate.id = Math.random().toString(36).substr(2, 10); // not applicable to SDP -- FIXME: should be unique, not just random
  587. return candidate;
  588. },
  589. build_icecandidate: function (cand) {
  590. var line = ['a=candidate:' + cand.foundation, cand.component, cand.protocol, cand.priority, cand.ip, cand.port, 'typ', cand.type].join(' ');
  591. line += ' ';
  592. switch (cand.type) {
  593. case 'srflx':
  594. case 'prflx':
  595. case 'relay':
  596. if (cand.hasOwnAttribute('rel-addr') && cand.hasOwnAttribute('rel-port')) {
  597. line += 'raddr';
  598. line += ' ';
  599. line += cand['rel-addr'];
  600. line += ' ';
  601. line += 'rport';
  602. line += ' ';
  603. line += cand['rel-port'];
  604. line += ' ';
  605. }
  606. break;
  607. }
  608. line += 'generation';
  609. line += ' ';
  610. line += cand.hasOwnAttribute('generation') ? cand.generation : '0';
  611. return line;
  612. },
  613. parse_ssrc: function (desc) {
  614. // proprietary mapping of a=ssrc lines
  615. // TODO: see "Jingle RTP Source Description" by Juberti and P. Thatcher on google docs
  616. // and parse according to that
  617. var lines = desc.split('\r\n'),
  618. data = {};
  619. for (var i = 0; i < lines.length; i++) {
  620. if (lines[i].substring(0, 7) == 'a=ssrc:') {
  621. var idx = lines[i].indexOf(' ');
  622. data[lines[i].substr(idx + 1).split(':', 2)[0]] = lines[i].substr(idx + 1).split(':', 2)[1];
  623. }
  624. }
  625. return data;
  626. },
  627. parse_rtcpfb: function (line) {
  628. var parts = line.substr(10).split(' ');
  629. var data = {};
  630. data.pt = parts.shift();
  631. data.type = parts.shift();
  632. data.params = parts;
  633. return data;
  634. },
  635. parse_extmap: function (line) {
  636. var parts = line.substr(9).split(' ');
  637. var data = {};
  638. data.value = parts.shift();
  639. if (data.value.indexOf('/') != -1) {
  640. data.direction = data.value.substr(data.value.indexOf('/') + 1);
  641. data.value = data.value.substr(0, data.value.indexOf('/'));
  642. } else {
  643. data.direction = 'both';
  644. }
  645. data.uri = parts.shift();
  646. data.params = parts;
  647. return data;
  648. },
  649. find_line: function (haystack, needle, sessionpart) {
  650. var lines = haystack.split('\r\n');
  651. for (var i = 0; i < lines.length; i++) {
  652. if (lines[i].substring(0, needle.length) == needle) {
  653. return lines[i];
  654. }
  655. }
  656. if (!sessionpart) {
  657. return false;
  658. }
  659. // search session part
  660. lines = sessionpart.split('\r\n');
  661. for (var j = 0; j < lines.length; j++) {
  662. if (lines[j].substring(0, needle.length) == needle) {
  663. return lines[j];
  664. }
  665. }
  666. return false;
  667. },
  668. find_lines: function (haystack, needle, sessionpart) {
  669. var lines = haystack.split('\r\n'),
  670. needles = [];
  671. for (var i = 0; i < lines.length; i++) {
  672. if (lines[i].substring(0, needle.length) == needle)
  673. needles.push(lines[i]);
  674. }
  675. if (needles.length || !sessionpart) {
  676. return needles;
  677. }
  678. // search session part
  679. lines = sessionpart.split('\r\n');
  680. for (var j = 0; j < lines.length; j++) {
  681. if (lines[j].substring(0, needle.length) == needle) {
  682. needles.push(lines[j]);
  683. }
  684. }
  685. return needles;
  686. },
  687. candidateToJingle: function (line) {
  688. // a=candidate:2979166662 1 udp 2113937151 192.168.2.100 57698 typ host generation 0
  689. // <candidate component=... foundation=... generation=... id=... ip=... network=... port=... priority=... protocol=... type=.../>
  690. if (line.substring(0, 12) != 'a=candidate:') {
  691. console.log('parseCandidate called with a line that is not a candidate line');
  692. console.log(line);
  693. return null;
  694. }
  695. if (line.substring(line.length - 2) == '\r\n') // chomp it
  696. line = line.substring(0, line.length - 2);
  697. var candidate = {},
  698. elems = line.split(' '),
  699. i;
  700. if (elems[6] != 'typ') {
  701. console.log('did not find typ in the right place');
  702. console.log(line);
  703. return null;
  704. }
  705. candidate.foundation = elems[0].substring(12);
  706. candidate.component = elems[1];
  707. candidate.protocol = elems[2].toLowerCase();
  708. candidate.priority = elems[3];
  709. candidate.ip = elems[4];
  710. candidate.port = elems[5];
  711. // elems[6] => "typ"
  712. candidate.type = elems[7];
  713. for (i = 8; i < elems.length; i += 2) {
  714. switch (elems[i]) {
  715. case 'raddr':
  716. candidate['rel-addr'] = elems[i + 1];
  717. break;
  718. case 'rport':
  719. candidate['rel-port'] = elems[i + 1];
  720. break;
  721. case 'generation':
  722. candidate.generation = elems[i + 1];
  723. break;
  724. default: // TODO
  725. console.log('not translating "' + elems[i] + '" = "' + elems[i + 1] + '"');
  726. }
  727. }
  728. candidate.network = '1';
  729. candidate.id = Math.random().toString(36).substr(2, 10); // not applicable to SDP -- FIXME: should be unique, not just random
  730. return candidate;
  731. },
  732. candidateFromJingle: function (cand) {
  733. var line = 'a=candidate:';
  734. line += cand.getAttribute('foundation');
  735. line += ' ';
  736. line += cand.getAttribute('component');
  737. line += ' ';
  738. line += cand.getAttribute('protocol'); //.toUpperCase(); // chrome M23 doesn't like this
  739. line += ' ';
  740. line += cand.getAttribute('priority');
  741. line += ' ';
  742. line += cand.getAttribute('ip');
  743. line += ' ';
  744. line += cand.getAttribute('port');
  745. line += ' ';
  746. line += 'typ';
  747. line += ' ' + cand.getAttribute('type');
  748. line += ' ';
  749. switch (cand.getAttribute('type')) {
  750. case 'srflx':
  751. case 'prflx':
  752. case 'relay':
  753. if (cand.getAttribute('rel-addr') && cand.getAttribute('rel-port')) {
  754. line += 'raddr';
  755. line += ' ';
  756. line += cand.getAttribute('rel-addr');
  757. line += ' ';
  758. line += 'rport';
  759. line += ' ';
  760. line += cand.getAttribute('rel-port');
  761. line += ' ';
  762. }
  763. break;
  764. }
  765. line += 'generation';
  766. line += ' ';
  767. line += cand.getAttribute('generation') || '0';
  768. return line + '\r\n';
  769. }
  770. };