Procházet zdrojové kódy

Rollback do renegotiate (#436)

* ref(JSPC): rollback doRenegotiate

Modify the code to the old strategy without shared 'doRenegotiate'
method.

* ref(JSPC): add/remove duplication

Removes duplication between add and remove remote stream methods.

* fix(JSPC): remove Timeout

Removes wait timeout for remote stream added/removed. It should not be
necessary given that the task executes on the modification queue and
the initial offer/answer should execute before
'source-add'/'source-remove'. Jingle 'session-invite'/'session-accept'
is sent, before any other notifications.

* feat(JSPC): verify SSRC changes

Will print an error if there was change to local SSRCs where it should
not happen (video mute on browsers where video stream is disposed on
mute).

* fix(JSPC): not always renegotiate

When adding/removing tracks as mute/unmute it only makes sense to
renegotiate if the initial offer/answer cycle has already been executed.

* ref(JSPC): remove duplication

Removes duplication around add/remove as unmute/mute.
dev1
Paweł Domas před 8 roky
rodič
revize
4cd335838b
1 změnil soubory, kde provedl 182 přidání a 172 odebrání
  1. 182
    172
      modules/xmpp/JingleSessionPC.js

+ 182
- 172
modules/xmpp/JingleSessionPC.js Zobrazit soubor

@@ -509,7 +509,7 @@ export default class JingleSessionPC extends JingleSession {
509 509
         // after the initial sRD/sLD offer/answer cycle was done (based on
510 510
         // the assumption that candidates are spawned after the offer/answer
511 511
         // and XMPP preserves order).
512
-        const workFunction = () => {
512
+        const workFunction = finishedCallback => {
513 513
             for (const iceCandidate of iceCandidates) {
514 514
                 this.peerconnection.addIceCandidate(
515 515
                     iceCandidate,
@@ -521,22 +521,12 @@ export default class JingleSessionPC extends JingleSession {
521 521
                     });
522 522
             }
523 523
 
524
-            // There's no need to renegotiate for ICE candidates added with
525
-            // 'peerconnection.addIceCandidate'
526
-            return false;
524
+            finishedCallback();
527 525
         };
528 526
 
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);
539
-            });
527
+        logger.debug(
528
+            `Queued add (${iceCandidates.length}) ICE candidates task...`);
529
+        this.modificationQueue.push(workFunction);
540 530
     }
541 531
 
542 532
     /**
@@ -1075,36 +1065,7 @@ export default class JingleSessionPC extends JingleSession {
1075 1065
      * @param elem An array of Jingle "content" elements.
1076 1066
      */
1077 1067
     addRemoteStream(elem) {
1078
-        if (!this.peerconnection.localDescription) {
1079
-            logger.warn('addSource - localDescription not ready yet');
1080
-            if (this._assertNotEnded('addRemoteStream')) {
1081
-                setTimeout(() => this.addRemoteStream(elem), 200);
1082
-            }
1083
-
1084
-            return;
1085
-        }
1086
-        logger.log('Processing add remote stream');
1087
-        logger.log(
1088
-            'ICE connection state: ', this.peerconnection.iceConnectionState);
1089
-
1090
-        this.readSsrcInfo(elem);
1091
-
1092
-        const workFunction = () => {
1093
-            const sdp = new SDP(this.peerconnection.remoteDescription.sdp);
1094
-            const addSsrcInfo = this._parseSsrcInfoFromSourceAdd(elem, sdp);
1095
-            const newRemoteSdp = this._processRemoteAddSource(addSsrcInfo);
1096
-
1097
-            return newRemoteSdp;
1098
-        };
1099
-
1100
-        // Queue and execute
1101
-        this._doRenegotiate('source-add', workFunction)
1102
-            .then(() => {
1103
-                logger.info('source-add - done!');
1104
-            })
1105
-            .catch(error => {
1106
-                logger.error('source-add error:', error);
1107
-            });
1068
+        this._addOrRemoveRemoteStream(true /* add */, elem);
1108 1069
     }
1109 1070
 
1110 1071
     /**
@@ -1112,38 +1073,69 @@ export default class JingleSessionPC extends JingleSession {
1112 1073
      * @param elem An array of Jingle "content" elements.
1113 1074
      */
