|
|
@@ -984,10 +984,20 @@ export default class JingleSessionPC extends JingleSession {
|
|
984
|
984
|
}
|
|
985
|
985
|
const workFunction = finishedCallback => {
|
|
986
|
986
|
const addTracks = [];
|
|
987
|
|
-
|
|
988
|
|
- for (const localTrack of localTracks) {
|
|
989
|
|
- addTracks.push(this.peerconnection.addTrack(localTrack, this.isInitiator));
|
|
|
987
|
+ const audioTracks = localTracks.filter(track => track.getType() === MediaType.AUDIO);
|
|
|
988
|
+ const videoTracks = localTracks.filter(track => track.getType() === MediaType.VIDEO);
|
|
|
989
|
+ let tracks = localTracks;
|
|
|
990
|
+
|
|
|
991
|
+ // Add only 1 video track at a time. Adding 2 or more video tracks to the peerconnection at the same time
|
|
|
992
|
+ // makes the browser go into a renegotiation loop by firing 'negotiationneeded' event after every
|
|
|
993
|
+ // renegotiation.
|
|
|
994
|
+ if (FeatureFlags.isMultiStreamSupportEnabled() && videoTracks.length > 1) {
|
|
|
995
|
+ tracks = [ ...audioTracks, videoTracks[0] ];
|
|
|
996
|
+ }
|
|
|
997
|
+ for (const track of tracks) {
|
|
|
998
|
+ addTracks.push(this.peerconnection.addTrack(track, this.isInitiator));
|
|
990
|
999
|
}
|
|
|
1000
|
+ videoTracks.length && videoTracks.splice(0, 1);
|
|
991
|
1001
|
|
|
992
|
1002
|
Promise.all(addTracks)
|
|
993
|
1003
|
.then(() => this.peerconnection.createOffer(this.mediaConstraints))
|
|
|
@@ -997,6 +1007,13 @@ export default class JingleSessionPC extends JingleSession {
|
|
997
|
1007
|
// the transformation chain.
|
|
998
|
1008
|
this.sendSessionInitiate(this.peerconnection.localDescription.sdp);
|
|
999
|
1009
|
})
|
|
|
1010
|
+ .then(() => {
|
|
|
1011
|
+ if (videoTracks.length) {
|
|
|
1012
|
+ return this.addTracks(videoTracks);
|
|
|
1013
|
+ }
|
|
|
1014
|
+
|
|
|
1015
|
+ return Promise.resolve();
|
|
|
1016
|
+ })
|
|
1000
|
1017
|
.then(() => finishedCallback(), error => finishedCallback(error));
|
|
1001
|
1018
|
};
|
|
1002
|
1019
|
|
|
|
@@ -1109,15 +1126,22 @@ export default class JingleSessionPC extends JingleSession {
|
|
1109
|
1126
|
setOfferAnswerCycle(jingleOfferAnswerIq, success, failure, localTracks = []) {
|
|
1110
|
1127
|
const workFunction = finishedCallback => {
|
|
1111
|
1128
|
const addTracks = [];
|
|
1112
|
|
-
|
|
1113
|
|
- for (const track of localTracks) {
|
|
|
1129
|
+ const audioTracks = localTracks.filter(track => track.getType() === MediaType.AUDIO);
|
|
|
1130
|
+ const videoTracks = localTracks.filter(track => track.getType() === MediaType.VIDEO);
|
|
|
1131
|
+ let tracks = localTracks;
|
|
|
1132
|
+
|
|
|
1133
|
+ // Add only 1 video track at a time. Adding 2 or more video tracks to the peerconnection at the same time
|
|
|
1134
|
+ // makes the browser go into a renegotiation loop by firing 'negotiationneeded' event after every
|
|
|
1135
|
+ // renegotiation.
|
|
|
1136
|
+ if (FeatureFlags.isMultiStreamSupportEnabled() && videoTracks.length > 1) {
|
|
|
1137
|
+ tracks = [ ...audioTracks, videoTracks[0] ];
|
|
|
1138
|
+ }
|
|
|
1139
|
+ for (const track of tracks) {
|
|
1114
|
1140
|
addTracks.push(this.peerconnection.addTrack(track, this.isInitiator));
|
|
1115
|
1141
|
}
|
|
1116
|
|
-
|
|
1117
|
|
- const newRemoteSdp
|
|
1118
|
|
- = this._processNewJingleOfferIq(jingleOfferAnswerIq);
|
|
1119
|
|
- const oldLocalSdp
|
|
1120
|
|
- = this.peerconnection.localDescription.sdp;
|
|
|
1142
|
+ videoTracks.length && videoTracks.splice(0, 1);
|
|
|
1143
|
+ const newRemoteSdp = this._processNewJingleOfferIq(jingleOfferAnswerIq);
|
|
|
1144
|
+ const oldLocalSdp = this.peerconnection.localDescription.sdp;
|
|
1121
|
1145
|
|
|
1122
|
1146
|
const bridgeSession
|
|
1123
|
1147
|
= $(jingleOfferAnswerIq)
|
|
|
@@ -1131,22 +1155,23 @@ export default class JingleSessionPC extends JingleSession {
|
|
1131
|
1155
|
|
|
1132
|
1156
|
Promise.all(addTracks)
|
|
1133
|
1157
|
.then(() => this._renegotiate(newRemoteSdp.raw))
|
|
|
1158
|
+ .then(() => {
|
|
|
1159
|
+ if (videoTracks.length) {
|
|
|
1160
|
+ return this.addTracks(videoTracks);
|
|
|
1161
|
+ }
|
|
|
1162
|
+
|
|
|
1163
|
+ return Promise.resolve();
|
|
|
1164
|
+ })
|
|
1134
|
1165
|
.then(() => {
|
|
1135
|
1166
|
if (this.state === JingleSessionState.PENDING) {
|
|
1136
|
1167
|
this.state = JingleSessionState.ACTIVE;
|
|
1137
|
1168
|
|
|
1138
|
|
- // #1 Sync up video transfer active/inactive only after
|
|
1139
|
|
- // the initial O/A cycle. We want to adjust the video
|
|
1140
|
|
- // media direction only in the local SDP and the Jingle
|
|
1141
|
|
- // contents direction included in the initial
|
|
1142
|
|
- // offer/answer is mapped to the remote SDP. Jingle
|
|
1143
|
|
- // 'content-modify' IQ is processed in a way that it
|
|
1144
|
|
- // will only modify local SDP when remote peer is no
|
|
1145
|
|
- // longer interested in receiving video content.
|
|
1146
|
|
- // Changing media direction in the remote SDP will mess
|
|
1147
|
|
- // up our SDP translation chain (simulcast, video mute,
|
|
1148
|
|
- // RTX etc.)
|
|
1149
|
|
- //
|
|
|
1169
|
+ // #1 Sync up video transfer active/inactive only after the initial O/A cycle. We want to
|
|
|
1170
|
+ // adjust the video media direction only in the local SDP and the Jingle contents direction
|
|
|
1171
|
+ // included in the initial offer/answer is mapped to the remote SDP. Jingle 'content-modify'
|
|
|
1172
|
+ // IQ is processed in a way that it will only modify local SDP when remote peer is no longer
|
|
|
1173
|
+ // interested in receiving video content. Changing media direction in the remote SDP will mess
|
|
|
1174
|
+ // up our SDP translation chain (simulcast, video mute, RTX etc.)
|
|
1150
|
1175
|
// #2 Sends the max frame height if it was set, before the session-initiate/accept
|
|
1151
|
1176
|
if (this.isP2P
|
|
1152
|
1177
|
&& (!this._localVideoActive || this.localRecvMaxFrameHeight)) {
|
|
|
@@ -1154,16 +1179,12 @@ export default class JingleSessionPC extends JingleSession {
|
|
1154
|
1179
|
}
|
|
1155
|
1180
|
}
|
|
1156
|
1181
|
|
|
1157
|
|
- // Old local SDP will be available when we're setting answer
|
|
1158
|
|
- // for the first time, but not when offer and it's fine
|
|
1159
|
|
- // since we're generating an answer now it will contain all
|
|
1160
|
|
- // our SSRCs
|
|
|
1182
|
+ // Old local SDP will be available when we're setting answer for the first time, but not when offer
|
|
|
1183
|
+ // and it's fine since we're generating an answer now it will contain all our SSRCs.
|
|
1161
|
1184
|
if (oldLocalSdp) {
|
|
1162
|
|
- const newLocalSdp
|
|
1163
|
|
- = new SDP(this.peerconnection.localDescription.sdp);
|
|
|
1185
|
+ const newLocalSdp = new SDP(this.peerconnection.localDescription.sdp);
|
|
1164
|
1186
|
|
|
1165
|
|
- this.notifyMySSRCUpdate(
|
|
1166
|
|
- new SDP(oldLocalSdp), newLocalSdp);
|
|
|
1187
|
+ this.notifyMySSRCUpdate(new SDP(oldLocalSdp), newLocalSdp);
|
|
1167
|
1188
|
}
|
|
1168
|
1189
|
})
|
|
1169
|
1190
|
.then(() => finishedCallback(), error => finishedCallback(error));
|
|
|
@@ -2015,20 +2036,25 @@ export default class JingleSessionPC extends JingleSession {
|
|
2015
|
2036
|
* Adds a new track to the peerconnection. This method needs to be called only when a secondary JitsiLocalTrack is
|
|
2016
|
2037
|
* being added to the peerconnection for the first time.
|
|
2017
|
2038
|
*
|
|
2018
|
|
- * @param {JitsiLocalTrack} localTrack track to be added to the peer connection.
|
|
|
2039
|
+ * @param {Array<JitsiLocalTrack>} localTracks - Tracks to be added to the peer connection.
|
|
2019
|
2040
|
* @returns {Promise<void>} that resolves when the track is successfully added to the peerconnection, rejected
|
|
2020
|
2041
|
* otherwise.
|
|
2021
|
2042
|
*/
|
|
2022
|
|
- addTrack(localTrack) {
|
|
2023
|
|
- if (!FeatureFlags.isMultiStreamSupportEnabled() || localTrack.type !== MediaType.VIDEO) {
|
|
2024
|
|
- return Promise.reject(new Error('Multiple tracks of a given media type are not supported'));
|
|
|
2043
|
+ addTracks(localTracks = null) {
|
|
|
2044
|
+ if (!FeatureFlags.isMultiStreamSupportEnabled()
|
|
|
2045
|
+ || !localTracks?.length
|
|
|
2046
|
+ || localTracks.find(track => track.getType() !== MediaType.VIDEO)) {
|
|
|
2047
|
+ return Promise.reject(new Error('Multiple tracks of the given media type are not supported'));
|
|
2025
|
2048
|
}
|
|
2026
|
2049
|
|
|
|
2050
|
+ const replaceTracks = [];
|
|
2027
|
2051
|
const workFunction = finishedCallback => {
|
|
2028
|
2052
|
const remoteSdp = new SDP(this.peerconnection.peerconnection.remoteDescription.sdp);
|
|
2029
|
2053
|
|
|
2030
|
|
- // Add a new transceiver by adding a new mline in the remote description.
|
|
2031
|
|
- remoteSdp.addMlineForNewLocalSource(MediaType.VIDEO);
|
|
|
2054
|
+ // Add transceivers by adding a new mline in the remote description for each track.
|
|
|
2055
|
+ for (const track of localTracks) {
|
|
|
2056
|
+ remoteSdp.addMlineForNewLocalSource(track.getType());
|
|
|
2057
|
+ }
|
|
2032
|
2058
|
|
|
2033
|
2059
|
// Always initiate a responder renegotiate since the new m-line is added to remote SDP.
|
|
2034
|
2060
|
const remoteDescription = new RTCSessionDescription({
|
|
|
@@ -2052,8 +2078,12 @@ export default class JingleSessionPC extends JingleSession {
|
|
2052
|
2078
|
} else {
|
|
2053
|
2079
|
logger.debug(`${this} renegotiation after addTrack executed - OK`);
|
|
2054
|
2080
|
|
|
2055
|
|
- // Replace the track on the newly generated transceiver.
|
|
2056
|
|
- return this.replaceTrack(null, localTrack)
|
|
|
2081
|
+ // Replace the tracks on the newly generated transceivers.
|
|
|
2082
|
+ for (const track of localTracks) {
|
|
|
2083
|
+ replaceTracks.push(this.replaceTrack(null, track));
|
|
|
2084
|
+ }
|
|
|
2085
|
+
|
|
|
2086
|
+ return Promise.all(replaceTracks)
|
|
2057
|
2087
|
.then(() => resolve())
|
|
2058
|
2088
|
.catch(() => reject());
|
|
2059
|
2089
|
}
|