Bläddra i källkod

feat(p2p): real "audio only" mode

When the lastN is set to 0 the P2P session will notify the remote peer
to stop sending video.
dev1
Paweł Domas 8 år sedan
förälder
incheckning
aa89f7c78c
4 ändrade filer med 317 tillägg och 47 borttagningar
  1. 28
    2
      JitsiConference.js
  2. 64
    15
      modules/RTC/TraceablePeerConnection.js
  3. 221
    30
      modules/xmpp/JingleSessionPC.js
  4. 4
    0
      modules/xmpp/strophe.jingle.js

+ 28
- 2
JitsiConference.js Visa fil

@@ -953,6 +953,20 @@ JitsiConference.prototype.setLastN = function(lastN) {
953 953
         throw new RangeError('lastN cannot be smaller than -1');
954 954
     }
955 955
     this.rtc.setLastN(n);
956
+
957
+    // If the P2P session is not fully established yet, we wait until it gets
958
+    // established.
959
+    if (this.p2pJingleSession) {
960
+        const isVideoActive = n !== 0;
961
+
962
+        this.p2pJingleSession
963
+            .setMediaTransferActive(true, isVideoActive)
964
+            .catch(error => {
965
+                logger.error(
966
+                    `Failed to adjust video transfer status (${isVideoActive})`,
967
+                    error);
968
+            });
969
+    }
956 970
 };
957 971
 
958 972
 /**
@@ -2076,7 +2090,7 @@ JitsiConference.prototype._removeRemoteTracks
2076 2090
  */
