瀏覽代碼

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>
dev1
Jaya Allamsetty 2 年之前
父節點
當前提交
28436e5727
沒有連結到貢獻者的電子郵件帳戶。

+ 15
- 12
JitsiConference.js 查看文件

1982
         return;
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
     if (participant) {
2006
     if (participant) {
2004
         delete this.participants[id];
2007
         delete this.participants[id];

+ 17
- 0
JitsiConferenceEventManager.js 查看文件

3
 
3
 
4
 import * as JitsiConferenceErrors from './JitsiConferenceErrors';
4
 import * as JitsiConferenceErrors from './JitsiConferenceErrors';
5
 import * as JitsiConferenceEvents from './JitsiConferenceEvents';
5
 import * as JitsiConferenceEvents from './JitsiConferenceEvents';
6
+import * as JitsiTrackEvents from './JitsiTrackEvents';
6
 import { SPEAKERS_AUDIO_LEVELS } from './modules/statistics/constants';
7
 import { SPEAKERS_AUDIO_LEVELS } from './modules/statistics/constants';
7
 import Statistics from './modules/statistics/statistics';
8
 import Statistics from './modules/statistics/statistics';
8
 import EventEmitterForwarder from './modules/util/EventEmitterForwarder';
9
 import EventEmitterForwarder from './modules/util/EventEmitterForwarder';
190
         }
191
         }
191
     });
192
     });
192
 
193
 
194
+    chatRoom.addListener(JitsiTrackEvents.TRACK_REMOVED, track => {
195
+        conference.eventEmitter.emit(JitsiConferenceEvents.TRACK_REMOVED, track);
196
+    });
197
+
193
     this.chatRoomForwarder.forward(XMPPEvents.ROOM_JOIN_ERROR,
198
     this.chatRoomForwarder.forward(XMPPEvents.ROOM_JOIN_ERROR,
194
         JitsiConferenceEvents.CONFERENCE_FAILED,
199
         JitsiConferenceEvents.CONFERENCE_FAILED,
195
         JitsiConferenceErrors.CONNECTION_ERROR);
200
         JitsiConferenceErrors.CONNECTION_ERROR);
