Browse Source

feat(ssrc-rewriting) Add initial implementation (#2136)

Instead of Jicofo signaling all the remote sources available in the call, the bridge now signals only a limited set of SSRCs and then rewrites the SSRC on the outgoing media streams. The SSRC mapping is done based on the sources requested by the clients through the receiver constraints. This limits the number of m-lines in the remote/local SDPs on the client and therefore results in better performance in large calls.

* Handle source remapping messages from bridge
* Added track_owner_changed events
* don't process an invalid rtx ssrc.
* keep track of remote ssrcs, only renegotiate on new ones.
* Change source name on remote track on ssrc remapping.
* Don't remove tracks on member leave.
* Remove (orphaned) tracks on session terminated.
* Use serial number (per media type) to create msid attribute.
* Update videoType on remapping.

Co-authored-by: James A <jqdrqgnq@users.noreply.github.com>
tags/v0.0.2
Jaya Allamsetty 1 year ago
parent
commit
28436e5727
No account linked to committer's email address

+ 15
- 12
JitsiConference.js View File

@@ -1982,23 +1982,26 @@ JitsiConference.prototype.onMemberLeft = function(jid, reason) {
1982 1982
         return;
1983 1983
     }
1984 1984
 
1985
-    const participant = this.participants[id];
1986
-    const mediaSessions = this.getMediaSessions();
1987
-    let tracksToBeRemoved = [];
1985
+    if (!FeatureFlags.isSsrcRewritingSupported()) {
1986
+        const mediaSessions = this.getMediaSessions();
1987
+        let tracksToBeRemoved = [];
1988
+
1989
+        for (const session of mediaSessions) {
1990
+            const remoteTracks = session.peerconnection.getRemoteTracks(id);
1988 1991
 
1989
-    for (const session of mediaSessions) {
1990
-        const remoteTracks = session.peerconnection.getRemoteTracks(id);
1992
+            remoteTracks && (tracksToBeRemoved = [ ...tracksToBeRemoved, ...remoteTracks ]);
1991 1993
 
1992
-        remoteTracks && (tracksToBeRemoved = [ ...tracksToBeRemoved, ...remoteTracks ]);
1994
+            // Remove the ssrcs from the remote description and renegotiate.
1995
+            session.removeRemoteStreamsOnLeave(id);
1996
+        }
1993 1997
 
1994
-        // Remove the ssrcs from the remote description and renegotiate.
1995
-        session.removeRemoteStreamsOnLeave(id);
1998
+        // Fire the event before renegotiation is done so that the thumbnails can be removed immediately.
1999
+        tracksToBeRemoved.forEach(track => {
2000
+            this.eventEmitter.emit(JitsiConferenceEvents.TRACK_REMOVED, track);
2001
+        });
1996 2002
     }
1997 2003
 
1998
-    // Fire the event before renegotiation is done so that the thumbnails can be removed immediately.
1999
-    tracksToBeRemoved.forEach(track => {
2000
-        this.eventEmitter.emit(JitsiConferenceEvents.TRACK_REMOVED, track);
2001
-    });
2004
+    const participant = this.participants[id];
2002 2005
 
2003 2006
     if (participant) {
2004 2007
         delete this.participants[id];

+ 17
- 0
JitsiConferenceEventManager.js View File

@@ -3,6 +3,7 @@ import { Strophe } from 'strophe.js';
3 3
 
4 4
 import * as JitsiConferenceErrors from './JitsiConferenceErrors';
5 5
 import * as JitsiConferenceEvents from './JitsiConferenceEvents';
6
+import * as JitsiTrackEvents from './JitsiTrackEvents';
6 7
 import { SPEAKERS_AUDIO_LEVELS } from './modules/statistics/constants';
7 8
 import Statistics from './modules/statistics/statistics';
8 9
 import EventEmitterForwarder from './modules/util/EventEmitterForwarder';
@@ -190,6 +191,10 @@ JitsiConferenceEventManager.prototype.setupChatRoomListeners = function() {
190 191
         }
191 192
     });
192 193
 
194
+    chatRoom.addListener(JitsiTrackEvents.TRACK_REMOVED, track => {
195
+        conference.eventEmitter.emit(JitsiConferenceEvents.TRACK_REMOVED, track);
196
+    });
197
+
193 198
     this.chatRoomForwarder.forward(XMPPEvents.ROOM_JOIN_ERROR,
194 199
         JitsiConferenceEvents.CONFERENCE_FAILED,
195 200
         JitsiConferenceErrors.CONNECTION_ERROR);
@@ -560,6 +565,18 @@ JitsiConferenceEventManager.prototype.setupRTCListeners = function() {
560 565
         conference.eventEmitter.emit(JitsiConferenceEvents.DATA_CHANNEL_OPENED);
561 566
     });
