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

RTCPeerConnection.js 70KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295
  1. /* global __filename, RTCIceGatherer, RTCIceTransport, RTCDtlsTransport,
  2. RTCRtpSender, RTCRtpReceiver */
  3. import { getLogger } from 'jitsi-meet-logger';
  4. import yaeti from 'yaeti';
  5. import RTCSessionDescription from './RTCSessionDescription';
  6. import * as utils from './utils';
  7. import { InvalidStateError } from './errors';
  8. import RandomUtil from '../../util/RandomUtil';
  9. import SDPUtil from '../../xmpp/SDPUtil';
  10. const logger = getLogger(__filename);
  11. const RTCSignalingState = {
  12. stable: 'stable',
  13. haveLocalOffer: 'have-local-offer',
  14. haveRemoteOffer: 'have-remote-offer',
  15. closed: 'closed'
  16. };
  17. const RTCIceGatheringState = {
  18. new: 'new',
  19. gathering: 'gathering',
  20. complete: 'complete'
  21. };
  22. const CNAME = `jitsi-ortc-cname-${RandomUtil.randomInt(10000, 99999)}`;
  23. /**
  24. * RTCPeerConnection shim for ORTC based endpoints (such as Edge).
  25. *
  26. * The interface is based on the W3C specification of 2015, which matches
  27. * the implementation of Chrome nowadays:
  28. *
  29. * https://www.w3.org/TR/2015/WD-webrtc-20150210/
  30. *
  31. * It also implements Plan-B for multi-stream, and assumes single BUNDLEd
  32. * transport and rtcp-mux.
  33. */
  34. export default class ortcRTCPeerConnection extends yaeti.EventTarget {
  35. /**
  36. */
  37. constructor(pcConfig) {
  38. super();
  39. logger.debug('constructor() pcConfig:', pcConfig);
  40. // Buffered local ICE candidates (in WebRTC format).
  41. // @type {sequence<RTCIceCandidate>}
  42. this._bufferedIceCandidates = [];
  43. // Closed flag.
  44. // @type {Boolean}
  45. this._closed = false;
  46. // RTCDtlsTransport.
  47. // @type {RTCDtlsTransport}
  48. this._dtlsTransport = null;
  49. // RTCIceGatherer.
  50. // @type {RTCIceGatherer}
  51. this._iceGatherer = null;
  52. // RTCPeerConnection iceGatheringState.
  53. // NOTE: This should not be needed, but Edge does not implement
  54. // iceGatherer.state.
  55. // @type {RTCIceGatheringState}
  56. this._iceGatheringState = RTCIceGatheringState.new;
  57. // RTCIceTransport.
  58. // @type {RTCIceTransport}
  59. this._iceTransport = null;
  60. // Local RTP capabilities (filtered with remote ones).
  61. // @type {RTCRtpCapabilities}
  62. this._localCapabilities = null;
  63. // Local RTCSessionDescription.
  64. // @type {RTCSessionDescription}
  65. this._localDescription = null;
  66. // Map with info regarding local media.
  67. // - index: MediaStreamTrack.id
  68. // - value: Object
  69. // - rtpSender: Associated RTCRtpSender instance
  70. // - stream: Associated MediaStream instance
  71. // - ssrc: Provisional or definitive SSRC
  72. // - rtxSsrc: Provisional or definitive SSRC for RTX
  73. // - sending: Boolean indicating whether rtpSender.send() was called.
  74. this._localTrackInfos = new Map();
  75. // Ordered Map with MID as key and kind as value.
  76. // @type {map<String, String>}
  77. this._mids = new Map();
  78. // Remote RTCSessionDescription.
  79. // @type {RTCSessionDescription}
  80. this._remoteDescription = null;
  81. // Map of remote streams.
  82. // - index: MediaStream.jitsiRemoteId (as signaled in remote SDP)
  83. // - value: MediaStream (locally generated so id does not match)
  84. // @type {map<Number, MediaStream>}
  85. this._remoteStreams = new Map();
  86. // Map with info about receiving media.
  87. // - index: Media SSRC
  88. // - value: Object
  89. // - kind: 'audio' / 'video'
  90. // - ssrc: Media SSRC
  91. // - rtxSsrc: RTX SSRC (may be unset)
  92. // - streamId: MediaStream.jitsiRemoteId
  93. // - trackId: MediaStreamTrack.jitsiRemoteId
  94. // - cname: CNAME
  95. // - stream: MediaStream
  96. // - track: MediaStreamTrack
  97. // - rtpReceiver: Associated RTCRtpReceiver instance
  98. // @type {map<Number, Object>}
  99. this._remoteTrackInfos = new Map();
  100. // Local SDP global fields.
  101. this._sdpGlobalFields = {
  102. id: SDPUtil.generateSsrc(),
  103. version: 0
  104. };
  105. // RTCPeerConnection signalingState.
  106. // @type {RTCSignalingState}
  107. this._signalingState = RTCSignalingState.stable;
  108. // Create the RTCIceGatherer.
  109. this._setIceGatherer(pcConfig);
  110. // Create the RTCIceTransport.
  111. this._setIceTransport(this._iceGatherer);
  112. // Create the RTCDtlsTransport.
  113. this._setDtlsTransport(this._iceTransport);
  114. }
  115. /**
  116. * Current ICE+DTLS connection state.
  117. * @return {RTCPeerConnectionState}
  118. */
  119. get connectionState() {
  120. return this._dtlsTransport.state;
  121. }
  122. /**
  123. * Current ICE connection state.
  124. * @return {RTCIceConnectionState}
  125. */
  126. get iceConnectionState() {
  127. return this._iceTransport.state;
  128. }
  129. /**
  130. * Current ICE gathering state.
  131. * @return {RTCIceGatheringState}
  132. */
  133. get iceGatheringState() {
  134. return this._iceGatheringState;
  135. }
  136. /**
  137. * Gets the local description.
  138. * @return {RTCSessionDescription}
  139. */
  140. get localDescription() {
  141. return this._localDescription;
  142. }
  143. /**
  144. * Gets the remote description.
  145. * @return {RTCSessionDescription}
  146. */
  147. get remoteDescription() {
  148. return this._remoteDescription;
  149. }
  150. /**
  151. * Current signaling state.
  152. * @return {RTCSignalingState}
  153. */
  154. get signalingState() {
  155. return this._signalingState;
  156. }
  157. /**
  158. * Adds a remote ICE candidate. Implements both the old callbacks based
  159. * signature and the new Promise based style.
  160. *
  161. * Arguments in Promise mode:
  162. * @param {RTCIceCandidate} candidate
  163. *
  164. * Arguments in callbacks mode:
  165. * @param {RTCIceCandidate} candidate
  166. * @param {function()} callback
  167. * @param {function(error)} errback
  168. */
  169. addIceCandidate(candidate, ...args) {
  170. let usePromise;
  171. let callback;
  172. let errback;
  173. if (!candidate) {
  174. throw new TypeError('candidate missing');
  175. }
  176. if (args.length === 0) {
  177. usePromise = true;
  178. } else {
  179. usePromise = false;
  180. callback = args[0];
  181. errback = args[1];
  182. if (typeof callback !== 'function') {
  183. throw new TypeError('callback missing');
  184. }
  185. if (typeof errback !== 'function') {
  186. throw new TypeError('errback missing');
  187. }
  188. }
  189. logger.debug('addIceCandidate() candidate:', candidate);
  190. if (usePromise) {
  191. return this._addIceCandidate(candidate);
  192. }
  193. this._addIceCandidate(candidate)
  194. .then(() => callback())
  195. .catch(error => errback(error));
  196. }
  197. /**
  198. * Adds a local MediaStream.
  199. * @param {MediaStream} stream.
  200. * NOTE: Deprecated API.
  201. */
  202. addStream(stream) {
  203. logger.debug('addStream()');
  204. this._addStream(stream);
  205. }
  206. /**
  207. * Closes the RTCPeerConnection and all the underlying ORTC objects.
  208. */
  209. close() {
  210. if (this._closed) {
  211. return;
  212. }
  213. this._closed = true;
  214. logger.debug('close()');
  215. this._updateAndEmitSignalingStateChange(RTCSignalingState.closed);
  216. // Close RTCIceGatherer.
  217. // NOTE: Not yet implemented by Edge.
  218. try {
  219. this._iceGatherer.close();
  220. } catch (error) {
  221. logger.warn(`iceGatherer.close() failed:${error}`);
  222. }
  223. // Close RTCIceTransport.
  224. try {
  225. this._iceTransport.stop();
  226. } catch (error) {
  227. logger.warn(`iceTransport.stop() failed:${error}`);
  228. }
  229. // Close RTCDtlsTransport.
  230. try {
  231. this._dtlsTransport.stop();
  232. } catch (error) {
  233. logger.warn(`dtlsTransport.stop() failed:${error}`);
  234. }
  235. // Close and clear RTCRtpSenders.
  236. for (const info of this._localTrackInfos.values()) {
  237. const rtpSender = info.rtpSender;
  238. try {
  239. rtpSender.stop();
  240. } catch (error) {
  241. logger.warn(`rtpSender.stop() failed:${error}`);
  242. }
  243. }
  244. this._localTrackInfos.clear();
  245. // Close and clear RTCRtpReceivers.
  246. for (const info of this._remoteTrackInfos.values()) {
  247. const rtpReceiver = info.rtpReceiver;
  248. try {
  249. rtpReceiver.stop();
  250. } catch (error) {
  251. logger.warn(`rtpReceiver.stop() failed:${error}`);
  252. }
  253. }
  254. this._remoteTrackInfos.clear();
  255. // Clear remote streams.
  256. this._remoteStreams.clear();
  257. }
  258. /**
  259. * Creates a local answer. Implements both the old callbacks based signature
  260. * and the new Promise based style.
  261. *
  262. * Arguments in Promise mode:
  263. * @param {RTCOfferOptions} [options]
  264. *
  265. * Arguments in callbacks mode:
  266. * @param {function(desc)} callback
  267. * @param {function(error)} errback
  268. * @param {MediaConstraints} [constraints]
  269. */
  270. createAnswer(...args) {
  271. let usePromise;
  272. let options;
  273. let callback;
  274. let errback;
  275. if (args.length <= 1) {
  276. usePromise = true;
  277. options = args[0];
  278. } else {
  279. usePromise = false;
  280. callback = args[0];
  281. errback = args[1];
  282. options = args[2];
  283. if (typeof callback !== 'function') {
  284. throw new TypeError('callback missing');
  285. }
  286. if (typeof errback !== 'function') {
  287. throw new TypeError('errback missing');
  288. }
  289. }
  290. logger.debug('createAnswer() options:', options);
  291. if (usePromise) {
  292. return this._createAnswer(options);
  293. }
  294. this._createAnswer(options)
  295. .then(desc => callback(desc))
  296. .catch(error => errback(error));
  297. }
  298. /**
  299. * Creates a RTCDataChannel.
  300. */
  301. createDataChannel() {
  302. logger.debug('createDataChannel()');
  303. // NOTE: DataChannels not implemented in Edge.
  304. throw new Error('createDataChannel() not supported in Edge');
  305. }
  306. /**
  307. * Creates a local offer. Implements both the old callbacks based signature
  308. * and the new Promise based style.
  309. *
  310. * Arguments in Promise mode:
  311. * @param {RTCOfferOptions} [options]
  312. *
  313. * Arguments in callbacks mode:
  314. * @param {function(desc)} callback
  315. * @param {function(error)} errback
  316. * @param {MediaConstraints} [constraints]
  317. */
  318. createOffer(...args) {
  319. let usePromise;
  320. let options;
  321. let callback;
  322. let errback;
  323. if (args.length <= 1) {
  324. usePromise = true;
  325. options = args[0];
  326. } else {
  327. usePromise = false;
  328. callback = args[0];
  329. errback = args[1];
  330. options = args[2];
  331. if (typeof callback !== 'function') {
  332. throw new TypeError('callback missing');
  333. }
  334. if (typeof errback !== 'function') {
  335. throw new TypeError('errback missing');
  336. }
  337. }
  338. logger.debug('createOffer() options:', options);
  339. if (usePromise) {
  340. return this._createOffer(options);
  341. }
  342. this._createOffer(options)
  343. .then(desc => callback(desc))
  344. .catch(error => errback(error));
  345. }
  346. /**
  347. * Gets a sequence of local MediaStreams.
  348. * @return {sequence<MediaStream>}
  349. */
  350. getLocalStreams() {
  351. return Array.from(this._localTrackInfos.values())
  352. .map(info => info.stream)
  353. .filter((elem, pos, arr) => arr.indexOf(elem) === pos);
  354. }
  355. /**
  356. * Gets a sequence of remote MediaStreams.
  357. * @return {sequence<MediaStream>}
  358. */
  359. getRemoteStreams() {
  360. return Array.from(this._remoteStreams.values());
  361. }
  362. /**
  363. * Get RTP statistics. Implements both the old callbacks based signature
  364. * and the new Promise based style.
  365. *
  366. * Arguments in Promise mode:
  367. * @param {MediaStreamTrack} [selector]
  368. *
  369. * Arguments in callbacks mode:
  370. * @param {MediaStreamTrack} [selector]
  371. * @param {function(desc)} callback
  372. * @param {function(error)} errback
  373. */
  374. getStats(...args) {
  375. let usePromise;
  376. let selector;
  377. let callback;
  378. let errback;
  379. if (typeof args[0] === 'function') {
  380. usePromise = false;
  381. callback = args[0];
  382. errback = args[1];
  383. } else if (typeof args[1] === 'function') {
  384. usePromise = false;
  385. selector = args[0];
  386. callback = args[1];
  387. errback = args[2];
  388. } else {
  389. usePromise = true;
  390. selector = args[0];
  391. }
  392. if (!usePromise && !errback) {
  393. errback = error => {
  394. logger.error(`getStats() failed: ${error}`);
  395. logger.error(error.stack);
  396. };
  397. }
  398. if (usePromise) {
  399. return this._getStats(selector);
  400. }
  401. this._getStats(selector)
  402. .then(stats => callback(stats))
  403. .catch(error => errback(error));
  404. }
  405. /**
  406. * Removes a local MediaStream.
  407. * @param {MediaStream} stream.
  408. * NOTE: Deprecated API.
  409. */
  410. removeStream(stream) {
  411. logger.debug('removeStream()');
  412. this._removeStream(stream);
  413. }
  414. /**
  415. * Applies a local description. Implements both the old callbacks based
  416. * signature and the new Promise based style.
  417. *
  418. * Arguments in Promise mode:
  419. * @param {RTCSessionDescriptionInit} desc
  420. *
  421. * Arguments in callbacks mode:
  422. * @param {RTCSessionDescription} desc
  423. * @param {function()} callback
  424. * @param {function(error)} errback
  425. */
  426. setLocalDescription(desc, ...args) {
  427. let usePromise;
  428. let callback;
  429. let errback;
  430. if (!desc) {
  431. throw new TypeError('description missing');
  432. }
  433. if (args.length === 0) {
  434. usePromise = true;
  435. } else {
  436. usePromise = false;
  437. callback = args[0];
  438. errback = args[1];
  439. if (typeof callback !== 'function') {
  440. throw new TypeError('callback missing');
  441. }
  442. if (typeof errback !== 'function') {
  443. throw new TypeError('errback missing');
  444. }
  445. }
  446. logger.debug('setLocalDescription() desc:', desc);
  447. if (usePromise) {
  448. return this._setLocalDescription(desc);
  449. }
  450. this._setLocalDescription(desc)
  451. .then(() => callback())
  452. .catch(error => errback(error));
  453. }
  454. /**
  455. * Applies a remote description. Implements both the old callbacks based
  456. * signature and the new Promise based style.
  457. *
  458. * Arguments in Promise mode:
  459. * @param {RTCSessionDescriptionInit} desc
  460. *
  461. * Arguments in callbacks mode:
  462. * @param {RTCSessionDescription} desc
  463. * @param {function()} callback
  464. * @param {function(error)} errback
  465. */
  466. setRemoteDescription(desc, ...args) {
  467. let usePromise;
  468. let callback;
  469. let errback;
  470. if (!desc) {
  471. throw new TypeError('description missing');
  472. }
  473. if (args.length === 0) {
  474. usePromise = true;
  475. } else {
  476. usePromise = false;
  477. callback = args[0];
  478. errback = args[1];
  479. if (typeof callback !== 'function') {
  480. throw new TypeError('callback missing');
  481. }
  482. if (typeof errback !== 'function') {
  483. throw new TypeError('errback missing');
  484. }
  485. }
  486. logger.debug('setRemoteDescription() desc:', desc);
  487. if (usePromise) {
  488. return this._setRemoteDescription(desc);
  489. }
  490. this._setRemoteDescription(desc)
  491. .then(() => callback())
  492. .catch(error => errback(error));
  493. }
  494. /**
  495. * Promise based implementation for addIceCandidate().
  496. * @return {Promise}
  497. * @private
  498. */
  499. _addIceCandidate(candidate) { // eslint-disable-line no-unused-vars
  500. if (this._closed) {
  501. return Promise.reject(
  502. new InvalidStateError('RTCPeerConnection closed'));
  503. }
  504. // NOTE: Edge does not support Trickle-ICE so just candidates in the
  505. // remote SDP are applied. Candidates given later would be just
  506. // ignored, so notify the called about that.
  507. return Promise.reject(new Error('addIceCandidate() not supported'));
  508. }
  509. /**
  510. * Implementation for addStream().
  511. * @private
  512. */
  513. _addStream(stream) {
  514. if (this._closed) {
  515. throw new InvalidStateError('RTCPeerConnection closed');
  516. }
  517. // Create a RTCRtpSender for each track.
  518. for (const track of stream.getTracks()) {
  519. // Ignore if ended.
  520. if (track.readyState === 'ended') {
  521. logger.warn('ignoring ended MediaStreamTrack');
  522. continue; // eslint-disable-line no-continue
  523. }
  524. // Ignore if track is already present.
  525. if (this._localTrackInfos.has(track.id)) {
  526. logger.warn('ignoring already handled MediaStreamTrack');
  527. continue; // eslint-disable-line no-continue
  528. }
  529. const rtpSender = new RTCRtpSender(track, this._dtlsTransport);
  530. // Store it in the map.
  531. this._localTrackInfos.set(track.id, {
  532. rtpSender,
  533. stream
  534. });
  535. }
  536. // Check for local tracks removal.
  537. for (const [ trackId, info ] of this._localTrackInfos) {
  538. const track = info.rtpSender.track;
  539. // Check if any of the local tracks has been stopped.
  540. if (track.readyState === 'ended') {
  541. logger.warn(
  542. '_addStream() an already handled track was stopped, '
  543. + `track.id:${track.id}`);
  544. try {
  545. info.rtpSender.stop();
  546. } catch (error) {
  547. logger.warn(`rtpSender.stop() failed:${error}`);
  548. }
  549. // Remove from the map.
  550. this._localTrackInfos.delete(track.id);
  551. // Also, if the stream was already handled, check whether tracks
  552. // have been removed via stream.removeTrack() and, if so, stop
  553. // their RtpSenders.
  554. } else if (info.stream === stream
  555. && !stream.getTrackById(trackId)) {
  556. logger.warn(
  557. '_addStream() a track in this stream was removed, '
  558. + `track.id:${trackId}`);
  559. try {
  560. info.rtpSender.stop();
  561. } catch (error) {
  562. logger.warn(`rtpSender.stop() failed:${error}`);
  563. }
  564. // Remove from the map.
  565. this._localTrackInfos.delete(track.id);
  566. }
  567. }
  568. // It may need to renegotiate.
  569. this._emitNegotiationNeeded();
  570. }
  571. /**
  572. * Promise based implementation for createAnswer().
  573. * @returns {Promise}
  574. * @private
  575. */
  576. _createAnswer(options) { // eslint-disable-line no-unused-vars
  577. if (this._closed) {
  578. return Promise.reject(
  579. new InvalidStateError('RTCPeerConnection closed'));
  580. }
  581. if (this.signalingState !== RTCSignalingState.haveRemoteOffer) {
  582. return Promise.reject(new InvalidStateError(
  583. `invalid signalingState "${this.signalingState}"`));
  584. }
  585. // Create an answer.
  586. const localDescription = this._createLocalDescription('answer');
  587. // Resolve with it.
  588. return Promise.resolve(localDescription);
  589. }
  590. /**
  591. * Creates the local RTCSessionDescription.
  592. * @param {String} type - 'offer' / 'answer'.
  593. * @return {RTCSessionDescription}
  594. */
  595. _createLocalDescription(type) {
  596. const sdpObject = {};
  597. const localIceParameters = this._iceGatherer.getLocalParameters();
  598. const localIceCandidates = this._iceGatherer.getLocalCandidates();
  599. const localDtlsParameters = this._dtlsTransport.getLocalParameters();
  600. const remoteDtlsParameters = this._dtlsTransport.getRemoteParameters();
  601. const localCapabilities = this._localCapabilities;
  602. const localTrackInfos = this._localTrackInfos;
  603. // Increase SDP version if an offer.
  604. if (type === 'offer') {
  605. this._sdpGlobalFields.version++;
  606. }
  607. // SDP global fields.
  608. sdpObject.version = 0;
  609. sdpObject.origin = {
  610. address: '127.0.0.1',
  611. ipVer: 4,
  612. netType: 'IN',
  613. sessionId: this._sdpGlobalFields.id,
  614. sessionVersion: this._sdpGlobalFields.version,
  615. username: 'jitsi-ortc-webrtc-shim'
  616. };
  617. sdpObject.name = '-';
  618. sdpObject.timing = {
  619. start: 0,
  620. stop: 0
  621. };
  622. sdpObject.msidSemantic = {
  623. semantic: 'WMS',
  624. token: '*'
  625. };
  626. sdpObject.groups = [
  627. {
  628. mids: Array.from(this._mids.keys()).join(' '),
  629. type: 'BUNDLE'
  630. }
  631. ];
  632. sdpObject.media = [];
  633. // DTLS fingerprint.
  634. sdpObject.fingerprint = {
  635. hash: localDtlsParameters.fingerprints[0].value,
  636. type: localDtlsParameters.fingerprints[0].algorithm
  637. };
  638. // Let's check whether there is video RTX.
  639. let hasVideoRtx = false;
  640. for (const codec of localCapabilities.codecs) {
  641. if (codec.kind === 'video' && codec.name === 'rtx') {
  642. hasVideoRtx = true;
  643. break;
  644. }
  645. }
  646. // Add m= sections.
  647. for (const [ mid, kind ] of this._mids) {
  648. addMediaSection.call(this, mid, kind);
  649. }
  650. // Create a RTCSessionDescription.
  651. const localDescription = new RTCSessionDescription({
  652. type,
  653. _sdpObject: sdpObject
  654. });
  655. logger.debug('_createLocalDescription():', localDescription);
  656. return localDescription;
  657. /**
  658. * Add a m= section.
  659. */
  660. function addMediaSection(mid, kind) {
  661. const mediaObject = {};
  662. // m= line.
  663. mediaObject.type = kind;
  664. switch (kind) {
  665. case 'audio':
  666. case 'video':
  667. mediaObject.protocol = 'RTP/SAVPF';
  668. mediaObject.port = 9;
  669. mediaObject.direction = 'sendrecv';
  670. break;
  671. case 'application':
  672. mediaObject.protocol = 'DTLS/SCTP';
  673. mediaObject.port = 0; // Reject m section.
  674. mediaObject.payloads = '0'; // Just put something.
  675. mediaObject.direction = 'inactive';
  676. break;
  677. }
  678. // c= line.
  679. mediaObject.connection = {
  680. ip: '127.0.0.1',
  681. version: 4
  682. };
  683. // a=mid attribute.
  684. mediaObject.mid = mid;
  685. // ICE.
  686. mediaObject.iceUfrag = localIceParameters.usernameFragment;
  687. mediaObject.icePwd = localIceParameters.password;
  688. mediaObject.candidates = [];
  689. for (const candidate of localIceCandidates) {
  690. const candidateObject = {};
  691. // rtcp-mux is assumed, so component is always 1 (RTP).
  692. candidateObject.component = 1;
  693. candidateObject.foundation = candidate.foundation;
  694. candidateObject.ip = candidate.ip;
  695. candidateObject.port = candidate.port;
  696. candidateObject.priority = candidate.priority;
  697. candidateObject.transport
  698. = candidate.protocol.toLowerCase();
  699. candidateObject.type = candidate.type;
  700. if (candidateObject.transport === 'tcp') {
  701. candidateObject.tcptype = candidate.tcpType;
  702. }
  703. mediaObject.candidates.push(candidateObject);
  704. }
  705. mediaObject.endOfCandidates = 'end-of-candidates';
  706. // DTLS.
  707. // If 'offer' always use 'actpass'.
  708. if (type === 'offer') {
  709. mediaObject.setup = 'actpass';
  710. } else {
  711. mediaObject.setup = remoteDtlsParameters.role === 'server'
  712. ? 'active' : 'passive';
  713. }
  714. if (kind === 'audio' || kind === 'video') {
  715. mediaObject.rtp = [];
  716. mediaObject.rtcpFb = [];
  717. mediaObject.fmtp = [];
  718. // Array of payload types.
  719. const payloads = [];
  720. // Add codecs.
  721. for (const codec of localCapabilities.codecs) {
  722. if (codec.kind && codec.kind !== kind) {
  723. continue; // eslint-disable-line no-continue
  724. }
  725. payloads.push(codec.preferredPayloadType);
  726. const rtpObject = {
  727. codec: codec.name,
  728. payload: codec.preferredPayloadType,
  729. rate: codec.clockRate
  730. };
  731. if (codec.numChannels > 1) {
  732. rtpObject.encoding = codec.numChannels;
  733. }
  734. mediaObject.rtp.push(rtpObject);
  735. // If codec has parameters add them into a=fmtp attributes.
  736. if (codec.parameters) {
  737. const paramFmtp = {
  738. config: '',
  739. payload: codec.preferredPayloadType
  740. };
  741. for (const name of Object.keys(codec.parameters)) {
  742. /* eslint-disable max-depth */
  743. if (paramFmtp.config) {
  744. paramFmtp.config += ';';
  745. }
  746. /* eslint-enable max-depth */
  747. paramFmtp.config
  748. += `${name}=${codec.parameters[name]}`;
  749. }
  750. if (paramFmtp.config) {
  751. mediaObject.fmtp.push(paramFmtp);
  752. }
  753. }
  754. // Set RTCP feedback.
  755. for (const fb of codec.rtcpFeedback || []) {
  756. mediaObject.rtcpFb.push({
  757. payload: codec.preferredPayloadType,
  758. subtype: fb.parameter || undefined,
  759. type: fb.type
  760. });
  761. }
  762. }
  763. // If there are no codecs, set this m section as unavailable.
  764. if (payloads.length === 0) {
  765. mediaObject.payloads = '9'; // Just put something.
  766. mediaObject.port = 0;
  767. mediaObject.direction = 'inactive';
  768. } else {
  769. mediaObject.payloads = payloads.join(' ');
  770. }
  771. // SSRCs.
  772. mediaObject.ssrcs = [];
  773. mediaObject.ssrcGroups = [];
  774. // Add RTP sending stuff.
  775. for (const info of localTrackInfos.values()) {
  776. const rtpSender = info.rtpSender;
  777. const streamId = info.stream.id;
  778. const track = rtpSender.track;
  779. // Ignore if ended.
  780. if (track.readyState === 'ended') {
  781. continue; // eslint-disable-line no-continue
  782. }
  783. if (track.kind !== kind) {
  784. continue; // eslint-disable-line no-continue
  785. }
  786. // Set a random provisional SSRC if not set.
  787. if (!info.ssrc) {
  788. info.ssrc = SDPUtil.generateSsrc();
  789. }
  790. // Whether RTX should be enabled.
  791. const enableRtx = hasVideoRtx && track.kind === 'video';
  792. // Set a random provisional RTX SSRC if not set.
  793. if (enableRtx && !info.rtxSsrc) {
  794. info.rtxSsrc = info.ssrc + 1;
  795. }
  796. mediaObject.ssrcs.push({
  797. attribute: 'cname',
  798. id: info.ssrc,
  799. value: CNAME
  800. });
  801. mediaObject.ssrcs.push({
  802. attribute: 'msid',
  803. id: info.ssrc,
  804. value: `${streamId} ${track.id}`
  805. });
  806. mediaObject.ssrcs.push({
  807. attribute: 'mslabel',
  808. id: info.ssrc,
  809. value: streamId
  810. });
  811. mediaObject.ssrcs.push({
  812. attribute: 'label',
  813. id: info.ssrc,
  814. value: track.id
  815. });
  816. if (enableRtx) {
  817. mediaObject.ssrcs.push({
  818. attribute: 'cname',
  819. id: info.rtxSsrc,
  820. value: CNAME
  821. });
  822. mediaObject.ssrcs.push({
  823. attribute: 'msid',
  824. id: info.rtxSsrc,
  825. value: `${streamId} ${track.id}`
  826. });
  827. mediaObject.ssrcs.push({
  828. attribute: 'mslabel',
  829. id: info.rtxSsrc,
  830. value: streamId
  831. });
  832. mediaObject.ssrcs.push({
  833. attribute: 'label',
  834. id: info.rtxSsrc,
  835. value: track.id
  836. });
  837. mediaObject.ssrcGroups.push({
  838. semantics: 'FID',
  839. ssrcs: `${info.ssrc} ${info.rtxSsrc}`
  840. });
  841. }
  842. }
  843. // RTP header extensions.
  844. mediaObject.ext = [];
  845. for (const extension of localCapabilities.headerExtensions) {
  846. if (extension.kind && extension.kind !== kind) {
  847. continue; // eslint-disable-line no-continue
  848. }
  849. mediaObject.ext.push({
  850. value: extension.preferredId,
  851. uri: extension.uri
  852. });
  853. }
  854. // a=rtcp-mux attribute.
  855. mediaObject.rtcpMux = 'rtcp-mux';
  856. // a=rtcp-rsize.
  857. mediaObject.rtcpRsize = 'rtcp-rsize';
  858. }
  859. // Add the media section.
  860. sdpObject.media.push(mediaObject);
  861. }
  862. }
  863. /**
  864. * Promise based implementation for createOffer().
  865. * @returns {Promise}
  866. * @private
  867. */
  868. _createOffer(options) { // eslint-disable-line no-unused-vars
  869. if (this._closed) {
  870. return Promise.reject(
  871. new InvalidStateError('RTCPeerConnection closed'));
  872. }
  873. if (this.signalingState !== RTCSignalingState.stable) {
  874. return Promise.reject(new InvalidStateError(
  875. `invalid signalingState "${this.signalingState}"`));
  876. }
  877. // NOTE: P2P mode not yet supported, so createOffer() should never be
  878. // called.
  879. return Promise.reject(new Error('createoOffer() not yet supported'));
  880. }
  881. /**
  882. * Emit 'addstream' event.
  883. * @private
  884. */
  885. _emitAddStream(stream) {
  886. if (this._closed) {
  887. return;
  888. }
  889. logger.debug('emitting "addstream"');
  890. const event = new yaeti.Event('addstream');
  891. event.stream = stream;
  892. this.dispatchEvent(event);
  893. }
  894. /**
  895. * May emit buffered ICE candidates.
  896. * @private
  897. */
  898. _emitBufferedIceCandidates() {
  899. if (this._closed) {
  900. return;
  901. }
  902. for (const sdpCandidate of this._bufferedIceCandidates) {
  903. if (!sdpCandidate) {
  904. continue; // eslint-disable-line no-continue
  905. }
  906. // Now we have set the MID values of the SDP O/A, so let's fill the
  907. // sdpMIndex of the candidate.
  908. sdpCandidate.sdpMIndex = this._mids.keys().next().value;
  909. logger.debug(
  910. 'emitting buffered "icecandidate", candidate:', sdpCandidate);
  911. const event = new yaeti.Event('icecandidate');
  912. event.candidate = sdpCandidate;
  913. this.dispatchEvent(event);
  914. }
  915. this._bufferedIceCandidates = [];
  916. }
  917. /**
  918. * May emit 'connectionstatechange' event.
  919. * @private
  920. */
  921. _emitConnectionStateChange() {
  922. if (this._closed && this.connectionState !== 'closed') {
  923. return;
  924. }
  925. logger.debug(
  926. 'emitting "connectionstatechange", connectionState:',
  927. this.connectionState);
  928. const event = new yaeti.Event('connectionstatechange');
  929. this.dispatchEvent(event);
  930. }
  931. /**
  932. * May emit 'icecandidate' event.
  933. * @private
  934. */
  935. _emitIceCandidate(candidate) {
  936. if (this._closed) {
  937. return;
  938. }
  939. let sdpCandidate = null;
  940. if (candidate) {
  941. // NOTE: We assume BUNDLE so let's just emit candidates for the
  942. // first m= section.
  943. const sdpMIndex = this._mids.keys().next().value;
  944. const sdpMLineIndex = 0;
  945. let sdpAttribute
  946. = `candidate:${candidate.foundation} 1 ${candidate.protocol}`
  947. + ` ${candidate.priority} ${candidate.ip} ${candidate.port}`
  948. + ` typ ${candidate.type}`;
  949. if (candidate.relatedAddress) {
  950. sdpAttribute += ` raddr ${candidate.relatedAddress}`;
  951. }
  952. if (candidate.relatedPort) {
  953. sdpAttribute += ` rport ${candidate.relatedPort}`;
  954. }
  955. if (candidate.protocol === 'tcp') {
  956. sdpAttribute += ` tcptype ${candidate.tcpType}`;
  957. }
  958. sdpCandidate = {
  959. candidate: sdpAttribute,
  960. component: 1, // rtcp-mux assumed, so always 1 (RTP).
  961. foundation: candidate.foundation,
  962. ip: candidate.ip,
  963. port: candidate.port,
  964. priority: candidate.priority,
  965. protocol: candidate.protocol,
  966. type: candidate.type,
  967. sdpMIndex,
  968. sdpMLineIndex
  969. };
  970. if (candidate.protocol === 'tcp') {
  971. sdpCandidate.tcptype = candidate.tcpType;
  972. }
  973. if (candidate.relatedAddress) {
  974. sdpCandidate.relatedAddress = candidate.relatedAddress;
  975. }
  976. if (candidate.relatedPort) {
  977. sdpCandidate.relatedPort = candidate.relatedPort;
  978. }
  979. }
  980. // If we don't have yet a local description, buffer the candidate.
  981. if (this._localDescription) {
  982. logger.debug(
  983. 'emitting "icecandidate", candidate:', sdpCandidate);
  984. const event = new yaeti.Event('icecandidate');
  985. event.candidate = sdpCandidate;
  986. this.dispatchEvent(event);
  987. } else {
  988. logger.debug(
  989. 'buffering gathered ICE candidate:', sdpCandidate);
  990. this._bufferedIceCandidates.push(sdpCandidate);
  991. }
  992. }
  993. /**
  994. * May emit 'iceconnectionstatechange' event.
  995. * @private
  996. */
  997. _emitIceConnectionStateChange() {
  998. if (this._closed && this.iceConnectionState !== 'closed') {
  999. return;
  1000. }
  1001. logger.debug(
  1002. 'emitting "iceconnectionstatechange", iceConnectionState:',
  1003. this.iceConnectionState);
  1004. const event = new yaeti.Event('iceconnectionstatechange');
  1005. this.dispatchEvent(event);
  1006. }
  1007. /**
  1008. * May emit 'negotiationneeded' event.
  1009. * @private
  1010. */
  1011. _emitNegotiationNeeded() {
  1012. // Ignore if signalingState is not 'stable'.
  1013. if (this.signalingState !== RTCSignalingState.stable) {
  1014. return;
  1015. }
  1016. logger.debug('emitting "negotiationneeded"');
  1017. const event = new yaeti.Event('negotiationneeded');
  1018. this.dispatchEvent(event);
  1019. }
  1020. /**
  1021. * Emit 'removestream' event.
  1022. * @private
  1023. */
  1024. _emitRemoveStream(stream) {
  1025. if (this._closed) {
  1026. return;
  1027. }
  1028. logger.debug('emitting "removestream"');
  1029. const event = new yaeti.Event('removestream');
  1030. event.stream = stream;
  1031. this.dispatchEvent(event);
  1032. }
  1033. /**
  1034. * Get RTP parameters for a RTCRtpReceiver.
  1035. * @private
  1036. * @return {RTCRtpParameters}
  1037. */
  1038. _getParametersForRtpReceiver(kind, data) {
  1039. const ssrc = data.ssrc;
  1040. const rtxSsrc = data.rtxSsrc;
  1041. const cname = data.cname;
  1042. const localCapabilities = this._localCapabilities;
  1043. const parameters = {
  1044. codecs: [],
  1045. degradationPreference: 'balanced',
  1046. encodings: [],
  1047. headerExtensions: [],
  1048. muxId: '',
  1049. rtcp: {
  1050. cname,
  1051. compound: true, // NOTE: Implemented in Edge.
  1052. mux: true,
  1053. reducedSize: true // NOTE: Not yet implemented in Edge.
  1054. }
  1055. };
  1056. const codecs = [];
  1057. let codecPayloadType;
  1058. for (const codecCapability of localCapabilities.codecs) {
  1059. if (codecCapability.kind !== kind
  1060. || codecCapability.name === 'rtx') {
  1061. continue; // eslint-disable-line no-continue
  1062. }
  1063. codecPayloadType = codecCapability.preferredPayloadType;
  1064. codecs.push({
  1065. clockRate: codecCapability.clockRate,
  1066. maxptime: codecCapability.maxptime,
  1067. mimeType: codecCapability.mimeType,
  1068. name: codecCapability.name,
  1069. numChannels: codecCapability.numChannels,
  1070. parameters: codecCapability.parameters,
  1071. payloadType: codecCapability.preferredPayloadType,
  1072. ptime: codecCapability.ptime,
  1073. rtcpFeedback: codecCapability.rtcpFeedback
  1074. });
  1075. break;
  1076. }
  1077. if (rtxSsrc) {
  1078. for (const codecCapability of localCapabilities.codecs) {
  1079. if (codecCapability.kind !== kind
  1080. || codecCapability.name !== 'rtx') {
  1081. continue; // eslint-disable-line no-continue
  1082. }
  1083. codecs.push({
  1084. clockRate: codecCapability.clockRate,
  1085. mimeType: codecCapability.mimeType,
  1086. name: 'rtx',
  1087. parameters: codecCapability.parameters,
  1088. payloadType: codecCapability.preferredPayloadType,
  1089. rtcpFeedback: codecCapability.rtcpFeedback
  1090. });
  1091. break;
  1092. }
  1093. }
  1094. parameters.codecs = codecs;
  1095. const encoding = {
  1096. active: true,
  1097. codecPayloadType,
  1098. ssrc
  1099. };
  1100. if (rtxSsrc) {
  1101. encoding.rtx = {
  1102. ssrc: rtxSsrc
  1103. };
  1104. }
  1105. parameters.encodings.push(encoding);
  1106. for (const extension of localCapabilities.headerExtensions) {
  1107. if (extension.kind !== kind) {
  1108. continue; // eslint-disable-line no-continue
  1109. }
  1110. parameters.headerExtensions.push({
  1111. encrypt: extension.preferredEncrypt,
  1112. id: extension.preferredId,
  1113. uri: extension.uri
  1114. });
  1115. }
  1116. return parameters;
  1117. }
  1118. /**
  1119. * Get RTP parameters for a RTCRtpSender.
  1120. * @private
  1121. * @return {RTCRtpParameters}
  1122. */
  1123. _getParametersForRtpSender(kind, data) {
  1124. const ssrc = data.ssrc;
  1125. const rtxSsrc = data.rtxSsrc;
  1126. const cname = CNAME;
  1127. const localCapabilities = this._localCapabilities;
  1128. const parameters = {
  1129. codecs: [],
  1130. degradationPreference: 'balanced',
  1131. encodings: [],
  1132. headerExtensions: [],
  1133. muxId: '',
  1134. rtcp: {
  1135. cname,
  1136. compound: true, // NOTE: Implemented in Edge.
  1137. mux: true,
  1138. reducedSize: true // NOTE: Not yet implemented in Edge.
  1139. }
  1140. };
  1141. const codecs = [];
  1142. let codecPayloadType;
  1143. for (const codecCapability of localCapabilities.codecs) {
  1144. if (codecCapability.kind !== kind
  1145. || codecCapability.name === 'rtx') {
  1146. continue; // eslint-disable-line no-continue
  1147. }
  1148. codecPayloadType = codecCapability.preferredPayloadType;
  1149. codecs.push({
  1150. clockRate: codecCapability.clockRate,
  1151. maxptime: codecCapability.maxptime,
  1152. mimeType: codecCapability.mimeType,
  1153. name: codecCapability.name,
  1154. numChannels: codecCapability.numChannels,
  1155. parameters: codecCapability.parameters,
  1156. payloadType: codecCapability.preferredPayloadType,
  1157. ptime: codecCapability.ptime,
  1158. rtcpFeedback: codecCapability.rtcpFeedback
  1159. });
  1160. break;
  1161. }
  1162. if (rtxSsrc) {
  1163. for (const codecCapability of localCapabilities.codecs) {
  1164. if (codecCapability.kind !== kind
  1165. || codecCapability.name !== 'rtx') {
  1166. continue; // eslint-disable-line no-continue
  1167. }
  1168. codecs.push({
  1169. clockRate: codecCapability.clockRate,
  1170. mimeType: codecCapability.mimeType,
  1171. name: 'rtx',
  1172. parameters: codecCapability.parameters,
  1173. payloadType: codecCapability.preferredPayloadType,
  1174. rtcpFeedback: codecCapability.rtcpFeedback
  1175. });
  1176. break;
  1177. }
  1178. }
  1179. parameters.codecs = codecs;
  1180. const encoding = {
  1181. active: true,
  1182. codecPayloadType,
  1183. ssrc
  1184. };
  1185. if (rtxSsrc) {
  1186. encoding.rtx = {
  1187. ssrc: rtxSsrc
  1188. };
  1189. }
  1190. parameters.encodings.push(encoding);
  1191. for (const extension of localCapabilities.headerExtensions) {
  1192. if (extension.kind !== kind) {
  1193. continue; // eslint-disable-line no-continue
  1194. }
  1195. parameters.headerExtensions.push({
  1196. encrypt: extension.preferredEncrypt,
  1197. id: extension.preferredId,
  1198. uri: extension.uri
  1199. });
  1200. }
  1201. return parameters;
  1202. }
  1203. /**
  1204. * Promise based implementation for getStats().
  1205. * @return {Promise} RTCStats dictionary.
  1206. * @private
  1207. */
  1208. _getStats(selector) { // eslint-disable-line no-unused-vars
  1209. if (this._closed) {
  1210. return Promise.reject(
  1211. new InvalidStateError('RTCPeerConnection closed'));
  1212. }
  1213. const iceGatherer = this._iceGatherer;
  1214. const iceTransport = this._iceTransport;
  1215. const rtpSenders = [];
  1216. const rtpReceivers = [];
  1217. const promises = [];
  1218. // Get RtpSenders.
  1219. for (const info of this._localTrackInfos.values()) {
  1220. const { rtpSender, sending } = info;
  1221. if (sending) {
  1222. rtpSenders.push(rtpSender);
  1223. }
  1224. }
  1225. // Get RtpReceivers.
  1226. for (const info of this._remoteTrackInfos.values()) {
  1227. const { rtpReceiver } = info;
  1228. rtpReceivers.push(rtpReceiver);
  1229. }
  1230. // Collect all the stats.
  1231. if (iceGatherer) {
  1232. promises.push(
  1233. iceGatherer.getStats()
  1234. .catch(() => null));
  1235. }
  1236. if (iceTransport) {
  1237. promises.push(
  1238. iceTransport.getStats()
  1239. .catch(() => null));
  1240. // NOTE: Proprietary stuff in Edge.
  1241. if (typeof iceTransport.msGetStats === 'function') {
  1242. promises.push(
  1243. iceTransport.msGetStats()
  1244. .catch(() => null));
  1245. }
  1246. }
  1247. for (const rtpSender of rtpSenders) {
  1248. const isAudio = rtpSender.track.kind === 'audio';
  1249. promises.push(rtpSender.getStats()
  1250. .then(data => {
  1251. // Remove audioLevel from type="track" stats if this is
  1252. // not an audio sender.
  1253. if (!isAudio) {
  1254. for (const key of Object.keys(data)) {
  1255. const stat = data[key];
  1256. if (stat.type === 'track') {
  1257. delete stat.audioLevel;
  1258. }
  1259. }
  1260. }
  1261. return data;
  1262. })
  1263. .catch(() => null));
  1264. }
  1265. for (const rtpReceiver of rtpReceivers) {
  1266. const isAudio = rtpReceiver.track.kind === 'audio';
  1267. promises.push(rtpReceiver.getStats()
  1268. .then(data => {
  1269. // Remove audioLevel from type="track" stats if this is
  1270. // not an audio receiver.
  1271. if (!isAudio) {
  1272. for (const key of Object.keys(data)) {
  1273. const stat = data[key];
  1274. if (stat.type === 'track') {
  1275. delete stat.audioLevel;
  1276. }
  1277. }
  1278. }
  1279. return data;
  1280. })
  1281. .catch(() => null));
  1282. }
  1283. return Promise.all(promises)
  1284. .then(datas => {
  1285. const stats = {};
  1286. for (const data of datas) {
  1287. if (!data) {
  1288. continue; // eslint-disable-line no-continue
  1289. }
  1290. for (const key of Object.keys(data)) {
  1291. stats[key] = data[key];
  1292. }
  1293. }
  1294. return stats;
  1295. });
  1296. }
  1297. /**
  1298. * Handles the local initial answer.
  1299. * @return {Promise}
  1300. * @private
  1301. */
  1302. _handleLocalInitialAnswer(desc) {
  1303. logger.debug('_handleLocalInitialAnswer(), desc:', desc);
  1304. const sdpObject = desc.sdpObject;
  1305. // Update local capabilities as decided by the app.
  1306. this._localCapabilities = utils.extractCapabilities(sdpObject);
  1307. logger.debug('local capabilities:', this._localCapabilities);
  1308. // NOTE: We assume that the answer given by the app does not change
  1309. // SSRC or PT values. If so, things won't work as expected.
  1310. }
  1311. /**
  1312. * Handles a local re-answer.
  1313. * @return {Promise}
  1314. * @private
  1315. */
  1316. _handleLocalReAnswer(desc) {
  1317. logger.debug('_handleLocalReAnswer(), desc:', desc);
  1318. const sdpObject = desc.sdpObject;
  1319. // Update local capabilities as decided by the app.
  1320. this._localCapabilities = utils.extractCapabilities(sdpObject);
  1321. logger.debug('local capabilities:', this._localCapabilities);
  1322. // NOTE: We assume that the answer given by the app does not change
  1323. // SSRC or PT values. If so, things won't work as expected.
  1324. }
  1325. /**
  1326. * Handles the remote initial offer.
  1327. * @return {Promise}
  1328. * @private
  1329. */
  1330. _handleRemoteInitialOffer(desc) {
  1331. logger.debug('_handleRemoteInitialOffer(), desc:', desc);
  1332. const sdpObject = desc.sdpObject;
  1333. // Set MID values.
  1334. this._mids = utils.extractMids(sdpObject);
  1335. // Get remote RTP capabilities.
  1336. const remoteCapabilities = utils.extractCapabilities(sdpObject);
  1337. logger.debug('remote capabilities:', remoteCapabilities);
  1338. // Get local RTP capabilities (filter them with remote capabilities).
  1339. this._localCapabilities
  1340. = utils.getLocalCapabilities(remoteCapabilities);
  1341. // Start ICE and DTLS.
  1342. this._startIceAndDtls(desc);
  1343. }
  1344. /**
  1345. * Handles a remote re-offer.
  1346. * @return {Promise}
  1347. * @private
  1348. */
  1349. _handleRemoteReOffer(desc) {
  1350. logger.debug('_handleRemoteReOffer(), desc:', desc);
  1351. const sdpObject = desc.sdpObject;
  1352. // Update MID values (just in case).
  1353. this._mids = utils.extractMids(sdpObject);
  1354. // Get remote RTP capabilities (filter them with remote capabilities).
  1355. const remoteCapabilities = utils.extractCapabilities(sdpObject);
  1356. logger.debug('remote capabilities:', remoteCapabilities);
  1357. // Update local RTP capabilities (just in case).
  1358. this._localCapabilities
  1359. = utils.getLocalCapabilities(remoteCapabilities);
  1360. }
  1361. /**
  1362. * Start receiving remote media.
  1363. */
  1364. _receiveMedia() {
  1365. logger.debug('_receiveMedia()');
  1366. const currentRemoteSsrcs = new Set(this._remoteTrackInfos.keys());
  1367. const newRemoteTrackInfos
  1368. = utils.extractTrackInfos(this._remoteDescription.sdpObject);
  1369. // Map of new remote MediaStream indexed by MediaStream.jitsiRemoteId.
  1370. const addedRemoteStreams = new Map();
  1371. // Map of remote MediaStream indexed by added MediaStreamTrack.
  1372. // NOTE: Just filled for already existing streams.
  1373. const addedRemoteTracks = new Map();
  1374. // Map of remote MediaStream indexed by removed MediaStreamTrack.
  1375. const removedRemoteTracks = new Map();
  1376. logger.debug(
  1377. '_receiveMedia() remote track infos:', newRemoteTrackInfos);
  1378. // Check new tracks.
  1379. for (const [ ssrc, info ] of newRemoteTrackInfos) {
  1380. // If already handled, ignore it.
  1381. if (currentRemoteSsrcs.has(ssrc)) {
  1382. continue; // eslint-disable-line no-continue
  1383. }
  1384. logger.debug(`_receiveMedia() new remote track, ssrc:${ssrc}`);
  1385. // Otherwise append to the map.
  1386. this._remoteTrackInfos.set(ssrc, info);
  1387. const kind = info.kind;
  1388. const rtxSsrc = info.rtxSsrc;
  1389. const streamRemoteId = info.streamId;
  1390. const trackRemoteId = info.trackId;
  1391. const cname = info.cname;
  1392. const isNewStream = !this._remoteStreams.has(streamRemoteId);
  1393. let stream;
  1394. if (isNewStream) {
  1395. logger.debug(
  1396. `_receiveMedia() new remote stream, id:${streamRemoteId}`);
  1397. // Create a new MediaStream.
  1398. stream = new MediaStream();
  1399. // Set custom property with the remote id.
  1400. stream.jitsiRemoteId = streamRemoteId;
  1401. addedRemoteStreams.set(streamRemoteId, stream);
  1402. this._remoteStreams.set(streamRemoteId, stream);
  1403. } else {
  1404. stream = this._remoteStreams.get(streamRemoteId);
  1405. }
  1406. const rtpReceiver = new RTCRtpReceiver(this._dtlsTransport, kind);
  1407. const parameters = this._getParametersForRtpReceiver(kind, {
  1408. ssrc,
  1409. rtxSsrc,
  1410. cname
  1411. });
  1412. // Store the track into the info object.
  1413. // NOTE: This should not be needed, but Edge has a bug:
  1414. // https://developer.microsoft.com/en-us/microsoft-edge/platform/
  1415. // issues/12399497/
  1416. info.track = rtpReceiver.track;
  1417. // Set error handler.
  1418. rtpReceiver.onerror = ev => {
  1419. logger.error('rtpReceiver "error" event, event:');
  1420. logger.error(ev);
  1421. };
  1422. // Fill the info with the stream and rtpReceiver.
  1423. info.stream = stream;
  1424. info.rtpReceiver = rtpReceiver;
  1425. logger.debug(
  1426. 'calling rtpReceiver.receive(), parameters:', parameters);
  1427. // Start receiving media.
  1428. try {
  1429. rtpReceiver.receive(parameters);
  1430. // Get the associated MediaStreamTrack.
  1431. const track = info.track;
  1432. // Set custom property with the remote id.
  1433. track.jitsiRemoteId = trackRemoteId;
  1434. // Add the track to the stream.
  1435. stream.addTrack(track);
  1436. if (!addedRemoteStreams.has(streamRemoteId)) {
  1437. addedRemoteTracks.set(track, stream);
  1438. }
  1439. } catch (error) {
  1440. logger.error(`rtpReceiver.receive() failed:${error.message}`);
  1441. logger.error(error);
  1442. }
  1443. }
  1444. // Check track removal.
  1445. for (const ssrc of currentRemoteSsrcs) {
  1446. if (newRemoteTrackInfos.has(ssrc)) {
  1447. continue; // eslint-disable-line no-continue
  1448. }
  1449. logger.debug(`_receiveMedia() remote track removed, ssrc:${ssrc}`);
  1450. const info = this._remoteTrackInfos.get(ssrc);
  1451. const stream = info.stream;
  1452. const track = info.track;
  1453. const rtpReceiver = info.rtpReceiver;
  1454. try {
  1455. rtpReceiver.stop();
  1456. } catch (error) {
  1457. logger.warn(`rtpReceiver.stop() failed:${error}`);
  1458. }
  1459. removedRemoteTracks.set(track, stream);
  1460. stream.removeTrack(track);
  1461. this._remoteTrackInfos.delete(ssrc);
  1462. }
  1463. // Emit MediaStream 'addtrack' for new tracks in already existing
  1464. // streams.
  1465. for (const [ track, stream ] of addedRemoteTracks) {
  1466. const event = new Event('addtrack');
  1467. event.track = track;
  1468. stream.dispatchEvent(event);
  1469. }
  1470. // Emit MediaStream 'removetrack' for removed tracks.
  1471. for (const [ track, stream ] of removedRemoteTracks) {
  1472. const event = new Event('removetrack');
  1473. event.track = track;
  1474. stream.dispatchEvent(event);
  1475. }
  1476. // Emit RTCPeerConnection 'addstream' for new remote streams.
  1477. for (const stream of addedRemoteStreams.values()) {
  1478. // Check whether at least a track was added, otherwise ignore it.
  1479. if (stream.getTracks().length === 0) {
  1480. logger.warn(
  1481. 'ignoring new stream for which no track could be added');
  1482. addedRemoteStreams.delete(stream.jitsiRemoteId);
  1483. this._remoteStreams.delete(stream.jitsiRemoteId);
  1484. } else {
  1485. this._emitAddStream(stream);
  1486. }
  1487. }
  1488. // Emit RTCPeerConnection 'removestream' for removed remote streams.
  1489. for (const [ streamRemoteId, stream ] of this._remoteStreams) {
  1490. if (stream.getTracks().length > 0) {
  1491. continue; // eslint-disable-line no-continue
  1492. }
  1493. this._remoteStreams.delete(streamRemoteId);
  1494. this._emitRemoveStream(stream);
  1495. }
  1496. }
  1497. /**
  1498. * Implementation for removeStream().
  1499. * @private
  1500. */
  1501. _removeStream(stream) {
  1502. if (this._closed) {
  1503. throw new InvalidStateError('RTCPeerConnection closed');
  1504. }
  1505. // Stop and remove the RTCRtpSender associated to each track.
  1506. for (const track of stream.getTracks()) {
  1507. // Ignore if track not present.
  1508. if (!this._localTrackInfos.has(track.id)) {
  1509. continue; // eslint-disable-line no-continue
  1510. }
  1511. const rtpSender = this._localTrackInfos.get(track.id).rtpSender;
  1512. try {
  1513. rtpSender.stop();
  1514. } catch (error) {
  1515. logger.warn(`rtpSender.stop() failed:${error}`);
  1516. }
  1517. // Remove from the map.
  1518. this._localTrackInfos.delete(track.id);
  1519. }
  1520. // It may need to renegotiate.
  1521. this._emitNegotiationNeeded();
  1522. }
  1523. /**
  1524. * Start sending our media to the remote.
  1525. */
  1526. _sendMedia() {
  1527. logger.debug('_sendMedia()');
  1528. for (const info of this._localTrackInfos.values()) {
  1529. // Ignore if already sending.
  1530. if (info.sending) {
  1531. continue; // eslint-disable-line no-continue
  1532. }
  1533. const rtpSender = info.rtpSender;
  1534. const ssrc = info.ssrc;
  1535. const rtxSsrc = info.rtxSsrc;
  1536. const track = rtpSender.track;
  1537. const kind = track.kind;
  1538. const parameters = this._getParametersForRtpSender(kind, {
  1539. ssrc,
  1540. rtxSsrc
  1541. });
  1542. logger.debug(
  1543. 'calling rtpSender.send(), parameters:', parameters);
  1544. // Start sending media.
  1545. try {
  1546. rtpSender.send(parameters);
  1547. // Update sending field.
  1548. info.sending = true;
  1549. } catch (error) {
  1550. logger.error(`rtpSender.send() failed:${error.message}`);
  1551. logger.error(error);
  1552. }
  1553. }
  1554. }
  1555. /**
  1556. * Creates the RTCDtlsTransport.
  1557. * @private
  1558. */
  1559. _setDtlsTransport(iceTransport) {
  1560. const dtlsTransport = new RTCDtlsTransport(iceTransport);
  1561. // NOTE: Not yet implemented by Edge.
  1562. dtlsTransport.onstatechange = () => {
  1563. logger.debug(
  1564. 'dtlsTransport "statechange" event, '
  1565. + `state:${dtlsTransport.state}`);
  1566. this._emitConnectionStateChange();
  1567. };
  1568. // NOTE: Not standard, but implemented by Edge.
  1569. dtlsTransport.ondtlsstatechange = () => {
  1570. logger.debug(
  1571. 'dtlsTransport "dtlsstatechange" event, '
  1572. + `state:${dtlsTransport.state}`);
  1573. this._emitConnectionStateChange();
  1574. };
  1575. dtlsTransport.onerror = ev => {
  1576. let message;
  1577. if (ev.message) {
  1578. message = ev.message;
  1579. } else if (ev.error) {
  1580. message = ev.error.message;
  1581. }
  1582. logger.error(`dtlsTransport "error" event, message:${message}`);
  1583. // TODO: Edge does not set state to 'failed' on error. We may
  1584. // hack it.
  1585. this._emitConnectionStateChange();
  1586. };
  1587. this._dtlsTransport = dtlsTransport;
  1588. }
  1589. /**
  1590. * Creates the RTCIceGatherer.
  1591. * @private
  1592. */
  1593. _setIceGatherer(pcConfig) {
  1594. const iceGatherOptions = {
  1595. gatherPolicy: pcConfig.iceTransportPolicy || 'all',
  1596. iceServers: pcConfig.iceServers || []
  1597. };
  1598. const iceGatherer = new RTCIceGatherer(iceGatherOptions);
  1599. // NOTE: Not yet implemented by Edge.
  1600. iceGatherer.onstatechange = () => {
  1601. logger.debug(
  1602. `iceGatherer "statechange" event, state:${iceGatherer.state}`);
  1603. this._updateAndEmitIceGatheringStateChange(iceGatherer.state);
  1604. };
  1605. iceGatherer.onlocalcandidate = ev => {
  1606. let candidate = ev.candidate;
  1607. // NOTE: Not yet implemented by Edge.
  1608. const complete = ev.complete;
  1609. logger.debug(
  1610. 'iceGatherer "localcandidate" event, candidate:', candidate);
  1611. // NOTE: Instead of null candidate or complete:true, current Edge
  1612. // signals end of gathering with an empty candidate object.
  1613. if (complete
  1614. || !candidate
  1615. || Object.keys(candidate).length === 0) {
  1616. candidate = null;
  1617. this._updateAndEmitIceGatheringStateChange(
  1618. RTCIceGatheringState.complete);
  1619. this._emitIceCandidate(null);
  1620. } else {
  1621. this._emitIceCandidate(candidate);
  1622. }
  1623. };
  1624. iceGatherer.onerror = ev => {
  1625. const errorCode = ev.errorCode;
  1626. const errorText = ev.errorText;
  1627. logger.error(
  1628. `iceGatherer "error" event, errorCode:${errorCode}, `
  1629. + `errorText:${errorText}`);
  1630. };
  1631. // NOTE: Not yet implemented by Edge, which starts gathering
  1632. // automatically.
  1633. try {
  1634. iceGatherer.gather();
  1635. } catch (error) {
  1636. logger.warn(`iceGatherer.gather() failed:${error}`);
  1637. }
  1638. this._iceGatherer = iceGatherer;
  1639. }
  1640. /**
  1641. * Creates the RTCIceTransport.
  1642. * @private
  1643. */
  1644. _setIceTransport(iceGatherer) {
  1645. const iceTransport = new RTCIceTransport(iceGatherer);
  1646. // NOTE: Not yet implemented by Edge.
  1647. iceTransport.onstatechange = () => {
  1648. logger.debug(
  1649. 'iceTransport "statechange" event, '
  1650. + `state:${iceTransport.state}`);
  1651. this._emitIceConnectionStateChange();
  1652. };
  1653. // NOTE: Not standard, but implemented by Edge.
  1654. iceTransport.onicestatechange = () => {
  1655. logger.debug(
  1656. 'iceTransport "icestatechange" event, '
  1657. + `state:${iceTransport.state}`);
  1658. if (iceTransport.state === 'completed') {
  1659. logger.debug(
  1660. 'nominated candidate pair:',
  1661. iceTransport.getNominatedCandidatePair());
  1662. }
  1663. this._emitIceConnectionStateChange();
  1664. };
  1665. iceTransport.oncandidatepairchange = ev => {
  1666. logger.debug(
  1667. 'iceTransport "candidatepairchange" event, '
  1668. + `pair:${ev.pair}`);
  1669. };
  1670. this._iceTransport = iceTransport;
  1671. }
  1672. /**
  1673. * Promise based implementation for setLocalDescription().
  1674. * @returns {Promise}
  1675. * @private
  1676. */
  1677. _setLocalDescription(desc) {
  1678. if (this._closed) {
  1679. return Promise.reject(
  1680. new InvalidStateError('RTCPeerConnection closed'));
  1681. }
  1682. let localDescription;
  1683. try {
  1684. localDescription = new RTCSessionDescription(desc);
  1685. } catch (error) {
  1686. return Promise.reject(new TypeError(
  1687. `invalid RTCSessionDescriptionInit: ${error}`));
  1688. }
  1689. switch (desc.type) {
  1690. case 'offer': {
  1691. if (this.signalingState !== RTCSignalingState.stable) {
  1692. return Promise.reject(new InvalidStateError(
  1693. `invalid signalingState "${this.signalingState}"`));
  1694. }
  1695. // NOTE: P2P mode not yet supported, so createOffer() should never
  1696. // has been called, neither setLocalDescription() with an offer.
  1697. return Promise.reject(new TypeError(
  1698. 'setLocalDescription() with type "offer" not supported'));
  1699. }
  1700. case 'answer': {
  1701. if (this.signalingState !== RTCSignalingState.haveRemoteOffer) {
  1702. return Promise.reject(new InvalidStateError(
  1703. `invalid signalingState "${this.signalingState}"`));
  1704. }
  1705. const isLocalInitialAnswer = Boolean(!this._localDescription);
  1706. return Promise.resolve()
  1707. .then(() => {
  1708. // Different handling for initial answer and re-answer.
  1709. if (isLocalInitialAnswer) {
  1710. return this._handleLocalInitialAnswer(localDescription);
  1711. } else { // eslint-disable-line no-else-return
  1712. return this._handleLocalReAnswer(localDescription);
  1713. }
  1714. })
  1715. .then(() => {
  1716. logger.debug('setLocalDescription() succeed');
  1717. // Update local description.
  1718. this._localDescription = localDescription;
  1719. // Update signaling state.
  1720. this._updateAndEmitSignalingStateChange(
  1721. RTCSignalingState.stable);
  1722. // If initial answer, emit buffered ICE candidates.
  1723. if (isLocalInitialAnswer) {
  1724. this._emitBufferedIceCandidates();
  1725. }
  1726. // Send our RTP.
  1727. this._sendMedia();
  1728. // Receive remote RTP.
  1729. this._receiveMedia();
  1730. })
  1731. .catch(error => {
  1732. logger.error(
  1733. `setLocalDescription() failed: ${error.message}`);
  1734. logger.error(error);
  1735. throw error;
  1736. });
  1737. }
  1738. default:
  1739. return Promise.reject(new TypeError(
  1740. `unsupported description.type "${desc.type}"`));
  1741. }
  1742. }
  1743. /**
  1744. * Promise based implementation for setRemoteDescription().
  1745. * @returns {Promise}
  1746. * @private
  1747. */
  1748. _setRemoteDescription(desc) {
  1749. if (this._closed) {
  1750. return Promise.reject(
  1751. new InvalidStateError('RTCPeerConnection closed'));
  1752. }
  1753. let remoteDescription;
  1754. try {
  1755. remoteDescription = new RTCSessionDescription(desc);
  1756. } catch (error) {
  1757. return Promise.reject(new TypeError(
  1758. `invalid RTCSessionDescriptionInit: ${error}`));
  1759. }
  1760. switch (desc.type) {
  1761. case 'offer': {
  1762. if (this.signalingState !== RTCSignalingState.stable) {
  1763. return Promise.reject(new InvalidStateError(
  1764. `invalid signalingState "${this.signalingState}"`));
  1765. }
  1766. const isRemoteInitialOffer = Boolean(!this._remoteDescription);
  1767. return Promise.resolve()
  1768. .then(() => {
  1769. // Different handling for initial answer and re-answer.
  1770. if (isRemoteInitialOffer) {
  1771. return this._handleRemoteInitialOffer(
  1772. remoteDescription);
  1773. } else { // eslint-disable-line no-else-return
  1774. return this._handleRemoteReOffer(remoteDescription);
  1775. }
  1776. })
  1777. .then(() => {
  1778. logger.debug('setRemoteDescription() succeed');
  1779. // Update remote description.
  1780. this._remoteDescription = remoteDescription;
  1781. // Update signaling state.
  1782. this._updateAndEmitSignalingStateChange(
  1783. RTCSignalingState.haveRemoteOffer);
  1784. })
  1785. .catch(error => {
  1786. logger.error(`setRemoteDescription() failed: ${error}`);
  1787. throw error;
  1788. });
  1789. }
  1790. case 'answer': {
  1791. if (this.signalingState !== RTCSignalingState.haveLocalOffer) {
  1792. return Promise.reject(new InvalidStateError(
  1793. `invalid signalingState "${this.signalingState}"`));
  1794. }
  1795. // NOTE: P2P mode not yet supported, so createOffer() should never
  1796. // has been called, neither setRemoteDescription() with an answer.
  1797. return Promise.reject(new TypeError(
  1798. 'setRemoteDescription() with type "answer" not supported'));
  1799. }
  1800. default:
  1801. return Promise.reject(new TypeError(
  1802. `unsupported description.type "${desc.type}"`));
  1803. }
  1804. }
  1805. /**
  1806. * Start ICE and DTLS connection procedures.
  1807. * @param {RTCSessionDescription} desc - Remote description.
  1808. */
  1809. _startIceAndDtls(desc) {
  1810. const sdpObject = desc.sdpObject;
  1811. const remoteIceParameters
  1812. = utils.extractIceParameters(sdpObject);
  1813. const remoteIceCandidates
  1814. = utils.extractIceCandidates(sdpObject);
  1815. const remoteDtlsParameters
  1816. = utils.extractDtlsParameters(sdpObject);
  1817. // Start the RTCIceTransport.
  1818. switch (desc.type) {
  1819. case 'offer':
  1820. this._iceTransport.start(
  1821. this._iceGatherer, remoteIceParameters, 'controlled');
  1822. break;
  1823. case 'answer':
  1824. this._iceTransport.start(
  1825. this._iceGatherer, remoteIceParameters, 'controlling');
  1826. break;
  1827. }
  1828. // Add remote ICE candidates.
  1829. // NOTE: Remove candidates that Edge doesn't like.
  1830. for (const candidate of remoteIceCandidates) {
  1831. if (candidate.port === 0 || candidate.port === 9) {
  1832. continue; // eslint-disable-line no-continue
  1833. }
  1834. this._iceTransport.addRemoteCandidate(candidate);
  1835. }
  1836. // Also signal a 'complete' candidate as per spec.
  1837. // NOTE: It should be {complete: true} but Edge prefers {}.
  1838. // NOTE: We know that addCandidate() is never used so we need to signal
  1839. // end of candidates (otherwise the RTCIceTransport never enters the
  1840. // 'completed' state).
  1841. this._iceTransport.addRemoteCandidate({});
  1842. // Set desired remote DTLS role (as we receive the offer).
  1843. switch (desc.type) {
  1844. case 'offer':
  1845. remoteDtlsParameters.role = 'server';
  1846. break;
  1847. case 'answer':
  1848. remoteDtlsParameters.role = 'client';
  1849. break;
  1850. }
  1851. // Start RTCDtlsTransport.
  1852. this._dtlsTransport.start(remoteDtlsParameters);
  1853. }
  1854. /**
  1855. * May update iceGatheringState and emit 'icegatheringstatechange' event.
  1856. * @private
  1857. */
  1858. _updateAndEmitIceGatheringStateChange(state) {
  1859. if (this._closed || state === this.iceGatheringState) {
  1860. return;
  1861. }
  1862. this._iceGatheringState = state;
  1863. logger.debug(
  1864. 'emitting "icegatheringstatechange", iceGatheringState:',
  1865. this.iceGatheringState);
  1866. const event = new yaeti.Event('icegatheringstatechange');
  1867. this.dispatchEvent(event);
  1868. }
  1869. /**
  1870. * May update signalingState and emit 'signalingstatechange' event.
  1871. * @private
  1872. */
  1873. _updateAndEmitSignalingStateChange(state) {
  1874. if (state === this.signalingState) {
  1875. return;
  1876. }
  1877. this._signalingState = state;
  1878. logger.debug(
  1879. 'emitting "signalingstatechange", signalingState:',
  1880. this.signalingState);
  1881. const event = new yaeti.Event('signalingstatechange');
  1882. this.dispatchEvent(event);
  1883. }
  1884. }