Browse Source

fix(multi-stream-support) Support muting of desktop track.

* fix(multi-stream-support) Support muting of desktop track.

* fix(multi-stream) Always initiate responder renegotiation even for p2p.
This is needed since the new m-line is always added to the remote description.

* fix(multi-stream) Support multi-stream only on Unfied plan endpoints.

* fix(multi-stream) Advertise source-name signaling to Jicofo when supported.

* fix(JitsiLocalTrack): Remove unnecessary reject.

* fix: add the correct source name attribute for the second video track.

* squash: Address review comments.
release-8443
Jaya Allamsetty 3 years ago
parent
commit
aed1fa446b
No account linked to committer's email address

+ 6
- 1
JitsiConference.js View File

1107
             return Promise.all(addTrackPromises)
1107
             return Promise.all(addTrackPromises)
1108
                 .then(() => {
1108
                 .then(() => {
1109
                     this._setupNewTrack(track);
1109
                     this._setupNewTrack(track);
1110
+                    this._sendBridgeVideoTypeMessage(track);
1111
+                    this._updateRoomPresence(this.getActiveMediaSession());
1110
 
1112
 
1111
-                    // TODO Update presence and sent videoType message.
1112
                     if (this.isMutedByFocus || this.isVideoMutedByFocus) {
1113
                     if (this.isMutedByFocus || this.isVideoMutedByFocus) {
1113
                         this._fireMuteChangeEvent(track);
1114
                         this._fireMuteChangeEvent(track);
1114
                     }
1115
                     }
1269
         logger.warn(`JitsiConference.replaceTrack oldTrack (${oldTrack} does not belong to this conference`);
1270
         logger.warn(`JitsiConference.replaceTrack oldTrack (${oldTrack} does not belong to this conference`);
1270
     }
1271
     }
1271
 
1272
 
1273
+    if (FeatureFlags.isMultiStreamSupportEnabled() && oldTrack && newTrack && oldTrack.isVideoTrack()) {
1274
+        newTrack.setSourceName(oldTrack.getSourceName());
1275
+    }
1276
+
1272
     // Now replace the stream at the lower levels
1277
     // Now replace the stream at the lower levels
1273
     return this._doReplaceTrack(oldTrackBelongsToConference ? oldTrack : null, newTrack)
1278
     return this._doReplaceTrack(oldTrackBelongsToConference ? oldTrack : null, newTrack)
1274
         .then(() => {
1279
         .then(() => {

+ 6
- 0
JitsiMeetJS.js View File

147
         Settings.init(options.externalStorage);
147
         Settings.init(options.externalStorage);
148
         Statistics.init(options);
148
         Statistics.init(options);
149
 
149
 
150
+        // Multi-stream is supported only on endpoints running in Unified plan mode and the flag to disable unified
151
+        // plan also needs to be taken into consideration.
152
+        if (typeof options.enableUnifiedOnChrome !== 'undefined') {
153
+            options.flags.enableUnifiedOnChrome = options.enableUnifiedOnChrome;
154
+        }
155
+
150
         // Configure the feature flags.
156
         // Configure the feature flags.
151
         FeatureFlags.init(options.flags || { });
157
         FeatureFlags.init(options.flags || { });
152
 
158
 

+ 9
- 4
modules/RTC/JitsiLocalTrack.js View File

20
     createNoDataFromSourceEvent
20
     createNoDataFromSourceEvent
21
 } from '../../service/statistics/AnalyticsEvents';
21
 } from '../../service/statistics/AnalyticsEvents';
22
 import browser from '../browser';
22
 import browser from '../browser';
23
+import FeatureFlags from '../flags/FeatureFlags';
23
 import Statistics from '../statistics/statistics';
24
 import Statistics from '../statistics/statistics';
24
 
25
 
25
 import JitsiTrack from './JitsiTrack';
26
 import JitsiTrack from './JitsiTrack';
354
      * @returns {Promise}
355
      * @returns {Promise}
355
      */
356
      */
356
     _setMuted(muted) {
357
     _setMuted(muted) {
357
-        if (this.isMuted() === muted) {
358
+        if (this.isMuted() === muted
359
+            && !(this.videoType === VideoType.DESKTOP && FeatureFlags.isMultiStreamSupportEnabled())) {
358
             return Promise.resolve();
360
             return Promise.resolve();
359
         }
361
         }
360
 
362
 
367
         // A function that will print info about muted status transition
369
         // A function that will print info about muted status transition
368
         const logMuteInfo = () => logger.info(`Mute ${this}: ${muted}`);
370
         const logMuteInfo = () => logger.info(`Mute ${this}: ${muted}`);
369
 
371
 
372
+        // In the multi-stream mode, desktop tracks are muted from jitsi-meet instead of being removed from the
373
+        // conference. This is needed because we don't want the client to signal a source-remove to the remote peer for
374
+        // the desktop track when screenshare is stopped. Later when screenshare is started again, the same sender will
375
+        // be re-used without the need for signaling a new ssrc through source-add.
370
         if (this.isAudioTrack()
376
         if (this.isAudioTrack()
371
-                || this.videoType === VideoType.DESKTOP
377
+                || (this.videoType === VideoType.DESKTOP && !FeatureFlags.isMultiStreamSupportEnabled())
372
                 || !browser.doesVideoMuteByStreamRemove()) {
378
                 || !browser.doesVideoMuteByStreamRemove()) {
373
             logMuteInfo();
379
             logMuteInfo();
374
 
380
 
442
                     this._startStreamEffect(this._streamEffect);
448
                     this._startStreamEffect(this._streamEffect);
443
                 }
449
                 }
444
 
450
 
445
-                this.containers.map(
446
-                    cont => RTCUtils.attachMediaStream(cont, this.stream));
451
+                this.containers.map(cont => RTCUtils.attachMediaStream(cont, this.stream));
447
 
452
 
448
                 return this._addStreamToConferenceAsUnmute();
453
                 return this._addStreamToConferenceAsUnmute();
449
             });
454
             });

+ 9
- 7
modules/RTC/TPCUtils.js View File

354
     replaceTrack(oldTrack, newTrack) {
354
     replaceTrack(oldTrack, newTrack) {
355
         const mediaType = newTrack?.getType() ?? oldTrack?.getType();
355
         const mediaType = newTrack?.getType() ?? oldTrack?.getType();
356
         const track = newTrack?.getTrack() ?? null;
356
         const track = newTrack?.getTrack() ?? null;
357
+        const isNewLocalSource = FeatureFlags.isMultiStreamSupportEnabled()
358
+            && this.pc.getLocalTracks(mediaType)?.length
359
+            && !oldTrack
360
+            && newTrack
361
+            && !newTrack.conference;
357
         let transceiver;
362
         let transceiver;
358
 
363
 
359
         // If old track exists, replace the track on the corresponding sender.
364
         // If old track exists, replace the track on the corresponding sender.
363
         // Find the first recvonly transceiver when more than one track of the same media type is being added to the pc.
368
         // Find the first recvonly transceiver when more than one track of the same media type is being added to the pc.
364
         // As part of the track addition, a new m-line was added to the remote description with direction set to
369
         // As part of the track addition, a new m-line was added to the remote description with direction set to
365
         // recvonly.
370
         // recvonly.
366
-        } else if (FeatureFlags.isMultiStreamSupportEnabled()
367
-            && this.pc.getLocalTracks(mediaType)?.length
368
-            && !newTrack.conference) {
371
+        } else if (isNewLocalSource) {
369
             transceiver = this.pc.peerconnection.getTransceivers().find(
372
             transceiver = this.pc.peerconnection.getTransceivers().find(
370
                 t => t.receiver.track.kind === mediaType
373
                 t => t.receiver.track.kind === mediaType
371
                 && t.direction === MediaDirection.RECVONLY
374
                 && t.direction === MediaDirection.RECVONLY
372
                 && t.currentDirection === MediaDirection.INACTIVE);
375
                 && t.currentDirection === MediaDirection.INACTIVE);
373
 
376
 
374
-        // For unmute operations, find the transceiver based on the track index in the source name if present, otherwise
375
-        // it is assumed to be the first local track that was added to the peerconnection.
377
+        // For mute/unmute operations, find the transceiver based on the track index in the source name if present,
378
+        // otherwise it is assumed to be the first local track that was added to the peerconnection.
376
         } else {
379
         } else {
377
             transceiver = this.pc.peerconnection.getTransceivers().find(t => t.receiver.track.kind === mediaType);
380
             transceiver = this.pc.peerconnection.getTransceivers().find(t => t.receiver.track.kind === mediaType);
378
-
379
-            const sourceName = newTrack?.getSourceName();
381
+            const sourceName = newTrack?.getSourceName() ?? oldTrack?.getSourceName();
380
 
382
 
381
             if (sourceName) {
383
             if (sourceName) {
382
                 const trackIndex = Number(sourceName.split('-')[1].substring(1));
384
                 const trackIndex = Number(sourceName.split('-')[1].substring(1));

+ 3
- 5
modules/RTC/TraceablePeerConnection.js View File

2008
 TraceablePeerConnection.prototype.removeTrackMute = function(localTrack) {
2008
 TraceablePeerConnection.prototype.removeTrackMute = function(localTrack) {
2009
     const webRtcStream = localTrack.getOriginalStream();
2009
     const webRtcStream = localTrack.getOriginalStream();
2010
 
2010
 
2011
-    this.trace(
2012
-        'removeStreamMute',
2013
-        localTrack.rtcId, webRtcStream ? webRtcStream.id : null);
2011
+    this.trace('removeTrackMute', localTrack.rtcId, webRtcStream ? webRtcStream.id : null);
2014
 
2012
 
2015
-    if (!this._assertTrackBelongs('removeStreamMute', localTrack)) {
2013
+    if (!this._assertTrackBelongs('removeTrackMute', localTrack)) {
2016
         // Abort - nothing to be done here
2014
         // Abort - nothing to be done here
2017
         return Promise.reject('Track not found in the peerconnection');
2015
         return Promise.reject('Track not found in the peerconnection');
2018
     }
2016
     }
2028
         return Promise.resolve(true);
2026
         return Promise.resolve(true);
2029
     }
2027
     }
2030
 
2028
 
2031
-    logger.error(`${this} removeStreamMute - no WebRTC stream for track=${localTrack}`);
2029
+    logger.error(`${this} removeTrackMute - no WebRTC stream for track=${localTrack}`);
2032
 
2030
 
2033
     return Promise.reject('Stream not found');
2031
     return Promise.reject('Stream not found');
2034
 };
2032
 };

+ 9
- 2
modules/flags/FeatureFlags.js View File

1
 import { getLogger } from '@jitsi/logger';
1
 import { getLogger } from '@jitsi/logger';
2
 
2
 
3
+import browser from '../browser';
4
+
3
 const logger = getLogger('FeatureFlags');
5
 const logger = getLogger('FeatureFlags');
4
 
6
 
5
 /**
7
 /**
15
         this._sourceNameSignaling = Boolean(flags.sourceNameSignaling);
17
         this._sourceNameSignaling = Boolean(flags.sourceNameSignaling);
16
         this._sendMultipleVideoStreams = Boolean(flags.sendMultipleVideoStreams);
18
         this._sendMultipleVideoStreams = Boolean(flags.sendMultipleVideoStreams);
17
 
19
 
20
+        // For Chromium, check if Unified plan is enabled.
21
+        this._usesUnifiedPlan = browser.supportsUnifiedPlan()
22
+            && (!browser.isChromiumBased() || (flags.enableUnifiedOnChrome ?? true));
23
+
18
         logger.info(`Source name signaling: ${this._sourceNameSignaling},`
24
         logger.info(`Source name signaling: ${this._sourceNameSignaling},`
19
-            + ` Send multiple video streams: ${this._sendMultipleVideoStreams}`);
25
+            + ` Send multiple video streams: ${this._sendMultipleVideoStreams},`
26
+            + ` uses Unified plan: ${this._usesUnifiedPlan}`);
20
     }
27
     }
21
 
28
 
22
     /**
29
     /**
25
      * @returns {boolean}
32
      * @returns {boolean}
26
      */
33
      */
27
     isMultiStreamSupportEnabled() {
34
     isMultiStreamSupportEnabled() {
28
-        return this._sourceNameSignaling && this._sendMultipleVideoStreams;
35
+        return this._sourceNameSignaling && this._sendMultipleVideoStreams && this._usesUnifiedPlan;
29
     }
36
     }
30
 
37
 
31
     /**
38
     /**

+ 12
- 8
modules/sdp/LocalSdpMunger.js View File

165
      */
165
      */
166
     _generateMsidAttribute(mediaType, trackId, streamId = null) {
166
     _generateMsidAttribute(mediaType, trackId, streamId = null) {
167
         if (!(mediaType && trackId)) {
167
         if (!(mediaType && trackId)) {
168
-            logger.warn(`Unable to munge local MSID - track id=${trackId} or media type=${mediaType} is missing`);
168
+            logger.error(`Unable to munge local MSID - track id=${trackId} or media type=${mediaType} is missing`);
169
 
169
 
170
             return null;
170
             return null;
171
         }
171
         }
210
                     const trackId = streamAndTrackIDs[1];
210
                     const trackId = streamAndTrackIDs[1];
211
 
211
 
212
                     // eslint-disable-next-line max-depth
212
                     // eslint-disable-next-line max-depth
213
-                    if (FeatureFlags.isMultiStreamSupportEnabled()
214
-                        && this.tpc.usesUnifiedPlan()
215
-                        && mediaType === MediaType.VIDEO) {
213
+                    if (FeatureFlags.isMultiStreamSupportEnabled() && mediaType === MediaType.VIDEO) {
216
 
214
 
217
                         // eslint-disable-next-line max-depth
215
                         // eslint-disable-next-line max-depth
218
                         if (streamId === '-' || !streamId) {
216
                         if (streamId === '-' || !streamId) {
260
                 const msidExists = mediaSection.ssrcs
258
                 const msidExists = mediaSection.ssrcs
261
                     .find(ssrc => ssrc.id === source && ssrc.attribute === 'msid');
259
                     .find(ssrc => ssrc.id === source && ssrc.attribute === 'msid');
262
 
260
 
263
-                if (!msidExists) {
261
+                if (!msidExists && trackId) {
264
                     const generatedMsid = this._generateMsidAttribute(mediaType, trackId);
262
                     const generatedMsid = this._generateMsidAttribute(mediaType, trackId);
265
 
263
 
266
                     mediaSection.ssrcs.push({
264
                     mediaSection.ssrcs.push({
327
             this._injectSourceNames(audioMLine);
325
             this._injectSourceNames(audioMLine);
328
         }
326
         }
329
 
327
 
330
-        const videoMLine = transformer.selectMedia(MediaType.VIDEO)?.[0];
328
+        const videoMlines = transformer.selectMedia(MediaType.VIDEO);
329
+
330
+        if (!FeatureFlags.isMultiStreamSupportEnabled()) {
331
+            videoMlines.splice(1);
332
+        }
331
 
333
 
332
-        if (videoMLine) {
334
+        for (const videoMLine of videoMlines) {
333
             this._transformMediaIdentifiers(videoMLine);
335
             this._transformMediaIdentifiers(videoMLine);
334
             this._injectSourceNames(videoMLine);
336
             this._injectSourceNames(videoMLine);
335
         }
337
         }
364
 
366
 
365
         for (const source of sources) {
367
         for (const source of sources) {
366
             const nameExists = mediaSection.ssrcs.find(ssrc => ssrc.id === source && ssrc.attribute === 'name');
368
             const nameExists = mediaSection.ssrcs.find(ssrc => ssrc.id === source && ssrc.attribute === 'name');
369
+            const msid = mediaSection.ssrcs.find(ssrc => ssrc.id === source && ssrc.attribute === 'msid')?.value;
370
+            const trackIndex = msid ? msid.split('-')[2] : null;
367
 
371
 
368
             if (!nameExists) {
372
             if (!nameExists) {
369
                 // Inject source names as a=ssrc:3124985624 name:endpointA-v0
373
                 // Inject source names as a=ssrc:3124985624 name:endpointA-v0
370
                 mediaSection.ssrcs.push({
374
                 mediaSection.ssrcs.push({
371
                     id: source,
375
                     id: source,
372
                     attribute: 'name',
376
                     attribute: 'name',
373
-                    value: getSourceNameForJitsiTrack(this.localEndpointId, mediaType, 0)
377
+                    value: getSourceNameForJitsiTrack(this.localEndpointId, mediaType, trackIndex)
374
                 });
378
                 });
375
             }
379
             }
376
         }
380
         }

+ 9
- 4
modules/xmpp/JingleSessionPC.js View File

2020
      * otherwise.
2020
      * otherwise.
2021
      */
2021
      */
2022
     addTrack(localTrack) {
2022
     addTrack(localTrack) {
2023
-        if (!FeatureFlags.isMultiStreamSupportEnabled()
2024
-            || !this.usesUnifiedPlan
2025
-            || localTrack.type !== MediaType.VIDEO) {
2023
+        if (!FeatureFlags.isMultiStreamSupportEnabled() || localTrack.type !== MediaType.VIDEO) {
2026
             return Promise.reject(new Error('Multiple tracks of a given media type are not supported'));
2024
             return Promise.reject(new Error('Multiple tracks of a given media type are not supported'));
2027
         }
2025
         }
2028
 
2026
 
2031
 
2029
 
2032
             // Add a new transceiver by adding a new mline in the remote description.
2030
             // Add a new transceiver by adding a new mline in the remote description.
2033
             remoteSdp.addMlineForNewLocalSource(MediaType.VIDEO);
2031
             remoteSdp.addMlineForNewLocalSource(MediaType.VIDEO);
2034
-            this._renegotiate(remoteSdp.raw)
2032
+
2033
+            // Always initiate a responder renegotiate since the new m-line is added to remote SDP.
2034
+            const remoteDescription = new RTCSessionDescription({
2035
+                type: 'offer',
2036
+                sdp: remoteSdp.raw
2037
+            });
2038
+
2039
+            this._responderRenegotiate(remoteDescription)
2035
                 .then(() => finishedCallback(), error => finishedCallback(error));
2040
                 .then(() => finishedCallback(), error => finishedCallback(error));
2036
         };
2041
         };
2037
 
2042
 

+ 7
- 0
modules/xmpp/xmpp.js View File

9
 import { XMPPEvents } from '../../service/xmpp/XMPPEvents';
9
 import { XMPPEvents } from '../../service/xmpp/XMPPEvents';
10
 import browser from '../browser';
10
 import browser from '../browser';
11
 import { E2EEncryption } from '../e2ee/E2EEncryption';
11
 import { E2EEncryption } from '../e2ee/E2EEncryption';
12
+import FeatureFlags from '../flags/FeatureFlags';
12
 import Statistics from '../statistics/statistics';
13
 import Statistics from '../statistics/statistics';
13
 import GlobalOnErrorHandler from '../util/GlobalOnErrorHandler';
14
 import GlobalOnErrorHandler from '../util/GlobalOnErrorHandler';
14
 import Listenable from '../util/Listenable';
15
 import Listenable from '../util/Listenable';
249
         if (E2EEncryption.isSupported(this.options)) {
250
         if (E2EEncryption.isSupported(this.options)) {
250
             this.caps.addFeature(FEATURE_E2EE, false, true);
251
             this.caps.addFeature(FEATURE_E2EE, false, true);
251
         }
252
         }
253
+
254
+        // Advertise source-name signaling when the endpoint supports it.
255
+        if (FeatureFlags.isSourceNameSignalingEnabled()) {
256
+            logger.info('Source-name signaling is enabled');
257
+            this.caps.addFeature('http://jitsi.org/source-name');
258
+        }
252
     }
259
     }
253
 
260
 
254
     /**
261
     /**

+ 1
- 0
types/auto/modules/flags/FeatureFlags.d.ts View File

12
     init(flags: any): void;
12
     init(flags: any): void;
13
     _sourceNameSignaling: boolean;
13
     _sourceNameSignaling: boolean;
14
     _sendMultipleVideoStreams: boolean;
14
     _sendMultipleVideoStreams: boolean;
15
+    _usesUnifiedPlan: any;
15
     /**
16
     /**
16
      * Checks if multiple local video streams support is enabled.
17
      * Checks if multiple local video streams support is enabled.
17
      *
18
      *

Loading…
Cancel
Save