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.

TraceablePeerConnection.js 26KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677
  1. /* global mozRTCPeerConnection, webkitRTCPeerConnection */
  2. import { getLogger } from "jitsi-meet-logger";
  3. const logger = getLogger(__filename);
  4. import SdpConsistency from "./SdpConsistency.js";
  5. import RtxModifier from "./RtxModifier.js";
  6. var RTCBrowserType = require("../RTC/RTCBrowserType.js");
  7. var XMPPEvents = require("../../service/xmpp/XMPPEvents");
  8. var transform = require('sdp-transform');
  9. var SDP = require("./SDP");
  10. var SDPUtil = require("./SDPUtil");
  11. var SIMULCAST_LAYERS = 3;
  12. /**
  13. * Creates new instance of 'TraceablePeerConnection'.
  14. *
  15. * @param {object} ice_config WebRTC 'PeerConnection' ICE config
  16. * @param {object} constraints WebRTC 'PeerConnection' constraints
  17. * @param {object} options <tt>TracablePeerConnection</tt> config options.
  18. * @param {boolean} options.disableSimulcast if set to 'true' will disable
  19. * the simulcast
  20. * @param {boolean} options.disableRtx if set to 'true' will disable the RTX
  21. * @param {boolean} options.preferH264 if set to 'true' H264 will be preferred
  22. * over other video codecs.
  23. * @param {EventEmitter} eventEmitter the emitter which wil be used by the new
  24. * instance to emit events.
  25. *
  26. * @constructor
  27. */
  28. function TraceablePeerConnection(ice_config,
  29. constraints, options, eventEmitter) {
  30. var self = this;
  31. this.options = options;
  32. var RTCPeerConnectionType = null;
  33. if (RTCBrowserType.isFirefox()) {
  34. RTCPeerConnectionType = mozRTCPeerConnection;
  35. } else if (RTCBrowserType.isTemasysPluginUsed()) {
  36. RTCPeerConnectionType = RTCPeerConnection;
  37. } else {
  38. RTCPeerConnectionType = webkitRTCPeerConnection;
  39. }
  40. this.peerconnection = new RTCPeerConnectionType(ice_config, constraints);
  41. this.updateLog = [];
  42. this.stats = {};
  43. this.statsinterval = null;
  44. this.maxstats = 0; // limit to 300 values, i.e. 5 minutes; set to 0 to disable
  45. var Interop = require('sdp-interop').Interop;
  46. this.interop = new Interop();
  47. var Simulcast = require('sdp-simulcast');
  48. this.simulcast = new Simulcast({numOfLayers: SIMULCAST_LAYERS,
  49. explodeRemoteSimulcast: false});
  50. this.sdpConsistency = new SdpConsistency();
  51. this.rtxModifier = new RtxModifier();
  52. this.eventEmitter = eventEmitter;
  53. // override as desired
  54. this.trace = function (what, info) {
  55. /*logger.warn('WTRACE', what, info);
  56. if (info && RTCBrowserType.isIExplorer()) {
  57. if (info.length > 1024) {
  58. logger.warn('WTRACE', what, info.substr(1024));
  59. }
  60. if (info.length > 2048) {
  61. logger.warn('WTRACE', what, info.substr(2048));
  62. }
  63. }*/
  64. self.updateLog.push({
  65. time: new Date(),
  66. type: what,
  67. value: info || ""
  68. });
  69. };
  70. this.onicecandidate = null;
  71. this.peerconnection.onicecandidate = function (event) {
  72. // FIXME: this causes stack overflow with Temasys Plugin
  73. if (!RTCBrowserType.isTemasysPluginUsed())
  74. self.trace('onicecandidate', JSON.stringify(event.candidate, null, ' '));
  75. if (self.onicecandidate !== null) {
  76. self.onicecandidate(event);
  77. }
  78. };
  79. this.onaddstream = null;
  80. this.peerconnection.onaddstream = function (event) {
  81. self.trace('onaddstream', event.stream.id);
  82. if (self.onaddstream !== null) {
  83. self.onaddstream(event);
  84. }
  85. };
  86. this.onremovestream = null;
  87. this.peerconnection.onremovestream = function (event) {
  88. self.trace('onremovestream', event.stream.id);
  89. if (self.onremovestream !== null) {
  90. self.onremovestream(event);
  91. }
  92. };
  93. this.onsignalingstatechange = null;
  94. this.peerconnection.onsignalingstatechange = function (event) {
  95. self.trace('onsignalingstatechange', self.signalingState);
  96. if (self.onsignalingstatechange !== null) {
  97. self.onsignalingstatechange(event);
  98. }
  99. };
  100. this.oniceconnectionstatechange = null;
  101. this.peerconnection.oniceconnectionstatechange = function (event) {
  102. self.trace('oniceconnectionstatechange', self.iceConnectionState);
  103. if (self.oniceconnectionstatechange !== null) {
  104. self.oniceconnectionstatechange(event);
  105. }
  106. };
  107. this.onnegotiationneeded = null;
  108. this.peerconnection.onnegotiationneeded = function (event) {
  109. self.trace('onnegotiationneeded');
  110. if (self.onnegotiationneeded !== null) {
  111. self.onnegotiationneeded(event);
  112. }
  113. };
  114. self.ondatachannel = null;
  115. this.peerconnection.ondatachannel = function (event) {
  116. self.trace('ondatachannel', event);
  117. if (self.ondatachannel !== null) {
  118. self.ondatachannel(event);
  119. }
  120. };
  121. // XXX: do all non-firefox browsers which we support also support this?
  122. if (!RTCBrowserType.isFirefox() && this.maxstats) {
  123. this.statsinterval = window.setInterval(function() {
  124. self.peerconnection.getStats(function(stats) {
  125. var results = stats.result();
  126. var now = new Date();
  127. for (var i = 0; i < results.length; ++i) {
  128. results[i].names().forEach(function (name) {
  129. var id = results[i].id + '-' + name;
  130. if (!self.stats[id]) {
  131. self.stats[id] = {
  132. startTime: now,
  133. endTime: now,
  134. values: [],
  135. times: []
  136. };
  137. }
  138. self.stats[id].values.push(results[i].stat(name));
  139. self.stats[id].times.push(now.getTime());
  140. if (self.stats[id].values.length > self.maxstats) {
  141. self.stats[id].values.shift();
  142. self.stats[id].times.shift();
  143. }
  144. self.stats[id].endTime = now;
  145. });
  146. }
  147. });
  148. }, 1000);
  149. }
  150. }
  151. /**
  152. * Returns a string representation of a SessionDescription object.
  153. */
  154. var dumpSDP = function(description) {
  155. if (typeof description === 'undefined' || description == null) {
  156. return '';
  157. }
  158. return 'type: ' + description.type + '\r\n' + description.sdp;
  159. };
  160. /**
  161. * Returns map with keys msid and values ssrc.
  162. * @param desc the SDP that will be modified.
  163. */
  164. function extractSSRCMap(desc) {
  165. if (typeof desc !== 'object' || desc === null ||
  166. typeof desc.sdp !== 'string') {
  167. logger.warn('An empty description was passed as an argument.');
  168. return desc;
  169. }
  170. var ssrcList = {};
  171. var ssrcGroups = {};
  172. var session = transform.parse(desc.sdp);
  173. if (!Array.isArray(session.media))
  174. {
  175. return;
  176. }
  177. session.media.forEach(function (bLine) {
  178. if (!Array.isArray(bLine.ssrcs))
  179. {
  180. return;
  181. }
  182. if (typeof bLine.ssrcGroups !== 'undefined' &&
  183. Array.isArray(bLine.ssrcGroups)) {
  184. bLine.ssrcGroups.forEach(function (group) {
  185. if (typeof group.semantics !== 'undefined' &&
  186. typeof group.ssrcs !== 'undefined') {
  187. var primarySSRC = Number(group.ssrcs.split(' ')[0]);
  188. ssrcGroups[primarySSRC] = ssrcGroups[primarySSRC] || [];
  189. ssrcGroups[primarySSRC].push(group);
  190. }
  191. });
  192. }
  193. bLine.ssrcs.forEach(function (ssrc) {
  194. if(ssrc.attribute !== 'msid')
  195. return;
  196. ssrcList[ssrc.value] = ssrcList[ssrc.value] ||
  197. {groups: [], ssrcs: []};
  198. ssrcList[ssrc.value].ssrcs.push(ssrc.id);
  199. if(ssrcGroups[ssrc.id]){
  200. ssrcGroups[ssrc.id].forEach(function (group) {
  201. ssrcList[ssrc.value].groups.push(
  202. {primarySSRC: ssrc.id, group: group});
  203. });
  204. }
  205. });
  206. });
  207. return ssrcList;
  208. }
  209. /**
  210. * Takes a SessionDescription object and returns a "normalized" version.
  211. * Currently it only takes care of ordering the a=ssrc lines.
  212. */
  213. var normalizePlanB = function(desc) {
  214. if (typeof desc !== 'object' || desc === null ||
  215. typeof desc.sdp !== 'string') {
  216. logger.warn('An empty description was passed as an argument.');
  217. return desc;
  218. }
  219. var transform = require('sdp-transform');
  220. var session = transform.parse(desc.sdp);
  221. if (typeof session !== 'undefined' &&
  222. typeof session.media !== 'undefined' && Array.isArray(session.media)) {
  223. session.media.forEach(function (mLine) {
  224. // Chrome appears to be picky about the order in which a=ssrc lines
  225. // are listed in an m-line when rtx is enabled (and thus there are
  226. // a=ssrc-group lines with FID semantics). Specifically if we have
  227. // "a=ssrc-group:FID S1 S2" and the "a=ssrc:S2" lines appear before
  228. // the "a=ssrc:S1" lines, SRD fails.
  229. // So, put SSRC which appear as the first SSRC in an FID ssrc-group
  230. // first.
  231. var firstSsrcs = [];
  232. var newSsrcLines = [];
  233. if (typeof mLine.ssrcGroups !== 'undefined' &&
  234. Array.isArray(mLine.ssrcGroups)) {
  235. mLine.ssrcGroups.forEach(function (group) {
  236. if (typeof group.semantics !== 'undefined' &&
  237. group.semantics === 'FID') {
  238. if (typeof group.ssrcs !== 'undefined') {
  239. firstSsrcs.push(Number(group.ssrcs.split(' ')[0]));
  240. }
  241. }
  242. });
  243. }
  244. if (typeof mLine.ssrcs !== 'undefined' && Array.isArray(mLine.ssrcs)) {
  245. var i;
  246. for (i = 0; i<mLine.ssrcs.length; i++){
  247. if (typeof mLine.ssrcs[i] === 'object'
  248. && typeof mLine.ssrcs[i].id !== 'undefined'
  249. && firstSsrcs.indexOf(mLine.ssrcs[i].id) >= 0) {
  250. newSsrcLines.push(mLine.ssrcs[i]);
  251. delete mLine.ssrcs[i];
  252. }
  253. }
  254. for (i = 0; i<mLine.ssrcs.length; i++){
  255. if (typeof mLine.ssrcs[i] !== 'undefined') {
  256. newSsrcLines.push(mLine.ssrcs[i]);
  257. }
  258. }
  259. mLine.ssrcs = newSsrcLines;
  260. }
  261. });
  262. }
  263. var resStr = transform.write(session);
  264. return new RTCSessionDescription({
  265. type: desc.type,
  266. sdp: resStr
  267. });
  268. };
  269. var getters = {
  270. signalingState: function () {
  271. return this.peerconnection.signalingState;
  272. },
  273. iceConnectionState: function () {
  274. return this.peerconnection.iceConnectionState;
  275. },
  276. localDescription: function() {
  277. var desc = this.peerconnection.localDescription;
  278. this.trace('getLocalDescription::preTransform', dumpSDP(desc));
  279. // if we're running on FF, transform to Plan B first.
  280. if (RTCBrowserType.usesUnifiedPlan()) {
  281. desc = this.interop.toPlanB(desc);
  282. this.trace('getLocalDescription::postTransform (Plan B)',
  283. dumpSDP(desc));
  284. }
  285. return desc;
  286. },
  287. remoteDescription: function() {
  288. var desc = this.peerconnection.remoteDescription;
  289. this.trace('getRemoteDescription::preTransform', dumpSDP(desc));
  290. // if we're running on FF, transform to Plan B first.
  291. if (RTCBrowserType.usesUnifiedPlan()) {
  292. desc = this.interop.toPlanB(desc);
  293. this.trace('getRemoteDescription::postTransform (Plan B)', dumpSDP(desc));
  294. }
  295. return desc;
  296. }
  297. };
  298. Object.keys(getters).forEach(function (prop) {
  299. Object.defineProperty(
  300. TraceablePeerConnection.prototype,
  301. prop, {
  302. get: getters[prop]
  303. }
  304. );
  305. });
  306. TraceablePeerConnection.prototype.addStream = function (stream, ssrcInfo) {
  307. this.trace('addStream', stream ? stream.id : "null");
  308. if (stream)
  309. this.peerconnection.addStream(stream);
  310. if (ssrcInfo && ssrcInfo.type === "addMuted") {
  311. this.sdpConsistency.setPrimarySsrc(ssrcInfo.ssrc.ssrcs[0]);
  312. const simGroup =
  313. ssrcInfo.ssrc.groups.find(groupInfo => {
  314. return groupInfo.group.semantics === "SIM";
  315. });
  316. if (simGroup) {
  317. const simSsrcs = SDPUtil.parseGroupSsrcs(simGroup.group);
  318. this.simulcast.setSsrcCache(simSsrcs);
  319. }
  320. const fidGroups =
  321. ssrcInfo.ssrc.groups.filter(groupInfo => {
  322. return groupInfo.group.semantics === "FID";
  323. });
  324. if (fidGroups) {
  325. const rtxSsrcMapping = new Map();
  326. fidGroups.forEach(fidGroup => {
  327. const fidGroupSsrcs =
  328. SDPUtil.parseGroupSsrcs(fidGroup.group);
  329. const primarySsrc = fidGroupSsrcs[0];
  330. const rtxSsrc = fidGroupSsrcs[1];
  331. rtxSsrcMapping.set(primarySsrc, rtxSsrc);
  332. });
  333. this.rtxModifier.setSsrcCache(rtxSsrcMapping);
  334. }
  335. }
  336. };
  337. TraceablePeerConnection.prototype.removeStream = function (stream) {
  338. this.trace('removeStream', stream.id);
  339. // FF doesn't support this yet.
  340. if (this.peerconnection.removeStream) {
  341. this.peerconnection.removeStream(stream);
  342. }
  343. };
  344. TraceablePeerConnection.prototype.createDataChannel = function (label, opts) {
  345. this.trace('createDataChannel', label, opts);
  346. return this.peerconnection.createDataChannel(label, opts);
  347. };
  348. TraceablePeerConnection.prototype.setLocalDescription
  349. = function (description, successCallback, failureCallback) {
  350. this.trace('setLocalDescription::preTransform', dumpSDP(description));
  351. // if we're running on FF, transform to Plan A first.
  352. if (RTCBrowserType.usesUnifiedPlan()) {
  353. description = this.interop.toUnifiedPlan(description);
  354. this.trace('setLocalDescription::postTransform (Plan A)',
  355. dumpSDP(description));
  356. }
  357. var self = this;
  358. this.peerconnection.setLocalDescription(description,
  359. function () {
  360. self.trace('setLocalDescriptionOnSuccess');
  361. successCallback();
  362. },
  363. function (err) {
  364. self.trace('setLocalDescriptionOnFailure', err);
  365. self.eventEmitter.emit(XMPPEvents.SET_LOCAL_DESCRIPTION_FAILED,
  366. err, self.peerconnection);
  367. failureCallback(err);
  368. }
  369. );
  370. };
  371. TraceablePeerConnection.prototype.setRemoteDescription
  372. = function (description, successCallback, failureCallback) {
  373. this.trace('setRemoteDescription::preTransform', dumpSDP(description));
  374. // TODO the focus should squeze or explode the remote simulcast
  375. description = this.simulcast.mungeRemoteDescription(description);
  376. this.trace('setRemoteDescription::postTransform (simulcast)', dumpSDP(description));
  377. if (this.options.preferH264) {
  378. const parsedSdp = transform.parse(description.sdp);
  379. const videoMLine = parsedSdp.media.find(m => m.type === "video");
  380. SDPUtil.preferVideoCodec(videoMLine, "h264");
  381. description.sdp = transform.write(parsedSdp);
  382. }
  383. // if we're running on FF, transform to Plan A first.
  384. if (RTCBrowserType.usesUnifiedPlan()) {
  385. description.sdp = this.rtxModifier.stripRtx(description.sdp);
  386. this.trace('setRemoteDescription::postTransform (stripRtx)', dumpSDP(description));
  387. description = this.interop.toUnifiedPlan(description);
  388. this.trace('setRemoteDescription::postTransform (Plan A)', dumpSDP(description));
  389. }
  390. if (RTCBrowserType.usesPlanB()) {
  391. description = normalizePlanB(description);
  392. }
  393. var self = this;
  394. this.peerconnection.setRemoteDescription(description,
  395. function () {
  396. self.trace('setRemoteDescriptionOnSuccess');
  397. successCallback();
  398. },
  399. function (err) {
  400. self.trace('setRemoteDescriptionOnFailure', err);
  401. self.eventEmitter.emit(XMPPEvents.SET_REMOTE_DESCRIPTION_FAILED,
  402. err, self.peerconnection);
  403. failureCallback(err);
  404. }
  405. );
  406. /*
  407. if (this.statsinterval === null && this.maxstats > 0) {
  408. // start gathering stats
  409. }
  410. */
  411. };
  412. /**
  413. * Makes the underlying TraceablePeerConnection generate new SSRC for
  414. * the recvonly video stream.
  415. * @deprecated
  416. */
  417. TraceablePeerConnection.prototype.generateRecvonlySsrc = function() {
  418. // FIXME replace with SDPUtil.generateSsrc (when it's added)
  419. const newSSRC = this.generateNewStreamSSRCInfo().ssrcs[0];
  420. logger.info("Generated new recvonly SSRC: " + newSSRC);
  421. this.sdpConsistency.setPrimarySsrc(newSSRC);
  422. };
  423. TraceablePeerConnection.prototype.close = function () {
  424. this.trace('stop');
  425. if (this.statsinterval !== null) {
  426. window.clearInterval(this.statsinterval);
  427. this.statsinterval = null;
  428. }
  429. this.peerconnection.close();
  430. };
  431. /**
  432. * Modifies the values of the setup attributes (defined by
  433. * {@link http://tools.ietf.org/html/rfc4145#section-4}) of a specific SDP
  434. * answer in order to overcome a delay of 1 second in the connection
  435. * establishment between Chrome and Videobridge.
  436. *
  437. * @param {SDP} offer - the SDP offer to which the specified SDP answer is
  438. * being prepared to respond
  439. * @param {SDP} answer - the SDP to modify
  440. * @private
  441. */
  442. var _fixAnswerRFC4145Setup = function (offer, answer) {
  443. if (!RTCBrowserType.isChrome()) {
  444. // It looks like Firefox doesn't agree with the fix (at least in its
  445. // current implementation) because it effectively remains active even
  446. // after we tell it to become passive. Apart from Firefox which I tested
  447. // after the fix was deployed, I tested Chrome only. In order to prevent
  448. // issues with other browsers, limit the fix to Chrome for the time
  449. // being.
  450. return;
  451. }
  452. // XXX Videobridge is the (SDP) offerer and WebRTC (e.g. Chrome) is the
  453. // answerer (as orchestrated by Jicofo). In accord with
  454. // http://tools.ietf.org/html/rfc5245#section-5.2 and because both peers
  455. // are ICE FULL agents, Videobridge will take on the controlling role and
  456. // WebRTC will take on the controlled role. In accord with
  457. // https://tools.ietf.org/html/rfc5763#section-5, Videobridge will use the
  458. // setup attribute value of setup:actpass and WebRTC will be allowed to
  459. // choose either the setup attribute value of setup:active or
  460. // setup:passive. Chrome will by default choose setup:active because it is
  461. // RECOMMENDED by the respective RFC since setup:passive adds additional
  462. // latency. The case of setup:active allows WebRTC to send a DTLS
  463. // ClientHello as soon as an ICE connectivity check of its succeeds.
  464. // Unfortunately, Videobridge will be unable to respond immediately because
  465. // may not have WebRTC's answer or may have not completed the ICE
  466. // connectivity establishment. Even more unfortunate is that in the
  467. // described scenario Chrome's DTLS implementation will insist on
  468. // retransmitting its ClientHello after a second (the time is in accord
  469. // with the respective RFC) and will thus cause the whole connection
  470. // establishment to exceed at least 1 second. To work around Chrome's
  471. // idiosyncracy, don't allow it to send a ClientHello i.e. change its
  472. // default choice of setup:active to setup:passive.
  473. if (offer && answer
  474. && offer.media && answer.media
  475. && offer.media.length == answer.media.length) {
  476. answer.media.forEach(function (a, i) {
  477. if (SDPUtil.find_line(
  478. offer.media[i],
  479. 'a=setup:actpass',
  480. offer.session)) {
  481. answer.media[i]
  482. = a.replace(/a=setup:active/g, 'a=setup:passive');
  483. }
  484. });
  485. answer.raw = answer.session + answer.media.join('');
  486. }
  487. };
  488. TraceablePeerConnection.prototype.createAnswer
  489. = function (successCallback, failureCallback, constraints) {
  490. this.trace('createAnswer', JSON.stringify(constraints, null, ' '));
  491. this.peerconnection.createAnswer(
  492. (answer) => {
  493. try {
  494. this.trace(
  495. 'createAnswerOnSuccess::preTransform', dumpSDP(answer));
  496. // if we're running on FF, transform to Plan A first.
  497. if (RTCBrowserType.usesUnifiedPlan()) {
  498. answer = this.interop.toPlanB(answer);
  499. this.trace('createAnswerOnSuccess::postTransform (Plan B)',
  500. dumpSDP(answer));
  501. }
  502. /**
  503. * We don't keep ssrcs consitent for Firefox because rewriting
  504. * the ssrcs between createAnswer and setLocalDescription
  505. * breaks the caching in sdp-interop (sdp-interop must
  506. * know about all ssrcs, and it updates its cache in
  507. * toPlanB so if we rewrite them after that, when we
  508. * try and go back to unified plan it will complain
  509. * about unmapped ssrcs)
  510. */
  511. if (!RTCBrowserType.isFirefox()) {
  512. answer.sdp = this.sdpConsistency.makeVideoPrimarySsrcsConsistent(answer.sdp);
  513. this.trace('createAnswerOnSuccess::postTransform (make primary video ssrcs consistent)',
  514. dumpSDP(answer));
  515. }
  516. // Add simulcast streams if simulcast is enabled
  517. if (!this.options.disableSimulcast
  518. && this.simulcast.isSupported()) {
  519. answer = this.simulcast.mungeLocalDescription(answer);
  520. this.trace(
  521. 'createAnswerOnSuccess::postTransform (simulcast)',
  522. dumpSDP(answer));
  523. }
  524. if (!this.options.disableRtx && !RTCBrowserType.isFirefox()) {
  525. answer.sdp = this.rtxModifier.modifyRtxSsrcs(answer.sdp);
  526. this.trace(
  527. 'createAnswerOnSuccess::postTransform (rtx modifier)',
  528. dumpSDP(answer));
  529. }
  530. // Fix the setup attribute (see _fixAnswerRFC4145Setup for
  531. // details)
  532. let remoteDescription = new SDP(this.remoteDescription.sdp);
  533. let localDescription = new SDP(answer.sdp);
  534. _fixAnswerRFC4145Setup(remoteDescription, localDescription);
  535. answer.sdp = localDescription.raw;
  536. this.eventEmitter.emit(XMPPEvents.SENDRECV_STREAMS_CHANGED,
  537. extractSSRCMap(answer));
  538. successCallback(answer);
  539. } catch (e) {
  540. this.trace('createAnswerOnError', e);
  541. this.trace('createAnswerOnError', dumpSDP(answer));
  542. logger.error('createAnswerOnError', e, dumpSDP(answer));
  543. failureCallback(e);
  544. }
  545. },
  546. (err) => {
  547. this.trace('createAnswerOnFailure', err);
  548. this.eventEmitter.emit(XMPPEvents.CREATE_ANSWER_FAILED, err,
  549. this.peerconnection);
  550. failureCallback(err);
  551. },
  552. constraints
  553. );
  554. };
  555. TraceablePeerConnection.prototype.addIceCandidate
  556. // eslint-disable-next-line no-unused-vars
  557. = function (candidate, successCallback, failureCallback) {
  558. //var self = this;
  559. this.trace('addIceCandidate', JSON.stringify(candidate, null, ' '));
  560. this.peerconnection.addIceCandidate(candidate);
  561. /* maybe later
  562. this.peerconnection.addIceCandidate(candidate,
  563. function () {
  564. self.trace('addIceCandidateOnSuccess');
  565. successCallback();
  566. },
  567. function (err) {
  568. self.trace('addIceCandidateOnFailure', err);
  569. failureCallback(err);
  570. }
  571. );
  572. */
  573. };
  574. TraceablePeerConnection.prototype.getStats = function(callback, errback) {
  575. // TODO: Is this the correct way to handle Opera, Temasys?
  576. if (RTCBrowserType.isFirefox()
  577. || RTCBrowserType.isTemasysPluginUsed()
  578. || RTCBrowserType.isReactNative()) {
  579. // ignore for now...
  580. if(!errback)
  581. errback = function () {};
  582. this.peerconnection.getStats(null, callback, errback);
  583. } else {
  584. this.peerconnection.getStats(callback);
  585. }
  586. };
  587. /**
  588. * Generate ssrc info object for a stream with the following properties:
  589. * - ssrcs - Array of the ssrcs associated with the stream.
  590. * - groups - Array of the groups associated with the stream.
  591. */
  592. TraceablePeerConnection.prototype.generateNewStreamSSRCInfo = function () {
  593. let ssrcInfo = {ssrcs: [], groups: []};
  594. if (!this.options.disableSimulcast
  595. && this.simulcast.isSupported()) {
  596. for (let i = 0; i < SIMULCAST_LAYERS; i++) {
  597. ssrcInfo.ssrcs.push(SDPUtil.generateSsrc());
  598. }
  599. ssrcInfo.groups.push({
  600. primarySSRC: ssrcInfo.ssrcs[0],
  601. group: {ssrcs: ssrcInfo.ssrcs.join(" "), semantics: "SIM"}});
  602. ssrcInfo;
  603. } else {
  604. ssrcInfo = {ssrcs: [SDPUtil.generateSsrc()], groups: []};
  605. }
  606. if (!this.options.disableRtx) {
  607. // Specifically use a for loop here because we'll
  608. // be adding to the list we're iterating over, so we
  609. // only want to iterate through the items originally
  610. // on the list
  611. const currNumSsrcs = ssrcInfo.ssrcs.length;
  612. for (let i = 0; i < currNumSsrcs; ++i) {
  613. const primarySsrc = ssrcInfo.ssrcs[i];
  614. const rtxSsrc = SDPUtil.generateSsrc();
  615. ssrcInfo.ssrcs.push(rtxSsrc);
  616. ssrcInfo.groups.push({
  617. primarySSRC: primarySsrc,
  618. group: {
  619. ssrcs: primarySsrc + " " + rtxSsrc,
  620. semantics: "FID"
  621. }
  622. });
  623. }
  624. }
  625. return ssrcInfo;
  626. };
  627. module.exports = TraceablePeerConnection;