|
|
@@ -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.
|