2077 2091
 JitsiConference.prototype._resumeMediaTransferForJvbConnection = function() {
2078 2092
     logger.info('Resuming media transfer over the JVB connection...');
2079
-    this.jvbJingleSession.setMediaTransferActive(true).then(
2093
+    this.jvbJingleSession.setMediaTransferActive(true, true).then(
2080 2094
         () => {
2081 2095
             logger.info('Resumed media transfer over the JVB connection!');
2082 2096
         },
@@ -2103,6 +2117,18 @@ JitsiConference.prototype._setP2PStatus = function(newStatus) {
2103 2117
     this.p2p = newStatus;
2104 2118
     if (newStatus) {
2105 2119
         logger.info('Peer to peer connection established!');
2120
+
2121
+        // Sync up video transfer active in case p2pJingleSession not existed
2122
+        // when the lastN value was being adjusted.
2123
+        const isVideoActive = this.rtc.getLastN() !== 0;
2124
+
2125
+        this.p2pJingleSession
2126
+            .setMediaTransferActive(true, isVideoActive)
2127
+            .catch(error => {
2128
+                logger.error(
2129
+                    'Failed to sync up P2P video transfer status'
2130
+                        + `(${isVideoActive})`, error);
2131
+            });
2106 2132
     } else {
2107 2133
         logger.info('Peer to peer connection closed!');
2108 2134
     }
@@ -2172,7 +2198,7 @@ JitsiConference.prototype._startP2PSession = function(peerJid) {
2172 2198
  */
2173 2199
 JitsiConference.prototype._suspendMediaTransferForJvbConnection = function() {
2174 2200
     logger.info('Suspending media transfer over the JVB connection...');
2175
-    this.jvbJingleSession.setMediaTransferActive(false).then(
2201
+    this.jvbJingleSession.setMediaTransferActive(false, false).then(
2176 2202
         () => {
2177 2203
             logger.info('Suspended media transfer over the JVB connection !');
2178 2204
         },

+ 64
- 15
modules/RTC/TraceablePeerConnection.js Visa fil

@@ -60,13 +60,23 @@ export default function TraceablePeerConnection(
60 60
 
61 61
     /**
62 62
      * Indicates whether or not this peer connection instance is actively
63
-     * sending/receiving media. When set to <tt>false</tt> the SDP media
64
-     * direction will be adjusted to 'inactive' in order to suspend media
65
-     * transmission.
63
+     * sending/receiving audio media. When set to <tt>false</tt> the SDP audio
64
+     * media direction will be adjusted to 'inactive' in order to suspend
65
+     * the transmission.
66 66
      * @type {boolean}
67 67
      * @private
68 68
      */
69
-    this.mediaTransferActive = true;
69
+    this.audioTransferActive = true;
70
+
71
+    /**
72
+     * Indicates whether or not this peer connection instance is actively
73
+     * sending/receiving video media. When set to <tt>false</tt> the SDP video
74
+     * media direction will be adjusted to 'inactive' in order to suspend
75
+     * the transmission.
76
+     * @type {boolean}
77
+     * @private
78
+     */
79
+    this.videoTransferActive = true;
70 80
 
71 81
     /**
72 82
      * The parent instance of RTC service which created this
@@ -319,7 +329,7 @@ TraceablePeerConnection.prototype.getConnectionState = function() {
319 329
 /**
320 330
  * Obtains the media direction for given {@link MediaType}. The method takes
321 331
  * into account whether or not there are any local tracks for media and
322
- * the {@link mediaTransferActive} flag.
332
+ * the {@link audioTransferActive} and {@link videoTransferActive} flags.
323 333
  * @param {MediaType} mediaType
324 334
  * @return {string} one of the SDP direction constants ('sendrecv, 'recvonly'
325 335
  * etc.) which should be used when setting local description on the peer
@@ -328,7 +338,14 @@ TraceablePeerConnection.prototype.getConnectionState = function() {
328 338
  */
329 339
 TraceablePeerConnection.prototype._getDesiredMediaDirection
330 340
 = function(mediaType) {
331
-    if (this.mediaTransferActive) {
341
+    let mediaTransferActive = true;
342
+
343
+    if (mediaType === MediaType.AUDIO) {
344
+        mediaTransferActive = this.audioTransferActive;
345
+    } else if (mediaType === MediaType.VIDEO) {
346
+        mediaTransferActive = this.videoTransferActive;
347
+    }
348
+    if (mediaTransferActive) {
332 349
         return this.hasAnyTracksOfType(mediaType) ? 'sendrecv' : 'recvonly';
333 350
     }
334 351
 
@@ -1369,7 +1386,8 @@ TraceablePeerConnection.prototype._ensureSimulcastGroupIsLast
1369 1386
 
1370 1387
 /**
1371 1388
  * Will adjust audio and video media direction in the given SDP object to
1372
- * reflect the current status of the {@link mediaTransferActive} flag.
1389
+ * reflect the current status of the {@link audioTransferActive} and
1390
+ * {@link videoTransferActive} flags.
1373 1391
  * @param {Object} localDescription the WebRTC session description instance for
1374 1392
  * the local description.
1375 1393
  * @private
@@ -1456,16 +1474,25 @@ TraceablePeerConnection.prototype.setLocalDescription
1456 1474
 };
1457 1475
 
1458 1476
 /**
1459
- * Enables/disables media transmission on this peer connection. When disabled
1460
- * the SDP media direction in the local SDP will be adjusted to 'inactive' which
1461
- * means that no data will be received or sent, but the connection should be
1462
- * kept alive.
1463
- * @param {boolean} active <tt>true</tt> to enable the media transmission or
1464
- * <tt>false</tt> to disable.
1477
+ * Enables/disables audio media transmission on this peer connection. When
1478
+ * disabled the SDP audio media direction in the local SDP will be adjusted to
1479
+ * 'inactive' which means that no data will be sent nor accepted, but
1480
+ * the connection should be kept alive.
1481
+ * @param {boolean} active <tt>true</tt> to enable video media transmission or
1482
+ * <tt>false</tt> to disable. If the value is not a boolean the call will have
1483
+ * no effect.
1484
+ * @return {boolean} <tt>true</tt> if the value has changed and sRD/sLD cycle
1485
+ * needs to be executed in order for the changes to take effect or
1486
+ * <tt>false</tt> if the given value was the same as the previous one.
1465 1487
  * @public
1466 1488
  */
1467
-TraceablePeerConnection.prototype.setMediaTransferActive = function(active) {
1468
-    this.mediaTransferActive = active;
1489
+TraceablePeerConnection.prototype.setAudioTransferActive = function(active) {
1490
+    logger.debug(`${this} audio transfer active: ${active}`);
1491
+    const changed = this.audioTransferActive !== active;
1492
+
1493
+    this.audioTransferActive = active;
1494
+
1495
+    return changed;
1469 1496
 };
1470 1497
 
1471 1498
 TraceablePeerConnection.prototype.setRemoteDescription
@@ -1528,6 +1555,28 @@ TraceablePeerConnection.prototype.setRemoteDescription
1528 1555
         });
1529 1556
 };
1530 1557
 
1558
+/**
1559
+ * Enables/disables video media transmission on this peer connection. When
1560
+ * disabled the SDP video media direction in the local SDP will be adjusted to
1561
+ * 'inactive' which means that no data will be sent nor accepted, but
1562
+ * the connection should be kept alive.
1563
+ * @param {boolean} active <tt>true</tt> to enable video media transmission or
1564
+ * <tt>false</tt> to disable. If the value is not a boolean the call will have
1565
+ * no effect.
1566
+ * @return {boolean} <tt>true</tt> if the value has changed and sRD/sLD cycle
1567
+ * needs to be executed in order for the changes to take effect or
1568
+ * <tt>false</tt> if the given value was the same as the previous one.
1569
+ * @public
1570
+ */
1571
+TraceablePeerConnection.prototype.setVideoTransferActive = function(active) {
1572
+    logger.debug(`${this} video transfer active: ${active}`);
1573
+    const changed = this.videoTransferActive !== active;
1574
+
1575
+    this.videoTransferActive = active;
1576
+
1577
+    return changed;
1578
+};
1579
+
1531 1580
 /**
1532 1581
  * Makes the underlying TraceablePeerConnection generate new SSRC for
1533 1582
  * the recvonly video stream.

+ 221
- 30
modules/xmpp/JingleSessionPC.js Visa fil

@@ -25,6 +25,30 @@ const IQ_TIMEOUT = 10000;
25 25
  *
26 26
  */
27 27
 export default class JingleSessionPC extends JingleSession {
28
+    /**
29
+     * Parses 'senders' attribute of the video content.
30
+     * @param {jQuery} jingleContents
31
+     * @return {string|null} one of the values of content "senders" attribute
32
+     * defined by Jingle. If there is no "senders" attribute or if the value is
33
+     * invalid then <tt>null</tt> will be returned.
34
+     * @private
35
+     */
36
+    static parseVideoSenders(jingleContents) {
37
+        const videoContents = jingleContents.find('>content[name="video"]');
38
+
39
+        if (videoContents.length) {
40
+            const senders = videoContents[0].getAttribute('senders');
41
+
42
+            if (senders === 'both'
43
+                || senders === 'initiator'
44
+                || senders === 'responder'
45
+                || senders === 'none') {
46
+                return senders;
47
+            }
48
+        }
49
+
50
+        return null;
51
+    }
28 52
 
29 53
     /* eslint-disable max-params */
30 54
 
@@ -89,6 +113,34 @@ export default class JingleSessionPC extends JingleSession {
89 113
          */
90 114
         this._gatheringStartedTimestamp = null;
91 115
 
116
+        /**
117
+         * Indicates whether or not this session is willing to send/receive
118
+         * video media. When set to <tt>false</tt> the underlying peer
119
+         * connection will disable local video transfer and the remote peer will
120
+         * be will be asked to stop sending video via 'content-modify' IQ
121
+         * (the senders attribute of video contents will be adjusted
122
+         * accordingly). Note that this notification is sent only in P2P
123
+         * session, because Jicofo does not support it yet. Obviously when
124
+         * the value is changed from <tt>false</tt> to <tt>true</tt> another
125
+         * notification will be sent to resume video transfer on the remote
126
+         * side.
127
+         * @type {boolean}
128
+         * @private
129
+         */
130
+        this._localVideoActive = true;
131
+
132
+        /**
133
+         * Indicates whether or not the remote peer has video transfer active.
134
+         * When set to <tt>true</tt> it means that remote peer is neither
135
+         * sending nor willing to receive video. In such case we'll ask
136
+         * our peerconnection to stop sending video by calling
137
+         * {@link TraceablePeerConnection.setVideoTransferActive} with
138
+         * <tt>false</tt>.
139
+         * @type {boolean}
140
+         * @private
141
+         */
142
+        this._remoteVideoActive = true;
143
+
92 144
         /**
93 145
          * Marks that ICE gathering duration has been reported already. That
94 146
          * prevents reporting it again, after eventual 'transport-replace' (JVB
@@ -116,16 +168,6 @@ export default class JingleSessionPC extends JingleSession {
116 168
          */
117 169
         this.isP2P = isP2P;
118 170
 
119
-        /**
120
-         * Stores a state for
121
-         * {@link TraceablePeerConnection.mediaTransferActive} until
122
-         * {@link JingleSessionPC.peerconnection} is initialised and capable of
123
-         * handling the value.
124
-         * @type {boolean}
125
-         * @private
126
-         */
127
-        this.mediaTransferActive = true;
128
-
129 171
         /**
130 172
          * The signaling layer implementation.
131 173
          * @type {SignalingLayerImpl}
@@ -232,8 +274,6 @@ export default class JingleSessionPC extends JingleSession {
232 274
                     preferH264: this.room.options.preferH264
233 275
                 });
234 276
 
235
-        this.peerconnection.setMediaTransferActive(this.mediaTransferActive);
236
-
237 277
         this.peerconnection.onicecandidate = ev => {
238 278
             if (!ev) {
239 279
                 // There was an incomplete check for ev before which left
@@ -831,6 +871,21 @@ export default class JingleSessionPC extends JingleSession {
831 871
                 .then(() => {
832 872
                     if (this.state === JingleSessionState.PENDING) {
833 873
                         this.state = JingleSessionState.ACTIVE;
874
+
875
+                        // Sync up video transfer active/inactive only after
876
+                        // the initial O/A cycle. We want to adjust the video
877
+                        // media direction only in the local SDP and the Jingle
878
+                        // contents direction included in the initial
879
+                        // offer/answer is mapped to the remote SDP. Jingle
880
+                        // 'content-modify' IQ is processed in a way that it
881
+                        // will only modify local SDP when remote peer is no
882
+                        // longer interested in receiving video content.
883
+                        // Changing media direction in the remote SDP will mess
884
+                        // up our SDP translation chain (simulcast, video mute,
885
+                        // RTX etc.)
886
+                        if (this.isP2P && !this._localVideoActive) {
887
+                            this.sendContentModify(this._localVideoActive);
888
+                        }
834 889
                     }
835 890
 
836 891
                     // Old local SDP will be available when we're setting answer
@@ -973,6 +1028,44 @@ export default class JingleSessionPC extends JingleSession {
973 1028
         // this.connection.flush();
974 1029
     }
975 1030
 
1031
+    /**
1032
+     * Will send 'content-modify' IQ in order to ask the remote peer to
1033
+     * either stop or resume sending video media.
1034
+     * @param {boolean} videoTransferActive <tt>false</tt> to let the other peer
1035
+     * know that we're not sending nor interested in receiving video contents.
1036
+     * When set to <tt>true</tt> remote peer will be asked to resume video
1037
+     * transfer.
1038
+     * @private
1039
+     */
1040
+    sendContentModify(videoTransferActive) {
1041
+        const newSendersValue = videoTransferActive ? 'both' : 'none';
1042
+
1043
+        const sessionModify
1044
+            = $iq({
1045
+                to: this.peerjid,
1046
+                type: 'set'
1047
+            })
1048
+            .c('jingle', {
1049
+                xmlns: 'urn:xmpp:jingle:1',
1050
+                action: 'content-modify',
1051
+                initiator: this.initiator,
1052
+                sid: this.sid
1053
+            })
1054
+            .c('content', {
1055
+                name: 'video',
1056
+                senders: newSendersValue
1057
+            });
1058
+
1059
+        logger.info(
1060
+            `Sending content-modify, video senders: ${newSendersValue}`);
1061
+
1062
+        this.connection.sendIQ(
1063
+            sessionModify,
1064
+            null,
1065
+            this.newJingleErrorHandler(sessionModify),
1066
+            IQ_TIMEOUT);
1067
+    }
1068
+
976 1069
     /**
977 1070
      * Sends Jingle 'transport-accept' message which is a response to
978 1071
      * 'transport-replace'.
@@ -1747,31 +1840,60 @@ export default class JingleSessionPC extends JingleSession {
1747 1840
 
1748 1841
     /**
1749 1842
      * Resumes or suspends media transfer over the underlying peer connection.
1750
-     * @param {boolean} active <tt>true</tt> to enable media transfer or
1751
-     * <tt>false</tt> to suspend any media transmission.
1843
+     * @param {boolean} audioActive <tt>true</tt> to enable audio media
1844
+     * transfer or <tt>false</tt> to suspend audio media transmission.
1845
+     * @param {boolean} videoActive <tt>true</tt> to enable video media
1846
+     * transfer or <tt>false</tt> to suspend video media transmission.
1752 1847
      * @return {Promise} a <tt>Promise</tt> which will resolve once
1753 1848
      * the operation is done. It will be rejected with an error description as
1754 1849
      * a string in case anything goes wrong.
1755 1850
      */
1756
-    setMediaTransferActive(active) {
1851
+    setMediaTransferActive(audioActive, videoActive) {
1852
+        if (!this.peerconnection) {
1853
+            return Promise.reject(
1854
+                'Can not modify transfer active state,'
1855
+                    + ' before "initialize" is called');
1856
+        }
1857
+
1858
+        const logAudioStr = audioActive ? 'audio active' : 'audio inactive';
1859
+        const logVideoStr = videoActive ? 'video active' : 'video inactive';
1860
+
1861
+        logger.info(`Queued make ${logVideoStr}, ${logAudioStr} task...`);
1862
+
1757 1863
         const workFunction = finishedCallback => {
1864
+            const isSessionActive = this.state === JingleSessionState.ACTIVE;
1758 1865
 
1759 1866
             // Because the value is modified on the queue it's impossible to
1760 1867
             // check it's final value reliably prior to submitting the task.
1761 1868
             // The rule here is that the last submitted state counts.
1762
-            // Check the value here to avoid unnecessary renegotiation cycle.
1763
-            if (this.mediaTransferActive === active) {
1764
-                finishedCallback();
1765
-
1766
-                return;
1869
+            // Check the values here to avoid unnecessary renegotiation cycle.
1870
+            const audioActiveChanged
1871
+                = this.peerconnection.setAudioTransferActive(audioActive);
1872
+
1873
+            if (this._localVideoActive !== videoActive) {
1874
+                this._localVideoActive = videoActive;
1875
+
1876
+                // Do only for P2P - Jicofo will reply with 'bad-request'
1877
+                // We don't want to send 'content-modify', before the initial
1878
+                // O/A (state === JingleSessionState.ACTIVE), because that will
1879
+                // mess up video media direction in the remote SDP.
1880
+                // 'content-modify' when processed only affects the media
1881
+                // direction in the local SDP. We're doing that, because setting
1882
+                // 'inactive' on video media in remote SDP will mess up our SDP
1883
+                // translation chain (simulcast, RTX, video mute etc.).
1884
+                if (this.isP2P && isSessionActive) {
1885
+                    this.sendContentModify(videoActive);
1886
+                }
1767 1887
             }
1768
-            this.mediaTransferActive = active;
1769
-            if (this.peerconnection) {
1770
-                this.peerconnection.setMediaTransferActive(
1771
-                    this.mediaTransferActive);
1772 1888
 
1773
-                // Will do the sRD/sLD cycle to update SDPs and adjust the media
1774
-                // direction
1889
+            const pcVideoActiveChanged
1890
+                = this.peerconnection.setVideoTransferActive(
1891
+                    this._localVideoActive && this._remoteVideoActive);
1892
+
1893
+            // Will do the sRD/sLD cycle to update SDPs and adjust the media
1894
+            // direction
1895
+            if (isSessionActive
1896
+                    && (audioActiveChanged || pcVideoActiveChanged)) {
1775 1897
                 this._renegotiate()
1776 1898
                     .then(
1777 1899
                         finishedCallback,
@@ -1781,10 +1903,6 @@ export default class JingleSessionPC extends JingleSession {
1781 1903
             }
1782 1904
         };
1783 1905
 
1784
-        const logStr = active ? 'active' : 'inactive';
1785
-
1786
-        logger.info(`Queued make media transfer ${logStr} task...`);
1787
-
1788 1906
         return new Promise((resolve, reject) => {
1789 1907
             this.modificationQueue.push(
1790 1908
                 workFunction,
@@ -1798,6 +1916,79 @@ export default class JingleSessionPC extends JingleSession {
1798 1916
         });
1799 1917
     }
1800 1918
 
1919
+    /**
1920
+     * Will put and execute on the queue a session modify task. Currently it
1921
+     * only checks the senders attribute of the video content in order to figure
1922
+     * out if the remote peer has video in the inactive state (stored locally
1923
+     * in {@link _remoteVideoActive} - see field description for more info).
1924
+     * @param {jQuery} jingleContents jQuery selector pointing to the jingle
1925
+     * element of the session modify IQ.
1926
+     * @see {@link _remoteVideoActive}
1927
+     * @see {@link _localVideoActive}
1928
+     */
1929
+    modifyContents(jingleContents) {
1930
+        const newVideoSenders
1931
+            = JingleSessionPC.parseVideoSenders(jingleContents);
1932
+
1933
+        if (newVideoSenders === null) {
1934
+            logger.error(
1935
+                `${this} - failed to parse video "senders" attribute in`
1936
+                    + '"content-modify" action');
1937
+
1938
+            return;
1939
+        }
1940
+
1941
+        const workFunction = finishedCallback => {
1942
+            if (this._assertNotEnded('content-modify')
1943
+                    && this._modifyRemoteVideoActive(newVideoSenders)) {
1944
+                // Will do the sRD/sLD cycle to update SDPs and adjust
1945
+                // the media direction
1946
+                this._renegotiate()
1947
+                    .then(finishedCallback, finishedCallback /* (error) */);
1948
+            } else {
1949
+                finishedCallback();
1950
+            }
1951
+        };
1952
+
1953
+        logger.debug(
1954
+            `${this} queued "content-modify" task`
1955
+                + `(video senders="${newVideoSenders}")`);
1956
+
1957
+        this.modificationQueue.push(
1958
+            workFunction,
1959
+            error => {
1960
+                if (error) {
1961
+                    logger.error('"content-modify" failed', error);
1962
+                }
1963
+            });
1964
+    }
1965
+
1966
+    /**
1967
+     * Processes new value of remote video "senders" Jingle attribute and tries
1968
+     * to apply it for {@link _remoteVideoActive}.
1969
+     * @param {string} remoteVideoSenders the value of "senders" attribute of
1970
+     * Jingle video content element advertised by remote peer.
1971
+     * @return {boolean} <tt>true</tt> if the change affected state of
1972
+     * the underlying peerconnection and renegotiation is required for
1973
+     * the changes to take effect.
1974
+     * @private
1975
+     */
1976
+    _modifyRemoteVideoActive(remoteVideoSenders) {
1977
+        const isRemoteVideoActive
1978
+            = remoteVideoSenders === 'both'
1979
+                || (remoteVideoSenders === 'initiator' && this.isInitiator)
1980
+                || (remoteVideoSenders === 'responder' && !this.isInitiator);
1981
+
1982
+        if (isRemoteVideoActive !== this._remoteVideoActive) {
1983
+            logger.debug(
1984
+                `${this} new remote video active: ${isRemoteVideoActive}`);
1985
+            this._remoteVideoActive = isRemoteVideoActive;
1986
+        }
1987
+
1988
+        return this.peerconnection.setVideoTransferActive(
1989
+            this._localVideoActive && this._remoteVideoActive);
1990
+    }
1991
+
1801 1992
     /**
1802 1993
      * Figures out added/removed ssrcs and send update IQs.
1803 1994
      * @param oldSDP SDP object for old description.

+ 4
- 0
modules/xmpp/strophe.jingle.js Visa fil

@@ -172,6 +172,10 @@ class JingleConnectionPlugin extends ConnectionPlugin {
172 172
                 XMPPEvents.CALL_ACCEPTED, sess, $(iq).find('>jingle'));
173 173
             break;
174 174
         }
175
+        case 'content-modify': {
176
+            sess.modifyContents($(iq).find('>jingle'));
177
+            break;
178
+        }
175 179
         case 'transport-info': {
176 180
             this.eventEmitter.emit(
177 181
                 XMPPEvents.TRANSPORT_INFO, sess, $(iq).find('>jingle'));

Laddar…
Avbryt
Spara