562 567
 
568
+    rtc.addListener(RTCEvents.VIDEO_SSRCS_REMAPPED, msg => {
569
+        const sess = this.conference.getActiveMediaSession();
570
+
571
+        sess.videoSsrcsRemapped(msg);
572
+    });
573
+
574
+    rtc.addListener(RTCEvents.AUDIO_SSRCS_REMAPPED, msg => {
575
+        const sess = this.conference.getActiveMediaSession();
576
+
577
+        sess.audioSsrcsRemapped(msg);
578
+    });
579
+
563 580
     rtc.addListener(RTCEvents.ENDPOINT_MESSAGE_RECEIVED,
564 581
         (from, payload) => {
565 582
             const participant = conference.getParticipantById(from);

+ 5
- 0
JitsiMeetJS.ts View File

@@ -84,6 +84,11 @@ interface IJitsiMeetJSOptions {
84 84
     externalStorage?: Storage;
85 85
     flags?: {
86 86
         enableUnifiedOnChrome?: boolean;
87
+        receiveMultipleVideoStreams?: boolean;
88
+        runInLiteMode?: boolean;
89
+        sendMultipleVideoStreams?: boolean;
90
+        sourceNameSignaling?: boolean;
91
+        ssrcRewritingEnabled?: boolean;
87 92
     }
88 93
 }
89 94
 

+ 7
- 0
JitsiTrackEvents.spec.ts View File

@@ -12,6 +12,8 @@ describe( "/JitsiTrackEvents members", () => {
12 12
         TRACK_VIDEOTYPE_CHANGED,
13 13
         NO_DATA_FROM_SOURCE,
14 14
         NO_AUDIO_INPUT,
15
+        TRACK_OWNER_CHANGED,
16
+        TRACK_REMOVED,
15 17
         JitsiTrackEvents,
16 18
         ...others
17 19
     } = exported;
@@ -21,9 +23,12 @@ describe( "/JitsiTrackEvents members", () => {
21 23
         expect( TRACK_AUDIO_LEVEL_CHANGED ).toBe( 'track.audioLevelsChanged' );
22 24
         expect( TRACK_AUDIO_OUTPUT_CHANGED ).toBe( 'track.audioOutputChanged' );
23 25
         expect( TRACK_MUTE_CHANGED ).toBe( 'track.trackMuteChanged' );
26
+        expect( TRACK_STREAMING_STATUS_CHANGED ).toBe( 'track.streaming_status_changed' );
24 27
         expect( TRACK_VIDEOTYPE_CHANGED ).toBe( 'track.videoTypeChanged' );
25 28
         expect( NO_DATA_FROM_SOURCE ).toBe( 'track.no_data_from_source' );
26 29
         expect( NO_AUDIO_INPUT ).toBe( 'track.no_audio_input' );
30
+        expect( TRACK_OWNER_CHANGED ).toBe( 'track.owner_changed' );
31
+        expect( TRACK_REMOVED ).toBe( 'track.removed' );
27 32
 
28 33
         expect( JitsiTrackEvents ).toBeDefined();
29 34
 
@@ -35,6 +40,8 @@ describe( "/JitsiTrackEvents members", () => {
35 40
         expect( JitsiTrackEvents.TRACK_VIDEOTYPE_CHANGED ).toBe( 'track.videoTypeChanged' );
36 41
         expect( JitsiTrackEvents.NO_DATA_FROM_SOURCE ).toBe( 'track.no_data_from_source' );
37 42
         expect( JitsiTrackEvents.NO_AUDIO_INPUT ).toBe( 'track.no_audio_input' );
43
+        expect( JitsiTrackEvents.TRACK_OWNER_CHANGED ).toBe( 'track.owner_changed' );
44
+        expect( JitsiTrackEvents.TRACK_REMOVED ).toBe( 'track.removed' );
38 45
     } );
39 46
 
40 47
     it( "unknown members", () => {

+ 14
- 1
JitsiTrackEvents.ts View File

@@ -56,7 +56,18 @@ export enum JitsiTrackEvents {
56 56
      *
57 57
      * The current status value can be obtained by calling JitsiRemoteTrack.getTrackStreamingStatus().
58 58
      */
59
-    TRACK_STREAMING_STATUS_CHANGED = 'track.streaming_status_changed'
59
+    TRACK_STREAMING_STATUS_CHANGED = 'track.streaming_status_changed',
60
+
61
+    /**
62
+     * An SSRC has been remapped. The track is now associated with a new participant.
63
+     */
64
+    TRACK_OWNER_CHANGED = 'track.owner_changed',
65
+
66
+    /**
67
+     * A track is being removed. Fired when a session terminates for tracks
68
+     * that persist in ssrc-rewriting mode.
69
+     */
70
+    TRACK_REMOVED = 'track.removed',
60 71
 };
61 72
 
62 73
 // exported for backward compatibility
@@ -68,3 +79,5 @@ export const TRACK_VIDEOTYPE_CHANGED = JitsiTrackEvents.TRACK_VIDEOTYPE_CHANGED;
68 79
 export const NO_DATA_FROM_SOURCE = JitsiTrackEvents.NO_DATA_FROM_SOURCE;
69 80
 export const NO_AUDIO_INPUT = JitsiTrackEvents.NO_AUDIO_INPUT;
70 81
 export const TRACK_STREAMING_STATUS_CHANGED = JitsiTrackEvents.TRACK_STREAMING_STATUS_CHANGED;
82
+export const TRACK_OWNER_CHANGED = JitsiTrackEvents.TRACK_OWNER_CHANGED;
83
+export const TRACK_REMOVED = JitsiTrackEvents.TRACK_REMOVED;

+ 10
- 0
modules/RTC/BridgeChannel.js View File

@@ -413,6 +413,16 @@ export default class BridgeChannel {
413 413
                 logger.info(`Received ServerHello, version=${obj.version}.`);
414 414
                 break;
415 415
             }
416
+            case 'VideoSourcesMap': {
417
+                logger.info(`Received VideoSourcesMap: ${JSON.stringify(obj.mappedSources)}`);
418
+                emitter.emit(RTCEvents.VIDEO_SSRCS_REMAPPED, obj);
419
+                break;
420
+            }
421
+            case 'AudioSourcesMap': {
422
+                logger.info(`Received AudioSourcesMap: ${JSON.stringify(obj.mappedSources)}`);
423
+                emitter.emit(RTCEvents.AUDIO_SSRCS_REMAPPED, obj);
424
+                break;
425
+            }
416 426
             default: {
417 427
                 logger.debug('Channel JSON-formatted message: ', obj);
418 428
 

+ 13
- 1
modules/RTC/JitsiRemoteTrack.js View File

@@ -266,6 +266,18 @@ export default class JitsiRemoteTrack extends JitsiTrack {
266 266
         return this._sourceName;
267 267
     }
268 268
 
269
+    /**
270
+     * Update the properties when the track is remapped to another source.
271
+     *
272
+     * @param {string} owner The endpoint ID of the new owner.
273
+     * @param {string} name The new source name.
274
+     */
275
+    setNewSource(owner, name) {
276
+        this.ownerEndpointId = owner;
277
+        this._sourceName = name;
278
+        this.emit(JitsiTrackEvents.TRACK_OWNER_CHANGED, owner);
279
+    }
280
+
269 281
     /**
270 282
      * Changes the video type of the track.
271 283
      *
@@ -485,6 +497,6 @@ export default class JitsiRemoteTrack extends JitsiTrack {
485 497
      */
486 498
     toString() {
487 499
         return `RemoteTrack[userID: ${this.getParticipantId()}, type: ${this.getType()}, ssrc: ${
488
-            this.getSSRC()}, p2p: ${this.isP2P}, sourceName: ${this._sourceName}, status: ${this._getStatus()}]`;
500
+            this.getSSRC()}, p2p: ${this.isP2P}, sourceName: ${this._sourceName}, status: {${this._getStatus()}}]`;
489 501
     }
490 502
 }

+ 22
- 1
modules/RTC/TraceablePeerConnection.js View File

@@ -182,6 +182,12 @@ export default function TraceablePeerConnection(
182 182
      */
183 183
     this.localSSRCs = new Map();
184 184
 
185
+    /**
186
+     * The set of remote SSRCs seen so far.
187
+     * Distinguishes new SSRCs from those that have been remapped.
188
+     */
189
+    this.remoteSSRCs = new Set();
190
+
185 191
     /**
186 192
      * The local ICE username fragment for this session.
187 193
      */
@@ -1053,7 +1059,7 @@ TraceablePeerConnection.prototype._createRemoteTrack = function(
1053 1059
             + 'deleting the existing track');
1054 1060
         const existingTrack = Array.from(userTracksByMediaType)[0];
1055 1061
 
1056
-        // The exisiting track needs to be removed here. This happens on Safari sometimes when a SSRC is removed from
1062
+        // The existing track needs to be removed here. This happens on Safari sometimes when an SSRC is removed from
1057 1063
         // the remote description and the browser doesn't fire a 'removetrack' event on the associated MediaStream.
1058 1064
         this._remoteTrackRemoved(existingTrack.getOriginalStream(), existingTrack.getTrack());
1059 1065
     }
@@ -3102,6 +3108,21 @@ TraceablePeerConnection.prototype._processLocalSSRCsMap = function(ssrcMap) {
3102 3108
     }
3103 3109
 };
3104 3110
 
3111
+/**
3112
+ * Track the SSRCs seen so far.
3113
+ * @param {int} ssrc - SSRC.
3114
+ * @return {boolean} - Whether this is a new SSRC.
3115
+ */
3116
+TraceablePeerConnection.prototype.addRemoteSsrc = function(ssrc) {
3117
+    const existing = this.remoteSSRCs.has(ssrc);
3118
+
3119
+    if (!existing) {
3120
+        this.remoteSSRCs.add(ssrc);
3121
+    }
3122
+
3123
+    return !existing;
3124
+};
3125
+
3105 3126
 TraceablePeerConnection.prototype.addIceCandidate = function(candidate) {
3106 3127
     this.trace('addIceCandidate', JSON.stringify({
3107 3128
         candidate: candidate.candidate,

+ 11
- 10
modules/flags/FeatureFlags.js View File

@@ -11,26 +11,27 @@ class FeatureFlags {
11 11
     /**
12 12
      * Configures the module.
13 13
      *
14
-     * @param {object} flags - The feature flags.
15
-     * @param {boolean=} flags.enableUnifiedOnChrome - Enable unified plan implementation support on Chromium.
16
-     * @param {boolean=} flags.runInLiteMode - Enables lite mode for testing to disable media decoding.
17
-     * @param {boolean=} flags.sourceNameSignaling - Enables source names in the signaling.
18
-     * @param {boolean=} flags.receiveMultipleVideoStreams - Signal support for receiving multiple video streams.
14
+     * @param {boolean} flags.runInLiteMode - Enables lite mode for testing to disable media decoding.
15
+     * @param {boolean} flags.receiveMultipleVideoStreams - Signal support for receiving multiple video streams.
16
+     * @param {boolean} flags.sendMultipleVideoStreams - Signal support for sending multiple video streams.
17
+     * @param {boolean} flags.sourceNameSignaling - Enables source names in the signaling.
18
+     * @param {boolean} flags.ssrcRewritingEnabled - Use SSRC rewriting. Requires sourceNameSignaling to be enabled.
19
+     * @param {boolean} flags.enableUnifiedOnChrome - Use unified plan signaling on chrome browsers.
19 20
      */
20 21
     init(flags) {
21
-        this._receiveMultipleVideoStreams = flags.receiveMultipleVideoStreams ?? true;
22 22
         this._runInLiteMode = Boolean(flags.runInLiteMode);
23
+        this._receiveMultipleVideoStreams = flags.receiveMultipleVideoStreams ?? true;
23 24
         this._sendMultipleVideoStreams = flags.sendMultipleVideoStreams ?? true;
24 25
         this._sourceNameSignaling = flags.sourceNameSignaling ?? true;
25
-        this._ssrcRewriting = Boolean(flags.ssrcRewritingEnabled);
26
+        this._ssrcRewriting = this._sourceNameSignaling && Boolean(flags.ssrcRewritingEnabled);
26 27
 
27 28
         // For Chromium, check if Unified plan is enabled.
28 29
         this._usesUnifiedPlan = browser.supportsUnifiedPlan()
29 30
             && (!browser.isChromiumBased() || (flags.enableUnifiedOnChrome ?? true));
30 31
 
31
-        logger.info(`Source name signaling: ${this._sourceNameSignaling},`
32
-            + ` Send multiple video streams: ${this._sendMultipleVideoStreams},`
33
-            + ` uses Unified plan: ${this._usesUnifiedPlan}`);
32
+        logger.info(`Send multiple video streams: ${this._sendMultipleVideoStreams},`
33
+            + ` Source name signaling: ${this._sourceNameSignaling},`
34
+            + ` Unified plan: ${this._usesUnifiedPlan}`);
34 35
     }
35 36
 
36 37
     /**

+ 161
- 1
modules/xmpp/JingleSessionPC.js View File

@@ -1,7 +1,8 @@
1 1
 import { getLogger } from '@jitsi/logger';
2 2
 import $ from 'jquery';
3
-import { $iq, Strophe } from 'strophe.js';
3
+import { $build, $iq, Strophe } from 'strophe.js';
4 4
 
5
+import { JitsiTrackEvents } from '../../JitsiTrackEvents';
5 6
 import * as CodecMimeType from '../../service/RTC/CodecMimeType';
6 7
 import { MediaDirection } from '../../service/RTC/MediaDirection';
7 8
 import { MediaType } from '../../service/RTC/MediaType';
@@ -57,6 +58,33 @@ function getEndpointId(jidOrEndpointId) {
57 58
     return Strophe.getResourceFromJid(jidOrEndpointId) || jidOrEndpointId;
58 59
 }
59 60
 
61
+/**
62
+ * Add "source" element as a child of "description" element.
63
+ * @param {Object} description The "description" element to add to.
64
+ * @param {Object} s Contains properties of the source being added.
65
+ * @param {Number} ssrc_ The SSRC.
66
+ * @param {String} msid The "msid" attribute.
67
+ */
68
+function _addSourceElement(description, s, ssrc_, msid) {
69
+
70
+    description.c('source', {
71
+        xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0',
72
+        ssrc: ssrc_,
73
+        name: s.source
74
+    })
75
+        .c('parameter', {
76
+            name: 'msid',
77
+            value: msid
78
+        })
79
+        .up()
80
+        .c('ssrc-info', {
81
+            xmlns: 'http://jitsi.org/jitmeet',
82
+            owner: s.owner
83
+        })
84
+        .up()
85
+        .up();
86
+}
87
+
60 88
 /**
61 89
  * @typedef {Object} JingleSessionPCOptions
62 90
  * @property {Object} abTesting - A/B testing related options (ask George).
@@ -297,6 +325,22 @@ export default class JingleSessionPC extends JingleSession {
297 325
          */
298 326
         this.remoteRecvMaxFrameHeight = undefined;
299 327
 
328
+        /**
329
+         * Number of remote video sources, in SSRC rewriting mode.
330
+         * Used to generate next unique msid attribute.
331
+         *
332
+         * @type {Number}
333
+         */
334
+        this.numRemoteVideoSources = 0;
335
+
336
+        /**
337
+         * Number of remote audio sources, in SSRC rewriting mode.
338
+         * Used to generate next unique msid attribute.
339
+         *
340
+         * @type {Number}
341
+         */
342
+        this.numRemoteAudioSources = 0;
343
+
300 344
         /**
301 345
          * Remote preference for the receive video max frame heights when source-name signaling is enabled.
302 346
          *
@@ -1669,6 +1713,12 @@ export default class JingleSessionPC extends JingleSession {
1669 1713
             this._removeSenderVideoConstraintsChangeListener();
1670 1714
         }
1671 1715
 
1716
+        if (FeatureFlags.isSsrcRewritingSupported() && this.peerconnection) {
1717
+            this.peerconnection.getRemoteTracks().forEach(track => {
1718
+                this.room.eventEmitter.emit(JitsiTrackEvents.TRACK_REMOVED, track);
1719
+            });
1720
+        }
1721
+
1672 1722
         this.close();
1673 1723
     }
1674 1724
 
@@ -1791,6 +1841,116 @@ export default class JingleSessionPC extends JingleSession {
1791 1841
         this._addOrRemoveRemoteStream(false /* remove */, elem);
1792 1842
     }
1793 1843
 
1844
+    /**
1845
+     * Filter remapped SSRCs.
1846
+     * Process owner change for existing SSRCs.
1847
+     * Return new ones for further processing.
1848
+     */
1849
+    getNewSources(msg) {
1850
+        const newSources = [];
1851
+
1852
+        for (const s of msg.mappedSources) {
1853
+            if (this.peerconnection.addRemoteSsrc(s.ssrc)) {
1854
+                logger.debug(`New SSRC ${s.ssrc}`);
1855
+                newSources[newSources.length] = s;
1856
+            } else {
1857
+                const track = this.peerconnection.getTrackBySSRC(s.ssrc);
1858
+
1859
+                if (track) {
1860
+                    logger.debug(`Existing SSRC ${s.ssrc}: new owner ${s.owner}. name=${s.source}`);
1861
+
1862
+                    if (s.videoType === 'CAMERA') {
1863
+                        track._setVideoType('camera');
1864
+                    } else if (s.videoType === 'DESKTOP') {
1865
+                        track._setVideoType('desktop');
1866
+                    }
1867
+
1868
+                    track.setNewSource(s.owner, s.source);
1869
+                } else {
1870
+                    logger.error(`Remapped SSRC ${s.ssrc} not found`);
1871
+                }
1872
+            }
1873
+        }
1874
+
1875
+        return newSources;
1876
+    }
1877
+
1878
+    /**
1879
+     * Process SSRC remappings for video sources.
1880
+     */
1881
+    videoSsrcsRemapped(msg) {
1882
+        const newSources = this.getNewSources(msg);
1883
+
1884
+        if (newSources.length > 0) {
1885
+
1886
+            let node = $build('content', {
1887
+                xmlns: 'urn:xmpp:jingle:1',
1888
+                name: 'video'
1889
+            }).c('description', {
1890
+                xmlns: 'urn:xmpp:jingle:apps:rtp:1',
1891
+                media: MediaType.VIDEO
1892
+            });
1893
+
1894
+            for (const s of newSources) {
1895
+                const idx = ++this.numRemoteVideoSources;
1896
+                const msid = `remote-video-${idx} remote-video-${idx}`;
1897
+
1898
+                _addSourceElement(node, s, s.ssrc, msid);
1899
+                if (s.rtx !== '-1') {
1900
+                    _addSourceElement(node, s, s.rtx, msid);
1901
+                    node.c('ssrc-group', {
1902
+                        xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0',
1903
+                        semantics: 'FID'
1904
+                    })
1905
+                        .c('source', {
1906
+                            xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0',
1907
+                            ssrc: s.ssrc
1908
+                        })
1909
+                        .up()
1910
+                        .c('source', {
1911
+                            xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0',
1912
+                            ssrc: s.rtx
1913
+                        })
1914
+                        .up()
1915
+                        .up();
1916
+                }
1917
+            }
1918
+
1919
+            node = node.up();
1920
+
1921
+            this._addOrRemoveRemoteStream(true /* add */, node.node);
1922
+        }
1923
+    }
1924
+
1925
+    /**
1926
+     * Process SSRC remappings for audio sources.
1927
+     */
1928
+    audioSsrcsRemapped(msg) {
1929
+        const newSources = this.getNewSources(msg);
1930
+
1931
+        if (newSources.length > 0) {
1932
+
1933
+            let node = $build('content', {
1934
+                xmlns: 'urn:xmpp:jingle:1',
1935
+                name: 'audio'
1936
+            }).c('description', {
1937
+                xmlns: 'urn:xmpp:jingle:apps:rtp:1',
1938
+                media: MediaType.AUDIO
1939
+            });
1940
+
1941
+            for (const s of newSources) {
1942
+                const idx = ++this.numRemoteAudioSources;
1943
+                const msid = `remote-audio-${idx} remote-audio-${idx}`;
1944
+
1945
+                _addSourceElement(node, s, s.ssrc, msid);
1946
+            }
1947
+
1948
+            node = node.up();
1949
+
1950
+            this._addOrRemoveRemoteStream(true /* add */, node.node);
1951
+        }
1952
+    }
1953
+
1794 1954
     /**
1795 1955
      * Handles the deletion of SSRCs associated with a remote user from the remote description when the user leaves.
1796 1956
      *

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

@@ -263,11 +263,6 @@ export default class XMPP extends Listenable {
263 263
             logger.info('Receiving multiple video streams is enabled');
264 264
             this.caps.addFeature('http://jitsi.org/receive-multiple-video-streams');
265 265
         }
266
-
267
-        if (FeatureFlags.isSsrcRewritingSupported()) {
268
-            logger.info('SSRC rewriting is supported');
269
-            this.caps.addFeature('http://jitsi.org/ssrc-rewriting');
270
-        }
271 266
     }
272 267
 
273 268
     /**

+ 8
- 0
service/RTC/RTCEvents.spec.ts View File

@@ -31,6 +31,8 @@ describe( "/service/RTC/RTCEvents members", () => {
31 31
         ENDPOINT_STATS_RECEIVED,
32 32
         LOCAL_UFRAG_CHANGED,
33 33
         REMOTE_UFRAG_CHANGED,
34
+        VIDEO_SSRCS_REMAPPED,
35
+        AUDIO_SSRCS_REMAPPED,
34 36
         RTCEvents,
35 37
         default: RTCEventsDefault,
36 38
         ...others
@@ -64,6 +66,8 @@ describe( "/service/RTC/RTCEvents members", () => {
64 66
         expect( ENDPOINT_STATS_RECEIVED ).toBe( 'rtc.endpoint_stats_received' );
65 67
         expect( LOCAL_UFRAG_CHANGED ).toBe( 'rtc.local_ufrag_changed' );
66 68
         expect( REMOTE_UFRAG_CHANGED ).toBe( 'rtc.remote_ufrag_changed' );
69
+        expect( VIDEO_SSRCS_REMAPPED ).toBe( 'rtc.video_ssrcs_remapped' );
70
+        expect( AUDIO_SSRCS_REMAPPED ).toBe( 'rtc.audio_ssrcs_remapped' );
67 71
 
68 72
         if ( RTCEvents ) {
69 73
             expect( RTCEvents.CREATE_ANSWER_FAILED ).toBe( 'rtc.create_answer_failed' );
@@ -92,6 +96,8 @@ describe( "/service/RTC/RTCEvents members", () => {
92 96
             expect( RTCEvents.ENDPOINT_STATS_RECEIVED ).toBe( 'rtc.endpoint_stats_received' );
93 97
             expect( RTCEvents.LOCAL_UFRAG_CHANGED ).toBe( 'rtc.local_ufrag_changed' );
94 98
             expect( RTCEvents.REMOTE_UFRAG_CHANGED ).toBe( 'rtc.remote_ufrag_changed' );
99
+            expect( RTCEvents.VIDEO_SSRCS_REMAPPED ).toBe( 'rtc.video_ssrcs_remapped' );
100
+            expect( RTCEvents.AUDIO_SSRCS_REMAPPED ).toBe( 'rtc.audio_ssrcs_remapped' );
95 101
         }
96 102
 
97 103
         if ( RTCEventsDefault ) {
@@ -121,6 +127,8 @@ describe( "/service/RTC/RTCEvents members", () => {
121 127
             expect( RTCEventsDefault.ENDPOINT_STATS_RECEIVED ).toBe( 'rtc.endpoint_stats_received' );
122 128
             expect( RTCEventsDefault.LOCAL_UFRAG_CHANGED ).toBe( 'rtc.local_ufrag_changed' );
123 129
             expect( RTCEventsDefault.REMOTE_UFRAG_CHANGED ).toBe( 'rtc.remote_ufrag_changed' );
130
+            expect( RTCEventsDefault.VIDEO_SSRCS_REMAPPED ).toBe( 'rtc.video_ssrcs_remapped' );
131
+            expect( RTCEventsDefault.AUDIO_SSRCS_REMAPPED ).toBe( 'rtc.audio_ssrcs_remapped' );
124 132
         }
125 133
     } );
126 134
 

+ 15
- 1
service/RTC/RTCEvents.ts View File

@@ -110,7 +110,19 @@ export enum RTCEvents {
110 110
      * is the source of the event.
111 111
      * The second argument is the actual "ufrag" string.
112 112
      */
113
-    REMOTE_UFRAG_CHANGED = 'rtc.remote_ufrag_changed'
113
+    REMOTE_UFRAG_CHANGED = 'rtc.remote_ufrag_changed',
114
+
115
+    /**
116
+     * Designates an event indicating that some received video SSRCs will now map to
117
+     * new remote sources.
118
+     */
119
+    VIDEO_SSRCS_REMAPPED = 'rtc.video_ssrcs_remapped',
120
+
121
+    /**
122
+     * Designates an event indicating that some received audio SSRCs will now map to
123
+     * new remote sources.
124
+     */
125
+    AUDIO_SSRCS_REMAPPED = 'rtc.audio_ssrcs_remapped'
114 126
 };
115 127
 
116 128
 export const CREATE_ANSWER_FAILED = RTCEvents.CREATE_ANSWER_FAILED;
@@ -140,6 +152,8 @@ export const ENDPOINT_MESSAGE_RECEIVED = RTCEvents.ENDPOINT_MESSAGE_RECEIVED;
140 152
 export const ENDPOINT_STATS_RECEIVED = RTCEvents.ENDPOINT_STATS_RECEIVED;
141 153
 export const LOCAL_UFRAG_CHANGED = RTCEvents.LOCAL_UFRAG_CHANGED;
142 154
 export const REMOTE_UFRAG_CHANGED = RTCEvents.REMOTE_UFRAG_CHANGED;
155
+export const VIDEO_SSRCS_REMAPPED = RTCEvents.VIDEO_SSRCS_REMAPPED;
156
+export const AUDIO_SSRCS_REMAPPED = RTCEvents.AUDIO_SSRCS_REMAPPED;
143 157
 
144 158
 // TODO: this was a pre-ES6 module using module.exports = RTCEvents which doesn't translate well
145 159
 // it is used in a number of places and should be updated to use the named export

Loading…
Cancel
Save