560
         conference.eventEmitter.emit(JitsiConferenceEvents.DATA_CHANNEL_OPENED);
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
     rtc.addListener(RTCEvents.ENDPOINT_MESSAGE_RECEIVED,
580
     rtc.addListener(RTCEvents.ENDPOINT_MESSAGE_RECEIVED,
564
         (from, payload) => {
581
         (from, payload) => {
565
             const participant = conference.getParticipantById(from);
582
             const participant = conference.getParticipantById(from);

+ 5
- 0
JitsiMeetJS.ts 查看文件

84
     externalStorage?: Storage;
84
     externalStorage?: Storage;
85
     flags?: {
85
     flags?: {
86
         enableUnifiedOnChrome?: boolean;
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 查看文件

12
         TRACK_VIDEOTYPE_CHANGED,
12
         TRACK_VIDEOTYPE_CHANGED,
13
         NO_DATA_FROM_SOURCE,
13
         NO_DATA_FROM_SOURCE,
14
         NO_AUDIO_INPUT,
14
         NO_AUDIO_INPUT,
15
+        TRACK_OWNER_CHANGED,
16
+        TRACK_REMOVED,
15
         JitsiTrackEvents,
17
         JitsiTrackEvents,
16
         ...others
18
         ...others
17
     } = exported;
19
     } = exported;
21
         expect( TRACK_AUDIO_LEVEL_CHANGED ).toBe( 'track.audioLevelsChanged' );
23
         expect( TRACK_AUDIO_LEVEL_CHANGED ).toBe( 'track.audioLevelsChanged' );
22
         expect( TRACK_AUDIO_OUTPUT_CHANGED ).toBe( 'track.audioOutputChanged' );
24
         expect( TRACK_AUDIO_OUTPUT_CHANGED ).toBe( 'track.audioOutputChanged' );
23
         expect( TRACK_MUTE_CHANGED ).toBe( 'track.trackMuteChanged' );
25
         expect( TRACK_MUTE_CHANGED ).toBe( 'track.trackMuteChanged' );
26
+        expect( TRACK_STREAMING_STATUS_CHANGED ).toBe( 'track.streaming_status_changed' );
24
         expect( TRACK_VIDEOTYPE_CHANGED ).toBe( 'track.videoTypeChanged' );
27
         expect( TRACK_VIDEOTYPE_CHANGED ).toBe( 'track.videoTypeChanged' );
25
         expect( NO_DATA_FROM_SOURCE ).toBe( 'track.no_data_from_source' );
28
         expect( NO_DATA_FROM_SOURCE ).toBe( 'track.no_data_from_source' );
26
         expect( NO_AUDIO_INPUT ).toBe( 'track.no_audio_input' );
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
         expect( JitsiTrackEvents ).toBeDefined();
33
         expect( JitsiTrackEvents ).toBeDefined();
29
 
34
 
35
         expect( JitsiTrackEvents.TRACK_VIDEOTYPE_CHANGED ).toBe( 'track.videoTypeChanged' );
40
         expect( JitsiTrackEvents.TRACK_VIDEOTYPE_CHANGED ).toBe( 'track.videoTypeChanged' );
36
         expect( JitsiTrackEvents.NO_DATA_FROM_SOURCE ).toBe( 'track.no_data_from_source' );
41
         expect( JitsiTrackEvents.NO_DATA_FROM_SOURCE ).toBe( 'track.no_data_from_source' );
37
         expect( JitsiTrackEvents.NO_AUDIO_INPUT ).toBe( 'track.no_audio_input' );
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
     it( "unknown members", () => {
47
     it( "unknown members", () => {

+ 14
- 1
JitsiTrackEvents.ts 查看文件

56
      *
56
      *
57
      * The current status value can be obtained by calling JitsiRemoteTrack.getTrackStreamingStatus().
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
 // exported for backward compatibility
73
 // exported for backward compatibility
68
 export const NO_DATA_FROM_SOURCE = JitsiTrackEvents.NO_DATA_FROM_SOURCE;
79
 export const NO_DATA_FROM_SOURCE = JitsiTrackEvents.NO_DATA_FROM_SOURCE;
69
 export const NO_AUDIO_INPUT = JitsiTrackEvents.NO_AUDIO_INPUT;
80
 export const NO_AUDIO_INPUT = JitsiTrackEvents.NO_AUDIO_INPUT;
70
 export const TRACK_STREAMING_STATUS_CHANGED = JitsiTrackEvents.TRACK_STREAMING_STATUS_CHANGED;
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 查看文件

413
                 logger.info(`Received ServerHello, version=${obj.version}.`);
413
                 logger.info(`Received ServerHello, version=${obj.version}.`);
414
                 break;
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
             default: {
426
             default: {
417
                 logger.debug('Channel JSON-formatted message: ', obj);
427
                 logger.debug('Channel JSON-formatted message: ', obj);
418
 
428
 

+ 13
- 1
modules/RTC/JitsiRemoteTrack.js 查看文件

266
         return this._sourceName;
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
      * Changes the video type of the track.
282
      * Changes the video type of the track.
271
      *
283
      *
485
      */
497
      */
486
     toString() {
498
     toString() {
487
         return `RemoteTrack[userID: ${this.getParticipantId()}, type: ${this.getType()}, ssrc: ${
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 查看文件

182
      */
182
      */
183
     this.localSSRCs = new Map();
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
      * The local ICE username fragment for this session.
192
      * The local ICE username fragment for this session.
187
      */
193
      */
1053
             + 'deleting the existing track');
1059
             + 'deleting the existing track');
1054
         const existingTrack = Array.from(userTracksByMediaType)[0];
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
         // the remote description and the browser doesn't fire a 'removetrack' event on the associated MediaStream.
1063
         // the remote description and the browser doesn't fire a 'removetrack' event on the associated MediaStream.
1058
         this._remoteTrackRemoved(existingTrack.getOriginalStream(), existingTrack.getTrack());
1064
         this._remoteTrackRemoved(existingTrack.getOriginalStream(), existingTrack.getTrack());
1059
     }
1065
     }
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
 TraceablePeerConnection.prototype.addIceCandidate = function(candidate) {
3126
 TraceablePeerConnection.prototype.addIceCandidate = function(candidate) {
3106
     this.trace('addIceCandidate', JSON.stringify({
3127
     this.trace('addIceCandidate', JSON.stringify({
3107
         candidate: candidate.candidate,
3128
         candidate: candidate.candidate,

+ 11
- 10
modules/flags/FeatureFlags.js 查看文件

11
     /**
11
     /**
12
      * Configures the module.
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
     init(flags) {
21
     init(flags) {
21
-        this._receiveMultipleVideoStreams = flags.receiveMultipleVideoStreams ?? true;
22
         this._runInLiteMode = Boolean(flags.runInLiteMode);
22
         this._runInLiteMode = Boolean(flags.runInLiteMode);
23
+        this._receiveMultipleVideoStreams = flags.receiveMultipleVideoStreams ?? true;
23
         this._sendMultipleVideoStreams = flags.sendMultipleVideoStreams ?? true;
24
         this._sendMultipleVideoStreams = flags.sendMultipleVideoStreams ?? true;
24
         this._sourceNameSignaling = flags.sourceNameSignaling ?? true;
25
         this._sourceNameSignaling = flags.sourceNameSignaling ?? true;
25
-        this._ssrcRewriting = Boolean(flags.ssrcRewritingEnabled);
26
+        this._ssrcRewriting = this._sourceNameSignaling && Boolean(flags.ssrcRewritingEnabled);
26
 
27
 
27
         // For Chromium, check if Unified plan is enabled.
28
         // For Chromium, check if Unified plan is enabled.
28
         this._usesUnifiedPlan = browser.supportsUnifiedPlan()
29
         this._usesUnifiedPlan = browser.supportsUnifiedPlan()
29
             && (!browser.isChromiumBased() || (flags.enableUnifiedOnChrome ?? true));
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 查看文件

1
 import { getLogger } from '@jitsi/logger';
1
 import { getLogger } from '@jitsi/logger';
2
 import $ from 'jquery';
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
 import * as CodecMimeType from '../../service/RTC/CodecMimeType';
6
 import * as CodecMimeType from '../../service/RTC/CodecMimeType';
6
 import { MediaDirection } from '../../service/RTC/MediaDirection';
7
 import { MediaDirection } from '../../service/RTC/MediaDirection';
7
 import { MediaType } from '../../service/RTC/MediaType';
8
 import { MediaType } from '../../service/RTC/MediaType';
57
     return Strophe.getResourceFromJid(jidOrEndpointId) || jidOrEndpointId;
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
  * @typedef {Object} JingleSessionPCOptions
89
  * @typedef {Object} JingleSessionPCOptions
62
  * @property {Object} abTesting - A/B testing related options (ask George).
90
  * @property {Object} abTesting - A/B testing related options (ask George).
297
          */
325
          */
298
         this.remoteRecvMaxFrameHeight = undefined;
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
          * Remote preference for the receive video max frame heights when source-name signaling is enabled.
345
          * Remote preference for the receive video max frame heights when source-name signaling is enabled.
302
          *
346
          *
1669
             this._removeSenderVideoConstraintsChangeListener();
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
         this.close();
1722
         this.close();
1673
     }
1723
     }
1674
 
1724
 
1791
         this._addOrRemoveRemoteStream(false /* remove */, elem);
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
      * Handles the deletion of SSRCs associated with a remote user from the remote description when the user leaves.
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 查看文件

263
             logger.info('Receiving multiple video streams is enabled');
263
             logger.info('Receiving multiple video streams is enabled');
264
             this.caps.addFeature('http://jitsi.org/receive-multiple-video-streams');
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 查看文件

31
         ENDPOINT_STATS_RECEIVED,
31
         ENDPOINT_STATS_RECEIVED,
32
         LOCAL_UFRAG_CHANGED,
32
         LOCAL_UFRAG_CHANGED,
33
         REMOTE_UFRAG_CHANGED,
33
         REMOTE_UFRAG_CHANGED,
34
+        VIDEO_SSRCS_REMAPPED,
35
+        AUDIO_SSRCS_REMAPPED,
34
         RTCEvents,
36
         RTCEvents,
35
         default: RTCEventsDefault,
37
         default: RTCEventsDefault,
36
         ...others
38
         ...others
64
         expect( ENDPOINT_STATS_RECEIVED ).toBe( 'rtc.endpoint_stats_received' );
66
         expect( ENDPOINT_STATS_RECEIVED ).toBe( 'rtc.endpoint_stats_received' );
65
         expect( LOCAL_UFRAG_CHANGED ).toBe( 'rtc.local_ufrag_changed' );
67
         expect( LOCAL_UFRAG_CHANGED ).toBe( 'rtc.local_ufrag_changed' );
66
         expect( REMOTE_UFRAG_CHANGED ).toBe( 'rtc.remote_ufrag_changed' );
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
         if ( RTCEvents ) {
72
         if ( RTCEvents ) {
69
             expect( RTCEvents.CREATE_ANSWER_FAILED ).toBe( 'rtc.create_answer_failed' );
73
             expect( RTCEvents.CREATE_ANSWER_FAILED ).toBe( 'rtc.create_answer_failed' );
92
             expect( RTCEvents.ENDPOINT_STATS_RECEIVED ).toBe( 'rtc.endpoint_stats_received' );
96
             expect( RTCEvents.ENDPOINT_STATS_RECEIVED ).toBe( 'rtc.endpoint_stats_received' );
93
             expect( RTCEvents.LOCAL_UFRAG_CHANGED ).toBe( 'rtc.local_ufrag_changed' );
97
             expect( RTCEvents.LOCAL_UFRAG_CHANGED ).toBe( 'rtc.local_ufrag_changed' );
94
             expect( RTCEvents.REMOTE_UFRAG_CHANGED ).toBe( 'rtc.remote_ufrag_changed' );
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
         if ( RTCEventsDefault ) {
103
         if ( RTCEventsDefault ) {
121
             expect( RTCEventsDefault.ENDPOINT_STATS_RECEIVED ).toBe( 'rtc.endpoint_stats_received' );
127
             expect( RTCEventsDefault.ENDPOINT_STATS_RECEIVED ).toBe( 'rtc.endpoint_stats_received' );
122
             expect( RTCEventsDefault.LOCAL_UFRAG_CHANGED ).toBe( 'rtc.local_ufrag_changed' );
128
             expect( RTCEventsDefault.LOCAL_UFRAG_CHANGED ).toBe( 'rtc.local_ufrag_changed' );
123
             expect( RTCEventsDefault.REMOTE_UFRAG_CHANGED ).toBe( 'rtc.remote_ufrag_changed' );
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 查看文件

110
      * is the source of the event.
110
      * is the source of the event.
111
      * The second argument is the actual "ufrag" string.
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
 export const CREATE_ANSWER_FAILED = RTCEvents.CREATE_ANSWER_FAILED;
128
 export const CREATE_ANSWER_FAILED = RTCEvents.CREATE_ANSWER_FAILED;
140
 export const ENDPOINT_STATS_RECEIVED = RTCEvents.ENDPOINT_STATS_RECEIVED;
152
 export const ENDPOINT_STATS_RECEIVED = RTCEvents.ENDPOINT_STATS_RECEIVED;
141
 export const LOCAL_UFRAG_CHANGED = RTCEvents.LOCAL_UFRAG_CHANGED;
153
 export const LOCAL_UFRAG_CHANGED = RTCEvents.LOCAL_UFRAG_CHANGED;
142
 export const REMOTE_UFRAG_CHANGED = RTCEvents.REMOTE_UFRAG_CHANGED;
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
 // TODO: this was a pre-ES6 module using module.exports = RTCEvents which doesn't translate well
158
 // TODO: this was a pre-ES6 module using module.exports = RTCEvents which doesn't translate well
145
 // it is used in a number of places and should be updated to use the named export
159
 // it is used in a number of places and should be updated to use the named export

Loading…
取消
儲存