|
|
@@ -69,16 +69,6 @@ export default class JingleSessionPC extends JingleSession {
|
|
69
|
69
|
options) {
|
|
70
|
70
|
super(sid, me, peerjid, connection, mediaConstraints, iceConfig);
|
|
71
|
71
|
|
|
72
|
|
- /**
|
|
73
|
|
- * Stores "delayed" ICE candidates which are added to the PC once
|
|
74
|
|
- * the first sRD/sLD cycle is done. If there was at least one sRD/sLD
|
|
75
|
|
- * cycle already then the candidates are added as they come and this
|
|
76
|
|
- * queue is skipped.
|
|
77
|
|
- * @type {Array} an array of ICE candidate lines which can be added
|
|
78
|
|
- * directly to the PC
|
|
79
|
|
- */
|
|
80
|
|
- this.delayedIceCandidiates = [];
|
|
81
|
|
-
|
|
82
|
72
|
this.lasticecandidate = false;
|
|
83
|
73
|
this.closed = false;
|
|
84
|
74
|
|
|
|
@@ -163,27 +153,6 @@ export default class JingleSessionPC extends JingleSession {
|
|
163
|
153
|
return true;
|
|
164
|
154
|
}
|
|
165
|
155
|
|
|
166
|
|
- /**
|
|
167
|
|
- * Adds all "delayed" ICE candidates to the PeerConnection.
|
|
168
|
|
- * @private
|
|
169
|
|
- */
|
|
170
|
|
- _dequeIceCandidates() {
|
|
171
|
|
- this.delayedIceCandidiates.forEach(candidate => {
|
|
172
|
|
- const line = candidate.candidate;
|
|
173
|
|
-
|
|
174
|
|
- this.peerconnection.addIceCandidate(
|
|
175
|
|
- candidate,
|
|
176
|
|
- () => {
|
|
177
|
|
- logger.debug(`Add ICE candidate OK ${this}: ${line}`);
|
|
178
|
|
- },
|
|
179
|
|
- error => {
|
|
180
|
|
- logger.error(
|
|
181
|
|
- `Add ICE candidate failed ${this}: ${line}`, error);
|
|
182
|
|
- });
|
|
183
|
|
- });
|
|
184
|
|
- this.delayedIceCandidiates = [];
|
|
185
|
|
- }
|
|
186
|
|
-
|
|
187
|
156
|
/**
|
|
188
|
157
|
* Finds all "source" elements under RTC "description" in given Jingle IQ
|
|
189
|
158
|
* and adds 'ssrc-info' with the owner attribute set to
|
|
|
@@ -505,41 +474,69 @@ export default class JingleSessionPC extends JingleSession {
|
|
505
|
474
|
return;
|
|
506
|
475
|
}
|
|
507
|
476
|
|
|
508
|
|
- // NOTE operates on each content element, can't use () =>
|
|
509
|
|
- elem.each((contentIdx, content) => {
|
|
510
|
|
- $(content).find('transport>candidate')
|
|
|
477
|
+ const iceCandidates = [];
|
|
|
478
|
+
|
|
|
479
|
+ elem.find('>content>transport>candidate')
|
|
511
|
480
|
.each((idx, candidate) => {
|
|
512
|
481
|
let line = SDPUtil.candidateFromJingle(candidate);
|
|
513
|
482
|
|
|
514
|
483
|
line = line.replace('\r\n', '').replace('a=', '');
|
|
515
|
484
|
|
|
516
|
|
- // FIXME this code does not care to handle non-bundle transport
|
|
|
485
|
+ // FIXME this code does not care to handle
|
|
|
486
|
+ // non-bundle transport
|
|
517
|
487
|
const rtcCandidate = new RTCIceCandidate({
|
|
518
|
488
|
sdpMLineIndex: 0,
|
|
519
|
489
|
|
|
520
|
490
|
// FF comes up with more complex names like audio-23423,
|
|
521
|
491
|
// Given that it works on both Chrome and FF without
|
|
522
|
|
- // providing it, let's leave it like this for the time being
|
|
|
492
|
+ // providing it, let's leave it like this for the time
|
|
|
493
|
+ // being...
|
|
523
|
494
|
// sdpMid: 'audio',
|
|
524
|
495
|
candidate: line
|
|
525
|
496
|
});
|
|
526
|
497
|
|
|
527
|
|
- // Will delay the addition until the remoteDescription is set
|
|
528
|
|
- if (this.peerconnection.remoteDescription.sdp) {
|
|
529
|
|
- logger.debug(`Trying to add ICE candidate: ${line}`);
|
|
530
|
|
- this.peerconnection.addIceCandidate(
|
|
531
|
|
- rtcCandidate,
|
|
532
|
|
- () => logger.debug(`addIceCandidate ok: ${line}`),
|
|
533
|
|
- error => {
|
|
534
|
|
- logger.error(
|
|
535
|
|
- `addIceCandidate failed: ${line}`, error);
|
|
536
|
|
- });
|
|
537
|
|
- } else {
|
|
538
|
|
- logger.debug(`Delaying ICE candidate: ${line}`);
|
|
539
|
|
- this.delayedIceCandidiates.push(rtcCandidate);
|
|
540
|
|
- }
|
|
|
498
|
+ iceCandidates.push(rtcCandidate);
|
|
|
499
|
+ });
|
|
|
500
|
+
|
|
|
501
|
+ if (!iceCandidates.length) {
|
|
|
502
|
+ logger.error(
|
|
|
503
|
+ 'No ICE candidates to add ?', elem[0] && elem[0].outerHTML);
|
|
|
504
|
+
|
|
|
505
|
+ return;
|
|
|
506
|
+ }
|
|
|
507
|
+
|
|
|
508
|
+ // We want to have this task queued, so that we know it is executed,
|
|
|
509
|
+ // after the initial sRD/sLD offer/answer cycle was done (based on
|
|
|
510
|
+ // the assumption that candidates are spawned after the offer/answer
|
|
|
511
|
+ // and XMPP preserves order).
|
|
|
512
|
+ const workFunction = () => {
|
|
|
513
|
+ for (const iceCandidate of iceCandidates) {
|
|
|
514
|
+ this.peerconnection.addIceCandidate(
|
|
|
515
|
+ iceCandidate,
|
|
|
516
|
+ () => {
|
|
|
517
|
+ logger.debug('addIceCandidate ok!');
|
|
|
518
|
+ },
|
|
|
519
|
+ error => {
|
|
|
520
|
+ logger.error('addIceCandidate failed!', error);
|
|
|
521
|
+ });
|
|
|
522
|
+ }
|
|
|
523
|
+
|
|
|
524
|
+ // There's no need to renegotiate for ICE candidates added with
|
|
|
525
|
+ // 'peerconnection.addIceCandidate'
|
|
|
526
|
+ return false;
|
|
|
527
|
+ };
|
|
|
528
|
+
|
|
|
529
|
+ logger.debug(`Queued add ICE candidates(${iceCandidates.length}) task`);
|
|
|
530
|
+ this._doRenegotiate('add ICE candidate', workFunction)
|
|
|
531
|
+ .then(() => {
|
|
|
532
|
+ logger.debug('Add ICE candidate done !');
|
|
|
533
|
+ },
|
|
|
534
|
+ error => {
|
|
|
535
|
+ logger.error(
|
|
|
536
|
+ 'Failed to add ICE candidate',
|
|
|
537
|
+ elem[0] && elem[0].outerHTML,
|
|
|
538
|
+ error);
|
|
541
|
539
|
});
|
|
542
|
|
- });
|
|
543
|
540
|
}
|
|
544
|
541
|
|
|
545
|
542
|
/**
|
|
|
@@ -585,18 +582,27 @@ export default class JingleSessionPC extends JingleSession {
|
|
585
|
582
|
}
|
|
586
|
583
|
}
|
|
587
|
584
|
|
|
|
585
|
+ /* eslint-disable max-params */
|
|
588
|
586
|
/**
|
|
589
|
587
|
* Accepts incoming Jingle 'session-initiate' and should send
|
|
590
|
588
|
* 'session-accept' in result.
|
|
591
|
589
|
* @param jingleOffer jQuery selector pointing to the jingle element of
|
|
592
|
|
- * the offer IQ
|
|
|
590
|
+ * the offer IQ
|
|
593
|
591
|
* @param success callback called when we accept incoming session
|
|
594
|
|
- * successfully and receive RESULT packet to 'session-accept' sent.
|
|
|
592
|
+ * successfully and receive RESULT packet to 'session-accept' sent.
|
|
595
|
593
|
* @param failure function(error) called if for any reason we fail to accept
|
|
596
|
|
- * the incoming offer. 'error' argument can be used to log some
|
|
597
|
|
- * details about the error.
|
|
|
594
|
+ * the incoming offer. 'error' argument can be used to log some details
|
|
|
595
|
+ * about the error.
|
|
|
596
|
+ * @param {Array<JitsiLocalTrack>} [localTracks] the optional list of
|
|
|
597
|
+ * the local tracks that will be added, before the offer/answer cycle
|
|
|
598
|
+ * executes. We allow the localTracks to optionally be passed in so that
|
|
|
599
|
+ * the addition of the local tracks and the processing of the initial offer
|
|
|
600
|
+ * can all be done atomically. We want to make sure that any other
|
|
|
601
|
+ * operations which originate in the XMPP Jingle messages related with
|
|
|
602
|
+ * this session to be executed with an assumption that the initial
|
|
|
603
|
+ * offer/answer cycle has been executed already.
|
|
598
|
604
|
*/
|
|
599
|
|
- acceptOffer(jingleOffer, success, failure) {
|
|
|
605
|
+ acceptOffer(jingleOffer, success, failure, localTracks) {
|
|
600
|
606
|
this.setOfferAnswerCycle(
|
|
601
|
607
|
jingleOffer,
|
|
602
|
608
|
() => {
|
|
|
@@ -607,16 +613,25 @@ export default class JingleSessionPC extends JingleSession {
|
|
607
|
613
|
// modify sendSessionAccept method to do that
|
|
608
|
614
|
this.sendSessionAccept(success, failure);
|
|
609
|
615
|
},
|
|
610
|
|
- failure);
|
|
|
616
|
+ failure,
|
|
|
617
|
+ localTracks);
|
|
611
|
618
|
}
|
|
612
|
619
|
|
|
|
620
|
+ /* eslint-enable max-params */
|
|
|
621
|
+
|
|
613
|
622
|
/**
|
|
614
|
623
|
* Creates an offer and sends Jingle 'session-initiate' to the remote peer.
|
|
|
624
|
+ * @param {Array<JitsiLocalTrack>} localTracks the local tracks that will be
|
|
|
625
|
+ * added, before the offer/answer cycle executes (for the local track
|
|
|
626
|
+ * addition to be an atomic operation together with the offer/answer).
|
|
615
|
627
|
*/
|
|
616
|
|
- invite() {
|
|
|
628
|
+ invite(localTracks) {
|
|
617
|
629
|
if (!this.isInitiator) {
|
|
618
|
630
|
throw new Error('Trying to invite from the responder session');
|
|
619
|
631
|
}
|
|
|
632
|
+ for (const localTrack of localTracks) {
|
|
|
633
|
+ this.peerconnection.addTrack(localTrack);
|
|
|
634
|
+ }
|
|
620
|
635
|
this.peerconnection.createOffer(
|
|
621
|
636
|
this.sendSessionInitiate.bind(this),
|
|
622
|
637
|
error => logger.error('Failed to create offer', error),
|
|
|
@@ -686,6 +701,7 @@ export default class JingleSessionPC extends JingleSession {
|
|
686
|
701
|
});
|
|
687
|
702
|
}
|
|
688
|
703
|
|
|
|
704
|
+ /* eslint-disable max-params */
|
|
689
|
705
|
/**
|
|
690
|
706
|
* This is a setRemoteDescription/setLocalDescription cycle which starts at
|
|
691
|
707
|
* converting Strophe Jingle IQ into remote offer SDP. Once converted
|
|
|
@@ -695,9 +711,20 @@ export default class JingleSessionPC extends JingleSession {
|
|
695
|
711
|
* @param success callback called when sRD/sLD cycle finishes successfully.
|
|
696
|
712
|
* @param failure callback called with an error object as an argument if we
|
|
697
|
713
|
* fail at any point during setRD, createAnswer, setLD.
|
|
|
714
|
+ * @param {Array<JitsiLocalTrack>} [localTracks] the optional list of
|
|
|
715
|
+ * the local tracks that will be added, before the offer/answer cycle
|
|
|
716
|
+ * executes (for the local track addition to be an atomic operation together
|
|
|
717
|
+ * with the offer/answer).
|
|
698
|
718
|
*/
|
|
699
|
|
- setOfferAnswerCycle(jingleOfferAnswerIq, success, failure) {
|
|
|
719
|
+ setOfferAnswerCycle(jingleOfferAnswerIq, success, failure, localTracks) {
|
|
700
|
720
|
const workFunction = finishedCallback => {
|
|
|
721
|
+
|
|
|
722
|
+ if (localTracks) {
|
|
|
723
|
+ for (const track of localTracks) {
|
|
|
724
|
+ this.peerconnection.addTrack(track);
|
|
|
725
|
+ }
|
|
|
726
|
+ }
|
|
|
727
|
+
|
|
701
|
728
|
const newRemoteSdp
|
|
702
|
729
|
= this._processNewJingleOfferIq(jingleOfferAnswerIq);
|
|
703
|
730
|
|
|
|
@@ -721,6 +748,8 @@ export default class JingleSessionPC extends JingleSession {
|
|
721
|
748
|
});
|
|
722
|
749
|
}
|
|
723
|
750
|
|
|
|
751
|
+ /* eslint-enable max-params */
|
|
|
752
|
+
|
|
724
|
753
|
/**
|
|
725
|
754
|
* Although it states "replace transport" it does accept full Jingle offer
|
|
726
|
755
|
* which should contain new ICE transport details.
|
|
|
@@ -1254,7 +1283,6 @@ export default class JingleSessionPC extends JingleSession {
|
|
1254
|
1283
|
this.peerconnection.setLocalDescription(
|
|
1255
|
1284
|
answer,
|
|
1256
|
1285
|
() => {
|
|
1257
|
|
- this._dequeIceCandidates();
|
|
1258
|
1286
|
resolve();
|
|
1259
|
1287
|
},
|
|
1260
|
1288
|
error => {
|
|
|
@@ -1289,7 +1317,6 @@ export default class JingleSessionPC extends JingleSession {
|
|
1289
|
1317
|
this.peerconnection.setRemoteDescription(
|
|
1290
|
1318
|
remoteDescription,
|
|
1291
|
1319
|
() => {
|
|
1292
|
|
- this._dequeIceCandidates();
|
|
1293
|
1320
|
resolve();
|
|
1294
|
1321
|
},
|
|
1295
|
1322
|
error => reject(`setRemoteDescription failed: ${error}`)
|
|
|
@@ -1306,7 +1333,6 @@ export default class JingleSessionPC extends JingleSession {
|
|
1306
|
1333
|
this.peerconnection.setRemoteDescription(
|
|
1307
|
1334
|
remoteDescription,
|
|
1308
|
1335
|
() => {
|
|
1309
|
|
- this._dequeIceCandidates();
|
|
1310
|
1336
|
resolve();
|
|
1311
|
1337
|
},
|
|
1312
|
1338
|
error => reject(
|
|
|
@@ -1443,35 +1469,6 @@ export default class JingleSessionPC extends JingleSession {
|
|
1443
|
1469
|
return removeSsrcInfo;
|
|
1444
|
1470
|
}
|
|
1445
|
1471
|
|
|
1446
|
|
- /**
|
|
1447
|
|
- * Adds <tt>JitsiLocalTrack</tt>s to this session.
|
|
1448
|
|
- * @param {JitsiLocalTrack[]} tracks new local tracks that will be added.
|
|
1449
|
|
- * @return {Promise} a promise that will resolve once all local tracks are
|
|
1450
|
|
- * added. Will be rejected with a <tt>string</tt> which describes the error.
|
|
1451
|
|
- * NOTE(brian): there is a decent amount of overlap here with replaceStream
|
|
1452
|
|
- * that could be re-used...however we can't leverage that currently because
|
|
1453
|
|
- * the extra work we do here must be in the work function context and if we
|
|
1454
|
|
- * then called replaceTrack we'd be adding another task on the queue
|
|
1455
|
|
- * from within a task which would then deadlock. The 'replaceTrack' core
|
|
1456
|
|
- * logic should be moved into a helper function that could be called within
|
|
1457
|
|
- * the 'doReplaceStream' task or the 'doAddStream' task (for example)
|
|
1458
|
|
- */
|
|
1459
|
|
- addLocalTracks(tracks) {
|
|
1460
|
|
- const workFunction = () => {
|
|
1461
|
|
- if (!this.peerconnection) {
|
|
1462
|
|
- return 'Error: tried adding stream with no active peer'
|
|
1463
|
|
- + ' connection';
|
|
1464
|
|
- }
|
|
1465
|
|
- for (const track of tracks) {
|
|
1466
|
|
- this.peerconnection.addTrack(track);
|
|
1467
|
|
- }
|
|
1468
|
|
-
|
|
1469
|
|
- return true;
|
|
1470
|
|
- };
|
|
1471
|
|
-
|
|
1472
|
|
- return this._doRenegotiate('addStreams', workFunction);
|
|
1473
|
|
- }
|
|
1474
|
|
-
|
|
1475
|
1472
|
/**
|
|
1476
|
1473
|
* Adds local track back to this session, as part of the unmute operation.
|
|
1477
|
1474
|
* @param {JitsiLocalTrack} track
|