1114 1075
     removeRemoteStream(elem) {
1115
-        if (!this.peerconnection.localDescription) {
1116
-            logger.warn('removeSource - localDescription not ready yet');
1117
-            if (this._assertNotEnded('removeRemoteStream')) {
1118
-                setTimeout(() => this.removeRemoteStream(elem), 200);
1119
-            }
1076
+        this._addOrRemoveRemoteStream(false /* remove */, elem);
1077
+    }
1120 1078
 
1121
-            return;
1079
+    /**
1080
+     * Handles either Jingle 'source-add' or 'source-remove' message for this
1081
+     * Jingle session.
1082
+     * @param {boolean} isAdd <tt>true</tt> for 'source-add' or <tt>false</tt>
1083
+     * otherwise.
1084
+     * @param {Array<Element>} elem an array of Jingle "content" elements.
1085
+     * @private
1086
+     */
1087
+    _addOrRemoveRemoteStream(isAdd, elem) {
1088
+        const logPrefix = isAdd ? 'addRemoteStream' : 'removeRemoteStream';
1089
+
1090
+        if (isAdd) {
1091
+            this.readSsrcInfo(elem);
1122 1092
         }
1123 1093
 
1124
-        const workFunction = () => {
1125
-            logger.log('Remove remote stream');
1094
+        const workFunction = finishedCallback => {
1095
+            if (!this.peerconnection.localDescription
1096
+                || !this.peerconnection.localDescription.sdp) {
1097
+                const errMsg = `${logPrefix} - localDescription not ready yet`;
1098
+
1099
+                logger.error(errMsg);
1100
+                finishedCallback(errMsg);
1101
+
1102
+                return;
1103
+            }
1104
+
1105
+            logger.log(`Processing ${logPrefix}`);
1126 1106
             logger.log(
1127 1107
                 'ICE connection state: ',
1128 1108
                 this.peerconnection.iceConnectionState);
1129 1109
 
1110
+            const oldLocalSdp
1111
+                = new SDP(this.peerconnection.localDescription.sdp);
1130 1112
             const sdp = new SDP(this.peerconnection.remoteDescription.sdp);
1131
-            const removeSsrcInfo
1132
-                = this._parseSsrcInfoFromSourceRemove(elem, sdp);
1113
+            const addOrRemoveSsrcInfo
1114
+                = isAdd
1115
+                    ? this._parseSsrcInfoFromSourceAdd(elem, sdp)
1116
+                    : this._parseSsrcInfoFromSourceRemove(elem, sdp);
1133 1117
             const newRemoteSdp
1134
-                = this._processRemoteRemoveSource(removeSsrcInfo);
1118
+                = isAdd
1119
+                    ? this._processRemoteAddSource(addOrRemoveSsrcInfo)
1120
+                    : this._processRemoteRemoveSource(addOrRemoveSsrcInfo);
1121
+
1122
+            this._renegotiate(newRemoteSdp)
1123
+                .then(() => {
1124
+                    const newLocalSdp
1125
+                        = new SDP(this.peerconnection.localDescription.sdp);
1135 1126
 
1136
-            return newRemoteSdp;
1127
+                    logger.log(
1128
+                        `${logPrefix} - OK, SDPs: `, oldLocalSdp, newLocalSdp);
1129
+                    this.notifyMySSRCUpdate(oldLocalSdp, newLocalSdp);
1130
+                    finishedCallback();
1131
+                }, error => {
1132
+                    logger.error(`${logPrefix} failed:`, error);
1133
+                    finishedCallback(error);
1134
+                });
1137 1135
         };
1138 1136
 
1139 1137
         // Queue and execute
1140
-        this._doRenegotiate('source-remove', workFunction)
1141
-            .then(() => {
1142
-                logger.info('source-remove - done!');
1143
-            })
1144
-            .catch(error => {
1145
-                logger.error('source-remove error:', error);
1146
-            });
1138
+        this.modificationQueue.push(workFunction);
1147 1139
     }
1148 1140
 
1149 1141
     /**
@@ -1361,11 +1353,12 @@ export default class JingleSessionPC extends JingleSession {
1361 1353
      *  with no arguments or rejects with an error {string}
1362 1354
      */
1363 1355
     replaceTrack(oldTrack, newTrack) {
1364
-        const workFunction = () => {
1356
+        const workFunction = finishedCallback => {
1357
+            const oldLocalSdp = this.peerconnection.localDescription.sdp;
1358
+
1365 1359
             // NOTE the code below assumes that no more than 1 video track
1366 1360
             // can be added to the peer connection.
1367 1361
             // Transition from no video to video (possibly screen sharing)
1368
-
1369 1362
             if (!oldTrack && newTrack && newTrack.isVideoTrack()) {
1370 1363
                 // Clearing current primary SSRC will make
1371 1364
                 // the SdpConsistency generate a new one which will result
@@ -1390,10 +1383,32 @@ export default class JingleSessionPC extends JingleSession {
1390 1383
                 this.peerconnection.addTrack(newTrack);
1391 1384
             }
1392 1385
 
1393
-            return Boolean(oldTrack || newTrack); // Try to renegotiate
1386
+            if ((oldTrack || newTrack) && oldLocalSdp) {
1387
+                this._renegotiate()
1388
+                    .then(() => {
1389
+                        const newLocalSDP
1390
+                            = new SDP(
1391
+                                this.peerconnection.localDescription.sdp);
1392
+
1393
+                        this.notifyMySSRCUpdate(
1394
+                            new SDP(oldLocalSdp), newLocalSDP);
1395
+                        finishedCallback();
1396
+                    },
1397
+                    finishedCallback /* will be called with en error */);
1398
+            } else {
1399
+                finishedCallback();
1400
+            }
1394 1401
         };
1395 1402
 
1396
-        return this._doRenegotiate('replaceTrack', workFunction);
1403
+        this.modificationQueue.push(
1404
+            workFunction,
1405
+            error => {
1406
+                if (error) {
1407
+                    logger.error('Replace track error:', error);
1408
+                } else {
1409
+                    logger.info('Replace track done!');
1410
+                }
1411
+            });
1397 1412
     }
1398 1413
 
1399 1414
     /**
@@ -1469,6 +1484,44 @@ export default class JingleSessionPC extends JingleSession {
1469 1484
         return removeSsrcInfo;
1470 1485
     }
1471 1486
 
1487
+    /**
1488
+     * Will print an error if there is any difference, between the SSRCs given
1489
+     * in the <tt>oldSDP</tt> and the ones currently described in
1490
+     * the peerconnection's local description.
1491
+     * @param {string} operationName the operation's name which will be printed
1492
+     * in the error message.
1493
+     * @param {SDP} oldSDP the old local SDP which will be compared with
1494
+     * the current one.
1495
+     * @return {boolean} <tt>true</tt> if there was any change or <tt>false</tt>
1496
+     * otherwise.
1497
+     * @private
1498
+     */
1499
+    _verifyNoSSRCChanged(operationName, oldSDP) {
1500
+        const currentLocalSDP
1501
+            = new SDP(this.peerconnection.localDescription.sdp);
1502
+        let sdpDiff = new SDPDiffer(oldSDP, currentLocalSDP);
1503
+        const addedMedia = sdpDiff.getNewMedia();
1504
+
1505
+        if (Object.keys(addedMedia).length) {
1506
+            logger.error(
1507
+                `Some SSRC were added on ${operationName}`, addedMedia);
1508
+
1509
+            return false;
1510
+        }
1511
+
1512
+        sdpDiff = new SDPDiffer(currentLocalSDP, oldSDP);
1513
+        const removedMedia = sdpDiff.getNewMedia();
1514
+
1515
+        if (Object.keys(removedMedia).length) {
1516
+            logger.error(
1517
+                `Some SSRCs were removed on ${operationName}`, removedMedia);
1518
+
1519
+            return false;
1520
+        }
1521
+
1522
+        return true;
1523
+    }
1524
+
1472 1525
     /**
1473 1526
      * Adds local track back to this session, as part of the unmute operation.
1474 1527
      * @param {JitsiLocalTrack} track
@@ -1478,19 +1531,8 @@ export default class JingleSessionPC extends JingleSession {
1478 1531
      * goes wrong.
1479 1532
      */
1480 1533
     addTrackAsUnmute(track) {
1481
-        if (!track) {
1482
-            return Promise.reject('invalid "track" argument value');
1483
-        }
1484
-        const workFunction = () => {
1485
-            if (!this.peerconnection) {
1486
-                return 'Error: '
1487
-                    + 'tried adding track with no active peer connection';
1488
-            }
1489
-
1490
-            return this.peerconnection.addTrackUnmute(track);
1491
-        };
1492
-
1493
-        return this._doRenegotiate('addStreamAsUnmute', workFunction);
1534
+        return this._addRemoveTrackAsMuteUnmute(
1535
+            false /* add as unmute */, track);
1494 1536
     }
1495 1537
 
1496 1538
     /**
@@ -1502,112 +1544,66 @@ export default class JingleSessionPC extends JingleSession {
1502 1544
      * the error if anything goes wrong.
1503 1545
      */
1504 1546
     removeTrackAsMute(track) {
1505
-        if (!track) {
1506
-            return Promise.reject('invalid "stream" argument value');
1507
-        }
1508
-        const workFunction = () => {
1509
-            if (!this.peerconnection) {
1510
-                return false;
1511
-            }
1512
-
1513
-            return this.peerconnection.removeTrackMute(track);
1514
-        };
1515
-
1516
-        return this._doRenegotiate('remove-as-mute', workFunction);
1547
+        return this._addRemoveTrackAsMuteUnmute(
1548
+            true /* remove as mute */, track);
1517 1549
     }
1518 1550
 
1519 1551
     /**
1520
-     * A worker function that is scheduled and executed on the
1521
-     * {@link modificationQueue}. Local SDPs from before and after the function
1522
-     * is executed are compared and 'source-add'/'source-remove' notifications
1523
-     * are being sent to remote participants (to propagate changes in local
1524
-     * streams description).
1525
-     * @name JingleSessionPC~WorkerFunction
1526
-     * @function
1527
-     * @returns {boolean|string|SDP} there are several things that this
1528
-     * function can return:
1529
-     * <tt>true</tt> if the renegotiation should follow the function execution
1530
-     * <tt>false</tt> if there should be no renegotiation
1531
-     * <tt>SDP</tt> that will be set as the remote description in the
1532
-     * renegotiation process
1533
-     * <tt>string</tt> which stands for the error description, no renegotiation
1534
-     * will be done in that case it's returned
1535
-     */
1536
-    /**
1537
-     * Does the logic of doing the session renegotiation by updating local and
1538
-     * remote session descriptions. Will compare the local description, before
1539
-     * and after the renegotiation to update local streams description (sends
1540
-     * "source-add"/"source-remove" notifications).
1541
-     * @param {string} actionName the name of the action which will appear in
1542
-     * the events logged to the logger.
1543
-     * @param {WorkerFunction} workFunction a function that will be executed on
1544
-     * the queue. See type description for moe info.
1552
+     * See {@link addTrackAsUnmute} and {@link removeTrackAsMute}.
1553
+     * @param {boolean} isMute <tt>true</tt> for "remove as mute" or
1554
+     * <tt>false</tt> for "add as unmute".
1555
+     * @param {JitsiLocalTrack} track the track that will be added/removed
1545 1556
      * @private
1546 1557
      */
1547
-    _doRenegotiate(actionName, workFunction) {
1548
-        const workFunctionWrap = finishedCallback => {
1549
-            // Remember localSDP from before any modifications are done, by
1550
-            // the worker function
1551
-            let oldSdp = this.peerconnection.localDescription.sdp;
1552
-            let remoteSdp = null;
1553
-
1554
-            let modifySources = workFunction();
1558
+    _addRemoveTrackAsMuteUnmute(isMute, track) {
1559
+        if (!track) {
1560
+            return Promise.reject('invalid "track" argument value');
1561
+        }
1562
+        const operationName = isMute ? 'removeTrackMute' : 'addTrackUnmute';
1563
+        const workFunction = finishedCallback => {
1564
+            const tpc = this.peerconnection;
1555 1565
 
1556
-            // workFunction can return error description
1557
-            if (typeof modifySources === 'string') {
1558
-                finishedCallback(modifySources);
1566
+            if (!tpc) {
1567
+                finishedCallback(
1568
+                    `Error:  tried ${operationName} track with no active peer`
1569
+                        + 'connection');
1559 1570
 
1560 1571
                 return;
1561
-            } else if (typeof modifySources === 'object') {
1562
-                remoteSdp = modifySources;
1563
-                modifySources = true;
1564
-            }
1565
-
1566
-            if (!oldSdp) {
1567
-                logger.info(
1568
-                    `${this}: ${actionName} - will NOT modify sources, `
1569
-                    + 'because there is no local SDP yet');
1570
-                modifySources = false;
1571
-            } else if (!this.peerconnection.remoteDescription.sdp) {
1572
-                logger.info(
1573
-                    `${this}: ${actionName} - will NOT modify sources, `
1574
-                    + 'because there is no remote SDP yet');
1575
-                modifySources = false;
1576 1572
             }
1577
-            if (!modifySources) {
1578
-                // ABORT
1573
+            const oldLocalSDP = tpc.localDescription.sdp;
1574
+            const tpcOperation
1575
+                = isMute
1576
+                    ? tpc.removeTrackMute.bind(tpc, track)
1577
+                    : tpc.addTrackUnmute.bind(tpc, track);
1578
+
1579
+            if (!tpcOperation()) {
1580
+                finishedCallback(`${operationName} failed!`);
1581
+            } else if (!oldLocalSDP || !tpc.remoteDescription.sdp) {
1579 1582
                 finishedCallback();
1580
-
1581
-                return;
1583
+            } else {
1584
+                this._renegotiate()
1585
+                    .then(() => {
1586
+                        // The results are ignored, as this check failure is not
1587
+                        // enough to fail the whole operation. It will log
1588
+                        // an error inside.
1589
+                        this._verifyNoSSRCChanged(
1590
+                            operationName, new SDP(oldLocalSDP));
1591
+                        finishedCallback();
1592
+                    },
1593
+                    finishedCallback /* will be called with an error */);
1582 1594
             }
1583
-
1584
-            // Convert to SDP object
1585
-            oldSdp = new SDP(oldSdp);
1586
-            this._renegotiate(remoteSdp)
1587
-                .then(() => {
1588
-                    const newSdp
1589
-                        = new SDP(this.peerconnection.localDescription.sdp);
1590
-
1591
-                    logger.log(`${actionName} - OK, SDPs: `, oldSdp, newSdp);
1592
-                    this.notifyMySSRCUpdate(oldSdp, newSdp);
1593
-                    finishedCallback();
1594
-                }, error => {
1595
-                    logger.error(`${actionName} renegotiate failed: `, error);
1596
-                    finishedCallback(error);
1597
-                });
1598 1595
         };
1599 1596
 
1600 1597
         return new Promise((resolve, reject) => {
1601 1598
             this.modificationQueue.push(
1602
-                workFunctionWrap,
1599
+                workFunction,
1603 1600
                 error => {
1604 1601
                     if (error) {
1605 1602
                         reject(error);
1606 1603
                     } else {
1607 1604
                         resolve();
1608 1605
                     }
1609
-                }
1610
-            );
1606
+                });
1611 1607
         });
1612 1608
     }
1613 1609
 
@@ -1620,7 +1616,7 @@ export default class JingleSessionPC extends JingleSession {
1620 1616
      * a string in case anything goes wrong.
1621 1617
      */
1622 1618
     setMediaTransferActive(active) {
1623
-        const workFunction = () => {
1619
+        const workFunction = finishedCallback => {
1624 1620
             this.mediaTransferActive = active;
1625 1621
             if (this.peerconnection) {
1626 1622
                 this.peerconnection.setMediaTransferActive(
@@ -1628,16 +1624,30 @@ export default class JingleSessionPC extends JingleSession {
1628 1624
 
1629 1625
                 // Will do the sRD/sLD cycle to update SDPs and adjust the media
1630 1626
                 // direction
1631
-                return true;
1627
+                this._renegotiate()
1628
+                    .then(
1629
+                        finishedCallback,
1630
+                        finishedCallback /* will be called with an error */);
1631
+            } else {
1632
+                finishedCallback();
1632 1633
             }
1633
-
1634
-            return false; // Do not renegotiate
1635 1634
         };
1636 1635
 
1637 1636
         const logStr = active ? 'active' : 'inactive';
1638 1637
 
1639
-        return this._doRenegotiate(
1640
-            `make media transfer ${logStr}`, workFunction);
1638
+        logger.info(`Queued make media transfer ${logStr} task...`);
1639
+
1640
+        return new Promise((resolve, reject) => {
1641
+            this.modificationQueue.push(
1642
+                workFunction,
1643
+                error => {
1644
+                    if (error) {
1645
+                        reject(error);
1646
+                    } else {
1647
+                        resolve();
1648
+                    }
1649
+                });
1650
+        });
1641 1651
     }
1642 1652
 
1643 1653
     /**

Načítá se…
Zrušit
Uložit